From a160450580eae58a2e1712a7f25fcec0914430e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Mon, 12 Aug 2024 09:00:47 +0200 Subject: [PATCH 1/8] 61: adds vertical() method to all switches --- example/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- example/ios/Runner/AppDelegate.swift | 2 +- example/lib/crazy_switch.dart | 4 +- example/lib/load_switch.dart | 8 +- example/lib/main.dart | 241 +++++++++--------- example/pubspec.lock | 76 +++--- example/test/widget_test.dart | 2 +- lib/src/properties.dart | 4 + lib/src/widgets/animated_toggle_switch.dart | 15 +- .../custom_animated_toggle_switch.dart | 131 +++++++++- pubspec.lock | 241 ++++++------------ pubspec.yaml | 1 + test/vertical_test.dart | 27 ++ 14 files changed, 413 insertions(+), 343 deletions(-) create mode 100644 test/vertical_test.dart diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index c9f8f38..1f6541e 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -127,7 +127,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index b52b2e6..e67b280 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ { return CustomAnimatedToggleSwitch( current: current, spacing: 36.0, - values: [false, true], + values: const [false, true], animationDuration: const Duration(milliseconds: 350), animationCurve: Curves.bounceOut, iconBuilder: (context, local, global) => const SizedBox(), @@ -41,7 +41,7 @@ class _CrazySwitchState extends State { return Container( alignment: Alignment.center, decoration: - BoxDecoration(color: Colors.white, shape: BoxShape.circle), + const BoxDecoration(color: Colors.white, shape: BoxShape.circle), child: Container( width: innerIndicatorSize * 0.4 + global.position * innerIndicatorSize * 0.6, diff --git a/example/lib/load_switch.dart b/example/lib/load_switch.dart index d0b6748..a143004 100644 --- a/example/lib/load_switch.dart +++ b/example/lib/load_switch.dart @@ -19,9 +19,9 @@ class _LoadSwitchState extends State { const borderWidth = 5.0; return CustomAnimatedToggleSwitch( height: height, - indicatorSize: Size.square(height), + indicatorSize: const Size.square(height), current: value, - values: [false, true], + values: const [false, true], onChanged: (newValue) { setState(() => value = newValue); return Future.delayed(const Duration(seconds: 2)); @@ -42,8 +42,8 @@ class _LoadSwitchState extends State { return Stack( fit: StackFit.expand, children: [ - Padding( - padding: const EdgeInsets.all(borderWidth), + const Padding( + padding: EdgeInsets.all(borderWidth), child: DecoratedBox( decoration: BoxDecoration( shape: BoxShape.circle, color: Colors.white)), diff --git a/example/lib/main.dart b/example/lib/main.dart index 1dc36e7..ccd9449 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -9,27 +9,29 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; void main() { - runApp(MyApp()); + runApp(const MyApp()); } class MyApp extends StatelessWidget { + const MyApp({super.key}); + @override Widget build(BuildContext context) { return MaterialApp( title: 'AnimatedToggleSwitch Demo', theme: ThemeData(scaffoldBackgroundColor: Colors.white), - home: MyHomePage(title: 'Example'), + home: const MyHomePage(title: 'AnimatedToggleSwitch'), ); } } class MyHomePage extends StatefulWidget { - MyHomePage({Key? key, required this.title}) : super(key: key); + const MyHomePage({Key? key, required this.title}) : super(key: key); final String title; @override - _MyHomePageState createState() => _MyHomePageState(); + State createState() => _MyHomePageState(); } class _MyHomePageState extends State { @@ -54,41 +56,43 @@ class _MyHomePageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'AnimatedToggleSwitch.dual:', ), ), - AnimatedToggleSwitch.dual( - current: positive, - first: false, - second: true, - spacing: 50.0, - style: const ToggleStyle( - borderColor: Colors.transparent, - boxShadow: const [ - BoxShadow( - color: Colors.black26, - spreadRadius: 1, - blurRadius: 2, - offset: Offset(0, 1.5), - ), - ], + SizedBox( + child: AnimatedToggleSwitch.dual( + current: positive, + first: false, + second: true, + spacing: 50.0, + style: const ToggleStyle( + borderColor: Colors.transparent, + boxShadow: [ + BoxShadow( + color: Colors.black26, + spreadRadius: 1, + blurRadius: 2, + offset: Offset(0, 1.5), + ), + ], + ), + borderWidth: 5.0, + height: 55, + onChanged: (b) => setState(() => positive = b), + styleBuilder: (b) => ToggleStyle( + indicatorColor: b ? Colors.red : Colors.green), + iconBuilder: (value) => value + ? const Icon(Icons.coronavirus_rounded) + : const Icon(Icons.tag_faces_rounded), + textBuilder: (value) => value + ? const Center(child: Text('Oh no...')) + : const Center(child: Text('Nice :)')), ), - borderWidth: 5.0, - height: 55, - onChanged: (b) => setState(() => positive = b), - styleBuilder: (b) => - ToggleStyle(indicatorColor: b ? Colors.red : Colors.green), - iconBuilder: (value) => value - ? Icon(Icons.coronavirus_rounded) - : Icon(Icons.tag_faces_rounded), - textBuilder: (value) => value - ? Center(child: Text('Oh no...')) - : Center(child: Text('Nice :)')), ), - SizedBox(height: 16.0), + const SizedBox(height: 16.0), AnimatedToggleSwitch.dual( current: positive, first: false, @@ -96,7 +100,7 @@ class _MyHomePageState extends State { spacing: 50.0, style: const ToggleStyle( borderColor: Colors.transparent, - boxShadow: const [ + boxShadow: [ BoxShadow( color: Colors.black26, spreadRadius: 1, @@ -130,28 +134,29 @@ class _MyHomePageState extends State { child: Text('Off', style: TextStyle(color: Colors.white))), ), - SizedBox(height: 16.0), + const 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), + data: const IconThemeData(color: Colors.white), child: AnimatedToggleSwitch.dual( current: positive, first: false, second: true, spacing: 45.0, animationDuration: const Duration(milliseconds: 600), - style: ToggleStyle( + style: const ToggleStyle( borderColor: Colors.transparent, indicatorColor: Colors.white, backgroundColor: Colors.black, ), customStyleBuilder: (context, local, global) { - if (global.position <= 0.0) + if (global.position <= 0.0) { return ToggleStyle(backgroundColor: Colors.red[800]); + } return ToggleStyle( backgroundGradient: LinearGradient( colors: [green, Colors.red[800]!], @@ -171,17 +176,17 @@ class _MyHomePageState extends State { Colors.red[800], green, global.position)), onChanged: (b) => setState(() => positive = b), iconBuilder: (value) => value - ? Icon(Icons.power_outlined, color: green, size: 32.0) + ? const 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')), + ? const Center(child: Text('Active')) + : const Center(child: Text('Inactive')), ), ), ), - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'AnimatedToggleSwitch.dual with loading animation:', ), @@ -189,7 +194,7 @@ class _MyHomePageState extends State { DefaultTextStyle.merge( style: const TextStyle(color: Colors.white), child: IconTheme.merge( - data: IconThemeData(color: Colors.white), + data: const IconThemeData(color: Colors.white), child: AnimatedToggleSwitch.dual( current: positive, first: false, @@ -199,7 +204,7 @@ class _MyHomePageState extends State { borderColor: Colors.transparent, backgroundColor: Colors.black, borderRadius: BorderRadius.all(Radius.circular(4.0)), - boxShadow: const [ + boxShadow: [ BoxShadow( color: Colors.purple, spreadRadius: 1, @@ -214,36 +219,36 @@ class _MyHomePageState extends State { const CupertinoActivityIndicator(color: Colors.white), onChanged: (b) { setState(() => positive = b); - return Future.delayed(Duration(seconds: 2)); + return Future.delayed(const Duration(seconds: 2)); }, styleBuilder: (b) => ToggleStyle( indicatorColor: b ? Colors.purple : Colors.green), iconBuilder: (value) => value - ? Icon(Icons.coronavirus_rounded) - : Icon(Icons.tag_faces_rounded), + ? const Icon(Icons.coronavirus_rounded) + : const Icon(Icons.tag_faces_rounded), textBuilder: (value) => value - ? Center( + ? const Center( child: Text('Oh no...', - style: const TextStyle(color: Colors.white))) - : Center(child: Text('Nice :)')), + style: TextStyle(color: Colors.white))) + : const Center(child: Text('Nice :)')), ), ), ), - SizedBox(height: 16.0), + const SizedBox(height: 16.0), AnimatedToggleSwitch.dual( current: positive, first: false, second: true, spacing: 45.0, animationDuration: const Duration(milliseconds: 600), - style: ToggleStyle( + style: const ToggleStyle( borderColor: Colors.transparent, indicatorColor: Colors.white, backgroundColor: Colors.amber, ), customStyleBuilder: (context, local, global) => ToggleStyle( backgroundGradient: LinearGradient( - colors: [Colors.green, Colors.red], + colors: const [Colors.green, Colors.red], stops: [ global.position - (1 - 2 * max(0, global.position - 0.5)) * 0.5, @@ -258,12 +263,12 @@ class _MyHomePageState extends State { Colors.red, Colors.green, global.position)), onChanged: (b) { setState(() => positive = b); - return Future.delayed(Duration(seconds: 2)); + return Future.delayed(const Duration(seconds: 2)); }, iconBuilder: (value) => value - ? Icon(Icons.power_outlined, + ? const Icon(Icons.power_outlined, color: Colors.green, size: 32.0) - : Icon(Icons.power_settings_new_rounded, + : const Icon(Icons.power_settings_new_rounded, color: Colors.red, size: 32.0), textBuilder: (value) => Center( child: Text( @@ -278,7 +283,7 @@ class _MyHomePageState extends State { padding: const EdgeInsets.all(8.0), child: Text.rich( TextSpan(children: [ - TextSpan(text: 'Switch inspired by package '), + const TextSpan(text: 'Switch inspired by package '), TextSpan( text: 'lite_rolling_switch', style: tappableTextStyle, @@ -293,7 +298,7 @@ class _MyHomePageState extends State { fontSize: 18.0, fontWeight: FontWeight.bold), child: IconTheme.merge( - data: IconThemeData(color: Colors.white), + data: const IconThemeData(color: Colors.white), child: AnimatedToggleSwitch.dual( current: positive, first: false, @@ -301,7 +306,7 @@ class _MyHomePageState extends State { spacing: 45.0, animationCurve: Curves.easeInOut, animationDuration: const Duration(milliseconds: 600), - style: ToggleStyle( + style: const ToggleStyle( borderColor: Colors.transparent, indicatorColor: Colors.white, backgroundColor: Colors.black, @@ -316,15 +321,15 @@ class _MyHomePageState extends State { Colors.red[800], green, global.position)), onChanged: (b) => setState(() => positive = b), iconBuilder: (value) => value - ? Icon(Icons.lightbulb_outline_rounded, + ? const Icon(Icons.lightbulb_outline_rounded, color: green, size: 28.0) : Icon(Icons.power_settings_new_rounded, color: Colors.red[800], size: 30.0), textBuilder: (value) => value - ? Align( + ? const Align( alignment: AlignmentDirectional.centerStart, child: Text('Active')) - : Align( + : const Align( alignment: AlignmentDirectional.centerEnd, child: Text('Inactive')), ), @@ -334,7 +339,7 @@ class _MyHomePageState extends State { padding: const EdgeInsets.all(8.0), child: Text.rich( TextSpan(children: [ - TextSpan(text: 'Switch inspired by package '), + const TextSpan(text: 'Switch inspired by package '), TextSpan( text: 'toggle_switch', style: tappableTextStyle, @@ -346,8 +351,8 @@ class _MyHomePageState extends State { AnimatedToggleSwitch.size( current: min(value, 2), style: ToggleStyle( - backgroundColor: Color(0xFF919191), - indicatorColor: Color(0xFFEC3345), + backgroundColor: const Color(0xFF919191), + indicatorColor: const Color(0xFFEC3345), borderColor: Colors.transparent, borderRadius: BorderRadius.circular(10.0), indicatorBorderRadius: BorderRadius.zero, @@ -383,7 +388,7 @@ class _MyHomePageState extends State { padding: const EdgeInsets.all(8.0), child: Text.rich( TextSpan(children: [ - TextSpan(text: 'Switch inspired by '), + const TextSpan(text: 'Switch inspired by '), TextSpan( text: 'Crazy Switch', style: tappableTextStyle, @@ -392,15 +397,15 @@ class _MyHomePageState extends State { ]), ), ), - Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 16.0), - child: const CrazySwitch(), + const Padding( + padding: EdgeInsets.only(top: 8.0, bottom: 16.0), + child: CrazySwitch(), ), Padding( padding: const EdgeInsets.all(8.0), child: Text.rich( TextSpan(children: [ - TextSpan(text: 'Switch inspired by '), + const TextSpan(text: 'Switch inspired by '), TextSpan( text: 'load_switch', style: tappableTextStyle, @@ -409,12 +414,12 @@ class _MyHomePageState extends State { ]), ), ), - Padding( - padding: const EdgeInsets.only(top: 8.0, bottom: 16.0), - child: const LoadSwitch(), + const Padding( + padding: EdgeInsets.only(top: 8.0, bottom: 16.0), + child: LoadSwitch(), ), - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'Standard AnimatedToggleSwitch.rolling:', ), @@ -425,12 +430,12 @@ class _MyHomePageState extends State { values: const [0, 1, 2, 3], onChanged: (i) { setState(() => value = i); - return Future.delayed(Duration(seconds: 3)); + return Future.delayed(const Duration(seconds: 3)); }, iconBuilder: rollingIconBuilder, ), - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'Switch with unselected value:', ), @@ -449,13 +454,13 @@ class _MyHomePageState extends State { return ToggleStyle(borderColor: color, indicatorColor: color); }, ), - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'Customized AnimatedToggleSwitch.rolling:', ), ), - SizedBox(height: 16.0), + const SizedBox(height: 16.0), AnimatedToggleSwitch.rolling( active: false, current: value, @@ -466,13 +471,13 @@ class _MyHomePageState extends State { value = i; loading = true; }); - return Future.delayed(Duration(seconds: 3)) + return Future.delayed(const Duration(seconds: 3)) .then((_) => setState(() => loading = false)); }, iconBuilder: rollingIconBuilder, - style: ToggleStyle( + style: const ToggleStyle( borderColor: Colors.transparent, - indicatorBoxShadow: const [ + indicatorBoxShadow: [ BoxShadow( color: Colors.black26, spreadRadius: 1, @@ -480,7 +485,7 @@ class _MyHomePageState extends State { offset: Offset(0, 1.5), ) ], - boxShadow: const [ + boxShadow: [ BoxShadow( color: Colors.black26, spreadRadius: 1, @@ -490,17 +495,17 @@ class _MyHomePageState extends State { ], ), ), - SizedBox(height: 16.0), + const SizedBox(height: 16.0), IconTheme.merge( - data: IconThemeData(color: Colors.white), + data: const IconThemeData(color: Colors.white), child: AnimatedToggleSwitch.rolling( current: value, values: const [0, 1, 2, 3], onChanged: (i) => setState(() => value = i), - style: ToggleStyle( + style: const ToggleStyle( indicatorColor: Colors.white, borderColor: Colors.transparent, - boxShadow: const [ + boxShadow: [ BoxShadow( color: Colors.black26, spreadRadius: 1, @@ -520,7 +525,7 @@ class _MyHomePageState extends State { ), ), ), - SizedBox(height: 16.0), + const SizedBox(height: 16.0), AnimatedToggleSwitch.rolling( current: value, allowUnlistedValues: true, @@ -543,7 +548,7 @@ class _MyHomePageState extends State { spacing: 20.0, loading: loading, ), - SizedBox(height: 16.0), + const SizedBox(height: 16.0), AnimatedToggleSwitch.rolling( current: nullableValue, allowUnlistedValues: true, @@ -556,34 +561,35 @@ class _MyHomePageState extends State { onChanged: (i) => setState(() => nullableValue = i), iconBuilder: rollingIconBuilder, borderWidth: 4.5, - style: ToggleStyle( + style: const ToggleStyle( indicatorColor: Colors.white, backgroundGradient: - const LinearGradient(colors: [Colors.red, Colors.blue]), + LinearGradient(colors: [Colors.red, Colors.blue]), borderColor: Colors.transparent, ), height: 55, spacing: 20.0, loading: loading, ), - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'You can make any other switch with CustomAnimatedToggleSwitch:', ), ), CustomAnimatedToggleSwitch( current: positive, - values: [false, true], + values: const [false, true], spacing: 0.0, - indicatorSize: Size.square(30.0), + indicatorSize: const Size.square(30.0), animationDuration: const Duration(milliseconds: 200), animationCurve: Curves.linear, onChanged: (b) => setState(() => positive = b), iconBuilder: (context, local, global) { return const SizedBox(); }, - cursors: ToggleCursors(defaultCursor: SystemMouseCursors.click), + cursors: const ToggleCursors( + defaultCursor: SystemMouseCursors.click), onTap: (_) => setState(() => positive = !positive), iconsTappable: false, wrapperBuilder: (context, global, child) { @@ -596,10 +602,8 @@ class _MyHomePageState extends State { height: 20.0, child: DecoratedBox( decoration: BoxDecoration( - color: Color.lerp( - Colors.black26, - theme.colorScheme.background, - global.position), + color: Color.lerp(Colors.black26, + theme.colorScheme.surface, global.position), borderRadius: const BorderRadius.all(Radius.circular(50.0)), ), @@ -615,7 +619,8 @@ class _MyHomePageState extends State { decoration: BoxDecoration( color: Color.lerp( Colors.white, theme.primaryColor, global.position), - borderRadius: BorderRadius.all(Radius.circular(50.0)), + borderRadius: + const BorderRadius.all(Radius.circular(50.0)), boxShadow: const [ BoxShadow( color: Colors.black38, @@ -628,8 +633,8 @@ class _MyHomePageState extends State { ); }, ), - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'AnimatedToggleSwitch.size with some custom settings:', ), @@ -643,7 +648,7 @@ class _MyHomePageState extends State { styleAnimationType: AnimationType.onHover, iconBuilder: (value) => Icon( value.isEven ? Icons.cancel : Icons.access_time_rounded), - style: ToggleStyle( + style: const ToggleStyle( borderColor: Colors.transparent, ), borderWidth: 0.0, @@ -656,7 +661,7 @@ class _MyHomePageState extends State { }, onChanged: (i) { setState(() => value = i); - return Future.delayed(Duration(seconds: 3)); + return Future.delayed(const Duration(seconds: 3)); }, ), const SizedBox(height: 16.0), @@ -673,7 +678,7 @@ class _MyHomePageState extends State { borderColor: Colors.transparent, borderRadius: BorderRadius.circular(10.0), boxShadow: [ - BoxShadow( + const BoxShadow( color: Colors.black26, spreadRadius: 1, blurRadius: 2, @@ -703,7 +708,7 @@ class _MyHomePageState extends State { borderColor: Colors.transparent, borderRadius: BorderRadius.circular(10.0), boxShadow: [ - BoxShadow( + const BoxShadow( color: Colors.black26, spreadRadius: 1, blurRadius: 2, @@ -714,8 +719,8 @@ class _MyHomePageState extends State { selectedIconScale: 1.0, onChanged: (b) => setState(() => positive = b), ), - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'AnimatedToggleSwitch.size with a more custom icon and TextDirection.rtl:', ), @@ -735,14 +740,14 @@ class _MyHomePageState extends State { ], ); }, - style: ToggleStyle(borderColor: Colors.transparent), + style: const ToggleStyle(borderColor: Colors.transparent), styleBuilder: (i) => ToggleStyle( indicatorColor: i.isEven == true ? Colors.amber : Colors.red), onChanged: (i) => setState(() => value = i), ), - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'AnimatedToggleSwitch.size with custom rotating animation:', ), @@ -778,8 +783,8 @@ class _MyHomePageState extends State { i.isEven == true ? Colors.green : Colors.tealAccent), onChanged: (i) => setState(() => value = i), ), - Padding( - padding: const EdgeInsets.all(8.0), + const Padding( + padding: EdgeInsets.all(8.0), child: Text( 'AnimatedToggleSwitch.rollingByHeight with custom indicatorSize and borderRadius:', ), @@ -792,7 +797,7 @@ class _MyHomePageState extends State { iconBuilder: rollingIconBuilder, indicatorSize: const Size.fromWidth(2), ), - SizedBox(height: 16.0), + const SizedBox(height: 16.0), AnimatedToggleSwitch.rollingByHeight( height: 50.0, current: value, diff --git a/example/pubspec.lock b/example/pubspec.lock index 9a178cc..3bf754a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -75,26 +75,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" matcher: dependency: transitive description: @@ -107,18 +107,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.15.0" path: dependency: transitive description: @@ -131,10 +131,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.8" sky_engine: dependency: transitive description: flutter @@ -184,74 +184,74 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.2" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" url: "https://pub.dev" source: hosted - version: "6.1.12" + version: "6.3.0" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: "3dd2388cc0c42912eee04434531a26a82512b9cb1827e0214430c9bcbddfe025" + sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9" url: "https://pub.dev" source: hosted - version: "6.0.38" + version: "6.3.8" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "6.3.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.2.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "1c4fdc0bfea61a70792ce97157e5cc17260f61abbe4f39354513f39ec6fd73b1" + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" url: "https://pub.dev" source: hosted - version: "2.0.18" + version: "2.3.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.1.2" vector_math: dependency: transitive description: @@ -264,10 +264,18 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + url: "https://pub.dev" + source: hosted + version: "14.2.4" + web: + dependency: transitive + description: + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "1.0.0" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart index 49fe357..da833c4 100644 --- a/example/test/widget_test.dart +++ b/example/test/widget_test.dart @@ -3,6 +3,6 @@ import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('App starts successfully', (WidgetTester tester) async { - await tester.pumpWidget(MyApp()); + await tester.pumpWidget(const MyApp()); }); } diff --git a/lib/src/properties.dart b/lib/src/properties.dart index 59c042f..14eb976 100644 --- a/lib/src/properties.dart +++ b/lib/src/properties.dart @@ -55,6 +55,8 @@ class GlobalToggleProperties { final bool active; + final bool vertical; + /// This animation indicates whether the indicator is currently visible. /// /// [0.0] means it is not visible. @@ -77,6 +79,7 @@ class GlobalToggleProperties { required this.mode, required this.loadingAnimationValue, required this.active, + required this.vertical, required Animation indicatorAppearingAnimation, }) : _indicatorAppearingAnimation = indicatorAppearingAnimation; } @@ -112,6 +115,7 @@ class DetailedGlobalToggleProperties extends GlobalToggleProperties { required super.loadingAnimationValue, required super.active, required super.indicatorAppearingAnimation, + required super.vertical, }); } diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index 0b1311f..2f74f22 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -977,6 +977,8 @@ class AnimatedToggleSwitch AlignmentGeometry alignment = start ? AlignmentDirectional.centerStart : AlignmentDirectional.centerEnd; + final finalOffset = + global.vertical ? Offset(offset.dy, offset.dx) : offset; return Align( alignment: alignment, @@ -993,7 +995,7 @@ class AnimatedToggleSwitch child: Padding( padding: textMargin, child: Transform.translate( - offset: offset * local.animationValue * (left ? 1 : -1), + offset: (finalOffset * local.animationValue * (left ? 1 : -1)), child: Center( child: Opacity( opacity: opacityAnimation @@ -1017,6 +1019,13 @@ class AnimatedToggleSwitch }; } + /// {@macro animated_toggle_switch.method.vertical} + Widget vertical() { + return Builder(builder: (context) { + return _build(context).vertical(); + }); + } + static Widget _defaultLoadingIconBuilder(BuildContext context, DetailedGlobalToggleProperties properties) => const _MyLoading(); @@ -1043,6 +1052,10 @@ class AnimatedToggleSwitch @override Widget build(BuildContext context) { + return _build(context); + } + + CustomAnimatedToggleSwitch _build(BuildContext context) { ThemeData theme = Theme.of(context); BorderRadiusGeometry defaultBorderRadius = BorderRadius.circular(height / 2); diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index 7592a91..679e25d 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -193,6 +193,8 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { /// Listener for the current position and [ToggleMode] of the indicator. final PositionListener? positionListener; + final bool _vertical; + const CustomAnimatedToggleSwitch({ Key? key, required this.current, @@ -228,15 +230,124 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { this.allowUnlistedValues = false, this.active = true, this.positionListener, - }) : assert(foregroundIndicatorBuilder != null || + }) : _vertical = false, + assert(foregroundIndicatorBuilder != null || backgroundIndicatorBuilder != null), assert(separatorBuilder == null || (spacing > 0 && iconArrangement == IconArrangement.row)), super(key: key); + const CustomAnimatedToggleSwitch._({ + required this.current, + required this.values, + required this.wrapperBuilder, + required this.iconBuilder, + required this.foregroundIndicatorBuilder, + required this.backgroundIndicatorBuilder, + required this.indicatorAppearingBuilder, + required this.animationDuration, + required this.animationCurve, + required this.loadingAnimationDuration, + required this.loadingAnimationCurve, + required this.indicatorAppearingDuration, + required this.indicatorAppearingCurve, + required this.indicatorSize, + required this.onChanged, + required this.spacing, + required this.separatorBuilder, + required this.onTap, + required this.iconsTappable, + required this.iconArrangement, + required this.fittingMode, + required this.height, + required this.padding, + required this.minTouchTargetSize, + required this.dragStartDuration, + required this.dragStartCurve, + required this.textDirection, + required this.cursors, + required this.loading, + required this.allowUnlistedValues, + required this.active, + required this.positionListener, + required bool vertical, + }) : _vertical = vertical; + @override State> createState() => _CustomAnimatedToggleSwitchState(); + + /// {@template animated_toggle_switch.method.vertical} + /// Rotates this switch and its builders and returns a vertical version of itself. + /// {@endtemplate} + Widget vertical() { + if (_vertical) return this; + + return RotatedBox( + quarterTurns: 1, + child: CustomAnimatedToggleSwitch._( + vertical: true, + current: current, + values: values, + iconBuilder: (context, global, local) => RotatedBox( + quarterTurns: -1, + child: iconBuilder(context, global, local), + ), + indicatorAppearingBuilder: (context, value, indicator) => RotatedBox( + quarterTurns: -1, + child: indicatorAppearingBuilder( + context, + value, + RotatedBox(quarterTurns: 1, child: indicator), + ), + ), + animationDuration: animationDuration, + animationCurve: animationCurve, + indicatorAppearingDuration: indicatorAppearingDuration, + indicatorAppearingCurve: indicatorAppearingCurve, + indicatorSize: indicatorSize, + spacing: spacing, + iconsTappable: iconsTappable, + iconArrangement: iconArrangement, + fittingMode: fittingMode, + height: height, + padding: padding, + minTouchTargetSize: minTouchTargetSize, + dragStartDuration: dragStartDuration, + dragStartCurve: dragStartCurve, + cursors: cursors, + allowUnlistedValues: allowUnlistedValues, + active: active, + wrapperBuilder: wrapperBuilder, + foregroundIndicatorBuilder: foregroundIndicatorBuilder == null + ? null + : (BuildContext context, + DetailedGlobalToggleProperties global) { + return RotatedBox( + quarterTurns: -1, + child: foregroundIndicatorBuilder!(context, global), + ); + }, + backgroundIndicatorBuilder: backgroundIndicatorBuilder == null + ? null + : (BuildContext context, + DetailedGlobalToggleProperties global) { + return RotatedBox( + quarterTurns: -1, + child: backgroundIndicatorBuilder!(context, global), + ); + }, + loadingAnimationDuration: loadingAnimationDuration, + loadingAnimationCurve: loadingAnimationCurve, + onChanged: onChanged, + separatorBuilder: separatorBuilder, + onTap: onTap, + // by default the icons are ordered top down + textDirection: textDirection ?? TextDirection.ltr, + loading: loading, + positionListener: positionListener, + )); + } } class _CustomAnimatedToggleSwitchState @@ -457,7 +568,7 @@ class _CustomAnimatedToggleSwitchState @override Widget build(BuildContext context) { double spacing = widget.spacing; - final textDirection = _textDirectionOf(context); + final textDirection = widget.textDirection ?? _textDirectionOf(context); final loadingValue = _animationInfo.loading ? 1.0 : 0.0; final privateIndicatorAppearingAnimation = _PrivateAnimation(_appearingAnimation); @@ -507,6 +618,7 @@ class _CustomAnimatedToggleSwitchState active: widget.active, indicatorAppearingAnimation: privateIndicatorAppearingAnimation, + vertical: widget._vertical, ); Widget child = Padding( padding: widget.padding, @@ -620,6 +732,7 @@ class _CustomAnimatedToggleSwitchState active: widget.active, indicatorAppearingAnimation: privateIndicatorAppearingAnimation, + vertical: widget._vertical, ); List stack = [ @@ -758,7 +871,7 @@ class _CustomAnimatedToggleSwitchState double position = i * (properties.indicatorSize.width + properties.spacing); return Positioned.directional( - textDirection: _textDirectionOf(context), + textDirection: properties.textDirection, start: i == 0 ? position : position - properties.spacing, width: (i == 0 || i == widget.values.length - 1 ? 1 : 2) * properties.spacing + @@ -789,7 +902,7 @@ class _CustomAnimatedToggleSwitchState final length = properties.values.length; return [ Row( - textDirection: _textDirectionOf(context), + textDirection: properties.textDirection, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ for (int i = 0; i < length; i++) ...[ @@ -864,14 +977,12 @@ class _CustomAnimatedToggleSwitchState _setAnimationInfo(_animationInfo.none()); _checkValuePosition(); } - - /// Returns the [TextDirection] of the widget. - TextDirection _textDirectionOf(BuildContext context) => - widget.textDirection ?? - Directionality.maybeOf(context) ?? - TextDirection.ltr; } +/// Returns the [TextDirection] from the [BuildContext]. +TextDirection _textDirectionOf(BuildContext context) => + Directionality.maybeOf(context) ?? TextDirection.ltr; + /// The [Positioned] for an indicator. It is used as wrapper for /// [CustomAnimatedToggleSwitch.foregroundIndicatorBuilder] and /// [CustomAnimatedToggleSwitch.backgroundIndicatorBuilder]. diff --git a/pubspec.lock b/pubspec.lock index f463a74..5d36f9f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,31 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.7.0" args: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" async: dependency: transitive description: @@ -73,14 +78,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" - coverage: - dependency: transitive - description: - name: coverage - sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76" - url: "https://pub.dev" - source: hosted - version: "1.7.2" crypto: dependency: transitive description: @@ -89,6 +86,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + dartdoc: + dependency: "direct dev" + description: + name: dartdoc + sha256: c824d4691231964f8ba24e2fd6f573ac9878fa7a8c188c49f700da18eda79218 + url: "https://pub.dev" + source: hosted + version: "8.0.13" fake_async: dependency: transitive description: @@ -114,23 +127,15 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" flutter_test: dependency: "direct dev" 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: @@ -139,62 +144,38 @@ packages: 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: + html: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" url: "https://pub.dev" source: hosted - version: "0.6.7" + version: "0.15.4" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.1" lints: dependency: transitive description: @@ -211,6 +192,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" + markdown: + dependency: transitive + description: + name: markdown + sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + url: "https://pub.dev" + source: hosted + version: "7.2.2" matcher: dependency: transitive description: @@ -223,42 +220,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 - url: "https://pub.dev" - source: hosted - version: "1.11.0" - mime: - dependency: transitive - description: - name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.15.0" mocktail: dependency: "direct dev" description: name: mocktail - sha256: "9503969a7c2c78c7292022c70c0289ed6241df7a9ba720010c0b215af29a5a58" + sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" url: "https://pub.dev" source: hosted - version: "1.0.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" + version: "1.0.4" package_config: dependency: transitive description: @@ -275,14 +256,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.0" - pool: - dependency: transitive - description: - name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" - source: hosted - version: "1.5.1" pub_semver: dependency: transitive description: @@ -291,59 +264,11 @@ packages: 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: @@ -384,30 +309,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" - test: - dependency: transitive - description: - name: test - sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f - url: "https://pub.dev" - source: hosted - version: "1.24.9" test_api: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" - url: "https://pub.dev" - source: hosted - version: "0.6.1" - test_core: - dependency: transitive - description: - name: test_core - sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.5.9" + version: "0.7.2" typed_data: dependency: transitive description: @@ -428,10 +337,10 @@ packages: dependency: transitive description: name: vm_service - sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "13.0.0" + version: "14.2.4" watcher: dependency: transitive description: @@ -440,22 +349,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" - web_socket_channel: + web: dependency: transitive description: - name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + name: web + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 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" + version: "1.0.0" yaml: dependency: transitive description: @@ -465,5 +366,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-0 <4.0.0" - flutter: ">=1.17.0" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index 3a3cf09..bc4fbd7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,6 +17,7 @@ dev_dependencies: flutter_test: sdk: flutter mocktail: ^1.0.0 + dartdoc: ^8.0.13 flutter: diff --git a/test/vertical_test.dart b/test/vertical_test.dart new file mode 100644 index 0000000..f27bd41 --- /dev/null +++ b/test/vertical_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'helper.dart'; + +void _verifyVertical(WidgetTester tester, List values) { + final firstPos = tester.getCenter(find.byKey(iconKey(values.first))); + final lastPos = tester.getCenter(find.byKey(iconKey(values.last))); + expect((firstPos - lastPos).dx == 0, true, + reason: 'Icons do not have the same x-coordinate'); + expect((firstPos - lastPos).dy < 0, true, + reason: 'Icons should be ordered top down'); + +} + +void main() { + defaultTestAllSwitches('Switch respects TextDirection', + (tester, buildSwitch, type, values) async { + final current = values[1]; + await tester.pumpWidget(TestWrapper( + child: buildSwitch( + current: current, + iconBuilder: iconBuilder, + ).vertical(), + )); + _verifyVertical(tester, values); + }); +} From 247bfd249fb02432a75e1611f0f9f76cf9f9c49a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Mon, 12 Aug 2024 09:06:08 +0200 Subject: [PATCH 2/8] 61: dart format --- example/lib/main.dart | 6 ++++-- test/vertical_test.dart | 1 - 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index ccd9449..5d4c536 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -176,7 +176,8 @@ class _MyHomePageState extends State { Colors.red[800], green, global.position)), onChanged: (b) => setState(() => positive = b), iconBuilder: (value) => value - ? const Icon(Icons.power_outlined, color: green, size: 32.0) + ? const 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 @@ -219,7 +220,8 @@ class _MyHomePageState extends State { const CupertinoActivityIndicator(color: Colors.white), onChanged: (b) { setState(() => positive = b); - return Future.delayed(const Duration(seconds: 2)); + return Future.delayed( + const Duration(seconds: 2)); }, styleBuilder: (b) => ToggleStyle( indicatorColor: b ? Colors.purple : Colors.green), diff --git a/test/vertical_test.dart b/test/vertical_test.dart index f27bd41..3fd74f8 100644 --- a/test/vertical_test.dart +++ b/test/vertical_test.dart @@ -9,7 +9,6 @@ void _verifyVertical(WidgetTester tester, List values) { reason: 'Icons do not have the same x-coordinate'); expect((firstPos - lastPos).dy < 0, true, reason: 'Icons should be ordered top down'); - } void main() { From 4fcb72ce3b5a3aa5738bee3aeadd64015523a6bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Mon, 12 Aug 2024 16:35:35 +0200 Subject: [PATCH 3/8] 61: updates readme --- CHANGELOG.md | 4 ++++ README.md | 12 ++++++++++-- example/lib/main.dart | 18 ++++++++++++++++++ lib/src/widgets/animated_toggle_switch.dart | 3 +++ test/vertical_test.dart | 2 +- 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6e91e4..b7b9e93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.8.3 + +- adds support for vertical switches ([#61](https://github.com/splashbyte/animated_toggle_switch/issues/61)) + ## 0.8.2 (2024-02-26) - adds `clipBehavior` to `AnimatedToggleSwitch` ([#56](https://github.com/splashbyte/animated_toggle_switch/issues/56)) diff --git a/README.md b/README.md index 6e7ffad..ba30cbe 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ Fully customizable, draggable and animated switch with multiple choices and [smooth loading animation](#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](#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](#simple-rolling-animation) and still customize a lot if necessary. There are several options for [styling](#styling) it. +[Switches without an (initial) selection](#nullable-selection) and [vertical switches](#vertical-switches) are also possible. +Most builder arguments of `AnimatedToggleSwitch` have standard and a custom version. This ensures that you can [get started easily](#simple-rolling-animation) and still customize a lot if necessary. There are several options for [styling](#styling) it. For a slider with a similar look you can check out [action_slider](https://pub.dev/packages/action_slider). @@ -129,6 +129,14 @@ AnimatedToggleSwitch.rolling( ) ``` +### Vertical switches + +To use this feature, you simply have to call `vertical()` on the switch you want to make vertical. + +```dart +AnimatedToggleSwitch.rolling(...).vertical() +``` + ### Fully customizable toggle switch with `CustomAnimatedToggleSwitch` ```dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 5d4c536..8bae052 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -456,6 +456,24 @@ class _MyHomePageState extends State { return ToggleStyle(borderColor: color, indicatorColor: color); }, ), + const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + 'You can make any switch vertical:', + ), + ), + AnimatedToggleSwitch.rolling( + current: value, + values: const [0, 1, 2, 3], + indicatorIconScale: sqrt2, + style: const ToggleStyle( + indicatorColor: Colors.blue, + borderColor: Colors.blue, + backgroundColor: Colors.white, + ), + onChanged: (i) => setState(() => value = i), + iconBuilder: rollingIconBuilder, + ).vertical(), const Padding( padding: EdgeInsets.all(8.0), child: Text( diff --git a/lib/src/widgets/animated_toggle_switch.dart b/lib/src/widgets/animated_toggle_switch.dart index 2f74f22..c4b8982 100644 --- a/lib/src/widgets/animated_toggle_switch.dart +++ b/lib/src/widgets/animated_toggle_switch.dart @@ -1055,6 +1055,9 @@ class AnimatedToggleSwitch return _build(context); } + /// Separate build-method, so [build] does not return + /// [CustomAnimatedToggleSwitch], which would make changing the return type + /// back to [Widget] a breaking change. CustomAnimatedToggleSwitch _build(BuildContext context) { ThemeData theme = Theme.of(context); BorderRadiusGeometry defaultBorderRadius = diff --git a/test/vertical_test.dart b/test/vertical_test.dart index 3fd74f8..1eb7d9b 100644 --- a/test/vertical_test.dart +++ b/test/vertical_test.dart @@ -12,7 +12,7 @@ void _verifyVertical(WidgetTester tester, List values) { } void main() { - defaultTestAllSwitches('Switch respects TextDirection', + defaultTestAllSwitches('Vertical switch', (tester, buildSwitch, type, values) async { final current = values[1]; await tester.pumpWidget(TestWrapper( From b49199d9219f27050df7c2e5c6f5bcc15d4286cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Mon, 12 Aug 2024 17:17:55 +0200 Subject: [PATCH 4/8] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba30cbe..76b8860 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ AnimatedToggleSwitch.rolling( ``` ### Vertical switches - +![animated_toggle_switch_example_vertical](https://github.com/user-attachments/assets/b6b804d0-a8f0-47a2-bf73-e9e0b65773d7) To use this feature, you simply have to call `vertical()` on the switch you want to make vertical. ```dart From f971edd850bb63fa4b0f7707880d67625039bd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Mon, 12 Aug 2024 17:18:33 +0200 Subject: [PATCH 5/8] 61: fixes mouse cursor issues --- example/.gitignore | 1 - example/lib/main.dart | 1 + lib/animated_toggle_switch.dart | 2 +- .../custom_animated_toggle_switch.dart | 640 +++++++++--------- lib/src/widgets/drag_region.dart | 94 --- lib/src/widgets/hover_region.dart | 57 ++ pubspec.yaml | 2 +- 7 files changed, 388 insertions(+), 409 deletions(-) delete mode 100644 lib/src/widgets/drag_region.dart create mode 100644 lib/src/widgets/hover_region.dart diff --git a/example/.gitignore b/example/.gitignore index 0fa6b67..a1345d0 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -32,7 +32,6 @@ /build/ # Web related -lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols diff --git a/example/lib/main.dart b/example/lib/main.dart index 8bae052..81e7cdb 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -473,6 +473,7 @@ class _MyHomePageState extends State { ), onChanged: (i) => setState(() => value = i), iconBuilder: rollingIconBuilder, + loading: false, ).vertical(), const Padding( padding: EdgeInsets.all(8.0), diff --git a/lib/animated_toggle_switch.dart b/lib/animated_toggle_switch.dart index 69be9a9..ddf0292 100644 --- a/lib/animated_toggle_switch.dart +++ b/lib/animated_toggle_switch.dart @@ -18,5 +18,5 @@ part 'src/widgets/animation_type_builder.dart'; part 'src/foreground_indicator_transition.dart'; part 'src/widgets/animated_toggle_switch.dart'; part 'src/widgets/custom_animated_toggle_switch.dart'; -part 'src/widgets/drag_region.dart'; +part 'src/widgets/hover_region.dart'; part 'src/widgets/conditional_wrapper.dart'; diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index 679e25d..14a3705 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -39,8 +39,8 @@ enum FittingMode { none, preventHorizontalOverlapping } const _defaultIndicatorAppearingAnimationDuration = Duration(milliseconds: 350); const _defaultIndicatorAppearingAnimationCurve = Curves.easeOutBack; -Widget _defaultIndicatorAppearingBuilder( - BuildContext context, double value, Widget indicator) { +Widget _defaultIndicatorAppearingBuilder(BuildContext context, double value, + Widget indicator) { return Transform.scale(scale: value, child: indicator); } @@ -230,7 +230,8 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { this.allowUnlistedValues = false, this.active = true, this.positionListener, - }) : _vertical = false, + }) + : _vertical = false, assert(foregroundIndicatorBuilder != null || backgroundIndicatorBuilder != null), assert(separatorBuilder == null || @@ -289,18 +290,20 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { vertical: true, current: current, values: values, - iconBuilder: (context, global, local) => RotatedBox( - quarterTurns: -1, - child: iconBuilder(context, global, local), - ), - indicatorAppearingBuilder: (context, value, indicator) => RotatedBox( - quarterTurns: -1, - child: indicatorAppearingBuilder( - context, - value, - RotatedBox(quarterTurns: 1, child: indicator), - ), - ), + iconBuilder: (context, global, local) => + RotatedBox( + quarterTurns: -1, + child: iconBuilder(context, global, local), + ), + indicatorAppearingBuilder: (context, value, indicator) => + RotatedBox( + quarterTurns: -1, + child: indicatorAppearingBuilder( + context, + value, + RotatedBox(quarterTurns: 1, child: indicator), + ), + ), animationDuration: animationDuration, animationCurve: animationCurve, indicatorAppearingDuration: indicatorAppearingDuration, @@ -322,21 +325,21 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { foregroundIndicatorBuilder: foregroundIndicatorBuilder == null ? null : (BuildContext context, - DetailedGlobalToggleProperties global) { - return RotatedBox( - quarterTurns: -1, - child: foregroundIndicatorBuilder!(context, global), - ); - }, + DetailedGlobalToggleProperties global) { + return RotatedBox( + quarterTurns: -1, + child: foregroundIndicatorBuilder!(context, global), + ); + }, backgroundIndicatorBuilder: backgroundIndicatorBuilder == null ? null : (BuildContext context, - DetailedGlobalToggleProperties global) { - return RotatedBox( - quarterTurns: -1, - child: backgroundIndicatorBuilder!(context, global), - ); - }, + DetailedGlobalToggleProperties global) { + return RotatedBox( + quarterTurns: -1, + child: backgroundIndicatorBuilder!(context, global), + ); + }, loadingAnimationDuration: loadingAnimationDuration, loadingAnimationCurve: loadingAnimationCurve, onChanged: onChanged, @@ -351,7 +354,8 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { } class _CustomAnimatedToggleSwitchState - extends State> with TickerProviderStateMixin { + extends State> + with TickerProviderStateMixin { /// The [AnimationController] for the movement of the indicator. late final AnimationController _controller; @@ -374,9 +378,10 @@ class _CustomAnimatedToggleSwitchState bool get _isCurrentUnlisted => _currentIndex < 0; - double get _positionValue => _animationInfo - .valueAt(_animation.value) - .clamp(0, widget.values.length - 1); + double get _positionValue => + _animationInfo + .valueAt(_animation.value) + .clamp(0, widget.values.length - 1); PositionListenerInfo? _lastPositionListenerValue; @@ -392,17 +397,17 @@ class _CustomAnimatedToggleSwitchState _AnimationInfo(isValueSelected ? _currentIndex.toDouble() : 0.0) .setLoading(widget.loading ?? false); _controller = - AnimationController(vsync: this, duration: widget.animationDuration) - ..addStatusListener((status) { - if (status == AnimationStatus.completed && - _animationInfo.toggleMode != ToggleMode.dragged) { - _setAnimationInfo(_animationInfo.ended()); - } - }); + AnimationController(vsync: this, duration: widget.animationDuration) + ..addStatusListener((status) { + if (status == AnimationStatus.completed && + _animationInfo.toggleMode != ToggleMode.dragged) { + _setAnimationInfo(_animationInfo.ended()); + } + }); _animation = - CurvedAnimation(parent: _controller, curve: widget.animationCurve) - ..addListener(_callPositionListener); + CurvedAnimation(parent: _controller, curve: widget.animationCurve) + ..addListener(_callPositionListener); _appearingController = AnimationController( vsync: this, @@ -430,9 +435,9 @@ class _CustomAnimatedToggleSwitchState try { throw ArgumentError( 'The values in AnimatedToggleSwitch have to contain current if allowUnlistedValues is false.\n' - 'current: ${widget.current}\n' - 'values: ${widget.values}\n' - 'This error is only thrown in debug mode to minimize problems with the production app.'); + 'current: ${widget.current}\n' + 'values: ${widget.values}\n' + 'This error is only thrown in debug mode to minimize problems with the production app.'); } catch (e, s) { if (kDebugMode) rethrow; FlutterError.reportError(FlutterErrorDetails( @@ -516,13 +521,13 @@ class _CustomAnimatedToggleSwitchState /// Returns the value position by the local position of the cursor. /// It is mainly intended as a helper function for the build method. - double _doubleFromPosition( - double x, DetailedGlobalToggleProperties properties) { + double _doubleFromPosition(double x, + DetailedGlobalToggleProperties properties) { double result = (x.clamp( - properties.indicatorSize.width / 2, - properties.switchSize.width - - properties.indicatorSize.width / 2) - - properties.indicatorSize.width / 2) / + properties.indicatorSize.width / 2, + properties.switchSize.width - + properties.indicatorSize.width / 2) - + properties.indicatorSize.width / 2) / (properties.indicatorSize.width + properties.spacing); if (properties.textDirection == TextDirection.rtl) { result = widget.values.length - 1 - result; @@ -560,8 +565,8 @@ class _CustomAnimatedToggleSwitchState /// Returns the [TogglePosition] by the local position of the cursor. /// It is mainly intended as a helper function for the build method. - TogglePosition _togglePositionFromRealPosition( - double x, DetailedGlobalToggleProperties properties) { + TogglePosition _togglePositionFromRealPosition(double x, + DetailedGlobalToggleProperties properties) { return _togglePositionFromPositionValue(_doubleFromPosition(x, properties)); } @@ -571,16 +576,16 @@ class _CustomAnimatedToggleSwitchState final textDirection = widget.textDirection ?? _textDirectionOf(context); final loadingValue = _animationInfo.loading ? 1.0 : 0.0; final privateIndicatorAppearingAnimation = - _PrivateAnimation(_appearingAnimation); + _PrivateAnimation(_appearingAnimation); final defaultCursor = !_isActive ? (_animationInfo.loading - ? widget.cursors.loadingCursor - : widget.cursors.inactiveCursor) + ? widget.cursors.loadingCursor + : widget.cursors.inactiveCursor) : (widget.cursors.defaultCursor ?? - (widget.onTap == null - ? MouseCursor.defer - : SystemMouseCursors.click)); + (widget.onTap == null + ? MouseCursor.defer + : SystemMouseCursors.click)); return SizedBox( height: widget.height, @@ -589,274 +594,287 @@ class _CustomAnimatedToggleSwitchState cursor: defaultCursor, child: GestureDetector( behavior: HitTestBehavior.deferToChild, - onTapUp: (_) => _onTap(TapProperties( - tapped: null, - values: widget.values, - )), + onTapUp: (_) => + _onTap(TapProperties( + tapped: null, + values: widget.values, + )), child: TweenAnimationBuilder( duration: - widget.loadingAnimationDuration ?? widget.animationDuration, + widget.loadingAnimationDuration ?? widget.animationDuration, curve: widget.loadingAnimationCurve ?? widget.animationCurve, tween: Tween(begin: loadingValue, end: loadingValue), - builder: (context, loadingValue, child) => AnimatedBuilder( - animation: _animation, - builder: (context, child) { - double positionValue = _positionValue; - GlobalToggleProperties properties = GlobalToggleProperties( - position: positionValue, - current: widget.current, - currentIndex: _currentIndex, - previous: + builder: (context, loadingValue, child) => + AnimatedBuilder( + animation: _animation, + builder: (context, child) { + double positionValue = _positionValue; + GlobalToggleProperties< + T> properties = GlobalToggleProperties( + position: positionValue, + current: widget.current, + currentIndex: _currentIndex, + previous: _animationInfo.start.toInt() == _animationInfo.start ? widget.values[_animationInfo.start.toInt()] : null, - values: widget.values, - previousPosition: _animationInfo.start, - textDirection: textDirection, - mode: _animationInfo.toggleMode, - loadingAnimationValue: loadingValue, - active: widget.active, - indicatorAppearingAnimation: + values: widget.values, + previousPosition: _animationInfo.start, + textDirection: textDirection, + mode: _animationInfo.toggleMode, + loadingAnimationValue: loadingValue, + active: widget.active, + indicatorAppearingAnimation: privateIndicatorAppearingAnimation, - vertical: widget._vertical, - ); - Widget child = Padding( - padding: widget.padding, - child: LayoutBuilder( - builder: (context, constraints) { - double height = constraints.maxHeight; - assert( + vertical: widget._vertical, + ); + Widget child = Padding( + padding: widget.padding, + child: LayoutBuilder( + builder: (context, constraints) { + double height = constraints.maxHeight; + assert( constraints.maxWidth.isFinite || (widget.indicatorSize.width.isFinite && spacing.isFinite), 'With unbound width constraints ' - 'the width of the indicator and the spacing ' - "can't be infinite"); - assert( + 'the width of the indicator and the spacing ' + "can't be infinite"); + assert( widget.indicatorSize.width.isFinite || spacing.isFinite, 'The width of the indicator ' - 'or the spacing must be finite.'); - - // Recalculates the indicatorSize if its width or height is - // infinite. - Size indicatorSize = Size( - widget.indicatorSize.width.isInfinite - ? (constraints.maxWidth - - spacing * (widget.values.length - 1)) / + 'or the spacing must be finite.'); + + // Recalculates the indicatorSize if its width or height is + // infinite. + Size indicatorSize = Size( + widget.indicatorSize.width.isInfinite + ? (constraints.maxWidth - + spacing * (widget.values.length - 1)) / widget.values.length - : widget.indicatorSize.width, - widget.indicatorSize.height.isInfinite - ? height - : widget.indicatorSize.height); + : widget.indicatorSize.width, + widget.indicatorSize.height.isInfinite + ? height + : widget.indicatorSize.height); - if (spacing.isInfinite) { - spacing = (constraints.maxWidth - + if (spacing.isInfinite) { + spacing = (constraints.maxWidth - widget.indicatorSize.width * widget.values.length) / - (widget.values.length - 1); - } + (widget.values.length - 1); + } - // Calculates the required width of the widget. - double width = - indicatorSize.width * widget.values.length + - (widget.values.length - 1) * spacing; + // Calculates the required width of the widget. + double width = + indicatorSize.width * widget.values.length + + (widget.values.length - 1) * spacing; - // Handles the case that the required width of the widget - // cannot be used due to the given BoxConstraints. - if (widget.fittingMode == + // Handles the case that the required width of the widget + // cannot be used due to the given BoxConstraints. + if (widget.fittingMode == FittingMode.preventHorizontalOverlapping && - width > constraints.maxWidth) { - double factor = constraints.maxWidth / width; - spacing *= factor; - width = constraints.maxWidth; - indicatorSize = Size( - indicatorSize.width.isInfinite - ? width / widget.values.length - : factor * indicatorSize.width, - indicatorSize.height); - } else if (constraints.minWidth > width) { - spacing += (constraints.minWidth - width) / - (widget.values.length - 1); - width = constraints.minWidth; - } - - // The additional width of the indicator's hitbox needed - // to reach the minTouchTargetSize. - double dragDif = indicatorSize.width < + width > constraints.maxWidth) { + double factor = constraints.maxWidth / width; + spacing *= factor; + width = constraints.maxWidth; + indicatorSize = Size( + indicatorSize.width.isInfinite + ? width / widget.values.length + : factor * indicatorSize.width, + indicatorSize.height); + } else if (constraints.minWidth > width) { + spacing += (constraints.minWidth - width) / + (widget.values.length - 1); + width = constraints.minWidth; + } + + // The additional width of the indicator's hitbox needed + // to reach the minTouchTargetSize. + double dragDif = indicatorSize.width < widget.minTouchTargetSize - ? (widget.minTouchTargetSize - indicatorSize.width) - : 0; - - // The local position of the indicator. - double position = - (indicatorSize.width + spacing) * positionValue + - indicatorSize.width / 2; - - double leftPosition = textDirection == TextDirection.rtl - ? width - position - : position; - - bool isHoveringIndicator(Offset offset) { - if (!_isActive || _isCurrentUnlisted) { - return false; - } - double dx = textDirection == TextDirection.rtl - ? width - offset.dx - : offset.dx; - return position - - (indicatorSize.width + dragDif) / 2 <= + ? (widget.minTouchTargetSize - + indicatorSize.width) + : 0; + + // The local position of the indicator. + double position = + (indicatorSize.width + spacing) * + positionValue + + indicatorSize.width / 2; + + double leftPosition = textDirection == + TextDirection.rtl + ? width - position + : position; + + bool isHoveringIndicator(Offset offset) { + if (!_isActive || _isCurrentUnlisted) { + return false; + } + double dx = textDirection == TextDirection.rtl + ? width - offset.dx + : offset.dx; + return position - + (indicatorSize.width + dragDif) / 2 <= dx && - dx <= - (position + - (indicatorSize.width + dragDif) / 2); - } + dx <= + (position + + (indicatorSize.width + dragDif) / 2); + } - DetailedGlobalToggleProperties properties = + DetailedGlobalToggleProperties properties = DetailedGlobalToggleProperties( - spacing: spacing, - position: positionValue, - indicatorSize: indicatorSize, - switchSize: Size(width, height), - current: widget.current, - currentIndex: _currentIndex, - previous: _animationInfo.start.toInt() == + spacing: spacing, + position: positionValue, + indicatorSize: indicatorSize, + switchSize: Size(width, height), + current: widget.current, + currentIndex: _currentIndex, + previous: _animationInfo.start.toInt() == _animationInfo.start - ? widget.values[_animationInfo.start.toInt()] - : null, - values: widget.values, - previousPosition: _animationInfo.start, - textDirection: textDirection, - mode: _animationInfo.toggleMode, - loadingAnimationValue: loadingValue, - active: widget.active, - indicatorAppearingAnimation: - privateIndicatorAppearingAnimation, - vertical: widget._vertical, - ); - - List stack = [ - if (widget.backgroundIndicatorBuilder != null) - _Indicator( - key: const ValueKey(0), + ? widget.values[_animationInfo.start.toInt()] + : null, + values: widget.values, + previousPosition: _animationInfo.start, textDirection: textDirection, - height: height, - indicatorSize: indicatorSize, - position: position, - appearingAnimation: _appearingAnimation, - appearingBuilder: + mode: _animationInfo.toggleMode, + loadingAnimationValue: loadingValue, + active: widget.active, + indicatorAppearingAnimation: + privateIndicatorAppearingAnimation, + vertical: widget._vertical, + ); + + List stack = [ + if (widget.backgroundIndicatorBuilder != null) + _Indicator( + key: const ValueKey(0), + textDirection: textDirection, + height: height, + indicatorSize: indicatorSize, + position: position, + appearingAnimation: _appearingAnimation, + appearingBuilder: widget.indicatorAppearingBuilder, - child: widget.backgroundIndicatorBuilder!( - context, properties), - ), - if (widget.iconArrangement == IconArrangement.overlap) - ..._buildBackgroundStack(context, properties) - else - ..._buildBackgroundRow(context, properties), - if (widget.foregroundIndicatorBuilder != null) - _Indicator( - key: const ValueKey(1), - textDirection: textDirection, - height: height, - indicatorSize: indicatorSize, - position: position, - appearingAnimation: _appearingAnimation, - appearingBuilder: + child: widget.backgroundIndicatorBuilder!( + context, properties), + ), + if (widget.iconArrangement == + IconArrangement.overlap) + ..._buildBackgroundStack(context, properties) + else + ..._buildBackgroundRow(context, properties), + if (widget.foregroundIndicatorBuilder != null) + _Indicator( + key: const ValueKey(1), + textDirection: textDirection, + height: height, + indicatorSize: indicatorSize, + position: position, + appearingAnimation: _appearingAnimation, + appearingBuilder: widget.indicatorAppearingBuilder, - child: widget.foregroundIndicatorBuilder!( - context, properties), - ), - ]; - - return _WidgetPart( - left: loadingValue * - (leftPosition - 0.5 * indicatorSize.width), - width: indicatorSize.width + - (1 - loadingValue) * - (width - indicatorSize.width), - height: height, - child: ConstrainedBox( - constraints: constraints.loosen(), - child: SizedBox( - width: width, + child: widget.foregroundIndicatorBuilder!( + context, properties), + ), + ]; + + return _WidgetPart( + left: loadingValue * + (leftPosition - 0.5 * indicatorSize.width), + width: indicatorSize.width + + (1 - loadingValue) * + (width - indicatorSize.width), height: height, - // manual check if cursor is above indicator - // to make sure that GestureDetector and MouseRegion match. - // TODO: one widget for _DragRegion and GestureDetector to avoid redundancy - child: _HoverRegion( - hoverCursor: widget.cursors.tapCursor, - hoverCheck: (pos) => - widget.iconsTappable && - _doubleFromPosition(pos.dx, properties) - .round() != - _currentIndex, - child: _DragRegion( - dragging: _animationInfo.toggleMode == - ToggleMode.dragged, - draggingCursor: widget.cursors.draggingCursor, - dragCursor: widget.cursors.dragCursor, - hoverCheck: isHoveringIndicator, - defaultCursor: defaultCursor, - child: GestureDetector( - behavior: HitTestBehavior.translucent, - dragStartBehavior: DragStartBehavior.down, - onTapUp: (details) { - final togglePosition = - _togglePositionFromRealPosition( - details.localPosition.dx, - properties); - _onTap(TapProperties( - tapped: TapInfo._fromPosition( - togglePosition), - values: widget.values, - )); - if (!widget.iconsTappable) return; - if (togglePosition.value == - widget.current) { - return; + child: ConstrainedBox( + constraints: constraints.loosen(), + child: SizedBox( + width: width, + height: height, + // manual check if cursor is above indicator + // to make sure that GestureDetector and MouseRegion match. + // TODO: one widget for _DragRegion and GestureDetector to avoid redundancy + child: _HoverRegion( + defaultCursor: defaultCursor, + cursorByOffset: (pos) { + if (_animationInfo.toggleMode == + ToggleMode.dragged) { + return widget.cursors.draggingCursor; } - _onChanged(togglePosition.value); - }, - onHorizontalDragStart: (details) { - if (!isHoveringIndicator( - details.localPosition)) { - return; + if (isHoveringIndicator(pos)) { + return widget.cursors.dragCursor; } - _onDragged( + if (widget.iconsTappable && _doubleFromPosition( - details.localPosition.dx, - properties), - positionValue); - }, - onHorizontalDragUpdate: (details) { - _onDragUpdate(_doubleFromPosition( - details.localPosition.dx, - properties)); - }, - onHorizontalDragEnd: (details) { - _onDragEnd(); + pos.dx, properties) + .round() != + _currentIndex) { + return widget.cursors.tapCursor; + } + return null; }, - // DecoratedBox for gesture detection - child: DecoratedBox( - position: DecorationPosition.background, - decoration: const BoxDecoration(), - child: Stack( - clipBehavior: Clip.none, - children: stack, - )), + child: GestureDetector( + behavior: HitTestBehavior.translucent, + dragStartBehavior: DragStartBehavior + .down, + onTapUp: (details) { + final togglePosition = + _togglePositionFromRealPosition( + details.localPosition.dx, + properties); + _onTap(TapProperties( + tapped: TapInfo._fromPosition( + togglePosition), + values: widget.values, + )); + if (!widget.iconsTappable) return; + if (togglePosition.value == + widget.current) { + return; + } + _onChanged(togglePosition.value); + }, + onHorizontalDragStart: (details) { + if (!isHoveringIndicator( + details.localPosition)) { + return; + } + _onDragged( + _doubleFromPosition( + details.localPosition.dx, + properties), + positionValue); + }, + onHorizontalDragUpdate: (details) { + _onDragUpdate(_doubleFromPosition( + details.localPosition.dx, + properties)); + }, + onHorizontalDragEnd: (details) { + _onDragEnd(); + }, + // DecoratedBox for gesture detection + child: DecoratedBox( + position: DecorationPosition + .background, + decoration: const BoxDecoration(), + child: Stack( + clipBehavior: Clip.none, + children: stack, + )), + ), ), ), ), - ), - ), - ); - }, - ), - ); - return widget.wrapperBuilder + ); + }, + ), + ); + return widget.wrapperBuilder ?.call(context, properties, child) ?? - child; - }), + child; + }), ), ), ), @@ -864,8 +882,8 @@ class _CustomAnimatedToggleSwitchState } /// The builder of the icons for [IconArrangement.overlap]. - List _buildBackgroundStack( - BuildContext context, DetailedGlobalToggleProperties properties) { + List _buildBackgroundStack(BuildContext context, + DetailedGlobalToggleProperties properties) { return [ ...Iterable.generate(widget.values.length, (i) { double position = @@ -874,7 +892,7 @@ class _CustomAnimatedToggleSwitchState textDirection: properties.textDirection, start: i == 0 ? position : position - properties.spacing, width: (i == 0 || i == widget.values.length - 1 ? 1 : 2) * - properties.spacing + + properties.spacing + properties.indicatorSize.width, height: properties.indicatorSize.height, child: widget.iconBuilder( @@ -897,8 +915,8 @@ class _CustomAnimatedToggleSwitchState } /// The builder of the icons for [IconArrangement.row]. - List _buildBackgroundRow( - BuildContext context, DetailedGlobalToggleProperties properties) { + List _buildBackgroundRow(BuildContext context, + DetailedGlobalToggleProperties properties) { final length = properties.values.length; return [ Row( @@ -1062,30 +1080,28 @@ class _AnimationInfo { final ToggleMode toggleMode; final bool loading; - const _AnimationInfo( - this.start, { + const _AnimationInfo(this.start, { this.toggleMode = ToggleMode.none, this.loading = false, }) : end = start; - const _AnimationInfo._internal( - this.start, - this.end, { - this.toggleMode = ToggleMode.none, - this.loading = false, - }); + const _AnimationInfo._internal(this.start, + this.end, { + this.toggleMode = ToggleMode.none, + this.loading = false, + }); - const _AnimationInfo.animating( - this.start, - this.end, { - this.loading = false, - }) : toggleMode = ToggleMode.animating; + const _AnimationInfo.animating(this.start, + this.end, { + this.loading = false, + }) : toggleMode = ToggleMode.animating; _AnimationInfo toEnd(double end, {double? current}) => _AnimationInfo.animating(current ?? start, end, loading: loading); - _AnimationInfo none({double? current}) => _AnimationInfo(current ?? start, - toggleMode: ToggleMode.none, loading: loading); + _AnimationInfo none({double? current}) => + _AnimationInfo(current ?? start, + toggleMode: ToggleMode.none, loading: loading); _AnimationInfo ended() => _AnimationInfo(end, loading: loading); @@ -1106,12 +1122,12 @@ class _AnimationInfo { @override bool operator ==(Object other) => identical(this, other) || - other is _AnimationInfo && - runtimeType == other.runtimeType && - start == other.start && - end == other.end && - toggleMode == other.toggleMode && - loading == other.loading; + other is _AnimationInfo && + runtimeType == other.runtimeType && + start == other.start && + end == other.end && + toggleMode == other.toggleMode && + loading == other.loading; @override int get hashCode => diff --git a/lib/src/widgets/drag_region.dart b/lib/src/widgets/drag_region.dart deleted file mode 100644 index 88cee35..0000000 --- a/lib/src/widgets/drag_region.dart +++ /dev/null @@ -1,94 +0,0 @@ -part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; - -class _HoverRegion extends StatefulWidget { - final MouseCursor? cursor; - final MouseCursor hoverCursor; - final Widget child; - final bool Function(Offset offset) hoverCheck; - final MouseCursor defaultCursor; - - const _HoverRegion({ - Key? key, - this.cursor, - required this.hoverCursor, - required this.child, - this.hoverCheck = _defaultHoverCheck, - this.defaultCursor = MouseCursor.defer, - }) : super(key: key); - - static bool _defaultHoverCheck(Offset offset) => true; - - @override - State<_HoverRegion> createState() => _HoverRegionState(); -} - -class _HoverRegionState extends State<_HoverRegion> { - bool _hovering = false; - Offset? _position; - - @override - Widget build(BuildContext context) { - if (_position != null) _updateHovering(_position!, rebuild: false); - // Listener is necessary because MouseRegion.onHover only gets triggered - // without buttons pressed - return Listener( - behavior: HitTestBehavior.translucent, - onPointerHover: _updatePointer, - onPointerMove: _updatePointer, - child: GestureDetector( - child: MouseRegion( - opaque: false, - hitTestBehavior: HitTestBehavior.translucent, - cursor: widget.cursor ?? - (_hovering ? widget.hoverCursor : widget.defaultCursor), - onExit: (e) => _setHovering(false), - child: widget.child, - ), - ), - ); - } - - void _updatePointer(PointerEvent event, {bool rebuild = true}) { - _updateHovering(event.localPosition, rebuild: rebuild); - } - - void _updateHovering(Offset offset, {bool rebuild = true}) { - _setHovering(widget.hoverCheck(_position = offset), rebuild: rebuild); - } - - void _setHovering(bool hovering, {bool rebuild = true}) { - if (hovering == _hovering) return; - _hovering = hovering; - if (rebuild) setState(() {}); - } -} - -class _DragRegion extends StatelessWidget { - final bool dragging; - final Widget child; - final bool Function(Offset offset) hoverCheck; - final MouseCursor defaultCursor; - final MouseCursor dragCursor; - final MouseCursor draggingCursor; - - const _DragRegion({ - Key? key, - this.dragging = false, - required this.child, - this.hoverCheck = _HoverRegion._defaultHoverCheck, - this.defaultCursor = MouseCursor.defer, - this.dragCursor = SystemMouseCursors.grab, - this.draggingCursor = SystemMouseCursors.grabbing, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return _HoverRegion( - cursor: dragging ? draggingCursor : null, - hoverCursor: dragCursor, - hoverCheck: hoverCheck, - defaultCursor: defaultCursor, - child: child, - ); - } -} diff --git a/lib/src/widgets/hover_region.dart b/lib/src/widgets/hover_region.dart new file mode 100644 index 0000000..6a57873 --- /dev/null +++ b/lib/src/widgets/hover_region.dart @@ -0,0 +1,57 @@ +part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; + +class _HoverRegion extends StatefulWidget { + final Widget child; + final MouseCursor? Function(Offset offset) cursorByOffset; + final MouseCursor defaultCursor; + + const _HoverRegion({ + Key? key, + required this.child, + required this.cursorByOffset, + this.defaultCursor = MouseCursor.defer, + }) : super(key: key); + + @override + State<_HoverRegion> createState() => _HoverRegionState(); +} + +class _HoverRegionState extends State<_HoverRegion> { + Offset? _position; + MouseCursor? _cursor; + + @override + Widget build(BuildContext context) { + if (_position != null) _updateHovering(_position!, rebuild: false); + // Listener is necessary because MouseRegion.onHover only gets triggered + // without buttons pressed + return Listener( + behavior: HitTestBehavior.translucent, + onPointerHover: _updatePointer, + onPointerMove: _updatePointer, + child: GestureDetector( + child: MouseRegion( + opaque: false, + hitTestBehavior: HitTestBehavior.translucent, + cursor: _cursor ?? widget.defaultCursor, + onExit: (e) => _setCursor(null), + child: widget.child, + ), + ), + ); + } + + void _updatePointer(PointerEvent event, {bool rebuild = true}) { + _updateHovering(event.localPosition, rebuild: rebuild); + } + + void _updateHovering(Offset offset, {bool rebuild = true}) { + _setCursor(widget.cursorByOffset(_position = offset), rebuild: rebuild); + } + + void _setCursor(MouseCursor? cursor, {bool rebuild = true}) { + if (_cursor == cursor) return; + _cursor = cursor; + if (rebuild) setState(() {}); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index bc4fbd7..fba7e54 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dev_dependencies: flutter_test: sdk: flutter mocktail: ^1.0.0 - dartdoc: ^8.0.13 + dartdoc: any flutter: From 723ece71a4b55207b6125535a36005c9eb1057fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Mon, 12 Aug 2024 17:20:37 +0200 Subject: [PATCH 6/8] 61: dart format --- .../custom_animated_toggle_switch.dart | 642 +++++++++--------- 1 file changed, 314 insertions(+), 328 deletions(-) diff --git a/lib/src/widgets/custom_animated_toggle_switch.dart b/lib/src/widgets/custom_animated_toggle_switch.dart index 14a3705..0d73ce0 100644 --- a/lib/src/widgets/custom_animated_toggle_switch.dart +++ b/lib/src/widgets/custom_animated_toggle_switch.dart @@ -39,8 +39,8 @@ enum FittingMode { none, preventHorizontalOverlapping } const _defaultIndicatorAppearingAnimationDuration = Duration(milliseconds: 350); const _defaultIndicatorAppearingAnimationCurve = Curves.easeOutBack; -Widget _defaultIndicatorAppearingBuilder(BuildContext context, double value, - Widget indicator) { +Widget _defaultIndicatorAppearingBuilder( + BuildContext context, double value, Widget indicator) { return Transform.scale(scale: value, child: indicator); } @@ -230,8 +230,7 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { this.allowUnlistedValues = false, this.active = true, this.positionListener, - }) - : _vertical = false, + }) : _vertical = false, assert(foregroundIndicatorBuilder != null || backgroundIndicatorBuilder != null), assert(separatorBuilder == null || @@ -290,20 +289,18 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { vertical: true, current: current, values: values, - iconBuilder: (context, global, local) => - RotatedBox( - quarterTurns: -1, - child: iconBuilder(context, global, local), - ), - indicatorAppearingBuilder: (context, value, indicator) => - RotatedBox( - quarterTurns: -1, - child: indicatorAppearingBuilder( - context, - value, - RotatedBox(quarterTurns: 1, child: indicator), - ), - ), + iconBuilder: (context, global, local) => RotatedBox( + quarterTurns: -1, + child: iconBuilder(context, global, local), + ), + indicatorAppearingBuilder: (context, value, indicator) => RotatedBox( + quarterTurns: -1, + child: indicatorAppearingBuilder( + context, + value, + RotatedBox(quarterTurns: 1, child: indicator), + ), + ), animationDuration: animationDuration, animationCurve: animationCurve, indicatorAppearingDuration: indicatorAppearingDuration, @@ -325,21 +322,21 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { foregroundIndicatorBuilder: foregroundIndicatorBuilder == null ? null : (BuildContext context, - DetailedGlobalToggleProperties global) { - return RotatedBox( - quarterTurns: -1, - child: foregroundIndicatorBuilder!(context, global), - ); - }, + DetailedGlobalToggleProperties global) { + return RotatedBox( + quarterTurns: -1, + child: foregroundIndicatorBuilder!(context, global), + ); + }, backgroundIndicatorBuilder: backgroundIndicatorBuilder == null ? null : (BuildContext context, - DetailedGlobalToggleProperties global) { - return RotatedBox( - quarterTurns: -1, - child: backgroundIndicatorBuilder!(context, global), - ); - }, + DetailedGlobalToggleProperties global) { + return RotatedBox( + quarterTurns: -1, + child: backgroundIndicatorBuilder!(context, global), + ); + }, loadingAnimationDuration: loadingAnimationDuration, loadingAnimationCurve: loadingAnimationCurve, onChanged: onChanged, @@ -354,8 +351,7 @@ class CustomAnimatedToggleSwitch extends StatefulWidget { } class _CustomAnimatedToggleSwitchState - extends State> - with TickerProviderStateMixin { + extends State> with TickerProviderStateMixin { /// The [AnimationController] for the movement of the indicator. late final AnimationController _controller; @@ -378,10 +374,9 @@ class _CustomAnimatedToggleSwitchState bool get _isCurrentUnlisted => _currentIndex < 0; - double get _positionValue => - _animationInfo - .valueAt(_animation.value) - .clamp(0, widget.values.length - 1); + double get _positionValue => _animationInfo + .valueAt(_animation.value) + .clamp(0, widget.values.length - 1); PositionListenerInfo? _lastPositionListenerValue; @@ -397,17 +392,17 @@ class _CustomAnimatedToggleSwitchState _AnimationInfo(isValueSelected ? _currentIndex.toDouble() : 0.0) .setLoading(widget.loading ?? false); _controller = - AnimationController(vsync: this, duration: widget.animationDuration) - ..addStatusListener((status) { - if (status == AnimationStatus.completed && - _animationInfo.toggleMode != ToggleMode.dragged) { - _setAnimationInfo(_animationInfo.ended()); - } - }); + AnimationController(vsync: this, duration: widget.animationDuration) + ..addStatusListener((status) { + if (status == AnimationStatus.completed && + _animationInfo.toggleMode != ToggleMode.dragged) { + _setAnimationInfo(_animationInfo.ended()); + } + }); _animation = - CurvedAnimation(parent: _controller, curve: widget.animationCurve) - ..addListener(_callPositionListener); + CurvedAnimation(parent: _controller, curve: widget.animationCurve) + ..addListener(_callPositionListener); _appearingController = AnimationController( vsync: this, @@ -435,9 +430,9 @@ class _CustomAnimatedToggleSwitchState try { throw ArgumentError( 'The values in AnimatedToggleSwitch have to contain current if allowUnlistedValues is false.\n' - 'current: ${widget.current}\n' - 'values: ${widget.values}\n' - 'This error is only thrown in debug mode to minimize problems with the production app.'); + 'current: ${widget.current}\n' + 'values: ${widget.values}\n' + 'This error is only thrown in debug mode to minimize problems with the production app.'); } catch (e, s) { if (kDebugMode) rethrow; FlutterError.reportError(FlutterErrorDetails( @@ -521,13 +516,13 @@ class _CustomAnimatedToggleSwitchState /// Returns the value position by the local position of the cursor. /// It is mainly intended as a helper function for the build method. - double _doubleFromPosition(double x, - DetailedGlobalToggleProperties properties) { + double _doubleFromPosition( + double x, DetailedGlobalToggleProperties properties) { double result = (x.clamp( - properties.indicatorSize.width / 2, - properties.switchSize.width - - properties.indicatorSize.width / 2) - - properties.indicatorSize.width / 2) / + properties.indicatorSize.width / 2, + properties.switchSize.width - + properties.indicatorSize.width / 2) - + properties.indicatorSize.width / 2) / (properties.indicatorSize.width + properties.spacing); if (properties.textDirection == TextDirection.rtl) { result = widget.values.length - 1 - result; @@ -565,8 +560,8 @@ class _CustomAnimatedToggleSwitchState /// Returns the [TogglePosition] by the local position of the cursor. /// It is mainly intended as a helper function for the build method. - TogglePosition _togglePositionFromRealPosition(double x, - DetailedGlobalToggleProperties properties) { + TogglePosition _togglePositionFromRealPosition( + double x, DetailedGlobalToggleProperties properties) { return _togglePositionFromPositionValue(_doubleFromPosition(x, properties)); } @@ -576,16 +571,16 @@ class _CustomAnimatedToggleSwitchState final textDirection = widget.textDirection ?? _textDirectionOf(context); final loadingValue = _animationInfo.loading ? 1.0 : 0.0; final privateIndicatorAppearingAnimation = - _PrivateAnimation(_appearingAnimation); + _PrivateAnimation(_appearingAnimation); final defaultCursor = !_isActive ? (_animationInfo.loading - ? widget.cursors.loadingCursor - : widget.cursors.inactiveCursor) + ? widget.cursors.loadingCursor + : widget.cursors.inactiveCursor) : (widget.cursors.defaultCursor ?? - (widget.onTap == null - ? MouseCursor.defer - : SystemMouseCursors.click)); + (widget.onTap == null + ? MouseCursor.defer + : SystemMouseCursors.click)); return SizedBox( height: widget.height, @@ -594,287 +589,276 @@ class _CustomAnimatedToggleSwitchState cursor: defaultCursor, child: GestureDetector( behavior: HitTestBehavior.deferToChild, - onTapUp: (_) => - _onTap(TapProperties( - tapped: null, - values: widget.values, - )), + onTapUp: (_) => _onTap(TapProperties( + tapped: null, + values: widget.values, + )), child: TweenAnimationBuilder( duration: - widget.loadingAnimationDuration ?? widget.animationDuration, + widget.loadingAnimationDuration ?? widget.animationDuration, curve: widget.loadingAnimationCurve ?? widget.animationCurve, tween: Tween(begin: loadingValue, end: loadingValue), - builder: (context, loadingValue, child) => - AnimatedBuilder( - animation: _animation, - builder: (context, child) { - double positionValue = _positionValue; - GlobalToggleProperties< - T> properties = GlobalToggleProperties( - position: positionValue, - current: widget.current, - currentIndex: _currentIndex, - previous: + builder: (context, loadingValue, child) => AnimatedBuilder( + animation: _animation, + builder: (context, child) { + double positionValue = _positionValue; + GlobalToggleProperties properties = GlobalToggleProperties( + position: positionValue, + current: widget.current, + currentIndex: _currentIndex, + previous: _animationInfo.start.toInt() == _animationInfo.start ? widget.values[_animationInfo.start.toInt()] : null, - values: widget.values, - previousPosition: _animationInfo.start, - textDirection: textDirection, - mode: _animationInfo.toggleMode, - loadingAnimationValue: loadingValue, - active: widget.active, - indicatorAppearingAnimation: + values: widget.values, + previousPosition: _animationInfo.start, + textDirection: textDirection, + mode: _animationInfo.toggleMode, + loadingAnimationValue: loadingValue, + active: widget.active, + indicatorAppearingAnimation: privateIndicatorAppearingAnimation, - vertical: widget._vertical, - ); - Widget child = Padding( - padding: widget.padding, - child: LayoutBuilder( - builder: (context, constraints) { - double height = constraints.maxHeight; - assert( + vertical: widget._vertical, + ); + Widget child = Padding( + padding: widget.padding, + child: LayoutBuilder( + builder: (context, constraints) { + double height = constraints.maxHeight; + assert( constraints.maxWidth.isFinite || (widget.indicatorSize.width.isFinite && spacing.isFinite), 'With unbound width constraints ' - 'the width of the indicator and the spacing ' - "can't be infinite"); - assert( + 'the width of the indicator and the spacing ' + "can't be infinite"); + assert( widget.indicatorSize.width.isFinite || spacing.isFinite, 'The width of the indicator ' - 'or the spacing must be finite.'); - - // Recalculates the indicatorSize if its width or height is - // infinite. - Size indicatorSize = Size( - widget.indicatorSize.width.isInfinite - ? (constraints.maxWidth - - spacing * (widget.values.length - 1)) / + 'or the spacing must be finite.'); + + // Recalculates the indicatorSize if its width or height is + // infinite. + Size indicatorSize = Size( + widget.indicatorSize.width.isInfinite + ? (constraints.maxWidth - + spacing * (widget.values.length - 1)) / widget.values.length - : widget.indicatorSize.width, - widget.indicatorSize.height.isInfinite - ? height - : widget.indicatorSize.height); + : widget.indicatorSize.width, + widget.indicatorSize.height.isInfinite + ? height + : widget.indicatorSize.height); - if (spacing.isInfinite) { - spacing = (constraints.maxWidth - + if (spacing.isInfinite) { + spacing = (constraints.maxWidth - widget.indicatorSize.width * widget.values.length) / - (widget.values.length - 1); - } + (widget.values.length - 1); + } - // Calculates the required width of the widget. - double width = - indicatorSize.width * widget.values.length + - (widget.values.length - 1) * spacing; + // Calculates the required width of the widget. + double width = + indicatorSize.width * widget.values.length + + (widget.values.length - 1) * spacing; - // Handles the case that the required width of the widget - // cannot be used due to the given BoxConstraints. - if (widget.fittingMode == + // Handles the case that the required width of the widget + // cannot be used due to the given BoxConstraints. + if (widget.fittingMode == FittingMode.preventHorizontalOverlapping && - width > constraints.maxWidth) { - double factor = constraints.maxWidth / width; - spacing *= factor; - width = constraints.maxWidth; - indicatorSize = Size( - indicatorSize.width.isInfinite - ? width / widget.values.length - : factor * indicatorSize.width, - indicatorSize.height); - } else if (constraints.minWidth > width) { - spacing += (constraints.minWidth - width) / - (widget.values.length - 1); - width = constraints.minWidth; - } - - // The additional width of the indicator's hitbox needed - // to reach the minTouchTargetSize. - double dragDif = indicatorSize.width < + width > constraints.maxWidth) { + double factor = constraints.maxWidth / width; + spacing *= factor; + width = constraints.maxWidth; + indicatorSize = Size( + indicatorSize.width.isInfinite + ? width / widget.values.length + : factor * indicatorSize.width, + indicatorSize.height); + } else if (constraints.minWidth > width) { + spacing += (constraints.minWidth - width) / + (widget.values.length - 1); + width = constraints.minWidth; + } + + // The additional width of the indicator's hitbox needed + // to reach the minTouchTargetSize. + double dragDif = indicatorSize.width < widget.minTouchTargetSize - ? (widget.minTouchTargetSize - - indicatorSize.width) - : 0; - - // The local position of the indicator. - double position = - (indicatorSize.width + spacing) * - positionValue + - indicatorSize.width / 2; - - double leftPosition = textDirection == - TextDirection.rtl - ? width - position - : position; - - bool isHoveringIndicator(Offset offset) { - if (!_isActive || _isCurrentUnlisted) { - return false; - } - double dx = textDirection == TextDirection.rtl - ? width - offset.dx - : offset.dx; - return position - - (indicatorSize.width + dragDif) / 2 <= + ? (widget.minTouchTargetSize - indicatorSize.width) + : 0; + + // The local position of the indicator. + double position = + (indicatorSize.width + spacing) * positionValue + + indicatorSize.width / 2; + + double leftPosition = textDirection == TextDirection.rtl + ? width - position + : position; + + bool isHoveringIndicator(Offset offset) { + if (!_isActive || _isCurrentUnlisted) { + return false; + } + double dx = textDirection == TextDirection.rtl + ? width - offset.dx + : offset.dx; + return position - + (indicatorSize.width + dragDif) / 2 <= dx && - dx <= - (position + - (indicatorSize.width + dragDif) / 2); - } + dx <= + (position + + (indicatorSize.width + dragDif) / 2); + } - DetailedGlobalToggleProperties properties = + DetailedGlobalToggleProperties properties = DetailedGlobalToggleProperties( - spacing: spacing, - position: positionValue, - indicatorSize: indicatorSize, - switchSize: Size(width, height), - current: widget.current, - currentIndex: _currentIndex, - previous: _animationInfo.start.toInt() == + spacing: spacing, + position: positionValue, + indicatorSize: indicatorSize, + switchSize: Size(width, height), + current: widget.current, + currentIndex: _currentIndex, + previous: _animationInfo.start.toInt() == _animationInfo.start - ? widget.values[_animationInfo.start.toInt()] - : null, - values: widget.values, - previousPosition: _animationInfo.start, - textDirection: textDirection, - mode: _animationInfo.toggleMode, - loadingAnimationValue: loadingValue, - active: widget.active, - indicatorAppearingAnimation: + ? widget.values[_animationInfo.start.toInt()] + : null, + values: widget.values, + previousPosition: _animationInfo.start, + textDirection: textDirection, + mode: _animationInfo.toggleMode, + loadingAnimationValue: loadingValue, + active: widget.active, + indicatorAppearingAnimation: privateIndicatorAppearingAnimation, - vertical: widget._vertical, - ); - - List stack = [ - if (widget.backgroundIndicatorBuilder != null) - _Indicator( - key: const ValueKey(0), - textDirection: textDirection, - height: height, - indicatorSize: indicatorSize, - position: position, - appearingAnimation: _appearingAnimation, - appearingBuilder: + vertical: widget._vertical, + ); + + List stack = [ + if (widget.backgroundIndicatorBuilder != null) + _Indicator( + key: const ValueKey(0), + textDirection: textDirection, + height: height, + indicatorSize: indicatorSize, + position: position, + appearingAnimation: _appearingAnimation, + appearingBuilder: widget.indicatorAppearingBuilder, - child: widget.backgroundIndicatorBuilder!( - context, properties), - ), - if (widget.iconArrangement == - IconArrangement.overlap) - ..._buildBackgroundStack(context, properties) - else - ..._buildBackgroundRow(context, properties), - if (widget.foregroundIndicatorBuilder != null) - _Indicator( - key: const ValueKey(1), - textDirection: textDirection, - height: height, - indicatorSize: indicatorSize, - position: position, - appearingAnimation: _appearingAnimation, - appearingBuilder: + child: widget.backgroundIndicatorBuilder!( + context, properties), + ), + if (widget.iconArrangement == IconArrangement.overlap) + ..._buildBackgroundStack(context, properties) + else + ..._buildBackgroundRow(context, properties), + if (widget.foregroundIndicatorBuilder != null) + _Indicator( + key: const ValueKey(1), + textDirection: textDirection, + height: height, + indicatorSize: indicatorSize, + position: position, + appearingAnimation: _appearingAnimation, + appearingBuilder: widget.indicatorAppearingBuilder, - child: widget.foregroundIndicatorBuilder!( - context, properties), - ), - ]; - - return _WidgetPart( - left: loadingValue * - (leftPosition - 0.5 * indicatorSize.width), - width: indicatorSize.width + - (1 - loadingValue) * - (width - indicatorSize.width), + child: widget.foregroundIndicatorBuilder!( + context, properties), + ), + ]; + + return _WidgetPart( + left: loadingValue * + (leftPosition - 0.5 * indicatorSize.width), + width: indicatorSize.width + + (1 - loadingValue) * + (width - indicatorSize.width), + height: height, + child: ConstrainedBox( + constraints: constraints.loosen(), + child: SizedBox( + width: width, height: height, - child: ConstrainedBox( - constraints: constraints.loosen(), - child: SizedBox( - width: width, - height: height, - // manual check if cursor is above indicator - // to make sure that GestureDetector and MouseRegion match. - // TODO: one widget for _DragRegion and GestureDetector to avoid redundancy - child: _HoverRegion( - defaultCursor: defaultCursor, - cursorByOffset: (pos) { - if (_animationInfo.toggleMode == - ToggleMode.dragged) { - return widget.cursors.draggingCursor; - } - if (isHoveringIndicator(pos)) { - return widget.cursors.dragCursor; - } - if (widget.iconsTappable && - _doubleFromPosition( - pos.dx, properties) + // manual check if cursor is above indicator + // to make sure that GestureDetector and MouseRegion match. + // TODO: one widget for _DragRegion and GestureDetector to avoid redundancy + child: _HoverRegion( + defaultCursor: defaultCursor, + cursorByOffset: (pos) { + if (_animationInfo.toggleMode == + ToggleMode.dragged) { + return widget.cursors.draggingCursor; + } + if (isHoveringIndicator(pos)) { + return widget.cursors.dragCursor; + } + if (widget.iconsTappable && + _doubleFromPosition(pos.dx, properties) .round() != - _currentIndex) { - return widget.cursors.tapCursor; - } - return null; - }, - child: GestureDetector( - behavior: HitTestBehavior.translucent, - dragStartBehavior: DragStartBehavior - .down, - onTapUp: (details) { - final togglePosition = + _currentIndex) { + return widget.cursors.tapCursor; + } + return null; + }, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + dragStartBehavior: DragStartBehavior.down, + onTapUp: (details) { + final togglePosition = _togglePositionFromRealPosition( details.localPosition.dx, properties); - _onTap(TapProperties( - tapped: TapInfo._fromPosition( - togglePosition), - values: widget.values, - )); - if (!widget.iconsTappable) return; - if (togglePosition.value == - widget.current) { - return; - } - _onChanged(togglePosition.value); - }, - onHorizontalDragStart: (details) { - if (!isHoveringIndicator( - details.localPosition)) { - return; - } - _onDragged( - _doubleFromPosition( - details.localPosition.dx, - properties), - positionValue); - }, - onHorizontalDragUpdate: (details) { - _onDragUpdate(_doubleFromPosition( + _onTap(TapProperties( + tapped: + TapInfo._fromPosition(togglePosition), + values: widget.values, + )); + if (!widget.iconsTappable) return; + if (togglePosition.value == + widget.current) { + return; + } + _onChanged(togglePosition.value); + }, + onHorizontalDragStart: (details) { + if (!isHoveringIndicator( + details.localPosition)) { + return; + } + _onDragged( + _doubleFromPosition( details.localPosition.dx, - properties)); - }, - onHorizontalDragEnd: (details) { - _onDragEnd(); - }, - // DecoratedBox for gesture detection - child: DecoratedBox( - position: DecorationPosition - .background, - decoration: const BoxDecoration(), - child: Stack( - clipBehavior: Clip.none, - children: stack, - )), - ), - ), + properties), + positionValue); + }, + onHorizontalDragUpdate: (details) { + _onDragUpdate(_doubleFromPosition( + details.localPosition.dx, properties)); + }, + onHorizontalDragEnd: (details) { + _onDragEnd(); + }, + // DecoratedBox for gesture detection + child: DecoratedBox( + position: DecorationPosition.background, + decoration: const BoxDecoration(), + child: Stack( + clipBehavior: Clip.none, + children: stack, + )), ), ), - ); - }, - ), - ); - return widget.wrapperBuilder + ), + ), + ); + }, + ), + ); + return widget.wrapperBuilder ?.call(context, properties, child) ?? - child; - }), + child; + }), ), ), ), @@ -882,8 +866,8 @@ class _CustomAnimatedToggleSwitchState } /// The builder of the icons for [IconArrangement.overlap]. - List _buildBackgroundStack(BuildContext context, - DetailedGlobalToggleProperties properties) { + List _buildBackgroundStack( + BuildContext context, DetailedGlobalToggleProperties properties) { return [ ...Iterable.generate(widget.values.length, (i) { double position = @@ -892,7 +876,7 @@ class _CustomAnimatedToggleSwitchState textDirection: properties.textDirection, start: i == 0 ? position : position - properties.spacing, width: (i == 0 || i == widget.values.length - 1 ? 1 : 2) * - properties.spacing + + properties.spacing + properties.indicatorSize.width, height: properties.indicatorSize.height, child: widget.iconBuilder( @@ -915,8 +899,8 @@ class _CustomAnimatedToggleSwitchState } /// The builder of the icons for [IconArrangement.row]. - List _buildBackgroundRow(BuildContext context, - DetailedGlobalToggleProperties properties) { + List _buildBackgroundRow( + BuildContext context, DetailedGlobalToggleProperties properties) { final length = properties.values.length; return [ Row( @@ -1080,28 +1064,30 @@ class _AnimationInfo { final ToggleMode toggleMode; final bool loading; - const _AnimationInfo(this.start, { + const _AnimationInfo( + this.start, { this.toggleMode = ToggleMode.none, this.loading = false, }) : end = start; - const _AnimationInfo._internal(this.start, - this.end, { - this.toggleMode = ToggleMode.none, - this.loading = false, - }); + const _AnimationInfo._internal( + this.start, + this.end, { + this.toggleMode = ToggleMode.none, + this.loading = false, + }); - const _AnimationInfo.animating(this.start, - this.end, { - this.loading = false, - }) : toggleMode = ToggleMode.animating; + const _AnimationInfo.animating( + this.start, + this.end, { + this.loading = false, + }) : toggleMode = ToggleMode.animating; _AnimationInfo toEnd(double end, {double? current}) => _AnimationInfo.animating(current ?? start, end, loading: loading); - _AnimationInfo none({double? current}) => - _AnimationInfo(current ?? start, - toggleMode: ToggleMode.none, loading: loading); + _AnimationInfo none({double? current}) => _AnimationInfo(current ?? start, + toggleMode: ToggleMode.none, loading: loading); _AnimationInfo ended() => _AnimationInfo(end, loading: loading); @@ -1122,12 +1108,12 @@ class _AnimationInfo { @override bool operator ==(Object other) => identical(this, other) || - other is _AnimationInfo && - runtimeType == other.runtimeType && - start == other.start && - end == other.end && - toggleMode == other.toggleMode && - loading == other.loading; + other is _AnimationInfo && + runtimeType == other.runtimeType && + start == other.start && + end == other.end && + toggleMode == other.toggleMode && + loading == other.loading; @override int get hashCode => From b81f7cf24e0030be31fb9f52fb8c76ec17c5375c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Mon, 12 Aug 2024 17:24:44 +0200 Subject: [PATCH 7/8] 61: updates README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 76b8860..4a63787 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ AnimatedToggleSwitch.rolling( ### Vertical switches ![animated_toggle_switch_example_vertical](https://github.com/user-attachments/assets/b6b804d0-a8f0-47a2-bf73-e9e0b65773d7) -To use this feature, you simply have to call `vertical()` on the switch you want to make vertical. +You can get a vertical version of any switch by calling `vertical()` on it. ```dart AnimatedToggleSwitch.rolling(...).vertical() From 7c185be57b4d9900d52c3332e8dfd07517f8b247 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Kl=C3=BCpfel?= Date: Mon, 12 Aug 2024 17:27:25 +0200 Subject: [PATCH 8/8] 61: updates version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index fba7e54..8e8cd65 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.2 +version: 0.8.3 repository: https://github.com/splashbyte/animated_toggle_switch issue_tracker: https://github.com/splashbyte/animated_toggle_switch/issues