├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── flutter.yml │ └── publish.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── crazy_switch.dart │ ├── load_switch.dart │ └── main.dart ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ └── Icon-512.png │ ├── index.html │ └── manifest.json ├── lib ├── animated_toggle_switch.dart └── src │ ├── animations.dart │ ├── cursors.dart │ ├── foreground_indicator_transition.dart │ ├── properties.dart │ ├── style.dart │ ├── test_keys.dart │ ├── tweens.dart │ └── widgets │ ├── animated_toggle_switch.dart │ ├── animation_type_builder.dart │ ├── conditional_wrapper.dart │ ├── custom_animated_toggle_switch.dart │ └── hover_region.dart ├── pubspec.lock ├── pubspec.yaml ├── screenshots └── preview.webp └── test ├── directionality_test.dart ├── gesture_test.dart ├── helper.dart ├── icon_builder_test.dart ├── keys.dart ├── listener_test.dart ├── loading_test.dart ├── mocks.dart ├── separator_test.dart ├── style_test.dart ├── switch_size_test.dart ├── values_test.dart └── vertical_test.dart /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: splashbyte 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['buymeacoffee.com/splashbyte'] 14 | -------------------------------------------------------------------------------- /.github/workflows/flutter.yml: -------------------------------------------------------------------------------- 1 | name: Flutter Analyze & Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | 12 | - uses: subosito/flutter-action@v2 13 | 14 | - name: Install dependencies 15 | run: flutter pub get 16 | 17 | - name: Verify formatting 18 | run: dart format --output=none --set-exit-if-changed . 19 | 20 | - name: Analyze project source 21 | run: flutter analyze 22 | 23 | - name: Run tests 24 | run: flutter test --coverage 25 | 26 | - name: Upload coverage reports to Codecov 27 | uses: codecov/codecov-action@v3 28 | with: 29 | fail_ci_if_error: true 30 | token: ${{ secrets.CODECOV_TOKEN }} 31 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to pub.dev 2 | 3 | on: 4 | push: 5 | tags: 6 | - '[0-9]+.[0-9]+.[0-9]+*' 7 | 8 | jobs: 9 | publish: 10 | permissions: 11 | id-token: write 12 | uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 13 | with: 14 | environment: pub.dev -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | 76 | # Coverage 77 | coverage/ 78 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8962f6dc68ec8e2206ac2fa874da4a453856c7d3 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.8.5 (2025-04-28) 2 | 3 | - adds `padding` to `AnimatedToggleSwitch` ([#65](https://github.com/splashbyte/animated_toggle_switch/issues/65)) 4 | 5 | ## 0.8.4 (2024-12-18) 6 | 7 | - adds option to add `ToggleStyle` to `ThemeData` as `ThemeExtension` 8 | - removes usage of deprecated members 9 | 10 | ## 0.8.3 (2024-08-12) 11 | 12 | - adds support for vertical switches ([#61](https://github.com/splashbyte/animated_toggle_switch/issues/61)) 13 | 14 | ## 0.8.2 (2024-02-26) 15 | 16 | - adds `clipBehavior` to `AnimatedToggleSwitch` ([#56](https://github.com/splashbyte/animated_toggle_switch/issues/56)) 17 | 18 | ## 0.8.1 (2024-02-06) 19 | 20 | - adds `indicatorGradient` to `ToggleStyle` ([#44](https://github.com/splashbyte/animated_toggle_switch/issues/44)) 21 | - fixes problems with `backgroundGradient` and `backgroundColor` ([#46](https://github.com/splashbyte/animated_toggle_switch/issues/46)) 22 | - adds `AnimationType.none` 23 | - introduces `positionListener` ([#50](https://github.com/splashbyte/animated_toggle_switch/issues/50)) 24 | - fixes [#51](https://github.com/splashbyte/animated_toggle_switch/issues/51) 25 | 26 | ## 0.8.0 (2023-09-02) 27 | 28 | - adds tests for all `AnimatedToggleSwitch` constructors 29 | - adds `separatorBuilder`, `customSeparatorBuilder`, `style` and `styleAnimationType` to `AnimatedToggleSwitch` 30 | - adds `separatorBuilder` to `CustomAnimatedToggleSwitch` 31 | - adds `active` to all constructors ([#30](https://github.com/splashbyte/animated_toggle_switch/issues/30)) 32 | - adds `styleBuilder` and `styleList` to `AnimatedToggleSwitch` 33 | - adds `iconList` to `AnimatedToggleSwitch.size`, `AnimatedToggleSwitch.sizeByHeight`, `AnimatedToggleSwitch.rolling` and `AnimatedToggleSwitch.rollingByHeight` 34 | - BREAKING: moves many parameters in `AnimatedToggleSwitch` to `style`: 35 | - `innerColor` (renamed to `backgroundColor`) 36 | - `innerGradient` (renamed to `backgroundGradient`) 37 | - `borderColor` 38 | - `indicatorColor` 39 | - `borderRadius` 40 | - `indicatorBorderColor` 41 | - `indicatorBorder` 42 | - `indicatorBorder` 43 | - `foregroundBoxShadow` (renamed to `indicatorBoxShadow`) 44 | - `boxShadow` 45 | - BREAKING: moves all cursor parameters to `cursors` 46 | - BREAKING: removes `borderColorBuilder` in favor of the new `styleBuilder` 47 | - BREAKING: `indicatorAnimationType` handles `ToggleStyle.indicatorColor`, `ToggleStyle.indicatorBorderRadius`, `ToggleStyle.indicatorBorder` and `ToggleStyle.indicatorBoxShadow` now 48 | - BREAKING: adds parameter to `onTap` ([#41](https://github.com/splashbyte/animated_toggle_switch/issues/41)) 49 | - BREAKING: changes default background color from `ThemeData.scaffoldBackgroundColor` to `ThemeData.colorScheme.surface` 50 | - BREAKING: renames `dif` to `spacing` in all constructors 51 | - BREAKING: replaces `transitionType` with `indicatorTransition` in `AnimatedToggleSwitch.rolling()`, 52 | `AnimatedToggleSwitch.rollingByHeight()` and `AnimatedToggleSwitch.dual()` 53 | - BREAKING: removes `iconSize` and `selectedIconSize` in `AnimatedToggleSwitch` constructors 54 | - `rolling()`, `rollingByHeight()`, `dual()`: new parameter `indicatorIconScale` controls scaling now 55 | - `size()`, `sizeByHeight()`: new parameter `selectedIconScale` controls scaling now 56 | 57 | ## 0.7.0 (2023-06-19) 58 | 59 | - adds possibility that no valid value is selected (by setting `allowUnlistedValues` to `true`) 60 | - adds new parameters to almost all constructors: `allowUnlistedValues`, `indicatorAppearingBuilder`, `indicatorAppearingDuration`, `indicatorAppearingCurve` 61 | 62 | ## 0.6.2 (2023-03-09) 63 | 64 | - adds screenshot to pubspec.yaml 65 | 66 | ## 0.6.1 (2022-12-22) 67 | 68 | - adds examples for loading animation to README 69 | 70 | ## 0.6.0 (2022-12-22) 71 | 72 | - fixes README 73 | - fixes ([#28](https://github.com/splashbyte/animated_toggle_switch/issues/28)) 74 | - BREAKING: increases minimum SDK to 2.17 75 | - BREAKING: renames `value` to `current` and `previousValue` to `previous` in `DetailedGlobalToggleProperties` 76 | - BREAKING feature: Adds loading animation to all switches. You can disable it by setting `loading` to false. 77 | - adds `innerGradient` to `AnimatedToggleSwitch` 78 | - adds `transitionType` to `AnimatedToggleSwitch.rolling`, `AnimatedToggleSwitch.rollingByHeight` and `AnimatedToggleSwitch.dual` 79 | 80 | ## 0.5.2 (2022-04-22) 81 | 82 | - minor performance improvement 83 | - minor fixes 84 | - improves code documentation 85 | - adds `dragCursor` and `draggingCursor` to `CustomAnimatedToggleSwitch` 86 | - adds `iconsTappable`, `defaultCursor`, `dragCursor` and `draggingCursor` to `AnimatedToggleSwitch` 87 | 88 | ## 0.5.1 (2022-04-21) 89 | 90 | - fixes ([#20](https://github.com/splashbyte/animated_toggle_switch/issues/20)) 91 | 92 | ## 0.5.0 (2022-04-20) 93 | 94 | - minor performance improvement 95 | - fixes problems with tight constraints 96 | - BREAKING: changes default values of `animationOffset` and `clipAnimation` in `AnimatedToggleSwitch.dual` 97 | 98 | ## 0.4.0 (2022-04-03) 99 | 100 | - minor fixes and performance improvements 101 | - adds `indicatorBorderRadius` to `AnimatedToggleSwitch` 102 | - adds `animationOffset`, `clipAnimation` and `opacityAnimation` to `AnimatedToggleSwitch.dual` 103 | - BREAKING: sets default values of `animationOffset`, `clipAnimation` and `opacityAnimation` in `AnimatedToggleSwitch.dual` 104 | - BREAKING: renames `foregroundBorder` to `indicatorBorder` 105 | 106 | ## 0.3.1 (2022-03-23) 107 | 108 | - minor fixes 109 | 110 | ## 0.3.0 (2022-03-21) 111 | 112 | - introduces `CustomAnimatedToggleSwitch` for maximum customizability 113 | - most constructors of `AnimatedToggleSwitch` have a standard and a more customizable parameter for their builders now 114 | - full support of `TextDirection.rtl` 115 | - adds animation when dragging the switch 116 | - adds `minTouchTargetSize`, `dragStartDuration`, `dragStartCurve` and `textDirection` to `AnimatedToggleSwitch` 117 | - BREAKING: `TextDirection` is taken from `BuildContext` by default now!!! 118 | - BREAKING: changes parameters and names of some builders 119 | - BREAKING: renames `AnimatedToggleSwitch.byHeight` to `AnimatedToggleSwitch.customByHeight` 120 | - BREAKING: adds default `textMargin` to `AnimatedToggleSwitch.dual` 121 | - fixes ([#9](https://github.com/splashbyte/animated_toggle_switch/issues/9)) 122 | 123 | ## 0.2.3 (2022-02-28) 124 | 125 | - BREAKING: removes `indicatorType` 126 | - BREAKING: changes default `innerColor` 127 | - adds `BoxShadow` parameters 128 | 129 | ## 0.2.2 (2022-01-27) 130 | 131 | - minor performance improvements 132 | 133 | ## 0.2.2 (2022-01-27) 134 | 135 | - minor changes/fixes 136 | 137 | ## 0.2.1 (2021-10-03) 138 | 139 | - migrates to Flutter 2.5 140 | - minor changes/fixes 141 | 142 | ## 0.2.0 (2021-05-21) 143 | 144 | - minor Changes 145 | - fixes `FittingMode.preventHorizontalOverlapping` 146 | - improves Web support 147 | 148 | ## 0.1.3 (2021-03-27) 149 | 150 | - updates README.md 151 | 152 | ## 0.1.2 (2021-03-27) 153 | 154 | - adds `AnimatedToggleSwitch.dual` 155 | - adds some settings (`AnimationType`) 156 | 157 | ## 0.1.1 (2021-03-26) 158 | 159 | - minor fix 160 | 161 | ## 0.1.0 (2021-03-26) 162 | 163 | - initial release 164 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2022, SplashByte GbR 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnimatedToggleSwitch 2 | 3 | [![pub.dev](https://img.shields.io/pub/v/animated_toggle_switch.svg?style=flat?logo=dart)](https://pub.dev/packages/animated_toggle_switch) 4 | [![github](https://img.shields.io/static/v1?label=platform&message=flutter&color=1ebbfd)](https://github.com/SplashByte/animated_toggle_switch) 5 | [![likes](https://img.shields.io/pub/likes/animated_toggle_switch)](https://pub.dev/packages/animated_toggle_switch/score) 6 | [![downloads](https://img.shields.io/pub/dm/animated_toggle_switch)](https://pub.dev/packages/animated_toggle_switch/score) 7 | [![pub points](https://img.shields.io/pub/points/animated_toggle_switch)](https://pub.dev/packages/animated_toggle_switch/score) 8 | [![license](https://img.shields.io/github/license/SplashByte/animated_toggle_switch.svg)](https://github.com/SplashByte/animated_toggle_switch/blob/main/LICENSE) 9 | [![codecov](https://codecov.io/gh/splashbyte/animated_toggle_switch/branch/main/graph/badge.svg?token=NY1D6W88H2)](https://codecov.io/gh/splashbyte/animated_toggle_switch) 10 | [![buy me a coffee](https://img.shields.io/badge/-buy_me_a%C2%A0coffee-gray?logo=buy-me-a-coffee)](https://www.buymeacoffee.com/splashbyte) 11 | 12 | ### If you like this package, please like it on [pub.dev](https://pub.dev/packages/animated_toggle_switch) and star it on [GitHub](https://github.com/SplashByte/animated_toggle_switch). 13 | 14 | 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`. 15 | `LTR` and `RTL` are both supported. 16 | [Switches without an (initial) selection](#nullable-selection) and [vertical switches](#vertical-switches) are also possible. 17 | 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. 18 | 19 | For a slider with a similar look you can check out [action_slider](https://pub.dev/packages/action_slider). 20 | 21 | ## Example Usage 22 | ![usage](https://user-images.githubusercontent.com/43761463/114942384-c1200d00-9e44-11eb-9904-3cb1d7296da4.gif) 23 | 24 | ## Examples 25 | `AnimatedToggleSwitch.dual()` 26 | ![animated_toggle_switch_example_dual](https://user-images.githubusercontent.com/43761463/161432631-e6dd3d16-7b64-410b-a9fa-c956d3442598.gif) 27 | ![animated_toggle_switch_example_borderradius_builder](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/ee615f64-d897-43f1-b508-0318805195e4) 28 | ![animated_toggle_switch_example_gradient](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/b0d390fc-cd18-45ad-b2ce-61e453f098ad) 29 | 30 | Switch inspired by [lite_rolling_switch](https://pub.dev/packages/lite_rolling_switch) (made with `AnimatedToggleSwitch.dual()`) 31 | ![animated_toggle_switch_example_lite_rolling_switch](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/34751d16-cbb1-42b5-a14d-6bd3340d676a) 32 | 33 | Switch inspired by [toggle_switch](https://pub.dev/packages/toggle_switch) (made with `AnimatedToggleSwitch.size()`) 34 | ![animated_toggle_switch_example_toggle_switch](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/884c8433-3b11-4fe1-b2a8-c02599c56aee) 35 | 36 | Switch inspired by [crazy-switch](https://github.com/pedromassango/crazy-switch) (made with `CustomAnimatedToggleSwitch()`) 37 | ![animated_toggle_switch_example_crazy_switch](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/106afaf5-88a0-4d4b-ad59-2b22182d18be) 38 | 39 | Switch inspired by [load_switch](https://pub.dev/packages/load_switch) (made with `CustomAnimatedToggleSwitch()`) 40 | ![animated_toggle_switch_example_loading_switch](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/e8cd9e3f-b8a1-4dcc-8319-21d7875cef0e) 41 | 42 | `AnimatedToggleSwitch.rolling()` 43 | ![animated_toggle_switch_example_1](https://user-images.githubusercontent.com/43761463/161432579-9fe81c57-6463-45c3-a48f-75db666a3a22.gif) 44 | ![animated_toggle_switch_example_2](https://user-images.githubusercontent.com/43761463/161432589-d76f61f6-cb97-42e2-b1fd-8c5203a965fa.gif) 45 | ![animated_toggle_switch_example_gradient](https://user-images.githubusercontent.com/43761463/209117203-90a41ddc-db1c-41be-8375-5304317d1352.gif) 46 | ![animated_toggle_switch_example_borderradius_builder_2](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/e9a6328e-fc6a-4080-9868-1f0eaf60f6db) 47 | ![animated_toggle_switch_example_rolling_separator](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/562fa54d-6a03-4099-a61b-0bc386d22adb) 48 | 49 | 50 | You can build any other switch with `CustomAnimatedToggleSwitch()` 51 | ![animated_toggle_switch_example_custom_1](https://user-images.githubusercontent.com/43761463/161433015-c3ec634a-38da-463d-a06e-4ae0b29f77ed.gif) 52 | 53 | `AnimatedToggleSwitch.size()` 54 | ![animated_toggle_switch_example_size](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/805a0e3f-b3a2-4801-baf9-7a5509905452) 55 | ![animated_toggle_switch_example_size_2](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/ed2c1e50-1012-41ef-8218-71c1144e514b) 56 | 57 | `AnimatedToggleSwitch.size()` with custom rolling animation 58 | ![animated_toggle_switch_example_6](https://user-images.githubusercontent.com/43761463/161432744-f60b660d-30d9-4d1d-9b87-14b62bc54e39.gif) 59 | 60 | `AnimatedToggleSwitch.rolling()` with custom `indicatorSize`, `borderRadius` and `indicatorBorderRadius` 61 | ![animated_toggle_switch_example_7](https://user-images.githubusercontent.com/43761463/161432823-6cf3c855-2a9a-4f4a-9e5c-2951c4166f49.gif) 62 | ![animated_toggle_switch_example_8](https://user-images.githubusercontent.com/43761463/161432826-4b0c3e57-eed7-4567-8e7e-31b8a2ba6bee.gif) 63 | 64 | ## Easy Usage 65 | 66 | Easy to use and highly customizable. 67 | 68 | ### Simple rolling animation 69 | 70 | ```dart 71 | AnimatedToggleSwitch.rolling( 72 | current: value, 73 | values: [0, 1, 2, 3], 74 | onChanged: (i) => setState(() => value = i), 75 | iconBuilder: iconBuilder, 76 | // iconList: [...], you can use iconBuilder, customIconBuilder or iconList 77 | style: ToggleStyle(...), // optional style settings 78 | ... // many more parameters available 79 | ) 80 | ``` 81 | 82 | ### Styling 83 | #### Styling with constructor 84 | `style`, `styleBuilder`, `customStyleBuilder` and `styleList` can be used to style an `AnimatedToggleSwitch`. 85 | For the general look of the switch, you can use `style`. 86 | For parameters that should change with the selection, you can use `styleBuilder` or `styleList`. 87 | If you need additional parameters, you can use `customStyleBuilder`. 88 | ```dart 89 | AnimatedToggleSwitch.rolling( 90 | ... 91 | style: ToggleStyle(backgroundColor: Colors.red), // backgroundColor is set independently of the current selection 92 | styleBuilder: (value) => ToggleStyle(indicatorColor: value.isEven ? Colors.yellow : Colors.green)), // indicatorColor changes and animates its value with the selection 93 | ... 94 | ) 95 | ``` 96 | 97 | #### Styling with Theme 98 | You can also add `ToggleStyle` to `ThemeData`. 99 | This overwrites the default `ToggleStyle` values in all `AnimatedToggleSwitch` widgets in your `MaterialApp`. 100 | 101 | ```dart 102 | MaterialApp( 103 | theme: ThemeData(..., extensions: [ToggleStyle(...)]), 104 | ... 105 | ) 106 | ``` 107 | 108 | After that, you can access this `ToggleStyle` anywhere in your `MaterialApp`: 109 | 110 | ```dart 111 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 112 | ... 113 | 114 | Theme.of(context).toggleStyle 115 | ``` 116 | 117 | ### Loading animation 118 | ![animated_toggle_switch_example_rolling_loading](https://user-images.githubusercontent.com/43761463/209121057-2ff2bfc3-063e-4704-a981-f5cc5f54720a.gif) 119 | To use the loading animation, you simply have to return a `Future` in `onChanged` or `onTap`. 120 | You can alternatively control the loading manually with the `loading` parameter. 121 | Hence, to disable the loading animation, `loading: false` must be set. 122 | 123 | ```dart 124 | AnimatedToggleSwitch.rolling( 125 | current: value, 126 | values: [0, 1, 2, 3], 127 | onChanged: (i) async { 128 | setState(() => value = i); 129 | await Future.delayed(Duration(seconds: 3)); 130 | }, 131 | loading: false, // for deactivating loading animation 132 | iconBuilder: iconBuilder, 133 | ... // many more parameters available 134 | ) 135 | ``` 136 | 137 | ### Nullable selection 138 | ![animated_toggle_switch_example_unlisted_value](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/570f39e8-bc5c-4a19-a91a-d186d4bbd8fe) 139 | To use this feature, you simply have to set `allowUnlistedValues` to `true`. 140 | 141 | ```dart 142 | AnimatedToggleSwitch.rolling( 143 | allowUnlistedValues: true, 144 | current: nullableValue, // no selection if nullableValue is not contained in values 145 | values: const [0, 1, 2, 3], 146 | onChanged: (i) => setState(() => nullableValue = i), 147 | iconBuilder: iconBuilder, 148 | indicatorAppearingBuilder: ..., // appearing animation is fully customizable (optional) 149 | ) 150 | ``` 151 | 152 | ### Vertical switches 153 | ![animated_toggle_switch_example_vertical](https://github.com/user-attachments/assets/b6b804d0-a8f0-47a2-bf73-e9e0b65773d7) 154 | You can get a vertical version of any switch by calling `vertical()` on it. 155 | 156 | ```dart 157 | AnimatedToggleSwitch.rolling(...).vertical() 158 | ``` 159 | 160 | ### Fully customizable toggle switch with `CustomAnimatedToggleSwitch` 161 | 162 | ```dart 163 | CustomAnimatedToggleSwitch( 164 | current: value, 165 | values: [0, 1, 2, 3], 166 | wrapperBuilder: ..., // the builder for the wrapper around the whole switch 167 | iconBuilder: ..., // the builder for the icons 168 | foregroundIndicatorBuilder: ..., // a builder for an indicator in front of the icons 169 | backgroundIndicatorBuilder: ..., // a builder for an indicator behind the icons 170 | ... // many more parameters available 171 | ) 172 | ``` 173 | 174 | ### `AnimatedToggleSwitch.size` with some settings 175 | ![animated_toggle_switch_example_size](https://github.com/splashbyte/animated_toggle_switch/assets/43761463/805a0e3f-b3a2-4801-baf9-7a5509905452) 176 | ```dart 177 | AnimatedToggleSwitch.size( 178 | textDirection: TextDirection.rtl, 179 | current: value, 180 | values: const [0, 1, 2, 3], 181 | iconOpacity: 0.2, 182 | indicatorSize: const Size.fromWidth(100), 183 | iconBuilder: iconBuilder, 184 | borderWidth: 4.0, 185 | iconAnimationType: AnimationType.onHover, 186 | style: ToggleStyle( 187 | borderColor: Colors.transparent, 188 | borderRadius: BorderRadius.circular(10.0), 189 | boxShadow: [ 190 | BoxShadow( 191 | color: Colors.black26, 192 | spreadRadius: 1, 193 | blurRadius: 2, 194 | offset: Offset(0, 1.5), 195 | ), 196 | ], 197 | ), 198 | styleBuilder: (i) => ToggleStyle(indicatorColor: colorBuilder(i)), 199 | onChanged: (i) => setState(() => value = i), 200 | ) 201 | ``` 202 | 203 | ### Self-made rolling animation (with `foregroundIndicatorIconBuilder`) 204 | ![animated_toggle_switch_example_6](https://user-images.githubusercontent.com/43761463/161432744-f60b660d-30d9-4d1d-9b87-14b62bc54e39.gif) 205 | ```dart 206 | AnimatedToggleSwitch.size( 207 | current: value, 208 | values: const [0, 1, 2, 3], 209 | iconOpacity: 1.0, 210 | selectedIconScale: 1.0, 211 | indicatorSize: const Size.fromWidth(25), 212 | foregroundIndicatorIconBuilder: (context, global) { 213 | double pos = global.position; 214 | double transitionValue = pos - pos.floorToDouble(); 215 | return Transform.rotate( 216 | angle: 2.0 * pi * transitionValue, 217 | child: Stack(children: [ 218 | Opacity( 219 | opacity: 1 - transitionValue, 220 | child: iconBuilder(pos.floor(), global.indicatorSize)), 221 | Opacity( 222 | opacity: transitionValue, 223 | child: iconBuilder(pos.ceil(), global.indicatorSize)) 224 | ])); 225 | }, 226 | iconBuilder: iconBuilder, 227 | style: ToggleStyle( 228 | borderColor: Colors.red, 229 | borderRadius: BorderRadius.circular(8.0), 230 | indicatorBorderRadius: BorderRadius.zero, 231 | ), 232 | styleBuilder: (i) => ToggleStyle(indicatorColor: i.isEven == true ? Colors.green : Colors.tealAccent), 233 | onChanged: (i) => setState(() => value = i), 234 | ) 235 | ``` 236 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | analyzer: 4 | language: 5 | strict-casts: true 6 | strict-raw-types: true 7 | 8 | linter: 9 | rules: 10 | prefer_single_quotes: true 11 | parameter_assignments: true -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | 36 | # Symbolication related 37 | app.*.symbols 38 | 39 | # Obfuscation related 40 | app.*.map.json 41 | 42 | # Android Studio will place build artifacts here 43 | /android/app/debug 44 | /android/app/profile 45 | /android/app/release 46 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 8962f6dc68ec8e2206ac2fa874da4a453856c7d3 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | An example project for animated_toggle_switch. 4 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 34 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "com.example.example" 38 | minSdkVersion flutter.minSdkVersion 39 | targetSdkVersion 34 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 14 | 18 | 22 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.8.22' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.4.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1510; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | alwaysOutOfDate = 1; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | ); 178 | inputPaths = ( 179 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 180 | ); 181 | name = "Thin Binary"; 182 | outputPaths = ( 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | shellPath = /bin/sh; 186 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 187 | }; 188 | 9740EEB61CF901F6004384FC /* Run Script */ = { 189 | isa = PBXShellScriptBuildPhase; 190 | alwaysOutOfDate = 1; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | ); 194 | inputPaths = ( 195 | ); 196 | name = "Run Script"; 197 | outputPaths = ( 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | shellPath = /bin/sh; 201 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 202 | }; 203 | /* End PBXShellScriptBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | 97C146EA1CF9000F007C117D /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 211 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXVariantGroup section */ 218 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 219 | isa = PBXVariantGroup; 220 | children = ( 221 | 97C146FB1CF9000F007C117D /* Base */, 222 | ); 223 | name = Main.storyboard; 224 | sourceTree = ""; 225 | }; 226 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 227 | isa = PBXVariantGroup; 228 | children = ( 229 | 97C147001CF9000F007C117D /* Base */, 230 | ); 231 | name = LaunchScreen.storyboard; 232 | sourceTree = ""; 233 | }; 234 | /* End PBXVariantGroup section */ 235 | 236 | /* Begin XCBuildConfiguration section */ 237 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 243 | CLANG_CXX_LIBRARY = "libc++"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_COMMA = YES; 249 | CLANG_WARN_CONSTANT_CONVERSION = YES; 250 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 261 | CLANG_WARN_STRICT_PROTOTYPES = YES; 262 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 268 | ENABLE_NS_ASSERTIONS = NO; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | GCC_C_LANGUAGE_STANDARD = gnu99; 271 | GCC_NO_COMMON_BLOCKS = YES; 272 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 273 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 274 | GCC_WARN_UNDECLARED_SELECTOR = YES; 275 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 276 | GCC_WARN_UNUSED_FUNCTION = YES; 277 | GCC_WARN_UNUSED_VARIABLE = YES; 278 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 279 | MTL_ENABLE_DEBUG_INFO = NO; 280 | SDKROOT = iphoneos; 281 | SUPPORTED_PLATFORMS = iphoneos; 282 | TARGETED_DEVICE_FAMILY = "1,2"; 283 | VALIDATE_PRODUCT = YES; 284 | }; 285 | name = Profile; 286 | }; 287 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 288 | isa = XCBuildConfiguration; 289 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 290 | buildSettings = { 291 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 292 | CLANG_ENABLE_MODULES = YES; 293 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 294 | ENABLE_BITCODE = NO; 295 | INFOPLIST_FILE = Runner/Info.plist; 296 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 297 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 298 | PRODUCT_NAME = "$(TARGET_NAME)"; 299 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 300 | SWIFT_VERSION = 5.0; 301 | VERSIONING_SYSTEM = "apple-generic"; 302 | }; 303 | name = Profile; 304 | }; 305 | 97C147031CF9000F007C117D /* Debug */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | CLANG_ANALYZER_NONNULL = YES; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INFINITE_RECURSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 329 | CLANG_WARN_STRICT_PROTOTYPES = YES; 330 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 331 | CLANG_WARN_UNREACHABLE_CODE = YES; 332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 333 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 334 | COPY_PHASE_STRIP = NO; 335 | DEBUG_INFORMATION_FORMAT = dwarf; 336 | ENABLE_STRICT_OBJC_MSGSEND = YES; 337 | ENABLE_TESTABILITY = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_DYNAMIC_NO_PIC = NO; 340 | GCC_NO_COMMON_BLOCKS = YES; 341 | GCC_OPTIMIZATION_LEVEL = 0; 342 | GCC_PREPROCESSOR_DEFINITIONS = ( 343 | "DEBUG=1", 344 | "$(inherited)", 345 | ); 346 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 347 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 348 | GCC_WARN_UNDECLARED_SELECTOR = YES; 349 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 350 | GCC_WARN_UNUSED_FUNCTION = YES; 351 | GCC_WARN_UNUSED_VARIABLE = YES; 352 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 353 | MTL_ENABLE_DEBUG_INFO = YES; 354 | ONLY_ACTIVE_ARCH = YES; 355 | SDKROOT = iphoneos; 356 | TARGETED_DEVICE_FAMILY = "1,2"; 357 | }; 358 | name = Debug; 359 | }; 360 | 97C147041CF9000F007C117D /* Release */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | ALWAYS_SEARCH_USER_PATHS = NO; 364 | CLANG_ANALYZER_NONNULL = YES; 365 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 366 | CLANG_CXX_LIBRARY = "libc++"; 367 | CLANG_ENABLE_MODULES = YES; 368 | CLANG_ENABLE_OBJC_ARC = YES; 369 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 370 | CLANG_WARN_BOOL_CONVERSION = YES; 371 | CLANG_WARN_COMMA = YES; 372 | CLANG_WARN_CONSTANT_CONVERSION = YES; 373 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 374 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 375 | CLANG_WARN_EMPTY_BODY = YES; 376 | CLANG_WARN_ENUM_CONVERSION = YES; 377 | CLANG_WARN_INFINITE_RECURSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 380 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 381 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 382 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 383 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 384 | CLANG_WARN_STRICT_PROTOTYPES = YES; 385 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 386 | CLANG_WARN_UNREACHABLE_CODE = YES; 387 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 388 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 389 | COPY_PHASE_STRIP = NO; 390 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 391 | ENABLE_NS_ASSERTIONS = NO; 392 | ENABLE_STRICT_OBJC_MSGSEND = YES; 393 | GCC_C_LANGUAGE_STANDARD = gnu99; 394 | GCC_NO_COMMON_BLOCKS = YES; 395 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 396 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 397 | GCC_WARN_UNDECLARED_SELECTOR = YES; 398 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 399 | GCC_WARN_UNUSED_FUNCTION = YES; 400 | GCC_WARN_UNUSED_VARIABLE = YES; 401 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 402 | MTL_ENABLE_DEBUG_INFO = NO; 403 | SDKROOT = iphoneos; 404 | SUPPORTED_PLATFORMS = iphoneos; 405 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 406 | TARGETED_DEVICE_FAMILY = "1,2"; 407 | VALIDATE_PRODUCT = YES; 408 | }; 409 | name = Release; 410 | }; 411 | 97C147061CF9000F007C117D /* Debug */ = { 412 | isa = XCBuildConfiguration; 413 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 414 | buildSettings = { 415 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 416 | CLANG_ENABLE_MODULES = YES; 417 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 418 | ENABLE_BITCODE = NO; 419 | INFOPLIST_FILE = Runner/Info.plist; 420 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 421 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 424 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 425 | SWIFT_VERSION = 5.0; 426 | VERSIONING_SYSTEM = "apple-generic"; 427 | }; 428 | name = Debug; 429 | }; 430 | 97C147071CF9000F007C117D /* Release */ = { 431 | isa = XCBuildConfiguration; 432 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 433 | buildSettings = { 434 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 435 | CLANG_ENABLE_MODULES = YES; 436 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 437 | ENABLE_BITCODE = NO; 438 | INFOPLIST_FILE = Runner/Info.plist; 439 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 440 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 441 | PRODUCT_NAME = "$(TARGET_NAME)"; 442 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 443 | SWIFT_VERSION = 5.0; 444 | VERSIONING_SYSTEM = "apple-generic"; 445 | }; 446 | name = Release; 447 | }; 448 | /* End XCBuildConfiguration section */ 449 | 450 | /* Begin XCConfigurationList section */ 451 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 452 | isa = XCConfigurationList; 453 | buildConfigurations = ( 454 | 97C147031CF9000F007C117D /* Debug */, 455 | 97C147041CF9000F007C117D /* Release */, 456 | 249021D3217E4FDB00AE95B9 /* Profile */, 457 | ); 458 | defaultConfigurationIsVisible = 0; 459 | defaultConfigurationName = Release; 460 | }; 461 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 462 | isa = XCConfigurationList; 463 | buildConfigurations = ( 464 | 97C147061CF9000F007C117D /* Debug */, 465 | 97C147071CF9000F007C117D /* Release */, 466 | 249021D4217E4FDB00AE95B9 /* Profile */, 467 | ); 468 | defaultConfigurationIsVisible = 0; 469 | defaultConfigurationName = Release; 470 | }; 471 | /* End XCConfigurationList section */ 472 | }; 473 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 474 | } 475 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/crazy_switch.dart: -------------------------------------------------------------------------------- 1 | // This switch is inspired by https://github.com/pedromassango/crazy-switch 2 | 3 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class CrazySwitch extends StatefulWidget { 7 | const CrazySwitch({super.key}); 8 | 9 | @override 10 | State createState() => _CrazySwitchState(); 11 | } 12 | 13 | class _CrazySwitchState extends State { 14 | bool current = false; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | const red = Color(0xFFFD0821); 19 | const green = Color(0xFF46E82E); 20 | const borderWidth = 10.0; 21 | const height = 58.0; 22 | const innerIndicatorSize = height - 4 * borderWidth; 23 | 24 | return CustomAnimatedToggleSwitch( 25 | current: current, 26 | spacing: 36.0, 27 | values: const [false, true], 28 | animationDuration: const Duration(milliseconds: 350), 29 | animationCurve: Curves.bounceOut, 30 | iconBuilder: (context, local, global) => const SizedBox(), 31 | onTap: (_) => setState(() => current = !current), 32 | iconsTappable: false, 33 | onChanged: (b) => setState(() => current = b), 34 | height: height, 35 | padding: const EdgeInsets.all(borderWidth), 36 | indicatorSize: const Size.square(height - 2 * borderWidth), 37 | foregroundIndicatorBuilder: (context, global) { 38 | final color = Color.lerp(red, green, global.position)!; 39 | // You can replace the Containers with DecoratedBox/SizedBox/Center 40 | // for slightly better performance 41 | return Container( 42 | alignment: Alignment.center, 43 | decoration: 44 | const BoxDecoration(color: Colors.white, shape: BoxShape.circle), 45 | child: Container( 46 | width: innerIndicatorSize * 0.4 + 47 | global.position * innerIndicatorSize * 0.6, 48 | height: innerIndicatorSize, 49 | decoration: BoxDecoration( 50 | borderRadius: BorderRadius.circular(20.0), 51 | color: color, 52 | )), 53 | ); 54 | }, 55 | wrapperBuilder: (context, global, child) { 56 | final color = Color.lerp(red, green, global.position)!; 57 | return DecoratedBox( 58 | decoration: BoxDecoration( 59 | color: color, 60 | borderRadius: BorderRadius.circular(50.0), 61 | boxShadow: [ 62 | BoxShadow( 63 | color: color.withValues(alpha: 0.7), 64 | blurRadius: 12.0, 65 | offset: const Offset(0.0, 8.0), 66 | ), 67 | ], 68 | ), 69 | child: child, 70 | ); 71 | }, 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /example/lib/load_switch.dart: -------------------------------------------------------------------------------- 1 | // This switch is inspired by https://pub.dev/packages/load_switch 2 | 3 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class LoadSwitch extends StatefulWidget { 7 | const LoadSwitch({super.key}); 8 | 9 | @override 10 | State createState() => _LoadSwitchState(); 11 | } 12 | 13 | class _LoadSwitchState extends State { 14 | bool value = false; 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | const height = 50.0; 19 | const borderWidth = 5.0; 20 | return CustomAnimatedToggleSwitch( 21 | height: height, 22 | indicatorSize: const Size.square(height), 23 | current: value, 24 | values: const [false, true], 25 | onChanged: (newValue) { 26 | setState(() => value = newValue); 27 | return Future.delayed(const Duration(seconds: 2)); 28 | }, 29 | animationDuration: const Duration(milliseconds: 350), 30 | iconArrangement: IconArrangement.overlap, 31 | spacing: -16.0, 32 | wrapperBuilder: (context, global, child) => DecoratedBox( 33 | decoration: BoxDecoration( 34 | color: Color.lerp( 35 | Color.lerp(Colors.red, Colors.green, global.position), 36 | Colors.grey, 37 | global.loadingAnimationValue), 38 | borderRadius: 39 | const BorderRadius.all(Radius.circular(height / 2))), 40 | child: child), 41 | foregroundIndicatorBuilder: (context, global) { 42 | return Stack( 43 | fit: StackFit.expand, 44 | children: [ 45 | const Padding( 46 | padding: EdgeInsets.all(borderWidth), 47 | child: DecoratedBox( 48 | decoration: BoxDecoration( 49 | shape: BoxShape.circle, color: Colors.white)), 50 | ), 51 | Padding( 52 | padding: const EdgeInsets.all(borderWidth / 2), 53 | child: CircularProgressIndicator( 54 | strokeWidth: borderWidth, 55 | color: 56 | Colors.blue.withValues(alpha: global.loadingAnimationValue), 57 | ), 58 | ), 59 | ], 60 | ); 61 | }, 62 | iconBuilder: (context, local, global) => const SizedBox(), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | animated_toggle_switch: 5 | dependency: "direct main" 6 | description: 7 | path: ".." 8 | relative: true 9 | source: path 10 | version: "0.8.4" 11 | async: 12 | dependency: transitive 13 | description: 14 | name: async 15 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 16 | url: "https://pub.dev" 17 | source: hosted 18 | version: "2.12.0" 19 | boolean_selector: 20 | dependency: transitive 21 | description: 22 | name: boolean_selector 23 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 24 | url: "https://pub.dev" 25 | source: hosted 26 | version: "2.1.2" 27 | characters: 28 | dependency: transitive 29 | description: 30 | name: characters 31 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 32 | url: "https://pub.dev" 33 | source: hosted 34 | version: "1.4.0" 35 | clock: 36 | dependency: transitive 37 | description: 38 | name: clock 39 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 40 | url: "https://pub.dev" 41 | source: hosted 42 | version: "1.1.2" 43 | collection: 44 | dependency: transitive 45 | description: 46 | name: collection 47 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 48 | url: "https://pub.dev" 49 | source: hosted 50 | version: "1.19.1" 51 | fake_async: 52 | dependency: transitive 53 | description: 54 | name: fake_async 55 | sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" 56 | url: "https://pub.dev" 57 | source: hosted 58 | version: "1.3.2" 59 | flutter: 60 | dependency: "direct main" 61 | description: flutter 62 | source: sdk 63 | version: "0.0.0" 64 | flutter_test: 65 | dependency: "direct dev" 66 | description: flutter 67 | source: sdk 68 | version: "0.0.0" 69 | flutter_web_plugins: 70 | dependency: transitive 71 | description: flutter 72 | source: sdk 73 | version: "0.0.0" 74 | leak_tracker: 75 | dependency: transitive 76 | description: 77 | name: leak_tracker 78 | sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec 79 | url: "https://pub.dev" 80 | source: hosted 81 | version: "10.0.8" 82 | leak_tracker_flutter_testing: 83 | dependency: transitive 84 | description: 85 | name: leak_tracker_flutter_testing 86 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 87 | url: "https://pub.dev" 88 | source: hosted 89 | version: "3.0.9" 90 | leak_tracker_testing: 91 | dependency: transitive 92 | description: 93 | name: leak_tracker_testing 94 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 95 | url: "https://pub.dev" 96 | source: hosted 97 | version: "3.0.1" 98 | matcher: 99 | dependency: transitive 100 | description: 101 | name: matcher 102 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 103 | url: "https://pub.dev" 104 | source: hosted 105 | version: "0.12.17" 106 | material_color_utilities: 107 | dependency: transitive 108 | description: 109 | name: material_color_utilities 110 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 111 | url: "https://pub.dev" 112 | source: hosted 113 | version: "0.11.1" 114 | meta: 115 | dependency: transitive 116 | description: 117 | name: meta 118 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 119 | url: "https://pub.dev" 120 | source: hosted 121 | version: "1.16.0" 122 | path: 123 | dependency: transitive 124 | description: 125 | name: path 126 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 127 | url: "https://pub.dev" 128 | source: hosted 129 | version: "1.9.1" 130 | plugin_platform_interface: 131 | dependency: transitive 132 | description: 133 | name: plugin_platform_interface 134 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 135 | url: "https://pub.dev" 136 | source: hosted 137 | version: "2.1.8" 138 | sky_engine: 139 | dependency: transitive 140 | description: flutter 141 | source: sdk 142 | version: "0.0.0" 143 | source_span: 144 | dependency: transitive 145 | description: 146 | name: source_span 147 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 148 | url: "https://pub.dev" 149 | source: hosted 150 | version: "1.10.1" 151 | stack_trace: 152 | dependency: transitive 153 | description: 154 | name: stack_trace 155 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 156 | url: "https://pub.dev" 157 | source: hosted 158 | version: "1.12.1" 159 | stream_channel: 160 | dependency: transitive 161 | description: 162 | name: stream_channel 163 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 164 | url: "https://pub.dev" 165 | source: hosted 166 | version: "2.1.4" 167 | string_scanner: 168 | dependency: transitive 169 | description: 170 | name: string_scanner 171 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 172 | url: "https://pub.dev" 173 | source: hosted 174 | version: "1.4.1" 175 | term_glyph: 176 | dependency: transitive 177 | description: 178 | name: term_glyph 179 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 180 | url: "https://pub.dev" 181 | source: hosted 182 | version: "1.2.2" 183 | test_api: 184 | dependency: transitive 185 | description: 186 | name: test_api 187 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 188 | url: "https://pub.dev" 189 | source: hosted 190 | version: "0.7.4" 191 | url_launcher: 192 | dependency: "direct main" 193 | description: 194 | name: url_launcher 195 | sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" 196 | url: "https://pub.dev" 197 | source: hosted 198 | version: "6.3.0" 199 | url_launcher_android: 200 | dependency: transitive 201 | description: 202 | name: url_launcher_android 203 | sha256: "94d8ad05f44c6d4e2ffe5567ab4d741b82d62e3c8e288cc1fcea45965edf47c9" 204 | url: "https://pub.dev" 205 | source: hosted 206 | version: "6.3.8" 207 | url_launcher_ios: 208 | dependency: transitive 209 | description: 210 | name: url_launcher_ios 211 | sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e 212 | url: "https://pub.dev" 213 | source: hosted 214 | version: "6.3.1" 215 | url_launcher_linux: 216 | dependency: transitive 217 | description: 218 | name: url_launcher_linux 219 | sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af 220 | url: "https://pub.dev" 221 | source: hosted 222 | version: "3.2.0" 223 | url_launcher_macos: 224 | dependency: transitive 225 | description: 226 | name: url_launcher_macos 227 | sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" 228 | url: "https://pub.dev" 229 | source: hosted 230 | version: "3.2.0" 231 | url_launcher_platform_interface: 232 | dependency: transitive 233 | description: 234 | name: url_launcher_platform_interface 235 | sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" 236 | url: "https://pub.dev" 237 | source: hosted 238 | version: "2.3.2" 239 | url_launcher_web: 240 | dependency: transitive 241 | description: 242 | name: url_launcher_web 243 | sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" 244 | url: "https://pub.dev" 245 | source: hosted 246 | version: "2.3.3" 247 | url_launcher_windows: 248 | dependency: transitive 249 | description: 250 | name: url_launcher_windows 251 | sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" 252 | url: "https://pub.dev" 253 | source: hosted 254 | version: "3.1.2" 255 | vector_math: 256 | dependency: transitive 257 | description: 258 | name: vector_math 259 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 260 | url: "https://pub.dev" 261 | source: hosted 262 | version: "2.1.4" 263 | vm_service: 264 | dependency: transitive 265 | description: 266 | name: vm_service 267 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" 268 | url: "https://pub.dev" 269 | source: hosted 270 | version: "14.3.1" 271 | web: 272 | dependency: transitive 273 | description: 274 | name: web 275 | sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 276 | url: "https://pub.dev" 277 | source: hosted 278 | version: "1.0.0" 279 | sdks: 280 | dart: ">=3.7.0-0 <4.0.0" 281 | flutter: ">=3.22.0" 282 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: Example project for animated_toggle_switch. 3 | 4 | publish_to: 'none' 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: '>=3.0.0 <4.0.0' 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | animated_toggle_switch: 16 | path: .. 17 | url_launcher: ^6.1.12 18 | 19 | dev_dependencies: 20 | flutter_test: 21 | sdk: flutter 22 | 23 | flutter: 24 | 25 | uses-material-design: true -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/main.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | testWidgets('App starts successfully', (WidgetTester tester) async { 6 | await tester.pumpWidget(const MyApp()); 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | example 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /lib/animated_toggle_switch.dart: -------------------------------------------------------------------------------- 1 | library animated_toggle_switch; 2 | 3 | import 'dart:async'; 4 | import 'dart:math'; 5 | 6 | import 'package:animated_toggle_switch/src/test_keys.dart'; 7 | import 'package:flutter/cupertino.dart'; 8 | import 'package:flutter/foundation.dart'; 9 | import 'package:flutter/gestures.dart'; 10 | import 'package:flutter/material.dart'; 11 | 12 | part 'src/animations.dart'; 13 | part 'src/cursors.dart'; 14 | part 'src/foreground_indicator_transition.dart'; 15 | part 'src/properties.dart'; 16 | part 'src/style.dart'; 17 | part 'src/tweens.dart'; 18 | part 'src/widgets/animated_toggle_switch.dart'; 19 | part 'src/widgets/animation_type_builder.dart'; 20 | part 'src/widgets/conditional_wrapper.dart'; 21 | part 'src/widgets/custom_animated_toggle_switch.dart'; 22 | part 'src/widgets/hover_region.dart'; 23 | -------------------------------------------------------------------------------- /lib/src/animations.dart: -------------------------------------------------------------------------------- 1 | part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | 3 | // this Animation is not covered because it does not contain logic but 4 | // forwards all methods to its parent Animation. 5 | // coverage:ignore-start 6 | /// This class is a proxy for another animation. 7 | /// 8 | /// It is used for passing animations in builders without exposing the real 9 | /// animation to the user. 10 | class _PrivateAnimation extends Animation { 11 | final Animation _parent; 12 | 13 | _PrivateAnimation(this._parent); 14 | 15 | @override 16 | void addListener(VoidCallback listener) => _parent.addListener(listener); 17 | 18 | @override 19 | void addStatusListener(AnimationStatusListener listener) => 20 | _parent.addStatusListener(listener); 21 | 22 | @override 23 | void removeListener(VoidCallback listener) => 24 | _parent.removeListener(listener); 25 | 26 | @override 27 | void removeStatusListener(AnimationStatusListener listener) => 28 | _parent.removeStatusListener(listener); 29 | 30 | @override 31 | AnimationStatus get status => _parent.status; 32 | 33 | @override 34 | T get value => _parent.value; 35 | } 36 | // coverage:ignore-end 37 | -------------------------------------------------------------------------------- /lib/src/cursors.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; 3 | 4 | class ToggleCursors { 5 | /// [MouseCursor] to show when not hovering an indicator or a tappable icon. 6 | /// 7 | /// This defaults to [MouseCursor.defer] if [onTap] is [null] 8 | /// and to [SystemMouseCursors.click] otherwise. 9 | final MouseCursor? defaultCursor; 10 | 11 | /// [MouseCursor] to show when hovering an tappable icon. 12 | final MouseCursor tapCursor; 13 | 14 | /// [MouseCursor] to show when grabbing the indicators. 15 | final MouseCursor draggingCursor; 16 | 17 | /// [MouseCursor] to show when hovering the indicators. 18 | final MouseCursor dragCursor; 19 | 20 | /// [MouseCursor] to show during loading. 21 | final MouseCursor loadingCursor; 22 | 23 | /// [MouseCursor] to show when [active] is set to [false]. 24 | final MouseCursor inactiveCursor; 25 | 26 | const ToggleCursors({ 27 | this.defaultCursor, 28 | this.tapCursor = SystemMouseCursors.click, 29 | this.draggingCursor = SystemMouseCursors.grabbing, 30 | this.dragCursor = SystemMouseCursors.grab, 31 | this.loadingCursor = MouseCursor.defer, 32 | this.inactiveCursor = SystemMouseCursors.forbidden, 33 | }); 34 | 35 | const ToggleCursors.all(MouseCursor cursor) 36 | : defaultCursor = cursor, 37 | tapCursor = cursor, 38 | draggingCursor = cursor, 39 | dragCursor = cursor, 40 | loadingCursor = cursor, 41 | inactiveCursor = cursor; 42 | 43 | @override 44 | bool operator ==(Object other) => 45 | identical(this, other) || 46 | other is ToggleCursors && 47 | runtimeType == other.runtimeType && 48 | defaultCursor == other.defaultCursor && 49 | tapCursor == other.tapCursor && 50 | draggingCursor == other.draggingCursor && 51 | dragCursor == other.dragCursor && 52 | loadingCursor == other.loadingCursor && 53 | inactiveCursor == other.inactiveCursor; 54 | 55 | @override 56 | int get hashCode => 57 | defaultCursor.hashCode ^ 58 | tapCursor.hashCode ^ 59 | draggingCursor.hashCode ^ 60 | dragCursor.hashCode ^ 61 | loadingCursor.hashCode ^ 62 | inactiveCursor.hashCode; 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/foreground_indicator_transition.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; 3 | 4 | /// Different types of transitions for the foreground indicator. 5 | /// 6 | /// Currently this class is used mainly for deactivating the rolling animation in 7 | /// some constructors. 8 | //TODO: Sealed class after upgrade to Dart 3 9 | abstract class ForegroundIndicatorTransition { 10 | const ForegroundIndicatorTransition._(); 11 | 12 | /// Fades between the different icons and shows a rolling animation additionally. 13 | /// 14 | /// [rollingRadius] is the radius which will be used for calculating the rotation. 15 | /// If set to [null], a reasonable value is calculated from [indicatorSize] and [height]. 16 | const factory ForegroundIndicatorTransition.rolling({double? rollingRadius}) = 17 | _RollingForegroundIndicatorTransition; 18 | 19 | /// Fades between the different icons. 20 | const factory ForegroundIndicatorTransition.fading() = 21 | _FadingForegroundIndicatorTransition; 22 | } 23 | 24 | class _RollingForegroundIndicatorTransition 25 | extends ForegroundIndicatorTransition { 26 | /// The radius which will be used for calculating the rotation. 27 | /// 28 | /// If set to [null], a reasonable value is calculated from [indicatorSize], [borderWidth] and [height]. 29 | final double? rollingRadius; 30 | 31 | const _RollingForegroundIndicatorTransition({this.rollingRadius}) : super._(); 32 | } 33 | 34 | class _FadingForegroundIndicatorTransition 35 | extends ForegroundIndicatorTransition { 36 | const _FadingForegroundIndicatorTransition() : super._(); 37 | } 38 | -------------------------------------------------------------------------------- /lib/src/properties.dart: -------------------------------------------------------------------------------- 1 | // coverage:ignore-file 2 | part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; 3 | 4 | class ValueHolder { 5 | final T value; 6 | 7 | /// The index of [value] in [values]. 8 | /// 9 | /// If [values] does not contain [value], this value is set to [-1]. 10 | final int index; 11 | 12 | ValueHolder({required this.value, required this.index}); 13 | } 14 | 15 | class GlobalToggleProperties { 16 | /// The position of the indicator relative to the indices of the values. 17 | final double position; 18 | 19 | /// The current value which is given to the switch. 20 | /// 21 | /// Helpful if the value is generated e.g. 22 | /// when the switch constructor is called. 23 | final T current; 24 | 25 | /// The index of [current] in [values]. 26 | /// 27 | /// If [values] does not contain [current], [currentIndex] is set to [-1]. 28 | final int currentIndex; 29 | 30 | /// This value indicates if [values] does contain [current]. 31 | bool get isCurrentListed => currentIndex >= 0; 32 | 33 | /// The previous value of the switch. 34 | final T? previous; 35 | 36 | /// The values which are given to the switch. 37 | /// 38 | /// Helpful if the list is generated e.g. 39 | /// when the switch constructor is called. 40 | final List values; 41 | 42 | /// The previous position of the indicator relative 43 | /// to the indices of the values. 44 | final double previousPosition; 45 | 46 | /// The [TextDirection] of the switch. 47 | final TextDirection textDirection; 48 | 49 | /// The current [ToggleMode] of the switch. 50 | final ToggleMode mode; 51 | 52 | /// Indicates the progress of the loading animation. 53 | /// [0] means 'not loading' and [1] means 'loading'. 54 | final double loadingAnimationValue; 55 | 56 | final bool active; 57 | 58 | final bool vertical; 59 | 60 | /// This animation indicates whether the indicator is currently visible. 61 | /// 62 | /// [0.0] means it is not visible. 63 | /// 64 | /// [1.0] means it is fully visible. 65 | /// 66 | /// Depending on the curve of the animation, the value can also be below 0.0 or above 1.0. 67 | /// 68 | /// This will be publicly accessible in future releases. 69 | final Animation _indicatorAppearingAnimation; 70 | 71 | const GlobalToggleProperties({ 72 | required this.position, 73 | required this.current, 74 | required this.currentIndex, 75 | required this.previous, 76 | required this.values, 77 | required this.previousPosition, 78 | required this.textDirection, 79 | required this.mode, 80 | required this.loadingAnimationValue, 81 | required this.active, 82 | required this.vertical, 83 | required Animation indicatorAppearingAnimation, 84 | }) : _indicatorAppearingAnimation = indicatorAppearingAnimation; 85 | } 86 | 87 | class DetailedGlobalToggleProperties extends GlobalToggleProperties { 88 | /// The final width of the space between the icons. 89 | /// 90 | /// May differ from the value passed to the switch. 91 | final double spacing; 92 | 93 | /// The final size of the indicator. 94 | /// 95 | /// May differ from the value passed to the switch. 96 | final Size indicatorSize; 97 | 98 | /// The size of the switch exclusive the outer wrapper 99 | final Size switchSize; 100 | 101 | Size get spacingSize => Size(spacing, switchSize.height); 102 | 103 | const DetailedGlobalToggleProperties({ 104 | required this.spacing, 105 | required this.indicatorSize, 106 | required this.switchSize, 107 | required super.position, 108 | required super.current, 109 | required super.currentIndex, 110 | required super.previous, 111 | required super.values, 112 | required super.previousPosition, 113 | required super.textDirection, 114 | required super.mode, 115 | required super.loadingAnimationValue, 116 | required super.active, 117 | required super.indicatorAppearingAnimation, 118 | required super.vertical, 119 | }); 120 | } 121 | 122 | class LocalToggleProperties { 123 | /// The value. 124 | final T value; 125 | 126 | /// The index of [value]. 127 | /// 128 | /// If [values] does not contain [value], this [index] is set to [-1]. 129 | final int index; 130 | 131 | /// This value indicates if [values] does contain [value]. 132 | bool get isValueListed => index >= 0; 133 | 134 | const LocalToggleProperties({ 135 | required this.value, 136 | required this.index, 137 | }); 138 | } 139 | 140 | class StyledToggleProperties extends LocalToggleProperties { 141 | //TODO: Add style to this class 142 | 143 | const StyledToggleProperties({ 144 | required super.value, 145 | required super.index, 146 | }); 147 | } 148 | 149 | class AnimatedToggleProperties extends StyledToggleProperties { 150 | /// A value between [0] and [1]. 151 | /// 152 | /// [0] indicates that [value] is not selected. 153 | /// 154 | /// [1] indicates that [value] is selected. 155 | final double animationValue; 156 | 157 | AnimatedToggleProperties._fromLocal({ 158 | required this.animationValue, 159 | required LocalToggleProperties properties, 160 | }) : super(value: properties.value, index: properties.index); 161 | 162 | const AnimatedToggleProperties({ 163 | required super.value, 164 | required super.index, 165 | required this.animationValue, 166 | }); 167 | 168 | AnimatedToggleProperties copyWith({T? value, int? index}) { 169 | return AnimatedToggleProperties( 170 | value: value ?? this.value, 171 | index: index ?? this.index, 172 | animationValue: animationValue); 173 | } 174 | } 175 | 176 | class RollingProperties extends StyledToggleProperties { 177 | /// Indicates if the icon is in the foreground. 178 | /// 179 | /// For [RollingIconBuilder] it indicates if the icon will be on the indicator 180 | /// or in the background. 181 | final bool foreground; 182 | 183 | RollingProperties._fromLocal({ 184 | required bool foreground, 185 | required LocalToggleProperties properties, 186 | }) : this( 187 | foreground: foreground, 188 | value: properties.value, 189 | index: properties.index, 190 | ); 191 | 192 | const RollingProperties({ 193 | required this.foreground, 194 | required super.value, 195 | required super.index, 196 | }); 197 | } 198 | 199 | class SeparatorProperties { 200 | /// Index of the separator. 201 | /// 202 | /// The separator is located between the items at [index] and [index+1]. 203 | final int index; 204 | 205 | /// The position of the separator relative to the indices of the values. 206 | double get position => index + 0.5; 207 | 208 | const SeparatorProperties({ 209 | required this.index, 210 | }); 211 | } 212 | 213 | class TapProperties { 214 | /// Information about the point on which the user has tapped. 215 | /// 216 | /// This value can be [null] if the user taps on the border of an 217 | /// [AnimatedToggleSwitch] or on the wrapper of a 218 | /// [CustomAnimatedToggleSwitch]. 219 | final TapInfo? tapped; 220 | 221 | /// The values which are given to the switch. 222 | /// 223 | /// Helpful if the list is generated e.g. 224 | /// when the switch constructor is called. 225 | final List values; 226 | 227 | const TapProperties({ 228 | required this.tapped, 229 | required this.values, 230 | }); 231 | } 232 | 233 | class TogglePosition { 234 | /// The value next to [position]. 235 | final T value; 236 | 237 | /// The index of [value] in [values]. 238 | /// 239 | /// [index == position.round()] should always be [true]. 240 | final int index; 241 | 242 | /// The position relative to the indices of the values. 243 | /// 244 | /// [position] can be in the interval from [-0.5] to [values.length - 0.5]. 245 | /// 246 | /// [position.round() == index] should always be [true]. 247 | final double position; 248 | 249 | TogglePosition({ 250 | required this.value, 251 | required this.index, 252 | required this.position, 253 | }); 254 | 255 | @override 256 | bool operator ==(Object other) => 257 | identical(this, other) || 258 | other is PositionListenerInfo && 259 | runtimeType == other.runtimeType && 260 | value == other.mode && 261 | index == other.index && 262 | position == other.position; 263 | 264 | @override 265 | int get hashCode => value.hashCode ^ index.hashCode ^ position.hashCode; 266 | } 267 | 268 | class TapInfo extends TogglePosition { 269 | TapInfo({ 270 | required super.value, 271 | required super.index, 272 | required super.position, 273 | }); 274 | 275 | TapInfo._fromPosition(TogglePosition position) 276 | : this( 277 | value: position.value, 278 | index: position.index, 279 | position: position.position, 280 | ); 281 | } 282 | 283 | class PositionListenerInfo extends TogglePosition { 284 | final ToggleMode mode; 285 | 286 | PositionListenerInfo({ 287 | required super.value, 288 | required super.index, 289 | required super.position, 290 | required this.mode, 291 | }); 292 | 293 | PositionListenerInfo._fromPosition( 294 | TogglePosition position, ToggleMode mode) 295 | : this( 296 | value: position.value, 297 | index: position.index, 298 | position: position.position, 299 | mode: mode, 300 | ); 301 | 302 | @override 303 | bool operator ==(Object other) => 304 | identical(this, other) || 305 | other is PositionListenerInfo && 306 | runtimeType == other.runtimeType && 307 | value == other.value && 308 | index == other.index && 309 | position == other.position && 310 | mode == other.mode; 311 | 312 | @override 313 | int get hashCode => 314 | value.hashCode ^ index.hashCode ^ position.hashCode ^ mode.hashCode; 315 | } 316 | -------------------------------------------------------------------------------- /lib/src/style.dart: -------------------------------------------------------------------------------- 1 | part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | 3 | /// The base class for all toggle styles. 4 | abstract class _BaseToggleStyle extends ThemeExtension<_BaseToggleStyle> { 5 | const _BaseToggleStyle._(); 6 | 7 | ToggleStyleProperty? get _indicatorColor; 8 | 9 | ToggleStyleProperty? get _indicatorGradient; 10 | 11 | ToggleStyleProperty? get _backgroundColor; 12 | 13 | ToggleStyleProperty? get _backgroundGradient; 14 | 15 | ToggleStyleProperty? get _borderColor; 16 | 17 | ToggleStyleProperty? get _borderRadius; 18 | 19 | ToggleStyleProperty? get _indicatorBorderRadius; 20 | 21 | ToggleStyleProperty? get _indicatorBorder; 22 | 23 | ToggleStyleProperty>? get _indicatorBoxShadow; 24 | 25 | ToggleStyleProperty>? get _boxShadow; 26 | 27 | _BaseToggleStyle _merge( 28 | _BaseToggleStyle? other, 29 | BorderRadiusGeometry indicatorBorderRadiusDifference, 30 | ) => 31 | other == null 32 | ? this 33 | : _CustomToggleStyle._( 34 | indicatorColor: other._indicatorColor ?? _indicatorColor, 35 | indicatorGradient: other._indicatorGradient ?? 36 | (other._indicatorColor != null ? null : _indicatorGradient), 37 | backgroundColor: other._backgroundColor ?? _backgroundColor, 38 | backgroundGradient: other._backgroundGradient ?? 39 | (other._backgroundColor != null ? null : _backgroundGradient), 40 | borderColor: other._borderColor ?? _borderColor, 41 | borderRadius: other._borderRadius ?? _borderRadius, 42 | indicatorBorderRadius: other._indicatorBorderRadius ?? 43 | other._borderRadius?._map((value) => 44 | value.subtract(indicatorBorderRadiusDifference)) ?? 45 | _indicatorBorderRadius ?? 46 | _borderRadius?._map((value) => 47 | value.subtract(indicatorBorderRadiusDifference)), 48 | indicatorBorder: other._indicatorBorder ?? _indicatorBorder, 49 | indicatorBoxShadow: 50 | other._indicatorBoxShadow ?? _indicatorBoxShadow, 51 | boxShadow: other._boxShadow ?? _boxShadow, 52 | ); 53 | 54 | static _BaseToggleStyle Function( 55 | _BaseToggleStyle style1, _BaseToggleStyle style2, double t) _lerpFunction( 56 | AnimationType animationType) => 57 | (style1, style2, t) => _lerp(style1, style2, t, animationType); 58 | 59 | @override 60 | _BaseToggleStyle lerp(_BaseToggleStyle other, double t) => 61 | _lerp(this, other, t, AnimationType.none); 62 | 63 | static _BaseToggleStyle _lerp(_BaseToggleStyle style1, 64 | _BaseToggleStyle style2, double t, AnimationType animationType) => 65 | _CustomToggleStyle._( 66 | indicatorColor: ToggleStyleProperty._lerpConditional( 67 | style1._indicatorColor, 68 | style2._indicatorColor, 69 | t, 70 | Color.lerp, 71 | animationType), 72 | indicatorGradient: ToggleStyleProperty._lerpConditional( 73 | style1._indicatorGradient ?? 74 | style1._indicatorColor?._map((value) => value.toGradient()), 75 | style2._indicatorGradient ?? 76 | style2._indicatorColor?._map((value) => value.toGradient()), 77 | t, 78 | Gradient.lerp, 79 | animationType), 80 | backgroundColor: ToggleStyleProperty._lerpConditional( 81 | style1._backgroundColor, 82 | style2._backgroundColor, 83 | t, 84 | Color.lerp, 85 | animationType), 86 | backgroundGradient: ToggleStyleProperty._lerpConditional( 87 | style1._backgroundGradient ?? 88 | style1._backgroundColor?._map((value) => value.toGradient()), 89 | style2._backgroundGradient ?? 90 | style2._backgroundColor?._map((value) => value.toGradient()), 91 | t, 92 | Gradient.lerp, 93 | animationType), 94 | borderColor: ToggleStyleProperty._lerpConditional(style1._borderColor, 95 | style2._borderColor, t, Color.lerp, animationType), 96 | borderRadius: ToggleStyleProperty._lerpConditional(style1._borderRadius, 97 | style2._borderRadius, t, BorderRadiusGeometry.lerp, animationType), 98 | indicatorBorderRadius: ToggleStyleProperty._lerpConditional( 99 | style1._indicatorBorderRadius ?? style1._borderRadius, 100 | style2._indicatorBorderRadius ?? style2._borderRadius, 101 | t, 102 | BorderRadiusGeometry.lerp, 103 | animationType), 104 | indicatorBorder: ToggleStyleProperty._lerpConditional( 105 | style1._indicatorBorder, 106 | style2._indicatorBorder, 107 | t, 108 | BoxBorder.lerp, 109 | animationType), 110 | indicatorBoxShadow: ToggleStyleProperty._lerpConditional( 111 | style1._indicatorBoxShadow, 112 | style2._indicatorBoxShadow, 113 | t, 114 | BoxShadow.lerpList, 115 | animationType), 116 | boxShadow: ToggleStyleProperty._lerpConditional(style1._boxShadow, 117 | style2._boxShadow, t, BoxShadow.lerpList, animationType), 118 | ); 119 | } 120 | 121 | // coverage:ignore-start 122 | /// Currently not supported. 123 | class _CustomToggleStyle extends _BaseToggleStyle { 124 | @override 125 | Object get type => _CustomToggleStyle; 126 | 127 | @override 128 | final ToggleStyleProperty? _indicatorColor; 129 | @override 130 | final ToggleStyleProperty? _indicatorGradient; 131 | @override 132 | final ToggleStyleProperty? _backgroundColor; 133 | @override 134 | final ToggleStyleProperty? _backgroundGradient; 135 | @override 136 | final ToggleStyleProperty? _borderColor; 137 | @override 138 | final ToggleStyleProperty? _borderRadius; 139 | @override 140 | final ToggleStyleProperty? _indicatorBorderRadius; 141 | @override 142 | final ToggleStyleProperty? _indicatorBorder; 143 | @override 144 | final ToggleStyleProperty>? _indicatorBoxShadow; 145 | @override 146 | final ToggleStyleProperty>? _boxShadow; 147 | 148 | /// Default constructor for [_CustomToggleStyle]. 149 | /// 150 | /// If you don't want to disable the animation of single properties, 151 | /// you should use [ToggleStyle] instead. 152 | const _CustomToggleStyle({ 153 | ToggleStyleProperty? indicatorColor, 154 | ToggleStyleProperty? indicatorGradient, 155 | ToggleStyleProperty? backgroundColor, 156 | ToggleStyleProperty? backgroundGradient, 157 | ToggleStyleProperty? borderColor, 158 | ToggleStyleProperty? borderRadius, 159 | ToggleStyleProperty? indicatorBorderRadius, 160 | ToggleStyleProperty? indicatorBorder, 161 | ToggleStyleProperty>? indicatorBoxShadow, 162 | ToggleStyleProperty>? boxShadow, 163 | }) : _indicatorColor = indicatorColor, 164 | _indicatorGradient = indicatorGradient, 165 | _backgroundColor = backgroundColor, 166 | _backgroundGradient = backgroundGradient, 167 | _borderColor = borderColor, 168 | _borderRadius = borderRadius, 169 | _indicatorBorderRadius = indicatorBorderRadius, 170 | _indicatorBorder = indicatorBorder, 171 | _indicatorBoxShadow = indicatorBoxShadow, 172 | _boxShadow = boxShadow, 173 | super._(); 174 | 175 | const _CustomToggleStyle._({ 176 | required ToggleStyleProperty? indicatorColor, 177 | required ToggleStyleProperty? indicatorGradient, 178 | required ToggleStyleProperty? backgroundColor, 179 | required ToggleStyleProperty? backgroundGradient, 180 | required ToggleStyleProperty? borderColor, 181 | required ToggleStyleProperty? borderRadius, 182 | required ToggleStyleProperty? indicatorBorderRadius, 183 | required ToggleStyleProperty? indicatorBorder, 184 | required ToggleStyleProperty>? indicatorBoxShadow, 185 | required ToggleStyleProperty>? boxShadow, 186 | }) : _indicatorColor = indicatorColor, 187 | _indicatorGradient = indicatorGradient, 188 | _backgroundColor = backgroundColor, 189 | _backgroundGradient = backgroundGradient, 190 | _borderColor = borderColor, 191 | _borderRadius = borderRadius, 192 | _indicatorBorderRadius = indicatorBorderRadius, 193 | _indicatorBorder = indicatorBorder, 194 | _indicatorBoxShadow = indicatorBoxShadow, 195 | _boxShadow = boxShadow, 196 | super._(); 197 | 198 | ToggleStyleProperty? get indicatorColor => _indicatorColor; 199 | 200 | ToggleStyleProperty? get indicatorGradient => _indicatorGradient; 201 | 202 | ToggleStyleProperty? get backgroundColor => _backgroundColor; 203 | 204 | ToggleStyleProperty? get backgroundGradient => _backgroundGradient; 205 | 206 | ToggleStyleProperty? get borderColor => _borderColor; 207 | 208 | ToggleStyleProperty? get borderRadius => _borderRadius; 209 | 210 | ToggleStyleProperty? get indicatorBorderRadius => 211 | _indicatorBorderRadius; 212 | 213 | ToggleStyleProperty? get indicatorBorder => _indicatorBorder; 214 | 215 | ToggleStyleProperty>? get indicatorBoxShadow => 216 | _indicatorBoxShadow; 217 | 218 | ToggleStyleProperty>? get boxShadow => _boxShadow; 219 | 220 | @override 221 | _CustomToggleStyle copyWith({ 222 | ToggleStyleProperty? indicatorColor, 223 | ToggleStyleProperty? indicatorGradient, 224 | ToggleStyleProperty? backgroundColor, 225 | ToggleStyleProperty? backgroundGradient, 226 | ToggleStyleProperty? borderColor, 227 | ToggleStyleProperty? borderRadius, 228 | ToggleStyleProperty? indicatorBorderRadius, 229 | ToggleStyleProperty? indicatorBorder, 230 | ToggleStyleProperty>? indicatorBoxShadow, 231 | ToggleStyleProperty>? boxShadow, 232 | }) => 233 | _CustomToggleStyle._( 234 | indicatorColor: indicatorColor ?? this.indicatorColor, 235 | indicatorGradient: indicatorGradient ?? this.indicatorGradient, 236 | backgroundColor: backgroundColor ?? this.backgroundColor, 237 | backgroundGradient: backgroundGradient ?? this.backgroundGradient, 238 | borderColor: borderColor ?? this.borderColor, 239 | borderRadius: borderRadius ?? this.borderRadius, 240 | indicatorBorderRadius: 241 | indicatorBorderRadius ?? this.indicatorBorderRadius, 242 | indicatorBorder: indicatorBorder ?? this.indicatorBorder, 243 | indicatorBoxShadow: indicatorBoxShadow ?? this.indicatorBoxShadow, 244 | boxShadow: boxShadow ?? this.boxShadow, 245 | ); 246 | } 247 | // coverage:ignore-end 248 | 249 | class ToggleStyle extends _BaseToggleStyle { 250 | @override 251 | Object get type => ToggleStyle; 252 | 253 | /// Background color of the indicator. 254 | /// 255 | /// Defaults to [ThemeData.colorScheme.secondary]. 256 | final Color? indicatorColor; 257 | 258 | /// Gradient of the indicator. Overwrites [indicatorColor] if not [null]. 259 | final Gradient? indicatorGradient; 260 | 261 | /// Background color of the switch. 262 | /// 263 | /// Defaults to [ThemeData.colorScheme.surface]. 264 | final Color? backgroundColor; 265 | 266 | /// Gradient of the background. Overwrites [backgroundColor] if not [null]. 267 | final Gradient? backgroundGradient; 268 | 269 | /// Border color of the switch. 270 | /// 271 | /// Defaults to [ThemeData.colorScheme.secondary]. 272 | /// 273 | /// For deactivating please set this to [Colors.transparent]. 274 | final Color? borderColor; 275 | 276 | /// [BorderRadius] of the switch. 277 | final BorderRadiusGeometry? borderRadius; 278 | 279 | /// [BorderRadius] of the indicator. 280 | /// 281 | /// Defaults to [borderRadius] - [BorderRadius.circular(borderWidth)]. 282 | final BorderRadiusGeometry? indicatorBorderRadius; 283 | 284 | /// [BorderRadius] of the indicator. 285 | final BoxBorder? indicatorBorder; 286 | 287 | /// Shadow for the indicator [Container]. 288 | final List? indicatorBoxShadow; 289 | 290 | /// Shadow for the [Container] in the background. 291 | final List? boxShadow; 292 | 293 | /// Default constructor for [ToggleStyle]. 294 | const ToggleStyle({ 295 | this.indicatorColor, 296 | this.indicatorGradient, 297 | this.backgroundColor, 298 | this.backgroundGradient, 299 | this.borderColor, 300 | this.borderRadius, 301 | this.indicatorBorderRadius, 302 | this.indicatorBorder, 303 | this.indicatorBoxShadow, 304 | this.boxShadow, 305 | }) : super._(); 306 | 307 | const ToggleStyle._({ 308 | required this.indicatorColor, 309 | required this.indicatorGradient, 310 | required this.backgroundColor, 311 | required this.backgroundGradient, 312 | required this.borderColor, 313 | required this.borderRadius, 314 | required this.indicatorBorderRadius, 315 | required this.indicatorBorder, 316 | required this.indicatorBoxShadow, 317 | required this.boxShadow, 318 | }) : super._(); 319 | 320 | @override 321 | ToggleStyleProperty? get _backgroundColor => 322 | ToggleStyleProperty.nullable(backgroundColor); 323 | 324 | @override 325 | ToggleStyleProperty? get _backgroundGradient => 326 | ToggleStyleProperty.nullable(backgroundGradient); 327 | 328 | @override 329 | ToggleStyleProperty? get _borderColor => 330 | ToggleStyleProperty.nullable(borderColor); 331 | 332 | @override 333 | ToggleStyleProperty? get _borderRadius => 334 | ToggleStyleProperty.nullable(borderRadius); 335 | 336 | @override 337 | ToggleStyleProperty>? get _boxShadow => 338 | ToggleStyleProperty.nullable(boxShadow); 339 | 340 | @override 341 | ToggleStyleProperty? get _indicatorBorder => 342 | ToggleStyleProperty.nullable(indicatorBorder); 343 | 344 | @override 345 | ToggleStyleProperty? get _indicatorBorderRadius => 346 | ToggleStyleProperty.nullable(indicatorBorderRadius); 347 | 348 | @override 349 | ToggleStyleProperty>? get _indicatorBoxShadow => 350 | ToggleStyleProperty.nullable(indicatorBoxShadow); 351 | 352 | @override 353 | ToggleStyleProperty? get _indicatorColor => 354 | ToggleStyleProperty.nullable(indicatorColor); 355 | 356 | @override 357 | ToggleStyleProperty? get _indicatorGradient => 358 | ToggleStyleProperty.nullable(indicatorGradient); 359 | 360 | // coverage:ignore-start 361 | 362 | @override 363 | bool operator ==(Object other) => 364 | identical(this, other) || 365 | other is ToggleStyle && 366 | runtimeType == other.runtimeType && 367 | indicatorColor == other.indicatorColor && 368 | indicatorGradient == other.indicatorGradient && 369 | backgroundColor == other.backgroundColor && 370 | backgroundGradient == other.backgroundGradient && 371 | borderColor == other.borderColor && 372 | borderRadius == other.borderRadius && 373 | indicatorBorderRadius == other.indicatorBorderRadius && 374 | indicatorBorder == other.indicatorBorder && 375 | indicatorBoxShadow == other.indicatorBoxShadow && 376 | boxShadow == other.boxShadow; 377 | 378 | @override 379 | int get hashCode => 380 | indicatorColor.hashCode ^ 381 | indicatorGradient.hashCode ^ 382 | backgroundColor.hashCode ^ 383 | backgroundGradient.hashCode ^ 384 | borderColor.hashCode ^ 385 | borderRadius.hashCode ^ 386 | indicatorBorderRadius.hashCode ^ 387 | indicatorBorder.hashCode ^ 388 | indicatorBoxShadow.hashCode ^ 389 | boxShadow.hashCode; 390 | 391 | @override 392 | ToggleStyle copyWith({ 393 | Color? indicatorColor, 394 | Gradient? indicatorGradient, 395 | Color? backgroundColor, 396 | Gradient? backgroundGradient, 397 | Color? borderColor, 398 | BorderRadiusGeometry? borderRadius, 399 | BorderRadiusGeometry? indicatorBorderRadius, 400 | BoxBorder? indicatorBorder, 401 | List? indicatorBoxShadow, 402 | List? boxShadow, 403 | }) => 404 | ToggleStyle._( 405 | indicatorColor: indicatorColor ?? this.indicatorColor, 406 | indicatorGradient: indicatorGradient ?? this.indicatorGradient, 407 | backgroundColor: backgroundColor ?? this.backgroundColor, 408 | backgroundGradient: backgroundGradient ?? this.backgroundGradient, 409 | borderColor: borderColor ?? this.borderColor, 410 | borderRadius: borderRadius ?? this.borderRadius, 411 | indicatorBorderRadius: 412 | indicatorBorderRadius ?? this.indicatorBorderRadius, 413 | indicatorBorder: indicatorBorder ?? this.indicatorBorder, 414 | indicatorBoxShadow: indicatorBoxShadow ?? this.indicatorBoxShadow, 415 | boxShadow: boxShadow ?? this.boxShadow, 416 | ); 417 | 418 | @override 419 | ToggleStyle lerp(ToggleStyle other, double t) { 420 | final result = super.lerp(other, t); 421 | return ToggleStyle._( 422 | indicatorColor: result._indicatorColor?.value, 423 | indicatorGradient: result._indicatorGradient?.value, 424 | backgroundColor: result._backgroundColor?.value, 425 | backgroundGradient: result._backgroundGradient?.value, 426 | borderColor: result._borderColor?.value, 427 | borderRadius: result._borderRadius?.value, 428 | indicatorBorderRadius: result._indicatorBorderRadius?.value, 429 | indicatorBorder: result._indicatorBorder?.value, 430 | indicatorBoxShadow: result._indicatorBoxShadow?.value, 431 | boxShadow: result._boxShadow?.value, 432 | ); 433 | } 434 | 435 | // coverage:ignore-end 436 | } 437 | 438 | class ToggleStyleProperty { 439 | /// The value of this property. 440 | final T value; 441 | 442 | /// Indicates if this property will be animated when changed. 443 | final bool animationEnabled; 444 | 445 | const ToggleStyleProperty( 446 | this.value, { 447 | this.animationEnabled = true, 448 | }); 449 | 450 | static ToggleStyleProperty? nullable( 451 | T? value, { 452 | bool animationEnabled = true, 453 | }) => 454 | value == null 455 | ? null 456 | : ToggleStyleProperty(value, animationEnabled: animationEnabled); 457 | 458 | ToggleStyleProperty _map(S Function(T value) map) => 459 | ToggleStyleProperty( 460 | map(value), 461 | animationEnabled: animationEnabled, 462 | ); 463 | 464 | static ToggleStyleProperty? _lerpConditional( 465 | ToggleStyleProperty? prop1, 466 | ToggleStyleProperty? prop2, 467 | double t, 468 | T? Function(T?, T?, double) lerp, 469 | AnimationType animationType) { 470 | if (prop1?.animationEnabled != true && prop2?.animationEnabled != true) { 471 | if (animationType == AnimationType.onHover && t < 0.5) return prop1; 472 | return prop2; 473 | } 474 | return ToggleStyleProperty.nullable( 475 | lerp(prop1?.value, prop2?.value, t), 476 | animationEnabled: true, 477 | ); 478 | } 479 | } 480 | 481 | extension XThemeDataToggleStyle on ThemeData { 482 | ToggleStyle get toggleStyle => 483 | extension() ?? const ToggleStyle(); 484 | } 485 | -------------------------------------------------------------------------------- /lib/src/test_keys.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | /// This class should only be used in the animated_toggle_switch package itself or in its tests. 4 | abstract class AnimatedToggleSwitchTestKeys { 5 | const AnimatedToggleSwitchTestKeys._(); 6 | 7 | static const Key indicatorDecoratedBoxKey = _AnimatedToggleSwitchKey(0); 8 | } 9 | 10 | class _AnimatedToggleSwitchKey extends ValueKey { 11 | const _AnimatedToggleSwitchKey(super.value); 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/tweens.dart: -------------------------------------------------------------------------------- 1 | part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | 3 | class _CustomTween extends Tween { 4 | final V Function(V value1, V value2, double t) lerpFunction; 5 | 6 | _CustomTween(this.lerpFunction, {super.begin, super.end}); 7 | 8 | @override 9 | V lerp(double t) => lerpFunction(begin as V, end as V, t); 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/widgets/animation_type_builder.dart: -------------------------------------------------------------------------------- 1 | part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | 3 | class _AnimationTypeHoverBuilder extends StatefulWidget { 4 | final V Function(StyledToggleProperties local) valueProvider; 5 | final V Function(V value1, V value2, double t) lerp; 6 | final Widget Function(V value) builder; 7 | final GlobalToggleProperties properties; 8 | final Duration animationDuration; 9 | final Curve animationCurve; 10 | final Duration indicatorAppearingDuration; 11 | final Curve indicatorAppearingCurve; 12 | final bool animateExternalChanges; 13 | 14 | const _AnimationTypeHoverBuilder({ 15 | required this.valueProvider, 16 | required this.lerp, 17 | required this.builder, 18 | required this.properties, 19 | required this.animationDuration, 20 | required this.animationCurve, 21 | required this.indicatorAppearingDuration, 22 | required this.indicatorAppearingCurve, 23 | this.animateExternalChanges = true, 24 | }); 25 | 26 | @override 27 | State<_AnimationTypeHoverBuilder> createState() => 28 | _AnimationTypeHoverBuilderState(); 29 | } 30 | 31 | class _AnimationTypeHoverBuilderState 32 | extends State<_AnimationTypeHoverBuilder> { 33 | final _builderKey = GlobalKey(); 34 | T? _lastUnlistedValue; 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | if (!widget.properties.isCurrentListed) { 40 | _lastUnlistedValue = widget.properties.current; 41 | } 42 | } 43 | 44 | @override 45 | void didUpdateWidget(covariant _AnimationTypeHoverBuilder oldWidget) { 46 | super.didUpdateWidget(oldWidget); 47 | if (!widget.properties.isCurrentListed) { 48 | _lastUnlistedValue = widget.properties.current; 49 | } 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | final pos = widget.properties.position; 55 | final values = widget.properties.values; 56 | final index1 = pos.floor(); 57 | final index2 = pos.ceil(); 58 | V listedValueFunction() => widget.lerp( 59 | widget.valueProvider( 60 | StyledToggleProperties(value: values[index1], index: index1)), 61 | widget.valueProvider( 62 | StyledToggleProperties(value: values[index2], index: index2)), 63 | pos - pos.floor(), 64 | ); 65 | final indicatorAppearingAnimation = 66 | widget.properties._indicatorAppearingAnimation; 67 | return AnimatedBuilder( 68 | animation: indicatorAppearingAnimation, 69 | builder: (context, _) { 70 | final appearingValue = indicatorAppearingAnimation.value; 71 | if (appearingValue >= 1.0) { 72 | return _EmptyWidget( 73 | key: _builderKey, child: widget.builder(listedValueFunction())); 74 | } 75 | final unlistedValue = widget.valueProvider( 76 | StyledToggleProperties(value: _lastUnlistedValue as T, index: -1)); 77 | return TweenAnimationBuilder( 78 | duration: widget.animationDuration, 79 | curve: widget.animationCurve, 80 | tween: _CustomTween(widget.lerp, 81 | begin: unlistedValue, end: unlistedValue), 82 | builder: (context, unlistedValue, _) { 83 | return _EmptyWidget( 84 | key: _builderKey, 85 | child: widget.builder(appearingValue <= 0.0 86 | ? unlistedValue 87 | : widget.lerp( 88 | unlistedValue, listedValueFunction(), appearingValue)), 89 | ); 90 | }); 91 | }, 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/src/widgets/conditional_wrapper.dart: -------------------------------------------------------------------------------- 1 | part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | 3 | // this widget is not covered because it is not used currently 4 | // coverage:ignore-start 5 | class _ConditionalWrapper extends StatefulWidget { 6 | final Widget Function(BuildContext context, Widget child) wrapper; 7 | final bool condition; 8 | final Widget child; 9 | 10 | const _ConditionalWrapper({ 11 | required this.wrapper, 12 | required this.condition, 13 | required this.child, 14 | }); 15 | 16 | @override 17 | State<_ConditionalWrapper> createState() => _ConditionalWrapperState(); 18 | } 19 | 20 | class _ConditionalWrapperState extends State<_ConditionalWrapper> { 21 | final _childKey = GlobalKey(); 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | final child = _EmptyWidget(key: _childKey, child: widget.child); 26 | if (widget.condition) return widget.wrapper(context, child); 27 | return child; 28 | } 29 | } 30 | 31 | //ignore: unused_element 32 | class _ConditionalOpacity extends StatelessWidget { 33 | final double opacity; 34 | final Widget Function(BuildContext context) builder; 35 | 36 | const _ConditionalOpacity({required this.opacity, required this.builder}); 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return _ConditionalWrapper( 41 | wrapper: (context, child) => Opacity( 42 | opacity: opacity, 43 | child: builder(context), 44 | ), 45 | condition: opacity > 0.0, 46 | child: const SizedBox(), 47 | ); 48 | } 49 | } 50 | 51 | // coverage:ignore-end 52 | 53 | class _EmptyWidget extends StatelessWidget { 54 | final Widget child; 55 | 56 | const _EmptyWidget({super.key, required this.child}); 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | return child; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/src/widgets/hover_region.dart: -------------------------------------------------------------------------------- 1 | part of 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | 3 | class _HoverRegion extends StatefulWidget { 4 | final Widget child; 5 | final MouseCursor? Function(Offset offset) cursorByOffset; 6 | final MouseCursor defaultCursor; 7 | 8 | const _HoverRegion({ 9 | Key? key, 10 | required this.child, 11 | required this.cursorByOffset, 12 | this.defaultCursor = MouseCursor.defer, 13 | }) : super(key: key); 14 | 15 | @override 16 | State<_HoverRegion> createState() => _HoverRegionState(); 17 | } 18 | 19 | class _HoverRegionState extends State<_HoverRegion> { 20 | Offset? _position; 21 | MouseCursor? _cursor; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | if (_position != null) _updateHovering(_position!, rebuild: false); 26 | // Listener is necessary because MouseRegion.onHover only gets triggered 27 | // without buttons pressed 28 | return Listener( 29 | behavior: HitTestBehavior.translucent, 30 | onPointerHover: _updatePointer, 31 | onPointerMove: _updatePointer, 32 | child: GestureDetector( 33 | child: MouseRegion( 34 | opaque: false, 35 | hitTestBehavior: HitTestBehavior.translucent, 36 | cursor: _cursor ?? widget.defaultCursor, 37 | onExit: (e) => _setCursor(null), 38 | child: widget.child, 39 | ), 40 | ), 41 | ); 42 | } 43 | 44 | void _updatePointer(PointerEvent event, {bool rebuild = true}) { 45 | _updateHovering(event.localPosition, rebuild: rebuild); 46 | } 47 | 48 | void _updateHovering(Offset offset, {bool rebuild = true}) { 49 | _setCursor(widget.cursorByOffset(_position = offset), rebuild: rebuild); 50 | } 51 | 52 | void _setCursor(MouseCursor? cursor, {bool rebuild = true}) { 53 | if (_cursor == cursor) return; 54 | _cursor = cursor; 55 | if (rebuild) setState(() {}); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "76.0.0" 12 | _macros: 13 | dependency: transitive 14 | description: dart 15 | source: sdk 16 | version: "0.3.3" 17 | analyzer: 18 | dependency: transitive 19 | description: 20 | name: analyzer 21 | sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" 22 | url: "https://pub.dev" 23 | source: hosted 24 | version: "6.11.0" 25 | args: 26 | dependency: transitive 27 | description: 28 | name: args 29 | sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" 30 | url: "https://pub.dev" 31 | source: hosted 32 | version: "2.5.0" 33 | async: 34 | dependency: transitive 35 | description: 36 | name: async 37 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 38 | url: "https://pub.dev" 39 | source: hosted 40 | version: "2.12.0" 41 | boolean_selector: 42 | dependency: transitive 43 | description: 44 | name: boolean_selector 45 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 46 | url: "https://pub.dev" 47 | source: hosted 48 | version: "2.1.2" 49 | characters: 50 | dependency: transitive 51 | description: 52 | name: characters 53 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 54 | url: "https://pub.dev" 55 | source: hosted 56 | version: "1.4.0" 57 | clock: 58 | dependency: transitive 59 | description: 60 | name: clock 61 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 62 | url: "https://pub.dev" 63 | source: hosted 64 | version: "1.1.2" 65 | collection: 66 | dependency: transitive 67 | description: 68 | name: collection 69 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 70 | url: "https://pub.dev" 71 | source: hosted 72 | version: "1.19.1" 73 | convert: 74 | dependency: transitive 75 | description: 76 | name: convert 77 | sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" 78 | url: "https://pub.dev" 79 | source: hosted 80 | version: "3.1.1" 81 | crypto: 82 | dependency: transitive 83 | description: 84 | name: crypto 85 | sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab 86 | url: "https://pub.dev" 87 | source: hosted 88 | version: "3.0.3" 89 | csslib: 90 | dependency: transitive 91 | description: 92 | name: csslib 93 | sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" 94 | url: "https://pub.dev" 95 | source: hosted 96 | version: "1.0.0" 97 | dartdoc: 98 | dependency: "direct dev" 99 | description: 100 | name: dartdoc 101 | sha256: c824d4691231964f8ba24e2fd6f573ac9878fa7a8c188c49f700da18eda79218 102 | url: "https://pub.dev" 103 | source: hosted 104 | version: "8.0.13" 105 | fake_async: 106 | dependency: transitive 107 | description: 108 | name: fake_async 109 | sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" 110 | url: "https://pub.dev" 111 | source: hosted 112 | version: "1.3.2" 113 | file: 114 | dependency: transitive 115 | description: 116 | name: file 117 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 118 | url: "https://pub.dev" 119 | source: hosted 120 | version: "7.0.0" 121 | flutter: 122 | dependency: "direct main" 123 | description: flutter 124 | source: sdk 125 | version: "0.0.0" 126 | flutter_lints: 127 | dependency: "direct dev" 128 | description: 129 | name: flutter_lints 130 | sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 131 | url: "https://pub.dev" 132 | source: hosted 133 | version: "2.0.3" 134 | flutter_test: 135 | dependency: "direct dev" 136 | description: flutter 137 | source: sdk 138 | version: "0.0.0" 139 | glob: 140 | dependency: transitive 141 | description: 142 | name: glob 143 | sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "2.1.2" 147 | html: 148 | dependency: transitive 149 | description: 150 | name: html 151 | sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "0.15.4" 155 | leak_tracker: 156 | dependency: transitive 157 | description: 158 | name: leak_tracker 159 | sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "10.0.8" 163 | leak_tracker_flutter_testing: 164 | dependency: transitive 165 | description: 166 | name: leak_tracker_flutter_testing 167 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "3.0.9" 171 | leak_tracker_testing: 172 | dependency: transitive 173 | description: 174 | name: leak_tracker_testing 175 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "3.0.1" 179 | lints: 180 | dependency: transitive 181 | description: 182 | name: lints 183 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "2.1.1" 187 | logging: 188 | dependency: transitive 189 | description: 190 | name: logging 191 | sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "1.2.0" 195 | macros: 196 | dependency: transitive 197 | description: 198 | name: macros 199 | sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" 200 | url: "https://pub.dev" 201 | source: hosted 202 | version: "0.1.3-main.0" 203 | markdown: 204 | dependency: transitive 205 | description: 206 | name: markdown 207 | sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 208 | url: "https://pub.dev" 209 | source: hosted 210 | version: "7.2.2" 211 | matcher: 212 | dependency: transitive 213 | description: 214 | name: matcher 215 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 216 | url: "https://pub.dev" 217 | source: hosted 218 | version: "0.12.17" 219 | material_color_utilities: 220 | dependency: transitive 221 | description: 222 | name: material_color_utilities 223 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 224 | url: "https://pub.dev" 225 | source: hosted 226 | version: "0.11.1" 227 | meta: 228 | dependency: transitive 229 | description: 230 | name: meta 231 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 232 | url: "https://pub.dev" 233 | source: hosted 234 | version: "1.16.0" 235 | mocktail: 236 | dependency: "direct dev" 237 | description: 238 | name: mocktail 239 | sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" 240 | url: "https://pub.dev" 241 | source: hosted 242 | version: "1.0.4" 243 | package_config: 244 | dependency: transitive 245 | description: 246 | name: package_config 247 | sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" 248 | url: "https://pub.dev" 249 | source: hosted 250 | version: "2.1.0" 251 | path: 252 | dependency: transitive 253 | description: 254 | name: path 255 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 256 | url: "https://pub.dev" 257 | source: hosted 258 | version: "1.9.1" 259 | pub_semver: 260 | dependency: transitive 261 | description: 262 | name: pub_semver 263 | sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" 264 | url: "https://pub.dev" 265 | source: hosted 266 | version: "2.1.4" 267 | sky_engine: 268 | dependency: transitive 269 | description: flutter 270 | source: sdk 271 | version: "0.0.0" 272 | source_span: 273 | dependency: transitive 274 | description: 275 | name: source_span 276 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 277 | url: "https://pub.dev" 278 | source: hosted 279 | version: "1.10.1" 280 | stack_trace: 281 | dependency: transitive 282 | description: 283 | name: stack_trace 284 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 285 | url: "https://pub.dev" 286 | source: hosted 287 | version: "1.12.1" 288 | stream_channel: 289 | dependency: transitive 290 | description: 291 | name: stream_channel 292 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 293 | url: "https://pub.dev" 294 | source: hosted 295 | version: "2.1.4" 296 | string_scanner: 297 | dependency: transitive 298 | description: 299 | name: string_scanner 300 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 301 | url: "https://pub.dev" 302 | source: hosted 303 | version: "1.4.1" 304 | term_glyph: 305 | dependency: transitive 306 | description: 307 | name: term_glyph 308 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 309 | url: "https://pub.dev" 310 | source: hosted 311 | version: "1.2.2" 312 | test_api: 313 | dependency: transitive 314 | description: 315 | name: test_api 316 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 317 | url: "https://pub.dev" 318 | source: hosted 319 | version: "0.7.4" 320 | typed_data: 321 | dependency: transitive 322 | description: 323 | name: typed_data 324 | sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c 325 | url: "https://pub.dev" 326 | source: hosted 327 | version: "1.3.2" 328 | vector_math: 329 | dependency: transitive 330 | description: 331 | name: vector_math 332 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 333 | url: "https://pub.dev" 334 | source: hosted 335 | version: "2.1.4" 336 | vm_service: 337 | dependency: transitive 338 | description: 339 | name: vm_service 340 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" 341 | url: "https://pub.dev" 342 | source: hosted 343 | version: "14.3.1" 344 | watcher: 345 | dependency: transitive 346 | description: 347 | name: watcher 348 | sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" 349 | url: "https://pub.dev" 350 | source: hosted 351 | version: "1.1.0" 352 | web: 353 | dependency: transitive 354 | description: 355 | name: web 356 | sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 357 | url: "https://pub.dev" 358 | source: hosted 359 | version: "1.0.0" 360 | yaml: 361 | dependency: transitive 362 | description: 363 | name: yaml 364 | sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" 365 | url: "https://pub.dev" 366 | source: hosted 367 | version: "3.1.2" 368 | sdks: 369 | dart: ">=3.7.0-0 <4.0.0" 370 | flutter: ">=3.18.0-18.0.pre.54" 371 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: animated_toggle_switch 2 | description: Fully customizable, draggable and animated switch with multiple choices and smooth loading animation. It has prebuilt constructors for rolling and size animations. 3 | version: 0.8.5 4 | repository: https://github.com/splashbyte/animated_toggle_switch 5 | issue_tracker: https://github.com/splashbyte/animated_toggle_switch/issues 6 | 7 | environment: 8 | sdk: '>=2.17.0 <4.0.0' 9 | flutter: ">=1.17.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | 15 | dev_dependencies: 16 | flutter_lints: ^2.0.2 17 | flutter_test: 18 | sdk: flutter 19 | mocktail: ^1.0.0 20 | dartdoc: any 21 | 22 | flutter: 23 | 24 | funding: 25 | - https://buymeacoffee.com/splashbyte 26 | - https://ko-fi.com/splashbyte 27 | 28 | screenshots: 29 | - description: 'This image shows three examples of AnimatedToggleSwitch.' 30 | path: screenshots/preview.webp 31 | 32 | topics: 33 | - ui 34 | - switch 35 | - toggle 36 | - widget 37 | - animation 38 | -------------------------------------------------------------------------------- /screenshots/preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splashbyte/animated_toggle_switch/413c645c56407800ef8848a378f1045002dffe36/screenshots/preview.webp -------------------------------------------------------------------------------- /test/directionality_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | import 'helper.dart'; 6 | 7 | void _verifyTextDirection( 8 | WidgetTester tester, List values, TextDirection textDirection) { 9 | final firstPos = tester.getCenter(find.byKey(iconKey(values.first))); 10 | final lastPos = tester.getCenter(find.byKey(iconKey(values.last))); 11 | switch (textDirection) { 12 | case TextDirection.ltr: 13 | expect((firstPos - lastPos).dx < 0, true, 14 | reason: 'First icon is to the left of the second'); 15 | break; 16 | case TextDirection.rtl: 17 | expect((firstPos - lastPos).dx > 0, true, 18 | reason: 'First icon is to the right of the second'); 19 | break; 20 | } 21 | } 22 | 23 | void main() { 24 | defaultTestAllSwitches('Switch respects TextDirection', 25 | (tester, buildSwitch, type, values) async { 26 | final current = values[1]; 27 | await tester.pumpWidget(TestWrapper( 28 | textDirection: TextDirection.ltr, 29 | child: buildSwitch( 30 | current: current, 31 | iconBuilder: iconBuilder, 32 | ), 33 | )); 34 | _verifyTextDirection(tester, values, TextDirection.ltr); 35 | 36 | await tester.pumpWidget(TestWrapper( 37 | textDirection: TextDirection.rtl, 38 | child: buildSwitch( 39 | current: current, 40 | iconBuilder: iconBuilder, 41 | ), 42 | )); 43 | _verifyTextDirection(tester, values, TextDirection.rtl); 44 | 45 | await tester.pumpWidget(TestWrapper( 46 | child: buildSwitch( 47 | current: current, 48 | iconBuilder: iconBuilder, 49 | textDirection: TextDirection.ltr, 50 | ), 51 | )); 52 | _verifyTextDirection(tester, values, TextDirection.ltr); 53 | 54 | await tester.pumpWidget(TestWrapper( 55 | child: buildSwitch( 56 | current: current, 57 | iconBuilder: iconBuilder, 58 | textDirection: TextDirection.rtl, 59 | ), 60 | )); 61 | _verifyTextDirection(tester, values, TextDirection.rtl); 62 | }, testDual: false); 63 | } 64 | -------------------------------------------------------------------------------- /test/gesture_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mocktail/mocktail.dart'; 4 | 5 | import 'helper.dart'; 6 | import 'mocks.dart'; 7 | 8 | void main() { 9 | defaultTestAllSwitches('Switch handles taps correctly', 10 | (tester, buildSwitch, type, values) async { 11 | final current = values.first; 12 | final next = values.last; 13 | final tapFunction = MockFunction(); 14 | final changedFunction = MockOnChangedFunction(); 15 | 16 | await tester.pumpWidget(TestWrapper( 17 | child: buildSwitch( 18 | current: current, 19 | iconBuilder: iconBuilder, 20 | onTap: tapFunction, 21 | onChanged: changedFunction, 22 | ), 23 | )); 24 | verifyNever(() => tapFunction.call(any())); 25 | final currentFinder = find.byKey(iconKey(current)); 26 | final nextFinder = find.byKey(iconKey(next)); 27 | final switchFinder = find.byType(AnimatedToggleSwitch); 28 | 29 | await tester.tap(currentFinder, warnIfMissed: false); 30 | verify(() => tapFunction(any( 31 | that: isA>().having( 32 | (i) => i.tapped?.value, 'tapped.value', current)))).called(1); 33 | 34 | await tester.tap(nextFinder, warnIfMissed: false); 35 | verify(() => changedFunction(next)).called(1); 36 | verify(() => tapFunction(any( 37 | that: isA>() 38 | .having((i) => i.tapped?.value, 'tapped.value', next)))).called(1); 39 | 40 | // tap on the border of the switch 41 | await tester.tapAt(tester.getRect(switchFinder).centerLeft); 42 | verify(() => tapFunction(any( 43 | that: isA>() 44 | .having((i) => i.tapped, 'tapped', null)))).called(1); 45 | 46 | verifyNoMoreInteractions(tapFunction); 47 | verifyNoMoreInteractions(changedFunction); 48 | }, testDual: false); 49 | 50 | testWidgets('Tap on AnimatedToggleSwitch.dual triggers onChanged by default', 51 | (tester) async { 52 | final values = defaultValues.sublist(0, 2); 53 | final current = values.first; 54 | final next = values.last; 55 | final changedFunction = MockOnChangedFunction(); 56 | 57 | await tester.pumpWidget(TestWrapper( 58 | child: AnimatedToggleSwitch.dual( 59 | current: current, 60 | first: values.first, 61 | second: values.last, 62 | iconBuilder: (value) => iconBuilder(value, true), 63 | onChanged: changedFunction, 64 | ), 65 | )); 66 | final currentFinder = find.byKey(iconKey(current, foreground: true)); 67 | 68 | verifyNoMoreInteractions(changedFunction); 69 | 70 | await tester.tap(currentFinder, warnIfMissed: false); 71 | verify(() => changedFunction(next)).called(1); 72 | verifyNoMoreInteractions(changedFunction); 73 | }); 74 | 75 | defaultTestAllSwitches('Switch handles drags correctly', 76 | (tester, buildSwitch, type, values) async { 77 | final current = values.first; 78 | final next = values.last; 79 | final tapFunction = MockFunction(); 80 | final changedFunction = MockOnChangedFunction(); 81 | 82 | await tester.pumpWidget(TestWrapper( 83 | child: buildSwitch( 84 | current: current, 85 | iconBuilder: iconBuilder, 86 | onTap: tapFunction, 87 | onChanged: changedFunction, 88 | // Necessary for AnimatedToggleSwitch.dual 89 | spacing: 5.0, 90 | ), 91 | )); 92 | final currentFinder = find.byKey(iconKey(current)); 93 | final nextFinder = find.byKey(iconKey(next)); 94 | 95 | verifyNoMoreInteractions(changedFunction); 96 | 97 | await tester.drag(currentFinder, tester.getCenter(nextFinder), 98 | warnIfMissed: false); 99 | verify(() => changedFunction(next)).called(1); 100 | 101 | await tester.drag(nextFinder, tester.getCenter(currentFinder), 102 | warnIfMissed: false); 103 | 104 | verifyNoMoreInteractions(changedFunction); 105 | verifyNoMoreInteractions(tapFunction); 106 | }); 107 | 108 | defaultTestAllSwitches('Switch respects iconsTappable parameter', 109 | (tester, buildSwitch, type, values) async { 110 | final current = values.first; 111 | final next = values.last; 112 | final tapFunction = MockFunction(); 113 | final changedFunction = MockOnChangedFunction(); 114 | 115 | await tester.pumpWidget(TestWrapper( 116 | child: buildSwitch( 117 | current: current, 118 | iconBuilder: iconBuilder, 119 | onTap: tapFunction, 120 | onChanged: changedFunction, 121 | iconsTappable: false, 122 | ), 123 | )); 124 | verifyNever(() => tapFunction.call(any())); 125 | final currentFinder = find.byKey(iconKey(current)); 126 | final nextFinder = find.byKey(iconKey(next)); 127 | 128 | await tester.tap(currentFinder, warnIfMissed: false); 129 | verify(() => tapFunction(any())).called(1); 130 | 131 | await tester.tap(nextFinder, warnIfMissed: false); 132 | verify(() => tapFunction(any())).called(1); 133 | 134 | verifyNoMoreInteractions(changedFunction); 135 | }, testDual: false); 136 | } 137 | -------------------------------------------------------------------------------- /test/helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | import 'package:mocktail/mocktail.dart'; 7 | 8 | import 'keys.dart'; 9 | 10 | const defaultValues = [0, 1, 2, 3]; 11 | 12 | class TestWrapper extends StatelessWidget { 13 | final Widget child; 14 | final TextDirection textDirection; 15 | final TargetPlatform platform; 16 | 17 | const TestWrapper({ 18 | Key? key, 19 | required this.child, 20 | this.textDirection = TextDirection.ltr, 21 | this.platform = TargetPlatform.android, 22 | }) : super(key: key); 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | final theme = Theme.of(context); 27 | return Theme( 28 | data: theme.copyWith(platform: platform), 29 | child: Directionality( 30 | textDirection: textDirection, 31 | child: Center(child: child), 32 | ), 33 | ); 34 | } 35 | } 36 | 37 | void checkValidSwitchIconBuilderState( 38 | T current, ConstructorType type, List values) { 39 | for (var value in values) { 40 | final iconFinder = find.byKey(iconKey(value)); 41 | final iconForegroundFinder = find.byKey(iconKey(value, foreground: true)); 42 | expect(iconFinder, findsOneWidget); 43 | expect(iconForegroundFinder, 44 | current == value && type.isRolling ? findsOneWidget : findsNothing); 45 | } 46 | } 47 | 48 | List iconList(List values) => 49 | values.map((value) => iconBuilder(value, false)).toList(); 50 | 51 | Widget iconBuilder(T value, bool foreground) => SizedBox.expand( 52 | key: iconKey(value, foreground: foreground), 53 | child: const ColoredBox(color: Colors.black), 54 | ); 55 | 56 | Key iconKey(T value, {bool foreground = false}) => 57 | IconKey(value, foreground: foreground); 58 | 59 | Widget separatorBuilder(int index) => 60 | SizedBox.expand(key: separatorKey(index)); 61 | 62 | Key separatorKey(int index) => SeparatorKey(index); 63 | 64 | final loadingIconKey = GlobalKey(); 65 | 66 | Widget _loadingIconBuilder( 67 | BuildContext context, GlobalToggleProperties global) => 68 | SizedBox(key: loadingIconKey); 69 | 70 | typedef TestIconBuilder = Widget Function(T value, bool foreground); 71 | 72 | typedef SwitchBuilder = AnimatedToggleSwitch Function({ 73 | required T current, 74 | required List values, 75 | TestIconBuilder? iconBuilder, 76 | List? iconList, 77 | TextDirection? textDirection, 78 | ChangeCallback? onChanged, 79 | TapCallback? onTap, 80 | bool? loading, 81 | bool allowUnlistedValues, 82 | ToggleStyle? style, 83 | StyleBuilder? styleBuilder, 84 | CustomStyleBuilder? customStyleBuilder, 85 | List? styleList, 86 | bool? iconsTappable, 87 | double? spacing, 88 | SeparatorBuilder? separatorBuilder, 89 | FittingMode? fittingMode, 90 | Size? indicatorSize, 91 | AnimationType? indicatorAnimationType, 92 | AnimationType? iconAnimationType, 93 | PositionListener? positionListener, 94 | }); 95 | 96 | typedef SimpleSwitchBuilder = AnimatedToggleSwitch Function({ 97 | required T current, 98 | TestIconBuilder? iconBuilder, 99 | List? iconList, 100 | TextDirection? textDirection, 101 | ChangeCallback? onChanged, 102 | TapCallback? onTap, 103 | bool? loading, 104 | bool allowUnlistedValues, 105 | ToggleStyle? style, 106 | StyleBuilder? styleBuilder, 107 | CustomStyleBuilder? customStyleBuilder, 108 | List? styleList, 109 | bool? iconsTappable, 110 | double? spacing, 111 | SeparatorBuilder? separatorBuilder, 112 | FittingMode? fittingMode, 113 | Size? indicatorSize, 114 | AnimationType? indicatorAnimationType, 115 | AnimationType? iconAnimationType, 116 | PositionListener? positionListener, 117 | }); 118 | 119 | /// Tests all AnimatedToggleSwitch constructors 120 | void defaultTestAllSwitches( 121 | String description, 122 | FutureOr Function( 123 | WidgetTester tester, 124 | SimpleSwitchBuilder buildSwitch, 125 | ConstructorType type, 126 | List values) 127 | test, { 128 | bool testDual = true, 129 | bool testRolling = true, 130 | bool testCustom = true, 131 | bool testSize = true, 132 | }) { 133 | registerFallbackValue(const TapProperties( 134 | tapped: null, 135 | values: [], 136 | )); 137 | 138 | testAllSwitches( 139 | description, 140 | (tester, buildSwitch, type) => test( 141 | tester, 142 | ({ 143 | required int current, 144 | TestIconBuilder? iconBuilder, 145 | List? iconList, 146 | TextDirection? textDirection, 147 | ChangeCallback? onChanged, 148 | TapCallback? onTap, 149 | bool? loading, 150 | bool allowUnlistedValues = false, 151 | ToggleStyle? style, 152 | StyleBuilder? styleBuilder, 153 | CustomStyleBuilder? customStyleBuilder, 154 | List? styleList, 155 | bool? iconsTappable, 156 | double? spacing, 157 | SeparatorBuilder? separatorBuilder, 158 | FittingMode? fittingMode, 159 | Size? indicatorSize, 160 | AnimationType? indicatorAnimationType, 161 | AnimationType? iconAnimationType, 162 | PositionListener? positionListener, 163 | }) => 164 | buildSwitch( 165 | current: current, 166 | values: defaultValues, 167 | iconBuilder: iconBuilder, 168 | iconList: iconList, 169 | textDirection: textDirection, 170 | onChanged: onChanged, 171 | onTap: onTap, 172 | loading: loading, 173 | allowUnlistedValues: allowUnlistedValues, 174 | style: style, 175 | styleBuilder: styleBuilder, 176 | customStyleBuilder: customStyleBuilder, 177 | styleList: styleList, 178 | iconsTappable: iconsTappable, 179 | spacing: spacing, 180 | separatorBuilder: separatorBuilder, 181 | fittingMode: fittingMode, 182 | indicatorSize: indicatorSize, 183 | indicatorAnimationType: indicatorAnimationType, 184 | iconAnimationType: iconAnimationType, 185 | positionListener: positionListener, 186 | ), 187 | type, 188 | defaultValues, 189 | ), 190 | testRolling: testRolling, 191 | testCustom: testCustom, 192 | testSize: testSize, 193 | ); 194 | if (testDual) { 195 | final values = defaultValues.sublist(0, 2); 196 | testWidgets( 197 | '$description (AnimatedToggleSwitch.dual)', 198 | (tester) async => await test( 199 | tester, 200 | ({ 201 | required int current, 202 | TestIconBuilder? iconBuilder, 203 | List? iconList, 204 | TextDirection? textDirection, 205 | ChangeCallback? onChanged, 206 | TapCallback? onTap, 207 | bool? loading, 208 | bool allowUnlistedValues = false, 209 | ToggleStyle? style, 210 | StyleBuilder? styleBuilder, 211 | CustomStyleBuilder? customStyleBuilder, 212 | List? styleList, 213 | bool? iconsTappable, 214 | double? spacing, 215 | SeparatorBuilder? separatorBuilder, 216 | FittingMode? fittingMode, 217 | Size? indicatorSize, 218 | AnimationType? indicatorAnimationType, 219 | AnimationType? iconAnimationType, 220 | PositionListener? positionListener, 221 | }) => 222 | AnimatedToggleSwitch.dual( 223 | current: current, 224 | first: values[0], 225 | second: values[1], 226 | iconBuilder: 227 | iconBuilder == null ? null : (value) => iconBuilder(value, true), 228 | textBuilder: iconBuilder == null 229 | ? null 230 | : (value) => 231 | iconBuilder(values[(values.indexOf(value) + 1) % 2], false), 232 | textDirection: textDirection, 233 | onTap: onTap, 234 | onChanged: onChanged, 235 | loadingIconBuilder: _loadingIconBuilder, 236 | loading: loading, 237 | style: style ?? const ToggleStyle(), 238 | styleBuilder: styleBuilder, 239 | styleList: styleList, 240 | customStyleBuilder: customStyleBuilder, 241 | spacing: spacing ?? 40, 242 | fittingMode: fittingMode ?? FittingMode.preventHorizontalOverlapping, 243 | indicatorSize: indicatorSize ?? const Size.fromWidth(46.0), 244 | indicatorAnimationType: 245 | indicatorAnimationType ?? AnimationType.onHover, 246 | positionListener: positionListener, 247 | ), 248 | ConstructorType.dual, 249 | values, 250 | ), 251 | ); 252 | } 253 | } 254 | 255 | /// Tests all AnimatedToggleSwitch constructors except [AnimatedToggleSwitch.dual] 256 | void testAllSwitches( 257 | String description, 258 | FutureOr Function(WidgetTester tester, SwitchBuilder buildSwitch, 259 | ConstructorType type) 260 | test, { 261 | bool testRolling = true, 262 | bool testCustom = true, 263 | bool testSize = true, 264 | }) { 265 | if (testRolling) { 266 | testWidgets( 267 | '$description (AnimatedToggleSwitch.rolling)', 268 | (tester) async => await test( 269 | tester, 270 | ({ 271 | required T current, 272 | required List values, 273 | TestIconBuilder? iconBuilder, 274 | List? iconList, 275 | TextDirection? textDirection, 276 | ChangeCallback? onChanged, 277 | TapCallback? onTap, 278 | bool? loading, 279 | bool allowUnlistedValues = false, 280 | ToggleStyle? style, 281 | StyleBuilder? styleBuilder, 282 | CustomStyleBuilder? customStyleBuilder, 283 | List? styleList, 284 | bool? iconsTappable, 285 | double? spacing, 286 | SeparatorBuilder? separatorBuilder, 287 | FittingMode? fittingMode, 288 | Size? indicatorSize, 289 | AnimationType? indicatorAnimationType, 290 | AnimationType? iconAnimationType, 291 | PositionListener? positionListener, 292 | }) => 293 | AnimatedToggleSwitch.rolling( 294 | current: current, 295 | values: values, 296 | iconBuilder: iconBuilder, 297 | iconList: iconList, 298 | textDirection: textDirection, 299 | onTap: onTap, 300 | onChanged: onChanged, 301 | loadingIconBuilder: _loadingIconBuilder, 302 | loading: loading, 303 | allowUnlistedValues: allowUnlistedValues, 304 | style: style ?? const ToggleStyle(), 305 | styleBuilder: styleBuilder, 306 | customStyleBuilder: customStyleBuilder, 307 | styleList: styleList, 308 | iconsTappable: iconsTappable ?? true, 309 | spacing: spacing ?? 0.0, 310 | separatorBuilder: separatorBuilder, 311 | fittingMode: 312 | fittingMode ?? FittingMode.preventHorizontalOverlapping, 313 | indicatorSize: indicatorSize ?? const Size.fromWidth(46.0), 314 | indicatorAnimationType: 315 | indicatorAnimationType ?? AnimationType.onHover, 316 | positionListener: positionListener, 317 | ), 318 | ConstructorType.rolling, 319 | )); 320 | 321 | testWidgets( 322 | '$description (AnimatedToggleSwitch.rollingByHeight)', 323 | (tester) async => await test( 324 | tester, 325 | ({ 326 | required T current, 327 | required List values, 328 | TestIconBuilder? iconBuilder, 329 | List? iconList, 330 | TextDirection? textDirection, 331 | ChangeCallback? onChanged, 332 | TapCallback? onTap, 333 | bool? loading, 334 | bool allowUnlistedValues = false, 335 | ToggleStyle? style, 336 | StyleBuilder? styleBuilder, 337 | CustomStyleBuilder? customStyleBuilder, 338 | List? styleList, 339 | bool? iconsTappable, 340 | double? spacing, 341 | SeparatorBuilder? separatorBuilder, 342 | FittingMode? fittingMode, 343 | Size? indicatorSize, 344 | AnimationType? indicatorAnimationType, 345 | AnimationType? iconAnimationType, 346 | PositionListener? positionListener, 347 | }) => 348 | AnimatedToggleSwitch.rollingByHeight( 349 | current: current, 350 | values: values, 351 | iconBuilder: iconBuilder, 352 | iconList: iconList, 353 | textDirection: textDirection, 354 | onChanged: onChanged, 355 | onTap: onTap, 356 | loadingIconBuilder: _loadingIconBuilder, 357 | loading: loading, 358 | allowUnlistedValues: allowUnlistedValues, 359 | style: style ?? const ToggleStyle(), 360 | styleBuilder: styleBuilder, 361 | customStyleBuilder: customStyleBuilder, 362 | styleList: styleList, 363 | iconsTappable: iconsTappable ?? true, 364 | spacing: _convertToByHeightValue(spacing ?? 0.0, 50.0, 2.0), 365 | separatorBuilder: separatorBuilder, 366 | fittingMode: 367 | fittingMode ?? FittingMode.preventHorizontalOverlapping, 368 | indicatorSize: indicatorSize == null 369 | ? const Size.square(1.0) 370 | : Size( 371 | _convertToByHeightValue(indicatorSize.width, 50.0, 2.0), 372 | _convertToByHeightValue( 373 | indicatorSize.height, 50.0, 2.0)), 374 | indicatorAnimationType: 375 | indicatorAnimationType ?? AnimationType.onHover, 376 | positionListener: positionListener, 377 | ), 378 | ConstructorType.rolling, 379 | )); 380 | } 381 | if (testSize) { 382 | testWidgets( 383 | '$description (AnimatedToggleSwitch.size)', 384 | (tester) async => await test( 385 | tester, 386 | ({ 387 | required T current, 388 | required List values, 389 | TestIconBuilder? iconBuilder, 390 | List? iconList, 391 | TextDirection? textDirection, 392 | ChangeCallback? onChanged, 393 | TapCallback? onTap, 394 | bool? loading, 395 | bool allowUnlistedValues = false, 396 | ToggleStyle? style, 397 | StyleBuilder? styleBuilder, 398 | CustomStyleBuilder? customStyleBuilder, 399 | List? styleList, 400 | bool? iconsTappable, 401 | double? spacing, 402 | SeparatorBuilder? separatorBuilder, 403 | FittingMode? fittingMode, 404 | Size? indicatorSize, 405 | AnimationType? indicatorAnimationType, 406 | AnimationType? iconAnimationType, 407 | PositionListener? positionListener, 408 | }) => 409 | AnimatedToggleSwitch.size( 410 | current: current, 411 | values: values, 412 | iconBuilder: iconBuilder == null 413 | ? null 414 | : (value) => iconBuilder(value, false), 415 | iconList: iconList, 416 | textDirection: textDirection, 417 | onChanged: onChanged, 418 | onTap: onTap, 419 | loadingIconBuilder: _loadingIconBuilder, 420 | loading: loading, 421 | allowUnlistedValues: allowUnlistedValues, 422 | style: style ?? const ToggleStyle(), 423 | styleBuilder: styleBuilder, 424 | customStyleBuilder: customStyleBuilder, 425 | styleList: styleList, 426 | iconsTappable: iconsTappable ?? true, 427 | spacing: spacing ?? 0.0, 428 | separatorBuilder: separatorBuilder, 429 | selectedIconScale: 1.0, 430 | fittingMode: 431 | fittingMode ?? FittingMode.preventHorizontalOverlapping, 432 | indicatorSize: indicatorSize ?? const Size.fromWidth(46.0), 433 | indicatorAnimationType: 434 | indicatorAnimationType ?? AnimationType.onHover, 435 | positionListener: positionListener, 436 | ), 437 | ConstructorType.size, 438 | )); 439 | testWidgets( 440 | '$description (AnimatedToggleSwitch.sizeByHeight)', 441 | (tester) async => await test( 442 | tester, 443 | ({ 444 | required T current, 445 | required List values, 446 | TestIconBuilder? iconBuilder, 447 | List? iconList, 448 | TextDirection? textDirection, 449 | ChangeCallback? onChanged, 450 | TapCallback? onTap, 451 | bool? loading, 452 | bool allowUnlistedValues = false, 453 | ToggleStyle? style, 454 | StyleBuilder? styleBuilder, 455 | CustomStyleBuilder? customStyleBuilder, 456 | List? styleList, 457 | bool? iconsTappable, 458 | double? spacing, 459 | SeparatorBuilder? separatorBuilder, 460 | FittingMode? fittingMode, 461 | Size? indicatorSize, 462 | AnimationType? indicatorAnimationType, 463 | AnimationType? iconAnimationType, 464 | PositionListener? positionListener, 465 | }) => 466 | AnimatedToggleSwitch.sizeByHeight( 467 | current: current, 468 | values: values, 469 | iconBuilder: iconBuilder == null 470 | ? null 471 | : (value) => iconBuilder(value, false), 472 | iconList: iconList, 473 | textDirection: textDirection, 474 | onChanged: onChanged, 475 | onTap: onTap, 476 | loadingIconBuilder: _loadingIconBuilder, 477 | loading: loading, 478 | allowUnlistedValues: allowUnlistedValues, 479 | style: style ?? const ToggleStyle(), 480 | styleBuilder: styleBuilder, 481 | customStyleBuilder: customStyleBuilder, 482 | styleList: styleList, 483 | iconsTappable: iconsTappable ?? true, 484 | spacing: _convertToByHeightValue(spacing ?? 0.0, 50.0, 2.0), 485 | separatorBuilder: separatorBuilder, 486 | selectedIconScale: 1.0, 487 | fittingMode: 488 | fittingMode ?? FittingMode.preventHorizontalOverlapping, 489 | indicatorSize: indicatorSize == null 490 | ? const Size.square(1.0) 491 | : Size( 492 | _convertToByHeightValue(indicatorSize.width, 50.0, 2.0), 493 | _convertToByHeightValue( 494 | indicatorSize.height, 50.0, 2.0)), 495 | indicatorAnimationType: 496 | indicatorAnimationType ?? AnimationType.onHover, 497 | positionListener: positionListener, 498 | ), 499 | ConstructorType.size, 500 | )); 501 | } 502 | if (testCustom) { 503 | testWidgets( 504 | '$description (AnimatedToggleSwitch.custom)', 505 | (tester) async => await test( 506 | tester, 507 | ({ 508 | required T current, 509 | required List values, 510 | TestIconBuilder? iconBuilder, 511 | List? iconList, 512 | TextDirection? textDirection, 513 | ChangeCallback? onChanged, 514 | TapCallback? onTap, 515 | bool? loading, 516 | bool allowUnlistedValues = false, 517 | ToggleStyle? style, 518 | StyleBuilder? styleBuilder, 519 | CustomStyleBuilder? customStyleBuilder, 520 | List? styleList, 521 | bool? iconsTappable, 522 | double? spacing, 523 | SeparatorBuilder? separatorBuilder, 524 | FittingMode? fittingMode, 525 | Size? indicatorSize, 526 | AnimationType? indicatorAnimationType, 527 | AnimationType? iconAnimationType, 528 | PositionListener? positionListener, 529 | }) => 530 | AnimatedToggleSwitch.custom( 531 | current: current, 532 | values: values, 533 | animatedIconBuilder: iconBuilder == null 534 | ? null 535 | : (context, local, global) => 536 | iconBuilder(local.value, false), 537 | textDirection: textDirection, 538 | onChanged: onChanged, 539 | onTap: onTap, 540 | loadingIconBuilder: _loadingIconBuilder, 541 | loading: loading, 542 | allowUnlistedValues: allowUnlistedValues, 543 | style: style ?? const ToggleStyle(), 544 | styleBuilder: styleBuilder, 545 | customStyleBuilder: customStyleBuilder, 546 | styleList: styleList, 547 | iconsTappable: iconsTappable ?? true, 548 | spacing: spacing ?? 0.0, 549 | separatorBuilder: separatorBuilder, 550 | fittingMode: 551 | fittingMode ?? FittingMode.preventHorizontalOverlapping, 552 | indicatorSize: indicatorSize ?? const Size.fromWidth(46.0), 553 | indicatorAnimationType: 554 | indicatorAnimationType ?? AnimationType.onHover, 555 | iconAnimationType: 556 | iconAnimationType ?? AnimationType.onSelected, 557 | positionListener: positionListener, 558 | ), 559 | ConstructorType.custom, 560 | )); 561 | testWidgets( 562 | '$description (AnimatedToggleSwitch.customByHeight)', 563 | (tester) async => await test( 564 | tester, 565 | ({ 566 | required T current, 567 | required List values, 568 | TestIconBuilder? iconBuilder, 569 | List? iconList, 570 | TextDirection? textDirection, 571 | ChangeCallback? onChanged, 572 | TapCallback? onTap, 573 | bool? loading, 574 | bool allowUnlistedValues = false, 575 | ToggleStyle? style, 576 | StyleBuilder? styleBuilder, 577 | CustomStyleBuilder? customStyleBuilder, 578 | List? styleList, 579 | bool? iconsTappable, 580 | double? spacing, 581 | SeparatorBuilder? separatorBuilder, 582 | FittingMode? fittingMode, 583 | Size? indicatorSize, 584 | AnimationType? indicatorAnimationType, 585 | AnimationType? iconAnimationType, 586 | PositionListener? positionListener, 587 | }) => 588 | AnimatedToggleSwitch.customByHeight( 589 | current: current, 590 | values: values, 591 | animatedIconBuilder: iconBuilder == null 592 | ? null 593 | : (context, local, global) => 594 | iconBuilder(local.value, false), 595 | textDirection: textDirection, 596 | onChanged: onChanged, 597 | onTap: onTap, 598 | loadingIconBuilder: _loadingIconBuilder, 599 | loading: loading, 600 | allowUnlistedValues: allowUnlistedValues, 601 | style: style ?? const ToggleStyle(), 602 | styleBuilder: styleBuilder, 603 | customStyleBuilder: customStyleBuilder, 604 | styleList: styleList, 605 | iconsTappable: iconsTappable ?? true, 606 | spacing: _convertToByHeightValue(spacing ?? 0.0, 50.0, 2.0), 607 | separatorBuilder: separatorBuilder, 608 | fittingMode: 609 | fittingMode ?? FittingMode.preventHorizontalOverlapping, 610 | indicatorSize: indicatorSize == null 611 | ? const Size.square(1.0) 612 | : Size( 613 | _convertToByHeightValue(indicatorSize.width, 50.0, 2.0), 614 | _convertToByHeightValue( 615 | indicatorSize.height, 50.0, 2.0)), 616 | indicatorAnimationType: 617 | indicatorAnimationType ?? AnimationType.onHover, 618 | iconAnimationType: 619 | iconAnimationType ?? AnimationType.onSelected, 620 | positionListener: positionListener, 621 | ), 622 | ConstructorType.custom, 623 | )); 624 | } 625 | } 626 | 627 | double _convertToByHeightValue( 628 | double value, double height, double borderWidth) => 629 | value / (height - 2 * borderWidth); 630 | 631 | enum ConstructorType { 632 | custom, 633 | size, 634 | rolling(isRolling: true), 635 | dual(isRolling: true); 636 | 637 | final bool isRolling; 638 | 639 | const ConstructorType({this.isRolling = false}); 640 | } 641 | -------------------------------------------------------------------------------- /test/icon_builder_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'helper.dart'; 4 | 5 | void main() { 6 | defaultTestAllSwitches( 7 | 'Switch builds only one foreground icon & all background icons once', 8 | (tester, buildSwitch, type, values) async { 9 | final current = values[1]; 10 | await tester.pumpWidget(TestWrapper( 11 | child: buildSwitch( 12 | current: current, 13 | iconBuilder: iconBuilder, 14 | ), 15 | )); 16 | checkValidSwitchIconBuilderState(current, type, values); 17 | }); 18 | 19 | defaultTestAllSwitches( 20 | 'Switch builds only one foreground icon & all background icons once when using iconList', 21 | (tester, buildSwitch, type, values) async { 22 | final current = values[1]; 23 | await tester.pumpWidget(TestWrapper( 24 | child: buildSwitch( 25 | current: current, 26 | iconList: iconList(values), 27 | ), 28 | )); 29 | for (var value in values) { 30 | final iconFinder = find.byKey(iconKey(value)); 31 | expect( 32 | iconFinder, 33 | current == value && type.isRolling 34 | ? findsNWidgets(2) 35 | : findsOneWidget); 36 | } 37 | }, testDual: false, testCustom: false); 38 | 39 | defaultTestAllSwitches( 40 | 'Only one parameter from iconBuilder and iconList can be set.', 41 | (tester, buildSwitch, type, values) async { 42 | final current = values[1]; 43 | expect( 44 | () => buildSwitch( 45 | current: current, 46 | iconBuilder: iconBuilder, 47 | iconList: iconList(values), 48 | ), 49 | throwsAssertionError); 50 | }, testDual: false, testCustom: false); 51 | 52 | defaultTestAllSwitches('iconList must have the same length as values', 53 | (tester, buildSwitch, type, values) async { 54 | final current = values[1]; 55 | expect( 56 | () => buildSwitch( 57 | current: current, 58 | iconList: iconList(values).sublist(1), 59 | ), 60 | throwsAssertionError); 61 | }, testDual: false, testCustom: false); 62 | 63 | defaultTestAllSwitches('iconList must have the same length as values', 64 | (tester, buildSwitch, type, values) async { 65 | final current = values[1]; 66 | expect( 67 | () => buildSwitch( 68 | current: current, 69 | iconList: iconList(values).sublist(1), 70 | ), 71 | throwsAssertionError); 72 | }, testDual: false, testCustom: false); 73 | 74 | defaultTestAllSwitches( 75 | 'AnimatedToggleSwitch changes its state when current changes', 76 | (tester, buildSwitch, type, values) async { 77 | final current = values.first; 78 | final next = values.last; 79 | await tester.pumpWidget(TestWrapper( 80 | child: buildSwitch( 81 | current: current, 82 | iconBuilder: iconBuilder, 83 | ), 84 | )); 85 | checkValidSwitchIconBuilderState(current, type, values); 86 | await tester.pumpWidget(TestWrapper( 87 | child: buildSwitch( 88 | current: next, 89 | iconBuilder: iconBuilder, 90 | ), 91 | )); 92 | checkValidSwitchIconBuilderState(current, type, values); 93 | await tester.pump(const Duration(seconds: 1)); 94 | checkValidSwitchIconBuilderState(next, type, values); 95 | }); 96 | 97 | defaultTestAllSwitches('Switch shows two icons during animation (fading)', 98 | (tester, buildSwitch, type, values) async { 99 | final current = values[0]; 100 | final next = values[1]; 101 | await tester.pumpWidget(TestWrapper( 102 | child: buildSwitch( 103 | current: current, 104 | iconBuilder: iconBuilder, 105 | ), 106 | )); 107 | await tester.pumpWidget(TestWrapper( 108 | child: buildSwitch( 109 | current: next, 110 | iconBuilder: iconBuilder, 111 | ), 112 | )); 113 | await tester.pump(const Duration(milliseconds: 100)); 114 | for (var value in values) { 115 | final iconFinder = find.byKey(iconKey(value)); 116 | final iconForegroundFinder = find.byKey(iconKey(value, foreground: true)); 117 | expect(iconFinder, findsOneWidget); 118 | expect( 119 | iconForegroundFinder, 120 | current == value || next == value && type.isRolling 121 | ? findsOneWidget 122 | : findsNothing, 123 | ); 124 | } 125 | }, testCustom: false, testSize: false); 126 | } 127 | -------------------------------------------------------------------------------- /test/keys.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class IconKey extends LocalKey { 4 | final bool foreground; 5 | final T value; 6 | 7 | const IconKey(this.value, {this.foreground = false}); 8 | 9 | @override 10 | bool operator ==(Object other) => 11 | identical(this, other) || 12 | other is IconKey && 13 | foreground == other.foreground && 14 | value == other.value; 15 | 16 | @override 17 | int get hashCode => foreground.hashCode ^ value.hashCode; 18 | 19 | @override 20 | String toString() { 21 | return 'IconKey{foreground: $foreground, value: $value}'; 22 | } 23 | } 24 | 25 | class SeparatorKey extends LocalKey { 26 | final int index; 27 | 28 | const SeparatorKey(this.index); 29 | 30 | @override 31 | bool operator ==(Object other) => 32 | identical(this, other) || other is SeparatorKey && index == other.index; 33 | 34 | @override 35 | int get hashCode => index.hashCode; 36 | 37 | @override 38 | String toString() { 39 | return 'SeparatorKey{index: $index}'; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/listener_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | import 'package:mocktail/mocktail.dart'; 3 | 4 | import 'helper.dart'; 5 | import 'mocks.dart'; 6 | 7 | void main() { 8 | defaultTestAllSwitches('AnimatedToggleSwitch triggers positionListener', 9 | (tester, buildSwitch, type, values) async { 10 | final current = values.first; 11 | final nextIndex = values.length - 1; 12 | final next = values[nextIndex]; 13 | final positionListener = MockPositionListenerFunction(); 14 | await tester.pumpWidget(TestWrapper( 15 | child: buildSwitch( 16 | current: current, 17 | iconBuilder: iconBuilder, 18 | positionListener: positionListener, 19 | ), 20 | )); 21 | await tester.pumpWidget(TestWrapper( 22 | child: buildSwitch( 23 | current: next, 24 | iconBuilder: iconBuilder, 25 | positionListener: positionListener, 26 | ), 27 | )); 28 | await tester.pump(const Duration(seconds: 1)); 29 | verify(() => positionListener(PositionListenerInfo( 30 | value: next, 31 | index: nextIndex, 32 | position: nextIndex.toDouble(), 33 | mode: ToggleMode.none))).called(1); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /test/loading_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import 'helper.dart'; 7 | 8 | void main() { 9 | defaultTestAllSwitches( 10 | 'Switch starts loading by returning Future in onTap or onChanged', 11 | (tester, buildSwitch, type, values) async { 12 | final current = values.first; 13 | final next = values.last; 14 | const loadingDuration = Duration(seconds: 3); 15 | 16 | await tester.pumpWidget(TestWrapper( 17 | child: buildSwitch( 18 | current: current, 19 | iconBuilder: iconBuilder, 20 | onTap: (info) => Future.delayed(loadingDuration), 21 | onChanged: (_) => Future.delayed(loadingDuration), 22 | ), 23 | )); 24 | final currentFinder = find.byKey(iconKey(current)); 25 | final nextFinder = find.byKey(iconKey(next)); 26 | final loadingFinder = find.byKey(loadingIconKey); 27 | 28 | Future testLoading() async { 29 | expect(loadingFinder, findsNothing); 30 | await tester.pump(Duration.zero); 31 | await tester.pump(loadingDuration ~/ 2); 32 | expect(loadingFinder, findsOneWidget); 33 | await tester.pump(loadingDuration ~/ 2); 34 | await tester.pump(const Duration(milliseconds: 500)); 35 | expect(loadingFinder, findsNothing); 36 | } 37 | 38 | // tests onChanged 39 | await tester.tap(nextFinder, warnIfMissed: false); 40 | await testLoading(); 41 | 42 | // tests onTap 43 | await tester.tap(currentFinder, warnIfMissed: false); 44 | await testLoading(); 45 | }); 46 | 47 | defaultTestAllSwitches('Switch starts loading by setting loading parameter', 48 | (tester, buildSwitch, type, values) async { 49 | final current = values.first; 50 | 51 | await tester.pumpWidget(TestWrapper( 52 | child: buildSwitch( 53 | current: current, 54 | iconBuilder: iconBuilder, 55 | loading: false, 56 | ), 57 | )); 58 | final loadingFinder = find.byKey(loadingIconKey); 59 | 60 | await tester.pumpWidget(TestWrapper( 61 | child: buildSwitch( 62 | current: current, 63 | iconBuilder: iconBuilder, 64 | loading: true, 65 | ), 66 | )); 67 | expect(loadingFinder, findsNothing); 68 | await tester.pump(const Duration(milliseconds: 500)); 69 | expect(loadingFinder, findsOneWidget); 70 | await tester.pumpWidget(TestWrapper( 71 | child: buildSwitch( 72 | current: current, 73 | iconBuilder: iconBuilder, 74 | loading: false, 75 | ), 76 | )); 77 | expect(loadingFinder, findsOneWidget); 78 | await tester.pump(const Duration(milliseconds: 500)); 79 | expect(loadingFinder, findsNothing); 80 | }, testDual: false); 81 | 82 | defaultTestAllSwitches('Switch supports initial loading', 83 | (tester, buildSwitch, type, values) async { 84 | final current = values.first; 85 | 86 | await tester.pumpWidget(TestWrapper( 87 | child: buildSwitch( 88 | current: current, 89 | iconBuilder: iconBuilder, 90 | loading: true, 91 | ), 92 | )); 93 | final currentFinder = find.byKey(iconKey(current, foreground: true)); 94 | final loadingFinder = find.byKey(loadingIconKey); 95 | 96 | expect(loadingFinder, findsOneWidget); 97 | expect(currentFinder, findsNothing); 98 | }); 99 | 100 | defaultTestAllSwitches('Switch disables loading by setting loading to false', 101 | (tester, buildSwitch, type, values) async { 102 | final current = values.first; 103 | final next = values.last; 104 | const loadingDuration = Duration(seconds: 3); 105 | 106 | await tester.pumpWidget(TestWrapper( 107 | child: buildSwitch( 108 | current: current, 109 | iconBuilder: iconBuilder, 110 | onTap: (info) => Future.delayed(loadingDuration), 111 | onChanged: (_) => Future.delayed(loadingDuration), 112 | loading: false, 113 | ), 114 | )); 115 | final nextFinder = find.byKey(iconKey(next)); 116 | final loadingFinder = find.byKey(loadingIconKey); 117 | 118 | expect(loadingFinder, findsNothing); 119 | 120 | await tester.tap(nextFinder, warnIfMissed: false); 121 | await tester.pump(Duration.zero); 122 | await tester.pump(const Duration(milliseconds: 500)); 123 | 124 | expect(loadingFinder, findsNothing); 125 | 126 | await tester.pump(loadingDuration); 127 | }); 128 | 129 | testWidgets( 130 | 'Default loading animation switches between TargetPlatform', 131 | (tester) async { 132 | const values = defaultValues; 133 | final current = values.first; 134 | 135 | final circularProgressIndicatorFinder = 136 | find.byType(CircularProgressIndicator); 137 | final cupertinoActivityIndicatorFinder = 138 | find.byType(CupertinoActivityIndicator); 139 | 140 | await tester.pumpWidget(TestWrapper( 141 | platform: TargetPlatform.android, 142 | child: AnimatedToggleSwitch.rolling( 143 | current: current, 144 | values: values, 145 | loading: true, 146 | ), 147 | )); 148 | expect(circularProgressIndicatorFinder, findsOneWidget); 149 | expect(cupertinoActivityIndicatorFinder, findsNothing); 150 | 151 | await tester.pumpWidget(TestWrapper( 152 | platform: TargetPlatform.iOS, 153 | child: AnimatedToggleSwitch.rolling( 154 | current: current, 155 | values: values, 156 | loading: true, 157 | ), 158 | )); 159 | expect(circularProgressIndicatorFinder, findsNothing); 160 | expect(cupertinoActivityIndicatorFinder, findsOneWidget); 161 | }, 162 | ); 163 | } 164 | -------------------------------------------------------------------------------- /test/mocks.dart: -------------------------------------------------------------------------------- 1 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | import 'package:mocktail/mocktail.dart'; 3 | 4 | abstract class TestOnTapFunction { 5 | void call(TapProperties props); 6 | } 7 | 8 | abstract class TestOnChangedFunction { 9 | void call(T value); 10 | } 11 | 12 | abstract class TestPositionListenerFunction { 13 | void call(PositionListenerInfo positionListenerInfo); 14 | } 15 | 16 | class MockFunction extends Mock implements TestOnTapFunction {} 17 | 18 | class MockOnChangedFunction extends Mock 19 | implements TestOnChangedFunction {} 20 | 21 | class MockPositionListenerFunction extends Mock 22 | implements TestPositionListenerFunction {} 23 | -------------------------------------------------------------------------------- /test/separator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'helper.dart'; 4 | 5 | void _checkValidSeparatorPositions( 6 | WidgetTester tester, List values, double spacing) { 7 | for (int i = 0; i < values.length - 1; i++) { 8 | final separatorFinder = find.byKey(separatorKey(i)); 9 | final firstIconFinder = find.byKey(iconKey(values[i])); 10 | final secondIconFinder = find.byKey(iconKey(values[i + 1])); 11 | expect(separatorFinder, findsOneWidget); 12 | expect( 13 | tester.getBottomRight(firstIconFinder) - 14 | tester.getBottomLeft(separatorFinder), 15 | Offset.zero, 16 | reason: 'separator is located right of the predecessor icon', 17 | ); 18 | expect( 19 | tester.getBottomLeft(secondIconFinder) - 20 | tester.getBottomRight(separatorFinder), 21 | Offset.zero, 22 | reason: 'separator is located left of the successor icon', 23 | ); 24 | expect( 25 | tester.getBottomRight(separatorFinder).dx - 26 | tester.getBottomLeft(separatorFinder).dx, 27 | spacing, 28 | reason: 'separator has correct width', 29 | ); 30 | } 31 | } 32 | 33 | void main() { 34 | defaultTestAllSwitches('Switch builds separator at correct position', 35 | (tester, buildSwitch, type, values) async { 36 | const spacings = [5.0, 10.0, 17.0]; 37 | final current = values[1]; 38 | for (final spacing in spacings) { 39 | await tester.pumpWidget( 40 | TestWrapper( 41 | child: buildSwitch( 42 | current: current, 43 | spacing: spacing, 44 | iconBuilder: iconBuilder, 45 | separatorBuilder: separatorBuilder, 46 | ), 47 | ), 48 | ); 49 | _checkValidSeparatorPositions(tester, values, spacing); 50 | } 51 | }, testDual: false); 52 | defaultTestAllSwitches('separatorBuilder does not support spacing = 0.0', 53 | (tester, buildSwitch, type, values) async { 54 | final current = values.first; 55 | await tester.pumpWidget( 56 | TestWrapper( 57 | child: buildSwitch( 58 | current: current, 59 | spacing: 0.0, 60 | iconBuilder: iconBuilder, 61 | separatorBuilder: separatorBuilder, 62 | ), 63 | ), 64 | ); 65 | expect(tester.takeException(), isAssertionError); 66 | }, testDual: false); 67 | } 68 | -------------------------------------------------------------------------------- /test/style_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | import 'package:animated_toggle_switch/src/test_keys.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_test/flutter_test.dart'; 5 | 6 | import 'helper.dart'; 7 | 8 | void main() { 9 | defaultTestAllSwitches( 10 | 'Switch throws error if styleBuilder and customStyleBuilder are set both', 11 | (tester, buildSwitch, type, values) async { 12 | expect( 13 | () => buildSwitch( 14 | current: 100, 15 | styleBuilder: (v) => const ToggleStyle(), 16 | customStyleBuilder: (c, l, g) => const ToggleStyle(), 17 | ), 18 | throwsAssertionError, 19 | ); 20 | }); 21 | 22 | defaultTestAllSwitches( 23 | 'Switch respects indicatorAnimationType: AnimationType.none', 24 | (tester, buildSwitch, type, values) async { 25 | const styleList = [ 26 | ToggleStyle(indicatorColor: Colors.green), 27 | ToggleStyle(indicatorColor: Colors.red), 28 | ToggleStyle(indicatorColor: Colors.blue), 29 | ToggleStyle(indicatorColor: Colors.orange), 30 | ]; 31 | final current = values[0]; 32 | final next = values[1]; 33 | final currentColor = styleList[0].indicatorColor; 34 | final nextColor = styleList[1].indicatorColor; 35 | final indicatorBoxFinder = 36 | find.byKey(AnimatedToggleSwitchTestKeys.indicatorDecoratedBoxKey); 37 | BoxDecoration findIndicatorBox() => 38 | (tester.firstWidget(indicatorBoxFinder) as DecoratedBox).decoration 39 | as BoxDecoration; 40 | await tester.pumpWidget(TestWrapper( 41 | child: buildSwitch( 42 | current: current, 43 | iconBuilder: iconBuilder, 44 | styleList: styleList, 45 | indicatorAnimationType: AnimationType.none, 46 | ), 47 | )); 48 | expect(findIndicatorBox().color, equals(currentColor)); 49 | await tester.pumpWidget(TestWrapper( 50 | child: buildSwitch( 51 | current: next, 52 | iconBuilder: iconBuilder, 53 | styleList: styleList, 54 | indicatorAnimationType: AnimationType.none, 55 | ), 56 | )); 57 | expect(findIndicatorBox().color, equals(nextColor)); 58 | }, testDual: false); 59 | } 60 | -------------------------------------------------------------------------------- /test/switch_size_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:animated_toggle_switch/animated_toggle_switch.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | import 'helper.dart'; 6 | 7 | void main() { 8 | defaultTestAllSwitches( 9 | 'Switch overflows with FittingMode.none', 10 | (tester, buildSwitch, type, values) async { 11 | final current = values.first; 12 | 13 | await tester.pumpWidget(TestWrapper( 14 | child: SizedBox(width: 10.0, child: buildSwitch(current: current)))); 15 | expect(tester.takeException(), isNull); 16 | 17 | await tester.pumpWidget(TestWrapper( 18 | child: SizedBox( 19 | width: 30.0, 20 | child: buildSwitch( 21 | current: current, 22 | fittingMode: FittingMode.none, 23 | )))); 24 | expect(tester.takeException(), isA()); 25 | }, 26 | ); 27 | 28 | defaultTestAllSwitches( 29 | 'Switch expands its size when forced by constraints', 30 | (tester, buildSwitch, type, values) async { 31 | final current = values.first; 32 | final switchFinder = find.byType(AnimatedToggleSwitch); 33 | const width = 500.0; 34 | 35 | await tester.pumpWidget(TestWrapper( 36 | child: SizedBox( 37 | width: width, 38 | child: buildSwitch( 39 | current: current, 40 | )))); 41 | 42 | expect(tester.getSize(switchFinder).width, width); 43 | }, 44 | ); 45 | 46 | defaultTestAllSwitches( 47 | 'Switch expands its size when spacing is set to infinite', 48 | (tester, buildSwitch, type, values) async { 49 | final current = values.first; 50 | final switchFinder = find.byType(AnimatedToggleSwitch); 51 | const width = 500.0; 52 | 53 | await tester.pumpWidget(Center( 54 | child: SizedBox( 55 | width: width, 56 | child: TestWrapper( 57 | child: buildSwitch( 58 | current: current, 59 | spacing: double.infinity, 60 | ), 61 | )), 62 | )); 63 | expect(tester.getSize(switchFinder).width, width); 64 | }, 65 | ); 66 | 67 | defaultTestAllSwitches( 68 | 'Switch expands its size when indicatorSize.width is set to infinite', 69 | (tester, buildSwitch, type, values) async { 70 | final current = values.first; 71 | final switchFinder = find.byType(AnimatedToggleSwitch); 72 | const width = 500.0; 73 | 74 | await tester.pumpWidget(Center( 75 | child: SizedBox( 76 | width: width, 77 | child: TestWrapper( 78 | child: buildSwitch( 79 | current: current, 80 | indicatorSize: Size.infinite, 81 | ), 82 | )), 83 | )); 84 | expect(tester.getSize(switchFinder).width, width); 85 | }, 86 | ); 87 | 88 | defaultTestAllSwitches( 89 | 'Switch throws error when two of spacing, indicatorSize.width and constraints are infinite', 90 | (tester, buildSwitch, type, values) async { 91 | final current = values.first; 92 | const width = 500.0; 93 | 94 | await tester.pumpWidget(Center( 95 | child: SizedBox( 96 | width: width, 97 | child: TestWrapper( 98 | child: buildSwitch( 99 | current: current, 100 | indicatorSize: Size.infinite, 101 | spacing: double.infinity, 102 | ), 103 | )), 104 | )); 105 | expect(tester.takeException(), isAssertionError); 106 | 107 | await tester.pumpWidget(TestWrapper( 108 | child: ListView( 109 | scrollDirection: Axis.horizontal, 110 | children: [ 111 | buildSwitch( 112 | current: current, 113 | spacing: double.infinity, 114 | ), 115 | ], 116 | ), 117 | )); 118 | expect(tester.takeException(), isAssertionError); 119 | 120 | await tester.pumpWidget(TestWrapper( 121 | child: ListView( 122 | scrollDirection: Axis.horizontal, 123 | children: [ 124 | buildSwitch( 125 | current: current, 126 | indicatorSize: Size.infinite, 127 | ), 128 | ], 129 | ), 130 | )); 131 | expect(tester.takeException(), isAssertionError); 132 | }, 133 | ); 134 | } 135 | -------------------------------------------------------------------------------- /test/values_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'helper.dart'; 4 | 5 | void main() { 6 | defaultTestAllSwitches('Switch does not allow unlisted values', 7 | (tester, buildSwitch, type, values) async { 8 | await tester.pumpWidget(TestWrapper( 9 | child: buildSwitch( 10 | current: 100, 11 | iconBuilder: iconBuilder, 12 | ), 13 | )); 14 | 15 | expect(tester.takeException(), isA()); 16 | }); 17 | 18 | defaultTestAllSwitches( 19 | 'Switch respects allowUnlistedValues', 20 | (tester, buildSwitch, type, values) async { 21 | await tester.pumpWidget(TestWrapper( 22 | child: buildSwitch( 23 | current: 100, 24 | iconBuilder: iconBuilder, 25 | allowUnlistedValues: true, 26 | ), 27 | )); 28 | 29 | expect(tester.takeException(), null); 30 | }, 31 | testDual: false, 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /test/vertical_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'helper.dart'; 4 | 5 | void _verifyVertical(WidgetTester tester, List values) { 6 | final firstPos = tester.getCenter(find.byKey(iconKey(values.first))); 7 | final lastPos = tester.getCenter(find.byKey(iconKey(values.last))); 8 | expect((firstPos - lastPos).dx == 0, true, 9 | reason: 'Icons do not have the same x-coordinate'); 10 | expect((firstPos - lastPos).dy < 0, true, 11 | reason: 'Icons should be ordered top down'); 12 | } 13 | 14 | void main() { 15 | defaultTestAllSwitches('Vertical switch', 16 | (tester, buildSwitch, type, values) async { 17 | final current = values[1]; 18 | await tester.pumpWidget(TestWrapper( 19 | child: buildSwitch( 20 | current: current, 21 | iconBuilder: iconBuilder, 22 | ).vertical(), 23 | )); 24 | _verifyVertical(tester, values); 25 | }); 26 | } 27 | --------------------------------------------------------------------------------