├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example ├── .gitattributes ├── .gitignore ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── waya │ │ │ │ │ └── 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.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle.kts ├── app │ └── web │ │ ├── .last_build_id │ │ ├── assets │ │ ├── AssetManifest.bin │ │ ├── AssetManifest.bin.json │ │ ├── AssetManifest.json │ │ ├── FontManifest.json │ │ ├── NOTICES │ │ ├── fonts │ │ │ └── MaterialIcons-Regular.otf │ │ ├── packages │ │ │ └── fl_extended │ │ │ │ └── assets │ │ │ │ └── FlExtendedIcons.ttf │ │ └── shaders │ │ │ └── ink_sparkle.frag │ │ ├── canvaskit │ │ ├── canvaskit.js │ │ ├── canvaskit.js.symbols │ │ ├── canvaskit.wasm │ │ ├── chromium │ │ │ ├── canvaskit.js │ │ │ ├── canvaskit.js.symbols │ │ │ └── canvaskit.wasm │ │ ├── skwasm.js │ │ ├── skwasm.js.symbols │ │ ├── skwasm.wasm │ │ ├── skwasm_st.js │ │ ├── skwasm_st.js.symbols │ │ └── skwasm_st.wasm │ │ ├── favicon.png │ │ ├── flutter.js │ │ ├── flutter_bootstrap.js │ │ ├── flutter_service_worker.js │ │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ │ ├── index.html │ │ ├── main.dart.js │ │ ├── manifest.json │ │ └── version.json ├── build_web.sh ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ └── 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 │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ ├── main.dart │ └── module │ │ ├── auto_collapsing.dart │ │ ├── button_page.dart │ │ ├── components_page.dart │ │ ├── counter_page.dart │ │ ├── decorator_page.dart │ │ ├── flip_card_page.dart │ │ ├── page_view_page.dart │ │ ├── progress_page.dart │ │ ├── state_components_page.dart │ │ └── text_field_page.dart ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── app_icon_1024.png │ │ │ │ ├── app_icon_128.png │ │ │ │ ├── app_icon_16.png │ │ │ │ ├── app_icon_256.png │ │ │ │ ├── app_icon_32.png │ │ │ │ ├── app_icon_512.png │ │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Configs │ │ │ ├── AppInfo.xcconfig │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements │ └── RunnerTests │ │ └── RunnerTests.swift ├── pubspec.yaml └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json ├── format.sh ├── lib ├── flutter_waya.dart └── src │ ├── animation │ ├── animation_counter.dart │ ├── auto_collapsing.dart │ ├── bubble_button.dart │ ├── elastic_builder.dart │ ├── expansion_tiles.dart │ ├── popup_menu.dart │ ├── toggle_rotate.dart │ └── wave.dart │ ├── const │ └── styles.dart │ ├── counter.dart │ ├── dotted_line.dart │ ├── draggable_scrollbar.dart │ ├── editable_text │ ├── decorator_box.dart │ ├── pin_text_field.dart │ └── text_field.dart │ ├── event.dart │ ├── extended_state.dart │ ├── flip_card.dart │ ├── keep_alive_wrapper.dart │ ├── list_entry.dart │ ├── page_view │ ├── indicator.dart │ ├── page_view.dart │ └── transform.dart │ ├── progress │ ├── liquid_progress.dart │ └── progress.dart │ ├── rating_stars.dart │ ├── screen_adaptation.dart │ ├── shimmery.dart │ ├── system_ui_overlay_style.dart │ ├── tab_bar.dart │ ├── wrapper.dart │ └── x_switch.dart └── pubspec.yaml /.gitattributes: -------------------------------------------------------------------------------- 1 | *.h linguist-language=Dart 2 | *.m linguist-language=Dart 3 | *.html linguist-language=Dart 4 | *.xml linguist-language=Dart 5 | *.xml linguist-language=Dart 6 | *.java linguist-language=Dart 7 | *.json linguist-language=Dart 8 | *.wasm linguist-language=Dart 9 | *.js linguist-language=Dart 10 | *.md linguist-language=Dart 11 | *.cmd linguist-language=Dart 12 | *.swift linguist-language=Dart -------------------------------------------------------------------------------- /.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 | /lib/generated 33 | *.lock 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Exceptions to above rules. 39 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 40 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 11.2.5 2 | 3 | * Fix `FlPageViewController` scrolling error 4 | 5 | ## 11.2.3 6 | 7 | * Remove `DecoratorPendantVisibilityMode` from `DecoratorPendant`, please use `needEditing`, 8 | `needValue` and `needFocus` 9 | * To remove `BoxDecorative` from `DecoratorBox`, please use the `decoration` callback method 10 | * `DecoratorBox` changes `hasFocus` to `onFocus`, `isEditing` to onEditing, and adds `onValue` and 11 | `listenable` 12 | * Remove `DecoratorBoxState`,please use `DecoratorBox` 13 | 14 | ## 11.0.2 15 | 16 | * Removed `CheckBox`, `FlBadge`,`FlPopupMenuButton`,`FlSwiper`, `FlSwiperPagination`, 17 | `FlSwiperIndicator`, 18 | * Removed `CarouselSlider`,added `FlPageViewTransform`,`FlPageView` 19 | 20 | ## 10.0.1 21 | 22 | * `AutomaticKeepAliveWrapperState` renamed to `AutomaticKeepAliveClientMixinState` 23 | * `AutomaticKeepAliveWrapper` renamed to `AutomaticKeepAliveClientWrapper` 24 | 25 | ## 10.0.0 26 | 27 | * Migrate to 3.27.0 28 | 29 | ## 9.10.1 30 | 31 | * Remove `CounterAnimation`, add `AnimationCounter.down()`、`AnimationCounter.up()` 32 | * Remove `CountDown`, add `Counter.down()`、`Counter.up()` 33 | 34 | ## 9.9.0 35 | 36 | * Changed `FlProgress` to `FlLinearProgress` and made modifications to the parameters 37 | 38 | ## 9.8.0 39 | 40 | * Add some callbacks for `CountDown()` and remove `CountDownType` enumeration 41 | * Add `onStartTiming` `onStarts` `onEnds` callback method for `CountDown()` 42 | * Change the `onTap` of `SendVerificationCode()` to `onSendTap` 43 | * Change the `onStateChanged` of `SendVerificationCode()` to `onChanged` 44 | 45 | ## 9.7.1 46 | 47 | * Modify `DecoratorEntry` to `DecoratorPendant`,and adds the `maintainSize` property to determine 48 | whether to maintain the size 49 | * Modify `DecoratorPositioned` to `DecoratorPendantPosition` 50 | * Added `BoxDecorative` to `DecoratorBox` and `DecoratorBoxState` 51 | * Modify `.toDecoratorEntry()` to `.toDecoratorPendant()` 52 | * Remove the `needKeyBoard` and `focusNode` from `PINTextField` 53 | * Change the `controller` of `PINTextField` to mandatory 54 | * Hide `contextMenuBuilder` and `enableInteractiveSelection` for `PINTextField` 55 | * Modify some parameters of `ExpansionTiles` `PopupMenuButtonRotateBuilder` `ToggleRotate`, please 56 | refer to Example 57 | 58 | ## 9.6.0 59 | 60 | * Remove the `DropdownMenusButton` component, please use the `MultiPopupMenuButton` component 61 | * Remove the `DropdownMenuButton` component, please use the `PopupMenuButtonRotateBuilder` component 62 | 63 | ## 9.5.2 64 | 65 | * export `SystemUiOverlayStyleLight`、`SystemUiOverlayStyleDark` 66 | 67 | ## 9.5.1 68 | 69 | * Removed `fl_extended` 70 | 71 | ## 9.3.1 72 | 73 | * Change the `SendSMS` to `SendVerificationCode` 74 | * Change the `PinBox` to `PINTextField` 75 | * Change the `PinTextFieldBuilderConfig` to `PINTextFieldBuilderConfig` 76 | 77 | ## 9.1.4 78 | 79 | * Removed `AnchorScrollBuilder` 80 | * Add `Shimmery` 81 | 82 | ## 9.1.3 83 | 84 | * Add `FlipCardController` to `FlipCard()` 85 | 86 | ## 9.1.2 87 | 88 | * Fixed conflicts between `DropdownMenusButton` and official packages 89 | 90 | ## 9.1.1 91 | 92 | * Refactor the `DropdownMenuButton` and `DropdownMenusButton` 93 | 94 | ## 9.0.1 95 | 96 | * Split core extension to [fl_extended](https://pub.dev/packages/fl_extended) package 97 | * Modify all `GlobalWayUI()` to `FlExtended()` 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Wayaer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_waya 2 | 3 | ## 运行[Example](https://wayaer.github.io/flutter_waya/example/app/web/index.html#/)查看UI组件 -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml -------------------------------------------------------------------------------- /example/.gitattributes: -------------------------------------------------------------------------------- 1 | *.h linguist-language=Dart 2 | *.m linguist-language=Dart 3 | *.html linguist-language=Dart 4 | *.xml linguist-language=Dart 5 | *.xml linguist-language=Dart 6 | *.java linguist-language=Dart 7 | *.json linguist-language=Dart 8 | *.wasm linguist-language=Dart 9 | *.js linguist-language=Dart 10 | *.md linguist-language=Dart 11 | *.cmd linguist-language=Dart 12 | *.swift linguist-language=Dart -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | *.lock 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 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 | .metadata 33 | 34 | # Web related 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | .cxx/ 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/to/reference-keystore 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | -------------------------------------------------------------------------------- /example/android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id("dev.flutter.flutter-gradle-plugin") 6 | } 7 | 8 | android { 9 | namespace = "com.waya.example" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_11 15 | targetCompatibility = JavaVersion.VERSION_11 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_11.toString() 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.waya.example" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.getByName("debug") 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/waya/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.waya.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() 6 | -------------------------------------------------------------------------------- /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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | flutterSdkPath 8 | } 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0" 21 | id("com.android.application") version "8.7.0" apply false 22 | id("org.jetbrains.kotlin.android") version "1.8.22" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /example/app/web/.last_build_id: -------------------------------------------------------------------------------- 1 | 004f321dbdf51c5c86195542b683b1c3 -------------------------------------------------------------------------------- /example/app/web/assets/AssetManifest.bin: -------------------------------------------------------------------------------- 1 | /packages/fl_extended/assets/FlExtendedIcons.ttf  asset/packages/fl_extended/assets/FlExtendedIcons.ttf -------------------------------------------------------------------------------- /example/app/web/assets/AssetManifest.bin.json: -------------------------------------------------------------------------------- 1 | "DQEHL3BhY2thZ2VzL2ZsX2V4dGVuZGVkL2Fzc2V0cy9GbEV4dGVuZGVkSWNvbnMudHRmDAENAQcFYXNzZXQHL3BhY2thZ2VzL2ZsX2V4dGVuZGVkL2Fzc2V0cy9GbEV4dGVuZGVkSWNvbnMudHRm" -------------------------------------------------------------------------------- /example/app/web/assets/AssetManifest.json: -------------------------------------------------------------------------------- 1 | {"packages/fl_extended/assets/FlExtendedIcons.ttf":["packages/fl_extended/assets/FlExtendedIcons.ttf"]} -------------------------------------------------------------------------------- /example/app/web/assets/FontManifest.json: -------------------------------------------------------------------------------- 1 | [{"family":"MaterialIcons","fonts":[{"asset":"fonts/MaterialIcons-Regular.otf"}]},{"family":"packages/fl_extended/FlExtendedIcons","fonts":[{"asset":"packages/fl_extended/assets/FlExtendedIcons.ttf"}]}] -------------------------------------------------------------------------------- /example/app/web/assets/fonts/MaterialIcons-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/app/web/assets/fonts/MaterialIcons-Regular.otf -------------------------------------------------------------------------------- /example/app/web/assets/packages/fl_extended/assets/FlExtendedIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/app/web/assets/packages/fl_extended/assets/FlExtendedIcons.ttf -------------------------------------------------------------------------------- /example/app/web/canvaskit/canvaskit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/app/web/canvaskit/canvaskit.wasm -------------------------------------------------------------------------------- /example/app/web/canvaskit/chromium/canvaskit.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/app/web/canvaskit/chromium/canvaskit.wasm -------------------------------------------------------------------------------- /example/app/web/canvaskit/skwasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/app/web/canvaskit/skwasm.wasm -------------------------------------------------------------------------------- /example/app/web/canvaskit/skwasm_st.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/app/web/canvaskit/skwasm_st.wasm -------------------------------------------------------------------------------- /example/app/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/app/web/favicon.png -------------------------------------------------------------------------------- /example/app/web/flutter.js: -------------------------------------------------------------------------------- 1 | (()=>{var P=()=>navigator.vendor==="Google Inc."||navigator.agent==="Edg/",E=()=>typeof ImageDecoder>"u"?!1:P(),L=()=>typeof Intl.v8BreakIterator<"u"&&typeof Intl.Segmenter<"u",W=()=>{let n=[0,97,115,109,1,0,0,0,1,5,1,95,1,120,0];return WebAssembly.validate(new Uint8Array(n))},w={hasImageCodecs:E(),hasChromiumBreakIterators:L(),supportsWasmGC:W(),crossOriginIsolated:window.crossOriginIsolated};function l(...n){return new URL(C(...n),document.baseURI).toString()}function C(...n){return n.filter(t=>!!t).map((t,i)=>i===0?_(t):j(_(t))).filter(t=>t.length).join("/")}function j(n){let t=0;for(;t0&&n.charAt(t-1)==="/";)t--;return n.substring(0,t)}function T(n,t){return n.canvasKitBaseUrl?n.canvasKitBaseUrl:t.engineRevision&&!t.useLocalCanvasKit?C("https://www.gstatic.com/flutter-canvaskit",t.engineRevision):"canvaskit"}var v=class{constructor(){this._scriptLoaded=!1}setTrustedTypesPolicy(t){this._ttPolicy=t}async loadEntrypoint(t){let{entrypointUrl:i=l("main.dart.js"),onEntrypointLoaded:r,nonce:e}=t||{};return this._loadJSEntrypoint(i,r,e)}async load(t,i,r,e,a){a??=o=>{o.initializeEngine(r).then(c=>c.runApp())};let{entryPointBaseUrl:s}=r;if(t.compileTarget==="dart2wasm")return this._loadWasmEntrypoint(t,i,s,a);{let o=t.mainJsPath??"main.dart.js",c=l(s,o);return this._loadJSEntrypoint(c,a,e)}}didCreateEngineInitializer(t){typeof this._didCreateEngineInitializerResolve=="function"&&(this._didCreateEngineInitializerResolve(t),this._didCreateEngineInitializerResolve=null,delete _flutter.loader.didCreateEngineInitializer),typeof this._onEntrypointLoaded=="function"&&this._onEntrypointLoaded(t)}_loadJSEntrypoint(t,i,r){let e=typeof i=="function";if(!this._scriptLoaded){this._scriptLoaded=!0;let a=this._createScriptTag(t,r);if(e)console.debug("Injecting 37 | 38 | 39 | -------------------------------------------------------------------------------- /example/app/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Waya", 3 | "short_name": "Waya", 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 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /example/app/web/version.json: -------------------------------------------------------------------------------- 1 | {"app_name":"app","version":"1.0.0","build_number":"1","package_name":"app"} -------------------------------------------------------------------------------- /example/build_web.sh: -------------------------------------------------------------------------------- 1 | rm -rf 'app/web' 2 | 3 | echo "开始获取 packages 插件资源" 4 | 5 | flutter packages get 6 | 7 | echo "开始构建 web" 8 | 9 | flutter build web --base-href '/flutter_waya/example/app/web/' 10 | 11 | mv 'build/web' 'app/web' 12 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /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 | 33 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_ios_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 Flutter 2 | import UIKit 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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/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 | CFBundleDisplayName 8 | Waya 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Waya 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | UILaunchStoryboardName 33 | LaunchScreen 34 | UIMainStoryboardFile 35 | Main 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | CADisableMinimumFrameDurationOnPhone 50 | 51 | UIApplicationSupportsIndirectInputEvents 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /example/lib/module/auto_collapsing.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/main.dart'; 2 | import 'package:fl_extended/fl_extended.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_waya/flutter_waya.dart'; 5 | 6 | class ColorEntry extends StatelessWidget { 7 | const ColorEntry(this.index, this.color, 8 | {this.height = 80, this.width = 80, super.key}); 9 | 10 | final int index; 11 | final Color color; 12 | final double height; 13 | final double width; 14 | 15 | @override 16 | Widget build(BuildContext context) => Container( 17 | width: width, 18 | height: height, 19 | alignment: Alignment.center, 20 | color: color, 21 | child: BText(index.toString(), 22 | color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold)); 23 | } 24 | 25 | class AutoCollapsingPage extends StatefulWidget { 26 | const AutoCollapsingPage({super.key, this.direction = Axis.vertical}); 27 | 28 | final Axis direction; 29 | 30 | @override 31 | State createState() => _AutoCollapsingPageState(); 32 | } 33 | 34 | class _AutoCollapsingPageState extends State { 35 | ScrollController controller = ScrollController(); 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return ExtendedScaffold( 40 | isStack: true, 41 | appBar: AppBarText('AutoCollapsing'), 42 | mainAxisAlignment: MainAxisAlignment.center, 43 | padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), 44 | children: [ 45 | ListView.builder( 46 | controller: controller, 47 | itemCount: colorList.length, 48 | itemBuilder: (BuildContext context, int index) => ColorEntry( 49 | index, colorList[index], 50 | height: index & 3 == 0 ? 80 : 40)).expand, 51 | Padding( 52 | padding: const EdgeInsets.all(20), 53 | child: AutoCollapsingBuilder( 54 | controller: controller, 55 | minSize: 20, 56 | direction: widget.direction, 57 | maxSize: 300, 58 | child: Container( 59 | height: widget.direction == Axis.horizontal ? 300 : null, 60 | decoration: BoxDecoration( 61 | color: Colors.white, 62 | borderRadius: BorderRadius.circular(10)), 63 | ))) 64 | ]); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/lib/module/components_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/main.dart'; 2 | import 'package:fl_extended/fl_extended.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_waya/flutter_waya.dart'; 5 | 6 | class ComponentsPage extends StatelessWidget { 7 | const ComponentsPage({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) => ExtendedScaffold( 11 | isScroll: true, 12 | appBar: AppBarText('Components'), 13 | padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), 14 | children: [ 15 | const Partition('Shimmery', marginTop: 0), 16 | Shimmery( 17 | colors: [ 18 | Colors.transparent, 19 | Colors.yellow.withValues(alpha: 0.05), 20 | Colors.yellow.withValues(alpha: 0.9), 21 | Colors.yellow.withValues(alpha: 0.05), 22 | Colors.transparent, 23 | ], 24 | child: Universal( 25 | padding: const EdgeInsets.all(10), 26 | decoration: BoxDecoration( 27 | color: Colors.blue, 28 | borderRadius: BorderRadius.circular(9)), 29 | child: const BText('Shimmery', color: Colors.white))), 30 | const Partition('Wrapper'), 31 | const Row( 32 | mainAxisAlignment: MainAxisAlignment.spaceAround, 33 | children: [ 34 | Wrapper( 35 | formEnd: true, 36 | elevation: 1, 37 | child: Text('this is Wrapper', 38 | style: TextStyle(color: Colors.white))), 39 | Wrapper( 40 | formEnd: true, 41 | elevation: 1, 42 | style: SpineStyle.right, 43 | child: Text('this is Wrapper', 44 | style: TextStyle(color: Colors.white))), 45 | ]), 46 | const Partition('ExpansionTiles'), 47 | Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ 48 | ExpansionTile( 49 | title: const BText('Tile'), 50 | backgroundColor: 51 | context.theme.primaryColor.withValues(alpha: 0.2), 52 | children: 5.generate((int index) => Universal( 53 | margin: const EdgeInsets.all(12), 54 | alignment: Alignment.centerLeft, 55 | child: BText('item$index'))), 56 | ).expanded, 57 | ExpansionTiles( 58 | icon: (bool isExpanded) => Icon(Icons.expand_more, 59 | color: isExpanded ? context.theme.primaryColor : null), 60 | builder: (BuildContext context, GestureTapCallback onTap, 61 | bool isExpanded, Widget? rotation) { 62 | return ListTile( 63 | onTap: onTap, 64 | title: const BText('Tile'), 65 | trailing: rotation); 66 | }, 67 | backgroundColor: 68 | context.theme.primaryColor.withValues(alpha: 0.2), 69 | children: 5.generate((int index) => Universal( 70 | margin: const EdgeInsets.all(12), 71 | alignment: Alignment.centerLeft, 72 | child: BText('item$index'))), 73 | ).expanded, 74 | ]), 75 | const Partition('ToggleRotate'), 76 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 77 | ToggleRotate( 78 | duration: const Duration(milliseconds: 500), 79 | turns: 0.5, 80 | onChanged: (bool value) { 81 | log('ToggleRotate 0.5 : $value'); 82 | }, 83 | builder: (Widget child, rotate) => 84 | GestureDetector(onTap: rotate, child: child), 85 | icon: (bool value) => Icon( 86 | Icons.accessibility_sharp, 87 | size: 30, 88 | color: value ? context.theme.primaryColor : null, 89 | )), 90 | ToggleRotate( 91 | duration: const Duration(seconds: 1), 92 | turns: 1, 93 | onChanged: (bool value) { 94 | log('ToggleRotate 1 : $value'); 95 | }, 96 | builder: (Widget child, rotate) => 97 | GestureDetector(onTap: rotate, child: child), 98 | icon: (bool value) => Icon( 99 | Icons.accessibility_sharp, 100 | size: 30, 101 | color: value ? context.theme.primaryColor : null, 102 | )), 103 | ToggleRotate( 104 | duration: const Duration(seconds: 2), 105 | turns: 1.5, 106 | onChanged: (bool value) { 107 | log('ToggleRotate 1.5 : $value'); 108 | }, 109 | builder: (Widget child, rotate) => 110 | GestureDetector(onTap: rotate, child: child), 111 | icon: (bool value) => Icon( 112 | Icons.accessibility_sharp, 113 | size: 30, 114 | color: value ? context.theme.primaryColor : null, 115 | )), 116 | ]), 117 | const Partition('DottedLine'), 118 | Container( 119 | width: double.infinity, 120 | height: 40, 121 | alignment: Alignment.center, 122 | decoration: BoxDecoration( 123 | border: DottedLineBorder.all( 124 | color: context.theme.dividerColor)), 125 | child: CustomPaint( 126 | size: const Size(double.infinity, 1), 127 | painter: DottedLinePainter( 128 | color: context.theme.dividerColor, 129 | strokeWidth: 1, 130 | gap: 20))), 131 | const Partition('RatingStars'), 132 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 133 | RatingStars( 134 | value: 2.2, 135 | starSpacing: 4, 136 | builder: (bool selected) => Icon(Icons.star, 137 | color: selected ? Colors.yellow : Colors.grey)), 138 | RatingStars( 139 | value: 3.3, 140 | starSpacing: 4, 141 | builder: (bool selected) => Icon(Icons.star, 142 | color: selected ? Colors.yellow : Colors.grey)), 143 | ]), 144 | const SizedBox(height: 100), 145 | ]); 146 | } 147 | -------------------------------------------------------------------------------- /example/lib/module/counter_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:app/main.dart'; 4 | import 'package:fl_extended/fl_extended.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_waya/flutter_waya.dart'; 7 | 8 | class CounterPage extends StatelessWidget { 9 | const CounterPage({super.key}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ExtendedScaffold( 14 | isScroll: true, 15 | appBar: AppBarText('Counter'), 16 | padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), 17 | children: [ 18 | const Partition('AnimationCounter.up', marginTop: 0), 19 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 20 | ValueBuilder( 21 | initial: 100, 22 | builder: (_, int? value, updater) => AnimationCounter.up( 23 | style: CounterStyle.part, 24 | value: value.toString(), 25 | builder: (String text) => Universal( 26 | onTap: () { 27 | updater(value! + 2); 28 | }, 29 | child: BText(text, fontSize: 30)))), 30 | ValueBuilder( 31 | initial: 100, 32 | builder: (_, int? value, updater) => AnimationCounter.up( 33 | style: CounterStyle.all, 34 | value: value.toString(), 35 | builder: (String text) => Universal( 36 | onTap: () { 37 | updater(value! + 1); 38 | }, 39 | child: BText(text, fontSize: 30)))), 40 | ]), 41 | const Partition('AnimationCounter.down'), 42 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 43 | ValueBuilder( 44 | initial: 100, 45 | builder: (_, int? value, updater) => AnimationCounter.down( 46 | style: CounterStyle.part, 47 | value: value.toString(), 48 | builder: (String text) => Universal( 49 | onTap: () { 50 | updater(value! - 1); 51 | }, 52 | child: BText(text, fontSize: 30)))), 53 | ValueBuilder( 54 | initial: 100, 55 | builder: (_, int? value, updater) => AnimationCounter.down( 56 | style: CounterStyle.all, 57 | value: value.toString(), 58 | builder: (String text) => Universal( 59 | onTap: () { 60 | updater(value! - 2); 61 | }, 62 | child: BText(text, fontSize: 30)))), 63 | ]), 64 | const Partition('Counter.down'), 65 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 66 | Counter.down( 67 | builder: (Duration duration, bool isActive, start, stop) { 68 | return ElevatedButton( 69 | onPressed: isActive ? stop : start, 70 | child: AnimationCounter.down( 71 | value: duration.inSeconds.toString(), 72 | builder: (String value) => BText(value, fontSize: 20))); 73 | }), 74 | Counter.down( 75 | builder: (Duration duration, bool isActive, start, stop) { 76 | return ElevatedButton( 77 | onPressed: isActive ? stop : start, 78 | child: AnimationCounter.down( 79 | style: CounterStyle.all, 80 | value: duration.inSeconds.toString(), 81 | builder: (String value) => BText(value, fontSize: 20))); 82 | }), 83 | ]), 84 | const Partition('Counter.up'), 85 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 86 | Counter.up( 87 | max: const Duration(seconds: 300), 88 | builder: (Duration duration, bool isActive, start, stop) { 89 | return ElevatedButton( 90 | onPressed: isActive ? stop : start, 91 | child: AnimationCounter.up( 92 | value: duration.inSeconds.toString(), 93 | builder: (String value) => 94 | BText(value, fontSize: 20))); 95 | }), 96 | Counter.up( 97 | builder: (Duration duration, bool isActive, start, stop) { 98 | return ElevatedButton( 99 | onPressed: isActive ? stop : start, 100 | child: AnimationCounter.up( 101 | style: CounterStyle.all, 102 | value: duration.inSeconds.toString(), 103 | builder: (String value) => BText(value, fontSize: 20))); 104 | }), 105 | ]), 106 | const Partition('SendVerificationCode'), 107 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 108 | SendVerificationCode( 109 | gestureBuilder: (onTap, child) => 110 | ElevatedButton(onPressed: onTap, child: child), 111 | value: const Duration(seconds: 10), 112 | onChanged: (SendState value) { 113 | showToast(value.toString()); 114 | }, 115 | padding: 116 | const EdgeInsets.symmetric(vertical: 6, horizontal: 12), 117 | onSendTap: () async { 118 | await 2.seconds.delayed(); 119 | return true; 120 | }, 121 | builder: (SendState state, int i) { 122 | switch (state) { 123 | case SendState.none: 124 | return const Text('发送验证码'); 125 | case SendState.sending: 126 | return const Text('发送中'); 127 | case SendState.resend: 128 | return const Text('重新发送'); 129 | case SendState.countDown: 130 | return Row(children: [ 131 | Text('等待 '), 132 | AnimationCounter.down( 133 | value: '$i', builder: (value) => Text(value)), 134 | Text(' s') 135 | ]); 136 | } 137 | }), 138 | SendVerificationCode( 139 | gestureBuilder: (onTap, child) => 140 | ElevatedButton(onPressed: onTap, child: child), 141 | value: const Duration(seconds: 10), 142 | onChanged: (SendState value) { 143 | showToast(value.toString()); 144 | }, 145 | padding: 146 | const EdgeInsets.symmetric(vertical: 6, horizontal: 12), 147 | onSendTap: () async { 148 | final completer = Completer(); 149 | 2.seconds.delayed(() { 150 | completer.complete(true); 151 | }); 152 | return completer.future; 153 | }, 154 | builder: (SendState state, int i) { 155 | switch (state) { 156 | case SendState.none: 157 | return const Text('发送验证码'); 158 | case SendState.sending: 159 | return const Text('发送中'); 160 | case SendState.resend: 161 | return const Text('重新发送'); 162 | case SendState.countDown: 163 | return Row(children: [ 164 | Text('等待 '), 165 | AnimationCounter.down( 166 | value: '$i', builder: (value) => Text(value)), 167 | Text(' s') 168 | ]); 169 | } 170 | }), 171 | ]), 172 | ]); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /example/lib/module/decorator_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/main.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:fl_extended/fl_extended.dart'; 4 | import 'package:flutter_waya/flutter_waya.dart'; 5 | 6 | class DecoratorBoxPage extends StatelessWidget { 7 | const DecoratorBoxPage({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | Widget buildDecoration( 12 | Widget child, DecoratorBoxStatus status) => 13 | Container( 14 | decoration: BoxDecoration( 15 | color: context.theme.primaryColor.withValues(alpha: 0.2), 16 | border: BorderType.outline.toBorder(status.hasFocus 17 | ? BorderSide( 18 | color: context.theme.colorScheme.primary, width: 2) 19 | : BorderSide( 20 | color: context.theme.colorScheme.primaryContainer, 21 | width: 2)), 22 | borderRadius: BorderRadius.circular(8)), 23 | padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), 24 | child: child); 25 | return ExtendedScaffold( 26 | isScroll: true, 27 | appBar: AppBarText('DecoratorBox'), 28 | padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), 29 | children: [ 30 | const Partition('DecoratorBox hasFocus(true)', marginTop: 0), 31 | DecoratorBox( 32 | onFocus: () => true, 33 | decoration: buildDecoration, 34 | child: TextField()), 35 | const Partition('DecoratorBox hasFocus(false)', marginTop: 0), 36 | DecoratorBox( 37 | onFocus: () => false, 38 | decoration: buildDecoration, 39 | child: TextField()), 40 | _DecoratorBoxPage(), 41 | _DecoratorBoxPage(needEditing: true), 42 | _DecoratorBoxPage(needEditing: false), 43 | _DecoratorBoxPage(needFocus: true), 44 | _DecoratorBoxPage(needFocus: false), 45 | _DecoratorBoxPage(needFocus: true, needEditing: true), 46 | _DecoratorBoxPage(needFocus: true, needEditing: false), 47 | _DecoratorBoxPage(needFocus: false, needEditing: true), 48 | _DecoratorBoxPage(needFocus: false, needEditing: false), 49 | ]); 50 | } 51 | } 52 | 53 | class _DecoratorBoxPage extends StatefulWidget { 54 | const _DecoratorBoxPage({this.needFocus, this.needEditing}); 55 | 56 | final bool? needFocus; 57 | final bool? needEditing; 58 | 59 | @override 60 | State<_DecoratorBoxPage> createState() => _DecoratorBoxPageState(); 61 | } 62 | 63 | class _DecoratorBoxPageState extends State<_DecoratorBoxPage> { 64 | FocusNode focusNode = FocusNode(); 65 | TextEditingController controller = TextEditingController(); 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return Universal(children: [ 70 | Partition( 71 | 'DecoratorBox \nneedFocus(${widget.needFocus}) needEditing(${widget.needEditing})'), 72 | DecoratorBox( 73 | listenable: Listenable.merge([focusNode, controller]), 74 | onFocus: () => focusNode.hasFocus, 75 | onEditing: () => controller.text.isNotEmpty, 76 | onValue: () => controller.value, 77 | decoration: buildDecoration, 78 | footers: buildPendants, 79 | headers: buildPendants, 80 | prefixes: buildPendants, 81 | suffixes: buildPendants, 82 | child: TextField(focusNode: focusNode, controller: controller)), 83 | ]); 84 | } 85 | 86 | Widget buildDecoration( 87 | Widget child, DecoratorBoxStatus status) => 88 | Container( 89 | decoration: BoxDecoration( 90 | color: context.theme.primaryColor.withValues(alpha: 0.2), 91 | border: BorderType.outline.toBorder(status.hasFocus 92 | ? BorderSide( 93 | color: context.theme.colorScheme.primary, 94 | width: 95 | (status.value?.text.contains('1') ?? false) ? 4 : 2) 96 | : BorderSide( 97 | color: context.theme.colorScheme.primaryContainer, 98 | width: 2)), 99 | borderRadius: BorderRadius.circular(8)), 100 | padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), 101 | child: child); 102 | 103 | List> get buildPendants => 104 | DecoratorPendantPosition.values.builder((positioned) => 105 | DecoratorPendant( 106 | needFocus: widget.needFocus, 107 | needEditing: widget.needEditing, 108 | maintainSize: false, 109 | child: Text(positioned.name), 110 | positioned: positioned)); 111 | 112 | @override 113 | void dispose() { 114 | super.dispose(); 115 | focusNode.dispose(); 116 | controller.dispose(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /example/lib/module/flip_card_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:app/main.dart'; 4 | import 'package:fl_extended/fl_extended.dart'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_waya/flutter_waya.dart'; 7 | 8 | class FlipCardPage extends StatefulWidget { 9 | const FlipCardPage({super.key}); 10 | 11 | @override 12 | State createState() => _FlipCardPageState(); 13 | } 14 | 15 | class _FlipCardPageState extends State { 16 | FlipCardController controller = FlipCardController(); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return ExtendedScaffold( 21 | appBar: AppBarText('FlipCard'), 22 | padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), 23 | children: [ 24 | MouseRegion( 25 | onEnter: (_) { 26 | controller.skew(0.2); 27 | }, 28 | onExit: (_) { 29 | controller.skew(0); 30 | }, 31 | child: FlipCard( 32 | controller: controller, 33 | onFlip: (FlipCardState value) { 34 | log(value); 35 | }, 36 | onFlipDone: (FlipCardState value) { 37 | log(value); 38 | }, 39 | front: buildContent('Front', Colors.amberAccent), 40 | back: buildContent('Back', Colors.lightGreenAccent))) 41 | .expanded, 42 | 12.heightBox, 43 | Wrap(spacing: 10, runSpacing: 10, children: [ 44 | ElevatedText('toggle', onTap: () { 45 | controller.toggle(); 46 | }), 47 | ElevatedText('animateToggle', onTap: () { 48 | controller.animateToggle(); 49 | // setState(() {}); 50 | }), 51 | ElevatedText('skew', onTap: () { 52 | controller.skew(Random().nextDouble()); 53 | }), 54 | ElevatedText('hint', onTap: () { 55 | controller.hint(); 56 | }), 57 | ]) 58 | ]); 59 | } 60 | 61 | Widget buildContent(String text, Color color) => Container( 62 | alignment: Alignment.center, 63 | decoration: 64 | BoxDecoration(color: color, borderRadius: BorderRadius.circular(10)), 65 | child: BText(text, fontSize: 20, fontWeight: FontWeight.bold)); 66 | } 67 | -------------------------------------------------------------------------------- /example/lib/module/page_view_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/main.dart'; 2 | import 'package:fl_extended/fl_extended.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_waya/flutter_waya.dart'; 5 | 6 | class FlPageViewPage extends StatefulWidget { 7 | const FlPageViewPage({super.key}); 8 | 9 | @override 10 | State createState() => _FlPageViewPageState(); 11 | } 12 | 13 | class _FlPageViewPageState extends State { 14 | FlPageViewController controllerHorizontal = 15 | FlPageViewController(viewportFraction: 0.8); 16 | FlPageViewController controllerVertical = 17 | FlPageViewController(viewportFraction: 0.8, isLoop: true); 18 | 19 | int itemCount = 10; 20 | 21 | @override 22 | void initState() { 23 | super.initState(); 24 | addPostFrameCallback((_) { 25 | controllerHorizontal.startAutoPlay(); 26 | controllerVertical.startAutoPlay(); 27 | }); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return ExtendedScaffold( 33 | isScroll: true, 34 | appBar: AppBarText('FlPageView'), 35 | children: [ 36 | const Partition('FlPageView vertical', marginTop: 0), 37 | buildPageView(controllerVertical, Axis.vertical), 38 | const Partition('FlPageView horizontal'), 39 | buildPageView(controllerHorizontal, Axis.horizontal), 40 | const Partition('FlIndicator'), 41 | buildIndicator(controllerHorizontal), 42 | 20.heightBox, 43 | ]); 44 | } 45 | 46 | Widget buildPageView(FlPageViewController controller, Axis direction) => 47 | FlPageView( 48 | controller: controller, 49 | itemCount: itemCount, 50 | autoPlay: true, 51 | builder: (FlPageViewController pageController, int? itemCount) => 52 | PageView.builder( 53 | controller: pageController, 54 | scrollDirection: direction, 55 | itemCount: itemCount, 56 | itemBuilder: (_, int index) { 57 | final colorScheme = Theme.of(context).colorScheme; 58 | final child = Container( 59 | alignment: Alignment.center, 60 | color: index.isEven 61 | ? colorScheme.primary 62 | : colorScheme.primaryContainer, 63 | child: Text( 64 | 'index:${pageController.getIndex(index)}\nrealIndex: $index')); 65 | return Flex( 66 | direction: direction == Axis.horizontal 67 | ? Axis.vertical 68 | : Axis.horizontal, 69 | children: [ 70 | FlPageViewTransform( 71 | controller: pageController, 72 | scrollDirection: direction, 73 | index: index, 74 | style: FlPageViewTransformStyle.scale, 75 | child: child) 76 | .expanded, 77 | SizedBox(width: 5, height: 5), 78 | FlPageViewTransform( 79 | controller: pageController, 80 | scrollDirection: direction, 81 | style: FlPageViewTransformStyle.zoom, 82 | index: index, 83 | child: child) 84 | .expanded, 85 | ]); 86 | }), 87 | ); 88 | 89 | Widget buildIndicator(FlPageViewController controller) => ListenableBuilder( 90 | listenable: controller, 91 | builder: (_, __) { 92 | final position = controller.getPage() ?? 0; 93 | return Column(children: [ 94 | FlIndicator( 95 | space: 20, 96 | count: itemCount, 97 | index: position.floor(), 98 | style: FlIndicatorStyle.slide, 99 | position: position), 100 | const SizedBox(height: 10), 101 | FlIndicator( 102 | space: 20, 103 | count: itemCount, 104 | index: position.floor(), 105 | style: FlIndicatorStyle.color, 106 | position: position), 107 | const SizedBox(height: 10), 108 | FlIndicator( 109 | space: 20, 110 | count: itemCount, 111 | index: position.floor(), 112 | style: FlIndicatorStyle.none, 113 | position: position), 114 | const SizedBox(height: 10), 115 | FlIndicator( 116 | space: 20, 117 | count: itemCount, 118 | index: position.floor(), 119 | style: FlIndicatorStyle.scale, 120 | position: position), 121 | const SizedBox(height: 10), 122 | FlIndicator( 123 | space: 20, 124 | count: itemCount, 125 | index: position.floor(), 126 | style: FlIndicatorStyle.drop, 127 | position: position), 128 | const SizedBox(height: 10), 129 | FlIndicator( 130 | space: 20, 131 | count: itemCount, 132 | index: position.floor(), 133 | style: FlIndicatorStyle.warm, 134 | position: position), 135 | ]); 136 | }); 137 | } 138 | -------------------------------------------------------------------------------- /example/lib/module/progress_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/main.dart'; 2 | import 'package:fl_extended/fl_extended.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_waya/flutter_waya.dart'; 5 | 6 | class ProgressBarPage extends StatefulWidget { 7 | const ProgressBarPage({super.key}); 8 | 9 | @override 10 | State createState() => _ProgressBarPageState(); 11 | } 12 | 13 | class _ProgressBarPageState extends State 14 | with SingleTickerProviderStateMixin { 15 | late AnimationController animation; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | animation = AnimationController(duration: 6.seconds, vsync: this); 21 | animation.repeat(); 22 | } 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | return ExtendedScaffold(appBar: AppBarText('Progress'), children: [ 27 | const Partition('FlLinearProgress', marginTop: 0), 28 | FlLinearProgress( 29 | width: 300, 30 | height: 20, 31 | percent: 0.8, 32 | animation: true, 33 | mainAxisAlignment: MainAxisAlignment.center, 34 | onChanged: (value) { 35 | log('FlLinearProgress use animation:$value'); 36 | }, 37 | builder: (_, double percent) => Stack(children: [ 38 | Positioned( 39 | right: 300 - 300 * percent, 40 | top: 0, 41 | bottom: 0, 42 | child: 43 | BText(percent.toStringAsFixed(1), color: Colors.white)) 44 | ]), 45 | strokeCap: StrokeCap.round, 46 | progressColor: Colors.lightGreen, 47 | backgroundColor: Colors.black12), 48 | const Partition('FlLinearProgress.gradient'), 49 | FlLinearProgress.gradient( 50 | width: 300, 51 | height: 20, 52 | percent: 1, 53 | animation: true, 54 | repeat: true, 55 | linearGradient: const LinearGradient(colors: Colors.accents), 56 | mainAxisAlignment: MainAxisAlignment.center, 57 | duration: const Duration(seconds: 4), 58 | builder: (_, double percent) => Stack(children: [ 59 | Positioned( 60 | top: 0, 61 | bottom: 0, 62 | right: 300 - 300 * percent, 63 | child: 64 | BText(percent.toStringAsFixed(1), color: Colors.white)), 65 | ]), 66 | strokeCap: StrokeCap.round, 67 | backgroundColor: Colors.black12), 68 | const Partition('FlLinearProgress.gradient isClip:false'), 69 | FlLinearProgress.gradient( 70 | width: 300, 71 | height: 20, 72 | percent: 0.9, 73 | animation: true, 74 | repeat: true, 75 | isClip: false, 76 | linearGradient: const LinearGradient(colors: Colors.accents), 77 | mainAxisAlignment: MainAxisAlignment.center, 78 | duration: const Duration(seconds: 4), 79 | builder: (_, double percent) => Stack(children: [ 80 | Positioned( 81 | top: 0, 82 | bottom: 0, 83 | right: 300 - 300 * percent, 84 | child: 85 | BText(percent.toStringAsFixed(1), color: Colors.white)), 86 | ]), 87 | strokeCap: StrokeCap.round, 88 | backgroundColor: Colors.black12), 89 | const SizedBox(height: 20), 90 | const Partition('LiquidProgressIndicator'), 91 | AnimatedBuilder( 92 | animation: animation, 93 | builder: (_, __) { 94 | return Column( 95 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 96 | children: [ 97 | LiquidProgressIndicator.circular( 98 | borderWidth: 2, 99 | borderColor: Colors.greenAccent, 100 | value: animation.value) 101 | .setSize(const Size(120, 120)), 102 | const SizedBox(height: 20), 103 | LiquidProgressIndicator.linear( 104 | borderWidth: 2, 105 | borderColor: Colors.greenAccent, 106 | borderRadius: 10, 107 | value: animation.value) 108 | .setSize(const Size(double.infinity, 30)), 109 | Row( 110 | mainAxisAlignment: MainAxisAlignment.spaceAround, 111 | children: [ 112 | LiquidProgressIndicator.custom( 113 | value: animation.value, 114 | direction: Axis.vertical, 115 | shapePath: _buildBoatPath()), 116 | LiquidProgressIndicator.custom( 117 | value: animation.value, 118 | direction: Axis.horizontal, 119 | shapePath: _buildSpeechBubblePath()), 120 | LiquidProgressIndicator.custom( 121 | value: animation.value, 122 | direction: Axis.vertical, 123 | shapePath: _buildHeartPath()), 124 | ]).expanded, 125 | const SizedBox(height: 20), 126 | SizedBox( 127 | width: double.infinity, 128 | height: 100, 129 | child: FlAnimationWave( 130 | value: 0.6, 131 | color: context.theme.primaryColor, 132 | direction: Axis.vertical)) 133 | ]); 134 | }).expanded, 135 | ]); 136 | } 137 | 138 | Path _buildBoatPath() { 139 | return Path() 140 | ..moveTo(15, 120) 141 | ..lineTo(0, 85) 142 | ..lineTo(50, 85) 143 | ..lineTo(50, 0) 144 | ..lineTo(105, 80) 145 | ..lineTo(60, 80) 146 | ..lineTo(60, 85) 147 | ..lineTo(120, 85) 148 | ..lineTo(105, 120) 149 | ..close(); 150 | } 151 | 152 | Path _buildSpeechBubblePath() { 153 | return Path() 154 | ..moveTo(50, 0) 155 | ..quadraticBezierTo(0, 0, 0, 37.5) 156 | ..quadraticBezierTo(0, 75, 25, 75) 157 | ..quadraticBezierTo(25, 95, 5, 95) 158 | ..quadraticBezierTo(35, 95, 40, 75) 159 | ..quadraticBezierTo(100, 75, 100, 37.5) 160 | ..quadraticBezierTo(100, 0, 50, 0) 161 | ..close(); 162 | } 163 | 164 | Path _buildHeartPath() { 165 | return Path() 166 | ..moveTo(55, 15) 167 | ..cubicTo(55, 12, 50, 0, 30, 0) 168 | ..cubicTo(0, 0, 0, 37.5, 0, 37.5) 169 | ..cubicTo(0, 55, 20, 77, 55, 95) 170 | ..cubicTo(90, 77, 110, 55, 110, 37.5) 171 | ..cubicTo(110, 37.5, 110, 0, 80, 0) 172 | ..cubicTo(65, 0, 55, 12, 55, 15) 173 | ..close(); 174 | } 175 | 176 | @override 177 | void dispose() { 178 | animation.dispose(); 179 | super.dispose(); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /example/lib/module/state_components_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/main.dart'; 2 | import 'package:fl_extended/fl_extended.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_waya/flutter_waya.dart'; 6 | 7 | class StateComponentsPage extends StatelessWidget { 8 | const StateComponentsPage({super.key}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ExtendedScaffold( 13 | isScroll: true, 14 | appBar: AppBarText('State Components'), 15 | padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), 16 | children: [ 17 | const Partition('Checkbox 官方附加状态版本'), 18 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 19 | ChangedBuilder( 20 | value: true, 21 | onChanged: (bool? value) {}, 22 | builder: (bool? value, onChanged) => Checkbox( 23 | tristate: true, value: value, onChanged: onChanged)), 24 | ChangedBuilder( 25 | value: true, 26 | onWaitChanged: (bool? value) async { 27 | await 1.seconds.delayed(); 28 | return value; 29 | }, 30 | builder: (bool? value, onChanged) => Checkbox( 31 | shape: const CircleBorder(), 32 | tristate: true, 33 | value: value, 34 | onChanged: onChanged)), 35 | ]), 36 | const Partition('XSwitch'), 37 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 38 | ChangedBuilder( 39 | value: true, 40 | onChanged: (bool value) {}, 41 | builder: (bool value, onChanged) => XSwitch( 42 | value: value, 43 | activeColor: Colors.deepPurple, 44 | size: const Size(40, 21), 45 | radius: 12, 46 | onChanged: onChanged)), 47 | ChangedBuilder( 48 | value: true, 49 | onWaitChanged: (bool value) async { 50 | await 1.seconds.delayed(); 51 | return value; 52 | }, 53 | builder: (bool value, onChanged) => XSwitch( 54 | value: value, 55 | size: const Size(60, 28), 56 | activeColor: Colors.deepPurple, 57 | onChanged: onChanged)), 58 | ]), 59 | const Partition('Switch 官方附加状态版本'), 60 | Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ 61 | ChangedBuilder( 62 | value: true, 63 | onChanged: (bool value) {}, 64 | builder: (bool value, onChanged) => 65 | Switch(value: value, onChanged: onChanged)), 66 | ChangedBuilder( 67 | value: true, 68 | onChanged: (bool value) {}, 69 | builder: (bool value, onChanged) => 70 | CupertinoSwitch(value: value, onChanged: onChanged)), 71 | ChangedBuilder( 72 | value: true, 73 | onWaitChanged: (bool value) async { 74 | await 1.seconds.delayed(); 75 | return value; 76 | }, 77 | builder: (bool value, onChanged) => 78 | CupertinoSwitch(value: value, onChanged: onChanged)), 79 | ]), 80 | const SizedBox(height: 100), 81 | ]); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /example/lib/module/text_field_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:app/main.dart'; 2 | import 'package:fl_extended/fl_extended.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_waya/flutter_waya.dart'; 5 | 6 | class TextFieldPage extends StatefulWidget { 7 | const TextFieldPage({super.key}); 8 | 9 | @override 10 | State createState() => _TextFieldPageState(); 11 | } 12 | 13 | class _TextFieldPageState extends State { 14 | TextEditingController controller = TextEditingController(); 15 | TextEditingController controller1 = TextEditingController(); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return ExtendedScaffold( 20 | isScroll: true, 21 | appBar: AppBarText('TextField'), 22 | padding: const EdgeInsets.fromLTRB(20, 0, 20, 20), 23 | children: [ 24 | const Partition('DecoratorBox with TextField', marginTop: 0), 25 | DecoratorBox( 26 | suffixes: [ 27 | Icon(Icons.ac_unit).toDecoratorPendant(), 28 | ], 29 | prefixes: [ 30 | Icon(Icons.ac_unit).toDecoratorPendant(), 31 | ], 32 | decoration: (Widget child, _) => Container( 33 | decoration: BoxDecoration( 34 | border: BorderType.underline.toBorder(BorderSide( 35 | color: context.theme.primaryColor, width: 1)), 36 | borderRadius: BorderRadius.circular(4), 37 | color: context.theme.primaryColor.withValues(alpha: 0.2)), 38 | child: child), 39 | child: TextField( 40 | decoration: 41 | const InputDecoration(isDense: true).copyWithNoneBorder)), 42 | 10.heightBox, 43 | DecoratorBox( 44 | suffixes: [ 45 | Icon(Icons.ac_unit).toDecoratorPendant( 46 | positioned: DecoratorPendantPosition.inner), 47 | ], 48 | prefixes: [ 49 | Icon(Icons.ac_unit).toDecoratorPendant( 50 | positioned: DecoratorPendantPosition.inner), 51 | ], 52 | decoration: (Widget child, _) => Container( 53 | decoration: BoxDecoration( 54 | border: BorderType.outline.toBorder(BorderSide( 55 | color: context.theme.primaryColor, width: 1)), 56 | borderRadius: BorderRadius.circular(4), 57 | color: context.theme.primaryColor.withValues(alpha: 0.2), 58 | ), 59 | child: child), 60 | child: TextField( 61 | decoration: 62 | const InputDecoration(isDense: true).copyWithNoneBorder, 63 | )), 64 | const Partition('PINTextField'), 65 | PINTextField( 66 | controller: controller, 67 | focusedDecoration: focusedDecoration, 68 | decoration: pinDecoration, 69 | onTap: () { 70 | showToast('点击 PINTextField'); 71 | }, 72 | spaces: const [ 73 | null, 74 | Icon(Icons.ac_unit), 75 | Icon(Icons.ac_unit), 76 | Icon(Icons.ac_unit), 77 | ], 78 | onDone: (value) { 79 | log('PINTextField onDone:$value'); 80 | }, 81 | onChanged: (value) { 82 | log('PINTextField onChanged:$value'); 83 | }), 84 | const Partition('PINTextField builder'), 85 | PINTextField( 86 | controller: controller1, 87 | focusedDecoration: focusedDecoration, 88 | decoration: pinDecoration, 89 | onChanged: (value) { 90 | log('PINTextField builder onChanged :$value'); 91 | }, 92 | onDone: (value) { 93 | log('PINTextField builder onDone:$value'); 94 | }, 95 | spaces: const [ 96 | null, 97 | Icon(Icons.ac_unit), 98 | Icon(Icons.ac_unit), 99 | Icon(Icons.ac_unit), 100 | ], 101 | onTap: () { 102 | showToast('点击 PINTextField'); 103 | }, 104 | builder: (PINTextFieldBuilderConfig config) => TextField( 105 | controller: config.controller, 106 | decoration: config.decoration, 107 | autofocus: config.autofocus, 108 | obscureText: config.obscureText, 109 | maxLines: config.maxLines, 110 | minLines: config.minLines, 111 | keyboardType: config.keyboardType, 112 | style: config.style, 113 | onTap: config.onTap, 114 | showCursor: config.showCursor, 115 | enableInteractiveSelection: config.enableInteractiveSelection, 116 | contextMenuBuilder: config.contextMenuBuilder, 117 | inputFormatters: config.inputFormatters)), 118 | ]); 119 | } 120 | 121 | BoxDecoration get focusedDecoration => BoxDecoration( 122 | borderRadius: BorderRadius.circular(4), 123 | color: context.theme.primaryColor.withValues(alpha: 0.2), 124 | border: Border.all(width: 2, color: context.theme.primaryColor)); 125 | 126 | BoxDecoration get pinDecoration => BoxDecoration( 127 | borderRadius: BorderRadius.circular(4), 128 | border: Border.all(width: 2, color: Colors.grey)); 129 | } 130 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | **/DerivedData/ 9 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import flutter_curiosity 9 | import shared_preferences_foundation 10 | 11 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 12 | CuriosityPlugin.register(with: registry.registrar(forPlugin: "CuriosityPlugin")) 13 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 14 | } 15 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '13.0' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | 32 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 33 | target 'RunnerTests' do 34 | inherit! :search_paths 35 | end 36 | end 37 | 38 | post_install do |installer| 39 | installer.pods_project.targets.each do |target| 40 | flutter_additional_macos_build_settings(target) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 64 | 66 | 72 | 73 | 74 | 75 | 81 | 83 | 89 | 90 | 91 | 92 | 94 | 95 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @main 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | 10 | override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { 11 | return true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = Waya 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.waya.example 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2025 com.waya.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.assets.movies.read-write 8 | 9 | com.apple.security.assets.music.read-write 10 | 11 | com.apple.security.assets.pictures.read-write 12 | 13 | com.apple.security.cs.allow-jit 14 | 15 | com.apple.security.files.downloads.read-write 16 | 17 | com.apple.security.files.user-selected.read-write 18 | 19 | com.apple.security.network.client 20 | 21 | com.apple.security.network.server 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.assets.movies.read-write 8 | 9 | com.apple.security.assets.music.read-write 10 | 11 | com.apple.security.assets.pictures.read-write 12 | 13 | com.apple.security.files.downloads.read-write 14 | 15 | com.apple.security.files.user-selected.read-write 16 | 17 | com.apple.security.network.client 18 | 19 | com.apple.security.network.server 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /example/macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: app 2 | description: Demonstrates how to use the waya package. 3 | publish_to: 'none' 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | flutter: '>=3.29.0' 9 | 10 | dependencies: 11 | flutter_web_plugins: 12 | sdk: flutter 13 | flutter: 14 | sdk: flutter 15 | flutter_curiosity: ^6.7.0 16 | device_preview_minus: ^1.2.0 17 | fl_extended: ^1.7.3 18 | flutter_waya: 19 | path: ../ 20 | 21 | dev_dependencies: 22 | flutter_lints: ^5.0.0 23 | 24 | flutter: 25 | uses-material-design: true 26 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wayaer/flutter_waya/cb7cbf517dc8f6cf9891eb10f261353438622f0c/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Waya 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Waya", 3 | "short_name": "Waya", 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 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /format.sh: -------------------------------------------------------------------------------- 1 | dart format lib 2 | dart format example/lib 3 | -------------------------------------------------------------------------------- /lib/flutter_waya.dart: -------------------------------------------------------------------------------- 1 | library; 2 | 3 | /// animation 4 | export 'src/animation/bubble_button.dart'; 5 | export 'src/animation/elastic_builder.dart'; 6 | export 'src/animation/animation_counter.dart'; 7 | export 'src/animation/toggle_rotate.dart'; 8 | export 'src/animation/wave.dart'; 9 | export 'src/animation/auto_collapsing.dart'; 10 | 11 | /// const 12 | export 'src/const/styles.dart'; 13 | 14 | /// progress 15 | export 'src/progress/progress.dart'; 16 | 17 | /// page_view 18 | export 'src/page_view/indicator.dart'; 19 | export 'src/page_view/transform.dart'; 20 | export 'src/page_view/page_view.dart'; 21 | 22 | /// editable_text 23 | export 'src/editable_text/text_field.dart'; 24 | export 'src/editable_text/pin_text_field.dart'; 25 | export 'src/editable_text/decorator_box.dart'; 26 | 27 | /// src 28 | export 'src/wrapper.dart'; 29 | export 'src/counter.dart'; 30 | export 'src/animation/popup_menu.dart'; 31 | export 'src/dotted_line.dart'; 32 | export 'src/draggable_scrollbar.dart'; 33 | export 'src/screen_adaptation.dart'; 34 | export 'src/x_switch.dart'; 35 | export 'src/animation/expansion_tiles.dart'; 36 | export 'src/rating_stars.dart'; 37 | export 'src/flip_card.dart'; 38 | export 'src/list_entry.dart'; 39 | export 'src/event.dart'; 40 | export 'src/shimmery.dart'; 41 | export 'src/tab_bar.dart'; 42 | export 'src/system_ui_overlay_style.dart'; 43 | export 'src/keep_alive_wrapper.dart'; 44 | -------------------------------------------------------------------------------- /lib/src/animation/animation_counter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_waya/src/extended_state.dart'; 3 | 4 | typedef AnimationCounterBuilder = Widget Function(String value); 5 | 6 | enum CounterStyle { part, all } 7 | 8 | /// animation counter 9 | class AnimationCounter extends StatefulWidget { 10 | const AnimationCounter.down({ 11 | super.key, 12 | this.style = CounterStyle.part, 13 | this.duration = const Duration(milliseconds: 300), 14 | required this.value, 15 | required this.builder, 16 | }) : isDown = true; 17 | 18 | const AnimationCounter.up({ 19 | super.key, 20 | this.style = CounterStyle.part, 21 | this.duration = const Duration(milliseconds: 300), 22 | required this.value, 23 | required this.builder, 24 | }) : isDown = false; 25 | 26 | /// value 27 | final String value; 28 | 29 | /// animation duration to change 30 | final Duration duration; 31 | 32 | /// animation type to change 33 | final CounterStyle style; 34 | 35 | /// value builder 36 | final AnimationCounterBuilder builder; 37 | 38 | /// animation direction 39 | final bool isDown; 40 | 41 | @override 42 | State createState() => _AnimationCounterState(); 43 | } 44 | 45 | class _AnimationCounterState extends ExtendedState 46 | with SingleTickerProviderStateMixin { 47 | late Animation preSlideAnimation; 48 | late Animation slideAnimation; 49 | late String value; 50 | late String preValue; 51 | late AnimationController controller; 52 | 53 | @override 54 | void initState() { 55 | super.initState(); 56 | controller = 57 | AnimationController(duration: widget.duration, value: 1, vsync: this); 58 | value = widget.value; 59 | preValue = value; 60 | slideAnimation = controller 61 | .drive(Tween(begin: const Offset(0.0, -1.0), end: Offset.zero)); 62 | preSlideAnimation = controller 63 | .drive(Tween(begin: Offset.zero, end: const Offset(0.0, 1.0))); 64 | } 65 | 66 | @override 67 | void didUpdateWidget(AnimationCounter oldWidget) { 68 | super.didUpdateWidget(oldWidget); 69 | if (controller.duration != widget.duration) { 70 | controller.duration = widget.duration; 71 | } 72 | if (widget.value != value) { 73 | if (mounted) animation(widget.value); 74 | } 75 | } 76 | 77 | @override 78 | Widget build(BuildContext context) { 79 | int didIndex = 0; 80 | if (preValue.length == value.length) { 81 | for (; didIndex < value.length; didIndex++) { 82 | if (value[didIndex] != preValue[didIndex]) break; 83 | } 84 | } 85 | final bool allChange = preValue.length != value.length || didIndex == 0; 86 | return ClipRect( 87 | child: AnimatedBuilder( 88 | animation: controller, 89 | builder: (_, __) { 90 | if (value == preValue) { 91 | return builder(value); 92 | } else if (widget.style == CounterStyle.part && !allChange) { 93 | return Row(mainAxisSize: MainAxisSize.min, children: [ 94 | builder(value.substring(0, didIndex)), 95 | buildStack(value.substring(didIndex, value.length), 96 | preValue.substring(didIndex, preValue.length)) 97 | ]); 98 | } 99 | return buildStack(value, preValue); 100 | })); 101 | } 102 | 103 | Widget buildStack(String value, String preValue) => 104 | Stack(fit: StackFit.passthrough, alignment: Alignment.center, children: [ 105 | FractionalTranslation( 106 | translation: 107 | widget.isDown ? slideAnimation.value : -slideAnimation.value, 108 | child: builder(value)), 109 | if (controller.value < 1) 110 | FractionalTranslation( 111 | translation: widget.isDown 112 | ? preSlideAnimation.value 113 | : -preSlideAnimation.value, 114 | child: builder(preValue)) 115 | ]); 116 | 117 | Widget builder(String value) => widget.builder(value); 118 | 119 | void animation(String newValue) { 120 | preValue = value; 121 | value = newValue; 122 | if (mounted) { 123 | controller.reset(); 124 | controller.forward(); 125 | } 126 | } 127 | 128 | @override 129 | void dispose() { 130 | controller.stop(); 131 | controller.dispose(); 132 | super.dispose(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /lib/src/animation/auto_collapsing.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_waya/src/extended_state.dart'; 3 | 4 | /// Automatically collapse your own src as the scrollview scrolls 5 | /// 随scrollview的滚动自动折叠自己的组件 6 | class AutoCollapsingBuilder extends StatefulWidget { 7 | const AutoCollapsingBuilder({ 8 | super.key, 9 | required this.controller, 10 | required this.child, 11 | this.minSize = 0, 12 | this.maxSize = 10, 13 | this.damping = 1, 14 | this.direction = Axis.vertical, 15 | this.reverse = false, 16 | this.duration = const Duration(milliseconds: 300), 17 | }); 18 | 19 | /// 滚动组件 Controller 20 | final ScrollController controller; 21 | 22 | /// 最小尺寸 23 | final double minSize; 24 | 25 | /// 最大尺寸 26 | final double maxSize; 27 | 28 | /// 阻尼参数 29 | /// 滚动距离*[damping]=[child]折叠距离 30 | final double damping; 31 | 32 | /// 横向或垂直 33 | final Axis direction; 34 | 35 | /// 反转 36 | final bool reverse; 37 | 38 | /// 子组件 39 | final Widget child; 40 | 41 | /// 动画时长 42 | final Duration duration; 43 | 44 | @override 45 | State createState() => _AutoCollapsingBuilderState(); 46 | } 47 | 48 | class _AutoCollapsingBuilderState extends ExtendedState { 49 | double _size = 0.0; 50 | double _previousOffset = 0.0; 51 | 52 | @override 53 | void initState() { 54 | super.initState(); 55 | _size = widget.maxSize; 56 | } 57 | 58 | @override 59 | void didChangeDependencies() { 60 | super.didChangeDependencies(); 61 | widget.controller.addListener(_onScroll); 62 | } 63 | 64 | void _onScroll() { 65 | final offset = widget.controller.offset; 66 | final distance = (offset - _previousOffset).abs(); 67 | final minSize = widget.minSize; 68 | final maxSize = widget.maxSize; 69 | 70 | if ((!widget.reverse && offset > _previousOffset) || 71 | (widget.reverse && offset < _previousOffset)) { 72 | if (_size > minSize) { 73 | _size -= distance * widget.damping; 74 | if (_size < minSize) { 75 | _size = minSize; 76 | } 77 | if (mounted) setState(() {}); 78 | } 79 | } else if ((!widget.reverse && offset < _previousOffset) || 80 | (widget.reverse && offset > _previousOffset)) { 81 | if (_size < maxSize) { 82 | _size += distance * widget.damping; 83 | if (_size > maxSize) { 84 | _size = maxSize; 85 | } 86 | if (mounted) setState(() {}); 87 | } 88 | } 89 | _previousOffset = offset; 90 | } 91 | 92 | @override 93 | Widget build(BuildContext context) => AnimatedContainer( 94 | duration: widget.duration, 95 | width: widget.direction == Axis.horizontal ? _size : null, 96 | height: widget.direction == Axis.vertical ? _size : null, 97 | child: widget.child); 98 | 99 | @override 100 | void dispose() { 101 | widget.controller.removeListener(_onScroll); 102 | super.dispose(); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/src/animation/elastic_builder.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_waya/src/extended_state.dart'; 3 | 4 | typedef ElasticBuilderCallback = Widget Function(BuildContext context, 5 | Function elasticUp, Function elastic, Function elasticDown); 6 | 7 | /// 弹性按钮 8 | class ElasticBuilder extends StatefulWidget { 9 | const ElasticBuilder( 10 | {super.key, 11 | this.withOpacity = false, 12 | this.alignment = Alignment.center, 13 | this.scale = 0.9, 14 | required this.builder, 15 | this.duration = const Duration(milliseconds: 100)}) 16 | : assert(scale <= 1.0 && scale > 0); 17 | 18 | final bool withOpacity; 19 | 20 | /// Use this value to determine the alignment of the animation. 21 | final Alignment alignment; 22 | 23 | /// Choose a value between 0.0 and 1.0. 24 | final double scale; 25 | 26 | final Duration duration; 27 | final ElasticBuilderCallback builder; 28 | 29 | @override 30 | State createState() => _ElasticBuilderState(); 31 | } 32 | 33 | class _ElasticBuilderState extends ExtendedState 34 | with SingleTickerProviderStateMixin { 35 | late AnimationController controller; 36 | late Animation animation; 37 | 38 | @override 39 | void initState() { 40 | super.initState(); 41 | initController(); 42 | initAnimation(); 43 | } 44 | 45 | void initController() { 46 | controller = AnimationController( 47 | vsync: this, 48 | lowerBound: 0.0, 49 | upperBound: 1.0, 50 | reverseDuration: widget.duration, 51 | duration: widget.duration); 52 | controller.value = 1; 53 | } 54 | 55 | void initAnimation() { 56 | animation = Tween(begin: widget.scale, end: 1.0) 57 | .animate(CurvedAnimation(parent: controller, curve: Curves.elasticOut)); 58 | } 59 | 60 | void elasticDown() { 61 | controller.value = 0; 62 | } 63 | 64 | Future elastic([Duration? duration]) async { 65 | controller.value = 0; 66 | await Future.delayed((duration ?? widget.duration)); 67 | controller.value = 1; 68 | } 69 | 70 | void elasticUp() { 71 | controller.value = 1; 72 | } 73 | 74 | @override 75 | void didUpdateWidget(covariant ElasticBuilder oldWidget) { 76 | super.didUpdateWidget(oldWidget); 77 | if (widget.duration != oldWidget.duration || 78 | widget.scale != oldWidget.scale) { 79 | controller.dispose(); 80 | initController(); 81 | initAnimation(); 82 | } 83 | if (mounted) setState(() {}); 84 | } 85 | 86 | @override 87 | Widget build(BuildContext context) => AnimatedBuilder( 88 | animation: animation, 89 | child: widget.builder(context, elasticUp, elastic, elasticDown), 90 | builder: (BuildContext context, Widget? child) { 91 | final scale = Transform.scale( 92 | scale: animation.value, alignment: widget.alignment, child: child); 93 | return widget.withOpacity 94 | ? Opacity( 95 | opacity: animation.value.clamp(0.5, 1.0).toDouble(), 96 | child: scale) 97 | : scale; 98 | }); 99 | 100 | @override 101 | void dispose() { 102 | controller.dispose(); 103 | super.dispose(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/src/animation/expansion_tiles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_waya/src/extended_state.dart'; 3 | 4 | typedef ExpansionTilesBuilderListTile = Widget Function(BuildContext context, 5 | VoidCallback expand, bool isExpanded, Widget? rotation); 6 | 7 | typedef ExpansionTilesRotationIconBuilder = Widget Function(bool isExpanded); 8 | 9 | extension ExtensionExpansionTiles on Widget { 10 | ExpansionTilesRotationIconBuilder get toExpansionTilesRotationIconBuilder => 11 | (isExpanded) => this; 12 | } 13 | 14 | /// 展开收起 15 | class ExpansionTiles extends StatefulWidget { 16 | const ExpansionTiles({ 17 | super.key, 18 | required this.builder, 19 | this.children = const [], 20 | this.child, 21 | this.isExpanded = false, 22 | this.backgroundColor, 23 | this.onExpansionChanged, 24 | this.duration = const Duration(milliseconds: 200), 25 | this.curve = Curves.fastOutSlowIn, 26 | this.icon, 27 | }); 28 | 29 | /// 初始状态是否展开, 30 | final bool isExpanded; 31 | 32 | /// 构造器 33 | final ExpansionTilesBuilderListTile builder; 34 | 35 | /// 旋转的图标 36 | final ExpansionTilesRotationIconBuilder? icon; 37 | 38 | /// 展开或关闭监听 39 | final ValueChanged? onExpansionChanged; 40 | 41 | /// 子元素, 42 | final List children; 43 | 44 | /// 子元素, 45 | final Widget? child; 46 | 47 | /// 展开时的背景颜色, 48 | final Color? backgroundColor; 49 | 50 | /// 展开时长 51 | final Duration duration; 52 | 53 | /// 动画曲线 54 | final Curve curve; 55 | 56 | @override 57 | State createState() => _ExpansionTilesState(); 58 | } 59 | 60 | class _ExpansionTilesState extends ExtendedState 61 | with SingleTickerProviderStateMixin { 62 | late AnimationController controller; 63 | Animation? iconTurns; 64 | late Animation heightFactor; 65 | 66 | bool isExpanded = false; 67 | 68 | @override 69 | void initState() { 70 | super.initState(); 71 | controller = AnimationController(duration: widget.duration, vsync: this); 72 | initTween(); 73 | isExpanded = widget.isExpanded; 74 | if (isExpanded) controller.value = 1.0; 75 | } 76 | 77 | void initTween() { 78 | heightFactor = controller.drive(CurveTween(curve: widget.curve)); 79 | if (widget.icon != null) { 80 | iconTurns = controller.drive(Tween(begin: 0.0, end: 0.5) 81 | .chain(CurveTween(curve: widget.curve))); 82 | } 83 | } 84 | 85 | void handleExpanded() { 86 | isExpanded = !isExpanded; 87 | if (isExpanded) { 88 | controller.forward(); 89 | setState(() {}); 90 | } else { 91 | controller.reverse().then((void value) { 92 | setState(() {}); 93 | }); 94 | } 95 | widget.onExpansionChanged?.call(isExpanded); 96 | } 97 | 98 | @override 99 | Widget build(BuildContext context) { 100 | final bool closed = !isExpanded && controller.isDismissed; 101 | return AnimatedBuilder( 102 | animation: controller.view, 103 | builder: (_, Widget? child) { 104 | final icon = widget.icon?.call(isExpanded); 105 | final current = Column(mainAxisSize: MainAxisSize.min, children: [ 106 | widget.builder( 107 | context, 108 | handleExpanded, 109 | isExpanded, 110 | icon == null || iconTurns == null 111 | ? null 112 | : RotationTransition(turns: iconTurns!, child: icon)), 113 | ClipRect( 114 | child: Align(heightFactor: heightFactor.value, child: child)) 115 | ]); 116 | if (widget.backgroundColor == null || !isExpanded) return current; 117 | return ColoredBox(color: widget.backgroundColor!, child: current); 118 | }, 119 | child: 120 | closed ? null : widget.child ?? Column(children: widget.children)); 121 | } 122 | 123 | @override 124 | void didUpdateWidget(covariant ExpansionTiles oldWidget) { 125 | super.didUpdateWidget(oldWidget); 126 | controller.duration = widget.duration; 127 | initTween(); 128 | if (widget.isExpanded != isExpanded) { 129 | handleExpanded(); 130 | } 131 | } 132 | 133 | @override 134 | void dispose() { 135 | controller.dispose(); 136 | super.dispose(); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /lib/src/animation/popup_menu.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_waya/flutter_waya.dart'; 3 | 4 | typedef MultiPopupMenuButtonItemBuilder = Widget Function( 5 | BuildContext context, 6 | K key, 7 | V? value, 8 | VoidCallback onOpened, 9 | VoidCallback onCanceled, 10 | PopupMenuItemSelected onSelected); 11 | 12 | class MultiPopupMenuButtonItem { 13 | MultiPopupMenuButtonItem({ 14 | required this.key, 15 | this.value, 16 | required this.builder, 17 | }); 18 | 19 | final MultiPopupMenuButtonItemBuilder builder; 20 | 21 | /// key 22 | final K key; 23 | 24 | /// value 25 | V? value; 26 | } 27 | 28 | typedef MultiPopupMenuButtonValueCallbackKV = void Function( 29 | BuildContext context, K key, V value); 30 | 31 | /// 多个 [PopupMenuButton] 旋转 Builder 32 | class MultiPopupMenuButton extends StatelessWidget { 33 | const MultiPopupMenuButton({ 34 | super.key, 35 | required this.menus, 36 | this.onOpened, 37 | this.onSelected, 38 | this.onCanceled, 39 | }); 40 | 41 | /// 菜单 42 | final List> menus; 43 | 44 | /// onOpened 45 | final MultiPopupMenuButtonValueCallbackKV? onOpened; 46 | 47 | /// onCanceled 48 | final MultiPopupMenuButtonValueCallbackKV? onCanceled; 49 | 50 | /// onSelected 51 | final MultiPopupMenuButtonValueCallbackKV? onSelected; 52 | 53 | @override 54 | Widget build(BuildContext context) { 55 | final children = menus.map((entry) { 56 | return entry.builder(context, entry.key, entry.value, () { 57 | this.onOpened?.call(context, entry.key, entry.value); 58 | }, () { 59 | this.onCanceled?.call(context, entry.key, entry.value); 60 | }, (V? value) { 61 | entry.value = value; 62 | this.onSelected?.call(context, entry.key, value); 63 | }); 64 | }).toList(); 65 | return Row( 66 | mainAxisAlignment: MainAxisAlignment.spaceAround, children: children); 67 | } 68 | } 69 | 70 | typedef PopupMenuButtonBuilder = Widget Function(BuildContext context, 71 | Widget rotateIcon, VoidCallback onOpened, VoidCallback onClosed); 72 | 73 | /// [PopupMenuButton] 旋转 Builder 74 | class PopupMenuButtonRotateBuilder extends StatelessWidget { 75 | const PopupMenuButtonRotateBuilder({ 76 | super.key, 77 | required this.builder, 78 | required this.icon, 79 | this.isRotate = false, 80 | this.turns = 0.5, 81 | this.clockwise = true, 82 | this.closedNeedsBuild = true, 83 | this.openedNeedsBuild = false, 84 | this.animationDuration = const Duration(milliseconds: 200), 85 | this.curve = Curves.fastOutSlowIn, 86 | this.onChanged, 87 | }); 88 | 89 | /// 旋转的icon 90 | final ToggleRotateIconBuilder icon; 91 | 92 | /// 初始是否旋转 93 | final bool isRotate; 94 | 95 | /// builder PopupMenuButton 96 | final PopupMenuButtonBuilder builder; 97 | 98 | /// 旋转圈数 99 | /// 1 = 一圈 0.5 = 半圈 2 = 两圈 100 | final double turns; 101 | 102 | /// 动画时长 103 | final Duration animationDuration; 104 | 105 | /// 是否顺时针旋转 106 | final bool clockwise; 107 | 108 | /// 动画曲线 109 | final Curve curve; 110 | 111 | /// 关闭时是否需要重建 112 | final bool closedNeedsBuild; 113 | 114 | /// 打开时是否需要重建 115 | final bool openedNeedsBuild; 116 | 117 | /// 变化监听 118 | final ToggleRotateCallback? onChanged; 119 | 120 | @override 121 | Widget build(BuildContext context) => ToggleRotate( 122 | turns: turns, 123 | duration: animationDuration, 124 | clockwise: clockwise, 125 | curve: curve, 126 | isRotate: isRotate, 127 | icon: icon, 128 | onChanged: onChanged, 129 | builder: (Widget child, ToggleRotateHandleRotation rotate) => builder( 130 | context, 131 | child, 132 | () => rotate(needsBuild: openedNeedsBuild), 133 | () => rotate(needsBuild: closedNeedsBuild))); 134 | } 135 | -------------------------------------------------------------------------------- /lib/src/animation/toggle_rotate.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_waya/src/extended_state.dart'; 3 | 4 | typedef ToggleRotateHandleRotation = void Function( 5 | {bool reset, bool needsBuild}); 6 | 7 | typedef ToggleRotateBuilder = Widget Function( 8 | Widget child, ToggleRotateHandleRotation rotate); 9 | 10 | typedef ToggleRotateIconBuilder = Widget Function(bool isRotation); 11 | 12 | typedef ToggleRotateCallback = void Function(bool isRotation); 13 | 14 | extension ExtensionWidgetToggleRotate on Widget { 15 | ToggleRotateIconBuilder get toToggleRotateIconBuilder => (isRotation) => this; 16 | } 17 | 18 | /// 旋转组件 19 | class ToggleRotate extends StatefulWidget { 20 | const ToggleRotate( 21 | {super.key, 22 | required this.icon, 23 | this.builder, 24 | this.turns = 1, 25 | this.clockwise = true, 26 | this.duration = const Duration(milliseconds: 200), 27 | this.curve = Curves.fastOutSlowIn, 28 | this.isRotate = false, 29 | this.onChanged}); 30 | 31 | /// 旋转的icon 32 | final ToggleRotateIconBuilder icon; 33 | 34 | /// 自定义非旋转区域 35 | final ToggleRotateBuilder? builder; 36 | 37 | /// 变化监听 38 | final ToggleRotateCallback? onChanged; 39 | 40 | /// 是否旋转 41 | final bool isRotate; 42 | 43 | /// 旋转圈数 44 | /// 1 = 一圈 0.5 = 半圈 2 = 两圈 45 | final double turns; 46 | 47 | /// 动画时长 48 | final Duration duration; 49 | 50 | /// 是否顺时针旋转 51 | final bool clockwise; 52 | 53 | /// 动画曲线 54 | final Curve curve; 55 | 56 | @override 57 | State createState() => _ToggleRotateState(); 58 | } 59 | 60 | class _ToggleRotateState extends ExtendedState 61 | with SingleTickerProviderStateMixin { 62 | late AnimationController controller; 63 | late Animation rotate; 64 | 65 | @override 66 | void initState() { 67 | controller = AnimationController(duration: widget.duration, vsync: this); 68 | controller.addStatusListener(statusListener); 69 | initTween(); 70 | super.initState(); 71 | WidgetsBinding.instance.addPostFrameCallback((_) { 72 | if (widget.isRotate) controller.forward(); 73 | }); 74 | } 75 | 76 | void statusListener(AnimationStatus status) { 77 | if (status == AnimationStatus.completed) { 78 | widget.onChanged?.call(true); 79 | } else if (status == AnimationStatus.dismissed) { 80 | widget.onChanged?.call(false); 81 | } 82 | } 83 | 84 | void initTween() { 85 | double begin = 0; 86 | double end = widget.turns; 87 | if (!widget.clockwise) { 88 | begin = end; 89 | end = 0; 90 | } 91 | rotate = controller.drive(Tween(begin: begin, end: end) 92 | .chain(CurveTween(curve: widget.curve))); 93 | } 94 | 95 | void handleRotation({bool reset = false, bool needsBuild = false}) { 96 | if (reset) controller.reset(); 97 | if (controller.value == 0.0) { 98 | controller.forward(); 99 | } else { 100 | controller.reverse(); 101 | } 102 | 103 | if (needsBuild) setState(() {}); 104 | } 105 | 106 | @override 107 | void didUpdateWidget(covariant ToggleRotate oldWidget) { 108 | super.didUpdateWidget(oldWidget); 109 | controller.duration = widget.duration; 110 | initTween(); 111 | if (oldWidget.isRotate != widget.isRotate) { 112 | handleRotation(); 113 | } 114 | } 115 | 116 | @override 117 | Widget build(BuildContext context) { 118 | final current = AnimatedBuilder( 119 | animation: controller.view, 120 | builder: (_, Widget? child) => RotationTransition( 121 | turns: rotate, child: widget.icon(controller.value == 1.0))); 122 | if (widget.builder == null) return current; 123 | return widget.builder!(current, handleRotation); 124 | } 125 | 126 | @override 127 | void dispose() { 128 | controller.removeStatusListener(statusListener); 129 | controller.dispose(); 130 | super.dispose(); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/src/animation/wave.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:flutter_waya/src/extended_state.dart'; 4 | 5 | /// 波浪动画 6 | class FlAnimationWave extends StatefulWidget { 7 | const FlAnimationWave({ 8 | super.key, 9 | required this.value, 10 | required this.color, 11 | required this.direction, 12 | }); 13 | 14 | final double value; 15 | final Color color; 16 | final Axis direction; 17 | 18 | @override 19 | State createState() => _FlAnimationWaveState(); 20 | } 21 | 22 | class _FlAnimationWaveState extends ExtendedState 23 | with SingleTickerProviderStateMixin { 24 | late AnimationController controller; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | controller = 30 | AnimationController(vsync: this, duration: const Duration(seconds: 2)); 31 | controller.repeat(); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) => AnimatedBuilder( 36 | animation: CurvedAnimation(parent: controller, curve: Curves.easeInOut), 37 | child: Container(color: widget.color), 38 | builder: (BuildContext context, Widget? child) => ClipPath( 39 | clipper: _FlAnimationWaveClipper( 40 | animationValue: controller.value, 41 | value: widget.value, 42 | direction: widget.direction), 43 | child: child)); 44 | 45 | @override 46 | void dispose() { 47 | controller.dispose(); 48 | super.dispose(); 49 | } 50 | } 51 | 52 | class _FlAnimationWaveClipper extends CustomClipper { 53 | _FlAnimationWaveClipper( 54 | {required this.animationValue, 55 | required this.value, 56 | required this.direction}); 57 | 58 | final double animationValue; 59 | final double value; 60 | final Axis direction; 61 | 62 | @override 63 | Path getClip(Size size) { 64 | switch (direction) { 65 | case Axis.horizontal: 66 | return Path() 67 | ..addPolygon(_generateHorizontalFlAnimationWavePath(size), false) 68 | ..lineTo(0.0, size.height) 69 | ..lineTo(0.0, 0.0) 70 | ..close(); 71 | case Axis.vertical: 72 | return Path() 73 | ..addPolygon(_generateVerticalFlAnimationWavePath(size), false) 74 | ..lineTo(size.width, size.height) 75 | ..lineTo(0.0, size.height) 76 | ..close(); 77 | } 78 | } 79 | 80 | List _generateHorizontalFlAnimationWavePath(Size size) { 81 | final List waveList = []; 82 | for (int i = -2; i <= size.height.toInt() + 2; i++) { 83 | final double waveHeight = size.width / 20; 84 | final double dx = 85 | sin((animationValue * 360 - i) % 360 * (pi / 180)) * waveHeight + 86 | (size.width * value); 87 | waveList.add(Offset(dx, i.toDouble())); 88 | } 89 | return waveList; 90 | } 91 | 92 | List _generateVerticalFlAnimationWavePath(Size size) { 93 | final List waveList = []; 94 | for (int i = -2; i <= size.width.toInt() + 2; i++) { 95 | final double waveHeight = size.height / 20; 96 | final double dy = 97 | sin((animationValue * 360 - i) % 360 * (pi / 180)) * waveHeight + 98 | (size.height - (size.height * value)); 99 | waveList.add(Offset(i.toDouble(), dy)); 100 | } 101 | return waveList; 102 | } 103 | 104 | @override 105 | bool shouldReclip(_FlAnimationWaveClipper oldClipper) => 106 | animationValue != oldClipper.animationValue; 107 | } 108 | -------------------------------------------------------------------------------- /lib/src/const/styles.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | List getBoxShadow( 4 | {int num = 1, 5 | Color color = Colors.black12, 6 | double? radius, 7 | BlurStyle blurStyle = BlurStyle.normal, 8 | double blurRadius = 0.05, 9 | double spreadRadius = 0.05, 10 | Offset? offset}) => 11 | List.generate( 12 | num, 13 | (index) => BoxShadow( 14 | color: color, 15 | blurStyle: blurStyle, 16 | blurRadius: radius ?? blurRadius, 17 | spreadRadius: radius ?? spreadRadius, 18 | offset: offset ?? const Offset(0, 0))); 19 | -------------------------------------------------------------------------------- /lib/src/editable_text/pin_text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:flutter_waya/flutter_waya.dart'; 4 | import 'package:flutter_waya/src/extended_state.dart'; 5 | 6 | typedef PINTextFieldBuilder = Widget Function(PINTextFieldBuilderConfig config); 7 | 8 | typedef PINTextFieldTextBuilder = Widget? Function(String text); 9 | 10 | typedef PINTextFieldValueChanged = void Function(String text); 11 | 12 | /// PIN 输入框 13 | class PINTextField extends StatefulWidget { 14 | const PINTextField({ 15 | super.key, 16 | required this.controller, 17 | this.onTap, 18 | this.onChanged, 19 | this.onDone, 20 | this.autoFocus = true, 21 | this.obscureText = false, 22 | this.maxLength = 4, 23 | this.inputFormatter, 24 | this.decoration, 25 | this.focusedDecoration, 26 | this.textStyle, 27 | this.textBuilder, 28 | this.boxSize = const Size(40, 40), 29 | this.spaces = const [], 30 | this.inputLimitFormatter = TextInputLimitFormatter.text, 31 | this.builder, 32 | }); 33 | 34 | final TextEditingController controller; 35 | 36 | /// 输入框点击 37 | final GestureTapCallback? onTap; 38 | 39 | /// 输入内容监听 40 | final PINTextFieldValueChanged? onChanged; 41 | 42 | /// 输入完成后回调 43 | final PINTextFieldValueChanged? onDone; 44 | 45 | /// 是否自动获取焦点 46 | final bool autoFocus; 47 | 48 | /// 是否隐藏文本 49 | final bool obscureText; 50 | 51 | /// 输入框数量 52 | final int maxLength; 53 | 54 | /// 默认输入框装饰器 55 | final Decoration? decoration; 56 | 57 | /// 有文字后的输入框装饰器 58 | final Decoration? focusedDecoration; 59 | 60 | /// 内文字样式 61 | final TextStyle? textStyle; 62 | 63 | /// text builder 64 | final PINTextFieldTextBuilder? textBuilder; 65 | 66 | /// box 方框的大小 67 | final Size boxSize; 68 | 69 | /// box 中间添加 东西 70 | final List spaces; 71 | 72 | /// 限制文本输入类型 包括键盘类型 73 | final TextInputLimitFormatter inputLimitFormatter; 74 | 75 | /// 额外配置输入框内容限制 76 | final List? inputFormatter; 77 | 78 | /// 构建 [TextField] 79 | final PINTextFieldBuilder? builder; 80 | 81 | @override 82 | State createState() => _PINTextFieldState(); 83 | } 84 | 85 | class _PINTextFieldState extends ExtendedState { 86 | String lastText = ''; 87 | 88 | @override 89 | void initState() { 90 | super.initState(); 91 | widget.controller.addListener(listener); 92 | } 93 | 94 | void listener() { 95 | final text = widget.controller.text; 96 | if (text != lastText) { 97 | widget.onChanged?.call(widget.controller.text); 98 | } 99 | lastText = text; 100 | if (text.length == widget.maxLength) { 101 | widget.onDone?.call(widget.controller.text); 102 | } 103 | } 104 | 105 | @override 106 | void didUpdateWidget(covariant PINTextField oldWidget) { 107 | super.didUpdateWidget(oldWidget); 108 | if (oldWidget.controller != widget.controller) { 109 | oldWidget.controller.removeListener(listener); 110 | widget.controller.addListener(listener); 111 | } 112 | } 113 | 114 | @override 115 | Widget build(BuildContext context) { 116 | return SizedBox( 117 | height: widget.boxSize.height, 118 | child: Stack(children: [ 119 | SizedBox(height: widget.boxSize.height, child: pinTextInput), 120 | AnimatedBuilder( 121 | animation: widget.controller, 122 | builder: (BuildContext context, Widget? child) => 123 | buildRow(widget.controller.text)), 124 | ])); 125 | } 126 | 127 | Widget buildRow(String text) { 128 | final List box = []; 129 | List texts = text.trim().split(''); 130 | bool hasFocus = false; 131 | for (int i = 0; i < widget.maxLength; i++) { 132 | String text = ''; 133 | if (i >= texts.length) { 134 | text = ''; 135 | } else { 136 | text = texts[i]; 137 | } 138 | if (text.isEmpty) hasFocus = true; 139 | if (text.trim().isNotEmpty && widget.obscureText) { 140 | text = '*'; 141 | } 142 | box.add(Container( 143 | height: widget.boxSize.height, 144 | width: widget.boxSize.width, 145 | decoration: hasFocus ? widget.decoration : widget.focusedDecoration, 146 | alignment: Alignment.center, 147 | child: widget.textBuilder?.call(text) ?? 148 | Text(text, style: widget.textStyle))); 149 | } 150 | final List children = []; 151 | final spaces = widget.spaces; 152 | if (spaces.isNotEmpty) { 153 | List.generate((box.length + 1), (int index) { 154 | if (index < spaces.length) { 155 | final Widget? space = spaces[index]; 156 | if (space != null) children.add(space); 157 | } 158 | if (index < box.length) children.add(box[index]); 159 | }); 160 | } 161 | return IgnorePointer( 162 | child: Row( 163 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 164 | children: spaces.isNotEmpty ? children : box)); 165 | } 166 | 167 | Widget get pinTextInput { 168 | final inputDecoration = 169 | const InputDecoration(contentPadding: EdgeInsets.zero, isDense: true) 170 | .copyWithNoneBorder; 171 | final config = PINTextFieldBuilderConfig( 172 | controller: widget.controller, 173 | decoration: inputDecoration, 174 | autofocus: widget.autoFocus, 175 | keyboardType: widget.inputLimitFormatter.toKeyboardType(), 176 | style: const TextStyle(color: Colors.transparent), 177 | onTap: widget.onTap, 178 | inputFormatters: widget.inputLimitFormatter.toTextInputFormatter() 179 | ..addAll([ 180 | LengthLimitingTextInputFormatter(widget.maxLength), 181 | if (widget.inputFormatter != null) ...widget.inputFormatter!, 182 | ]), 183 | contextMenuBuilder: 184 | (BuildContext context, EditableTextState editableTextState) => 185 | const SizedBox()); 186 | return widget.builder?.call(config) ?? 187 | TextField( 188 | controller: config.controller, 189 | decoration: config.decoration, 190 | autofocus: config.autofocus, 191 | obscureText: config.obscureText, 192 | maxLines: config.maxLines, 193 | minLines: config.minLines, 194 | keyboardType: config.keyboardType, 195 | style: config.style, 196 | onTap: config.onTap, 197 | showCursor: config.showCursor, 198 | enableInteractiveSelection: config.enableInteractiveSelection, 199 | contextMenuBuilder: config.contextMenuBuilder, 200 | inputFormatters: config.inputFormatters); 201 | } 202 | 203 | @override 204 | void dispose() { 205 | widget.controller.removeListener(listener); 206 | super.dispose(); 207 | } 208 | } 209 | 210 | class PINTextFieldBuilderConfig { 211 | const PINTextFieldBuilderConfig({ 212 | required this.decoration, 213 | required this.style, 214 | required this.inputFormatters, 215 | required this.keyboardType, 216 | required this.contextMenuBuilder, 217 | required this.controller, 218 | this.enableInteractiveSelection = false, 219 | this.autofocus = true, 220 | this.maxLines = 1, 221 | this.minLines = 1, 222 | this.showCursor = false, 223 | this.obscureText = false, 224 | this.onTap, 225 | }); 226 | 227 | /// [TextField] 装饰器 228 | final InputDecoration decoration; 229 | 230 | /// [TextField] 自动获取焦点 231 | final bool autofocus; 232 | 233 | /// [TextField] 自动获取焦点 234 | final bool obscureText; 235 | 236 | /// [TextField] 最大行数 默认 1 237 | final int maxLines; 238 | 239 | /// [TextField] 最小行数 默认 1 240 | final int minLines; 241 | 242 | /// [TextField] 输入文字样式 243 | final TextStyle style; 244 | 245 | /// [TextField] 是否显示光标 默认不显示 246 | final bool showCursor; 247 | 248 | /// [TextField] 输入文本格式显示 249 | final List inputFormatters; 250 | 251 | /// [TextField] 键盘弹出类型 252 | final TextInputType keyboardType; 253 | 254 | /// [TextField] controller 255 | final TextEditingController controller; 256 | 257 | /// [TextField] 是否启用选择器 258 | final bool enableInteractiveSelection; 259 | 260 | /// [TextField] 选择器ToolBar 261 | final EditableTextContextMenuBuilder contextMenuBuilder; 262 | 263 | /// 输入框点击 264 | final GestureTapCallback? onTap; 265 | } 266 | -------------------------------------------------------------------------------- /lib/src/editable_text/text_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | extension ExtensionInputDecoration on InputDecoration { 5 | InputDecoration get copyWithNoneBorder => copyWith( 6 | border: InputBorder.none, 7 | enabledBorder: InputBorder.none, 8 | focusedBorder: InputBorder.none, 9 | disabledBorder: InputBorder.none, 10 | errorBorder: InputBorder.none, 11 | focusedErrorBorder: InputBorder.none); 12 | } 13 | 14 | enum BorderType { 15 | /// outline 16 | outline, 17 | 18 | /// underline 19 | underline, 20 | 21 | /// none 22 | none, 23 | ; 24 | 25 | /// BorderType to Border 26 | Border? toBorder([BorderSide? borderSide]) { 27 | if (borderSide == null) return null; 28 | switch (this) { 29 | case BorderType.outline: 30 | return Border.fromBorderSide(borderSide); 31 | case BorderType.underline: 32 | return Border(bottom: borderSide); 33 | case BorderType.none: 34 | return null; 35 | } 36 | } 37 | 38 | /// BorderType to InputBorder 39 | InputBorder toInputBorder({ 40 | BorderSide borderSide = const BorderSide(), 41 | BorderRadius borderRadius = const BorderRadius.all(Radius.circular(4.0)), 42 | double gapPadding = 4.0, 43 | }) { 44 | switch (this) { 45 | case BorderType.outline: 46 | return OutlineInputBorder( 47 | gapPadding: gapPadding, 48 | borderRadius: borderRadius, 49 | borderSide: borderSide); 50 | case BorderType.underline: 51 | borderRadius = const BorderRadius.only( 52 | topLeft: Radius.circular(4.0), topRight: Radius.circular(4.0)); 53 | return UnderlineInputBorder( 54 | borderRadius: borderRadius, borderSide: borderSide); 55 | case BorderType.none: 56 | return InputBorder.none; 57 | } 58 | } 59 | } 60 | 61 | enum TextInputLimitFormatter { 62 | /// 字母和数字 63 | lettersNumbers('[a-zA-Z0-9]'), 64 | 65 | /// 密码 字母和数字和. 66 | password('[a-zA-Z0-9.]'), 67 | 68 | /// 整数 69 | number('[0-9]'), 70 | 71 | /// 文本 72 | text(''), 73 | 74 | /// 小数 75 | decimal('[0-9.]'), 76 | 77 | /// 字母 78 | letter('[a-zA-Z]'), 79 | 80 | /// 中文 81 | chinese('[\u4e00-\u9fa5]'), 82 | 83 | /// 邮箱 84 | email('[a-zA-Z0-9.@]'), 85 | 86 | /// 电话号码 87 | phone('[0-9-+]'), 88 | 89 | /// 身份证 90 | idCard('[0-9Xx]'), 91 | 92 | /// 正数 93 | positive('[+0-9.]'), 94 | 95 | /// 负数 96 | negative('[-0-9.]'), 97 | ; 98 | 99 | const TextInputLimitFormatter(this.regExp); 100 | 101 | final String regExp; 102 | 103 | /// TextInputLimitFormatter 转换为 `List` 104 | List toTextInputFormatter() { 105 | if (this == TextInputLimitFormatter.text) return []; 106 | final RegExp regExp = RegExp(this.regExp); 107 | return [FilteringTextInputFormatter.allow(regExp)]; 108 | } 109 | 110 | /// TextInputLimitFormatter 转换为 TextInputType 111 | TextInputType toKeyboardType() { 112 | switch (this) { 113 | case TextInputLimitFormatter.lettersNumbers: 114 | return TextInputType.name; 115 | case TextInputLimitFormatter.password: 116 | return TextInputType.visiblePassword; 117 | case TextInputLimitFormatter.number: 118 | return TextInputType.number; 119 | case TextInputLimitFormatter.text: 120 | return TextInputType.text; 121 | case TextInputLimitFormatter.decimal: 122 | return const TextInputType.numberWithOptions(decimal: true); 123 | case TextInputLimitFormatter.letter: 124 | return TextInputType.name; 125 | case TextInputLimitFormatter.chinese: 126 | return TextInputType.text; 127 | case TextInputLimitFormatter.email: 128 | return TextInputType.emailAddress; 129 | case TextInputLimitFormatter.phone: 130 | return TextInputType.phone; 131 | case TextInputLimitFormatter.idCard: 132 | return TextInputType.name; 133 | case TextInputLimitFormatter.positive: 134 | return const TextInputType.numberWithOptions( 135 | decimal: true, signed: true); 136 | case TextInputLimitFormatter.negative: 137 | return const TextInputType.numberWithOptions( 138 | decimal: true, signed: true); 139 | } 140 | } 141 | } 142 | 143 | /// 数字输入的精确控制 144 | class NumberLimitFormatter extends TextInputFormatter { 145 | NumberLimitFormatter(this.numberLength, this.decimalLength); 146 | 147 | final int decimalLength; 148 | final int numberLength; 149 | 150 | RegExp exp = RegExp(TextInputLimitFormatter.decimal.regExp); 151 | 152 | @override 153 | TextEditingValue formatEditUpdate( 154 | TextEditingValue oldValue, TextEditingValue newValue) { 155 | const String pointer = '.'; 156 | 157 | /// 输入完全删除 158 | if (newValue.text.isEmpty) return const TextEditingValue(); 159 | 160 | /// 只允许输入数字和小数点 161 | if (!exp.hasMatch(newValue.text)) return oldValue; 162 | 163 | /// 包含小数点的情况 164 | if (newValue.text.contains(pointer)) { 165 | /// 精度为0,即不含小数 166 | if (decimalLength == 0) return oldValue; 167 | 168 | /// 包含多个小数 169 | if (newValue.text.indexOf(pointer) != 170 | newValue.text.lastIndexOf(pointer)) { 171 | return oldValue; 172 | } 173 | 174 | final String input = newValue.text; 175 | final int index = input.indexOf(pointer); 176 | 177 | /// 小数点前位数 178 | final int lengthBeforePointer = input.substring(0, index).length; 179 | 180 | /// 整数部分大于约定长度 181 | if (lengthBeforePointer > numberLength) return oldValue; 182 | 183 | /// 小数点后位数 184 | final int lengthAfterPointer = 185 | input.substring(index, input.length).length - 1; 186 | 187 | /// 小数位大于精度 188 | if (lengthAfterPointer > decimalLength) return oldValue; 189 | } else if ( 190 | 191 | /// 以点开头 192 | newValue.text.startsWith(pointer) || 193 | 194 | /// 如果第1位为0,并且长度大于1,排除00,01-09所有非法输入 195 | (newValue.text.startsWith('0') && newValue.text.length > 1) || 196 | 197 | /// 如果整数长度超过约定长度 198 | newValue.text.length > numberLength) { 199 | return oldValue; 200 | } 201 | return newValue; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /lib/src/event.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | class Event { 4 | Event({bool sync = false}) 5 | : _streamController = StreamController.broadcast(sync: sync); 6 | 7 | /// `EventBus.customController(StreamController controller) : _streamController = controller;` 8 | StreamController get streamController => _streamController; 9 | 10 | Stream on() => T == dynamic 11 | ? streamController.stream 12 | : streamController.stream.where((dynamic event) => event is T).cast(); 13 | 14 | final StreamController _streamController; 15 | 16 | void send(T event) => _streamController.add(event); 17 | 18 | void close() => _streamController.close(); 19 | 20 | void listen(void Function(dynamic event) onData) => 21 | _streamController.stream.listen(onData); 22 | 23 | void error(Object error) => _streamController.addError(error); 24 | 25 | void stream(Stream stream) => _streamController.addStream(stream); 26 | } 27 | 28 | class EventFactory { 29 | factory EventFactory() => _getInstance(); 30 | 31 | EventFactory._internal() { 32 | event = Event(); 33 | } 34 | 35 | static EventFactory? get instance => _getInstance(); 36 | 37 | static EventFactory? _instance; 38 | 39 | late Event event; 40 | 41 | static EventFactory _getInstance() => _instance ??= EventFactory._internal(); 42 | } 43 | 44 | void sendEvent(dynamic message) => EventFactory.instance!.event.send(message); 45 | 46 | void eventDestroy() => EventFactory.instance!.event.close(); 47 | 48 | void eventListen(void Function(dynamic event) onData) => 49 | EventFactory.instance!.event.listen(onData); 50 | 51 | /// 订阅者回调签名 52 | typedef EventCallback = void Function(dynamic data); 53 | 54 | class EventBus { 55 | factory EventBus() => _singleton ??= EventBus._(); 56 | 57 | EventBus._(); 58 | 59 | static EventBus? _singleton; 60 | 61 | /// 保存事件订阅者队列,key:事件名(id),value: 对应事件的订阅者队列 62 | final Map?> _map = 63 | >{}; 64 | 65 | /// 添加订阅者 66 | void add(String eventName, EventCallback eventCallback) { 67 | _map[eventName] ??= []; 68 | _map[eventName]!.add(eventCallback); 69 | } 70 | 71 | /// 移除订阅者 72 | void remove(String eventName, [EventCallback? eventCallback]) { 73 | final List? list = _map[eventName]; 74 | if (list == null) return; 75 | if (eventCallback == null) { 76 | _map.remove(eventName); 77 | } else { 78 | list.remove(eventCallback); 79 | } 80 | } 81 | 82 | /// 触发事件,事件触发后该事件所有订阅者会被调用 83 | void emit(String eventName, [dynamic data]) { 84 | final List? list = _map[eventName]; 85 | if (list == null) return; 86 | final int len = list.length - 1; 87 | 88 | /// 反向遍历,防止订阅者在回调中移除自身带来的下标错位 89 | for (int i = len; i > -1; --i) { 90 | list[i](data); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/src/extended_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class ExtendedState extends State { 4 | @override 5 | void setState(VoidCallback fn) { 6 | if (!mounted) return; 7 | super.setState(fn); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/flip_card.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:math'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_waya/src/extended_state.dart'; 5 | 6 | enum FlipCardState { 7 | front, 8 | back; 9 | 10 | FlipCardState get toggle { 11 | if (this == FlipCardState.front) { 12 | return FlipCardState.back; 13 | } else { 14 | return FlipCardState.front; 15 | } 16 | } 17 | 18 | bool get isFront => this == FlipCardState.front; 19 | 20 | bool get isBack => this == FlipCardState.back; 21 | } 22 | 23 | typedef FlipCardOnFlipCallback = void Function(FlipCardState state); 24 | 25 | class FlipCardController { 26 | _FlipCardState? _flipCardState; 27 | 28 | FlipCardState? get flipCardState => _flipCardState?.flipCardState; 29 | 30 | void toggle() { 31 | _flipCardState?.toggle(); 32 | } 33 | 34 | Future animateToggle() async { 35 | await _flipCardState?.animateToggle(); 36 | } 37 | 38 | Future skew(double amount, 39 | {Duration? duration, Curve curve = Curves.linear}) async { 40 | await _flipCardState?.skew(amount, duration: duration, curve: curve); 41 | } 42 | 43 | Future hint( 44 | {Duration duration = const Duration(milliseconds: 150), 45 | Duration? total}) async { 46 | await _flipCardState?.hint(duration: duration, total: total); 47 | } 48 | } 49 | 50 | /// 翻转组件 51 | class FlipCard extends StatefulWidget { 52 | const FlipCard({ 53 | super.key, 54 | required this.front, 55 | required this.back, 56 | this.duration = const Duration(milliseconds: 250), 57 | this.onFlip, 58 | this.onFlipDone, 59 | this.direction = Axis.horizontal, 60 | this.touchFlip = true, 61 | this.initial = FlipCardState.front, 62 | this.alignment = Alignment.center, 63 | this.fill, 64 | this.controller, 65 | }); 66 | 67 | /// 初始正面 68 | final FlipCardState initial; 69 | 70 | /// 正面 71 | final Widget front; 72 | 73 | /// 反面 74 | final Widget back; 75 | 76 | /// 翻转动画时间 77 | final Duration duration; 78 | 79 | /// 翻转水平或垂直 80 | final Axis direction; 81 | 82 | /// 点击翻转 83 | final bool touchFlip; 84 | 85 | /// Fill 86 | final FlipCardState? fill; 87 | 88 | /// 开始翻转 89 | final FlipCardOnFlipCallback? onFlip; 90 | 91 | /// 翻转完成 92 | final FlipCardOnFlipCallback? onFlipDone; 93 | 94 | /// alignment 95 | final Alignment alignment; 96 | 97 | /// controller 98 | final FlipCardController? controller; 99 | 100 | @override 101 | State createState() => _FlipCardState(); 102 | } 103 | 104 | class _FlipCardState extends ExtendedState 105 | with SingleTickerProviderStateMixin { 106 | late AnimationController animationController; 107 | late Animation frontRotation; 108 | late Animation backRotation; 109 | 110 | FlipCardState flipCardState = FlipCardState.front; 111 | 112 | FlipCardController? controller; 113 | 114 | @override 115 | void initState() { 116 | controller = widget.controller; 117 | flipCardState = widget.initial; 118 | super.initState(); 119 | controller?._flipCardState = this; 120 | initController(); 121 | } 122 | 123 | void initController() { 124 | animationController = AnimationController( 125 | value: flipCardState.index.toDouble(), 126 | duration: widget.duration, 127 | vsync: this); 128 | frontRotation = TweenSequence([ 129 | TweenSequenceItem( 130 | tween: Tween(begin: 0.0, end: pi / 2) 131 | .chain(CurveTween(curve: Curves.easeIn)), 132 | weight: 50.0), 133 | TweenSequenceItem( 134 | tween: ConstantTween(pi / 2), weight: 50.0) 135 | ]).animate(animationController); 136 | backRotation = TweenSequence([ 137 | TweenSequenceItem( 138 | tween: ConstantTween(pi / 2), weight: 50.0), 139 | TweenSequenceItem( 140 | tween: Tween(begin: -pi / 2, end: 0.0) 141 | .chain(CurveTween(curve: Curves.easeOut)), 142 | weight: 50.0) 143 | ]).animate(animationController); 144 | } 145 | 146 | @override 147 | void didUpdateWidget(FlipCard oldWidget) { 148 | super.didUpdateWidget(oldWidget); 149 | animationController.duration = widget.duration; 150 | controller = widget.controller; 151 | controller?._flipCardState = this; 152 | } 153 | 154 | Future animateToggle() async { 155 | if (!mounted) return; 156 | widget.onFlip?.call(flipCardState); 157 | animationController.duration = widget.duration; 158 | final animation = flipCardState.isFront 159 | ? animationController.forward(from: 0) 160 | : animationController.reverse(from: 1); 161 | await animation.whenComplete(() { 162 | flipCardState = flipCardState.toggle; 163 | widget.onFlipDone?.call(flipCardState); 164 | }); 165 | } 166 | 167 | void toggle() { 168 | widget.onFlip?.call(flipCardState); 169 | flipCardState = flipCardState.toggle; 170 | animationController.value = flipCardState.index.toDouble(); 171 | widget.onFlipDone?.call(flipCardState); 172 | } 173 | 174 | @override 175 | Widget build(BuildContext context) { 176 | final child = Stack( 177 | alignment: widget.alignment, 178 | fit: StackFit.passthrough, 179 | children: [ 180 | buildContent(true, widget.fill == FlipCardState.front), 181 | buildContent(false, widget.fill == FlipCardState.back), 182 | ]); 183 | if (widget.touchFlip) { 184 | return GestureDetector( 185 | behavior: HitTestBehavior.translucent, 186 | onTap: animateToggle, 187 | child: child); 188 | } 189 | return child; 190 | } 191 | 192 | Widget buildContent(bool front, bool isFill) { 193 | final card = IgnorePointer( 194 | ignoring: front ? flipCardState.isBack : flipCardState.isFront, 195 | child: animationCard(front ? widget.front : widget.back, 196 | front ? frontRotation : backRotation)); 197 | if (isFill) return Positioned.fill(child: card); 198 | return card; 199 | } 200 | 201 | Widget animationCard(Widget child, Animation animation) => 202 | AnimatedBuilder( 203 | animation: animation, 204 | builder: (_, Widget? child) { 205 | var transform = Matrix4.identity(); 206 | transform.setEntry(3, 2, 0.001); 207 | if (widget.direction == Axis.vertical) { 208 | transform.rotateX(animation.value); 209 | } else { 210 | transform.rotateY(animation.value); 211 | } 212 | return Transform( 213 | transform: transform, 214 | filterQuality: FilterQuality.none, 215 | alignment: FractionalOffset.center, 216 | child: child); 217 | }, 218 | child: child); 219 | 220 | @override 221 | void dispose() { 222 | animationController.dispose(); 223 | super.dispose(); 224 | } 225 | 226 | Future skew(double amount, 227 | {Duration? duration, Curve curve = Curves.linear}) async { 228 | assert(0 <= amount && amount <= 1); 229 | final target = flipCardState.isFront ? amount : 1 - amount; 230 | await animationController 231 | .animateTo(target, duration: duration, curve: curve) 232 | .asStream() 233 | .first; 234 | } 235 | 236 | Future hint( 237 | {Duration duration = const Duration(milliseconds: 150), 238 | Duration? total}) async { 239 | if (animationController.isAnimating) return; 240 | final durationTotal = total ?? animationController.duration; 241 | final completer = Completer(); 242 | Duration? original = animationController.duration; 243 | animationController.duration = durationTotal; 244 | flipCardState.isFront 245 | ? animationController.forward() 246 | : animationController.reverse(); 247 | Timer(duration, () { 248 | (flipCardState.isFront 249 | ? animationController.reverse() 250 | : animationController.forward()) 251 | .whenComplete(() { 252 | completer.complete(); 253 | }); 254 | animationController.duration = original; 255 | }); 256 | await completer.future; 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /lib/src/keep_alive_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_waya/src/extended_state.dart'; 3 | 4 | abstract class AutomaticKeepAliveClientMixinState 5 | extends ExtendedState with AutomaticKeepAliveClientMixin { 6 | @override 7 | bool get wantKeepAlive => true; 8 | } 9 | 10 | class AutomaticKeepAliveClientWrapper extends StatefulWidget { 11 | const AutomaticKeepAliveClientWrapper({super.key, required this.child}); 12 | 13 | final Widget child; 14 | 15 | @override 16 | State createState() => 17 | _AutomaticKeepAliveClientWrapperState(); 18 | } 19 | 20 | class _AutomaticKeepAliveClientWrapperState 21 | extends AutomaticKeepAliveClientMixinState< 22 | AutomaticKeepAliveClientWrapper> { 23 | @override 24 | Widget build(BuildContext context) { 25 | super.build(context); 26 | return widget.child; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/src/list_entry.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ListEntry extends StatelessWidget { 4 | const ListEntry( 5 | {super.key, 6 | this.isThreeLine = false, 7 | this.enabled = true, 8 | this.dense = true, 9 | this.arrow = false, 10 | this.titleText = '', 11 | this.arrowSize = 15, 12 | this.onTap, 13 | this.heroTag, 14 | this.onDoubleTap, 15 | this.onLongPress, 16 | this.title, 17 | this.height, 18 | this.padding, 19 | this.margin, 20 | this.decoration, 21 | this.child, 22 | this.color, 23 | this.titleStyle, 24 | this.underlineColor, 25 | this.leading, 26 | this.subtitle, 27 | this.contentPadding, 28 | this.selected, 29 | this.prefix, 30 | this.arrowColor, 31 | this.arrowIcon}); 32 | 33 | /// 单击事件 34 | final GestureTapCallback? onTap; 35 | 36 | /// 双击事件 37 | final GestureTapCallback? onDoubleTap; 38 | 39 | /// 长按事件 40 | final GestureLongPressCallback? onLongPress; 41 | 42 | /// 显示三行 43 | final bool isThreeLine; 44 | 45 | /// 是否默认3行高度,subtitle不为空时才能使用 46 | final bool? selected; 47 | 48 | /// 设置为true后 高度变小 默认为true 49 | final bool dense; 50 | 51 | /// 内边距 52 | final EdgeInsetsGeometry? contentPadding; 53 | 54 | /// 左侧widget 55 | final Widget? leading; 56 | 57 | /// 副标题 58 | final Widget? subtitle; 59 | 60 | /// 右侧widget 61 | final Widget? child; 62 | 63 | /// 右边是否有箭头 64 | final bool arrow; 65 | final Widget? arrowIcon; 66 | final double arrowSize; 67 | final Color? arrowColor; 68 | 69 | /// 中间内容 70 | final Widget? title; 71 | final String titleText; 72 | final TextStyle? titleStyle; 73 | final String? heroTag; 74 | 75 | /// 高 76 | final double? height; 77 | 78 | /// 前缀 79 | final Widget? prefix; 80 | 81 | final EdgeInsetsGeometry? padding; 82 | final EdgeInsetsGeometry? margin; 83 | final Color? underlineColor; 84 | final Color? color; 85 | 86 | /// 是否可点击 87 | final bool enabled; 88 | 89 | /// 整个ListEntry装饰器 90 | final BoxDecoration? decoration; 91 | 92 | @override 93 | Widget build(BuildContext context) { 94 | final List children = []; 95 | if (prefix != null) children.add(prefix!); 96 | children.add(listTile); 97 | if (arrow || arrowIcon != null) children.add(arrowIcon ?? arrowWidget); 98 | Widget current = 99 | Row(mainAxisAlignment: MainAxisAlignment.center, children: children); 100 | if (onLongPress != null || onDoubleTap != null || onTap != null) { 101 | current = GestureDetector( 102 | onLongPress: enabled ? onLongPress : null, 103 | onDoubleTap: enabled ? onDoubleTap : null, 104 | onTap: enabled ? onTap : null, 105 | child: current); 106 | } 107 | return Container( 108 | height: height, 109 | margin: margin, 110 | padding: padding, 111 | decoration: decoration ?? defaultDecoration, 112 | child: current); 113 | } 114 | 115 | Decoration? get defaultDecoration => color != null || underlineColor != null 116 | ? BoxDecoration( 117 | color: color, 118 | border: underlineColor != null 119 | ? Border(bottom: BorderSide(color: underlineColor!, width: 0.8)) 120 | : null) 121 | : null; 122 | 123 | Widget get arrowWidget => 124 | Icon(Icons.arrow_forward_ios_rounded, size: arrowSize, color: arrowColor); 125 | 126 | Widget get listTile => Expanded( 127 | child: ListTile( 128 | contentPadding: contentPadding, 129 | title: hero(title ?? Text(titleText, style: titleStyle)), 130 | subtitle: subtitle, 131 | leading: leading, 132 | trailing: child, 133 | isThreeLine: isThreeLine, 134 | dense: dense, 135 | enabled: false, 136 | selected: selected ?? false)); 137 | 138 | Widget hero(Widget text) { 139 | if (heroTag != null) return Hero(tag: heroTag!, child: text); 140 | return text; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /lib/src/page_view/transform.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | enum FlPageViewTransformStyle { scale, zoom } 4 | 5 | /// [PageView.builder]itemBuilder 6 | class FlPageViewTransform extends StatelessWidget { 7 | const FlPageViewTransform({ 8 | super.key, 9 | required this.child, 10 | required this.controller, 11 | required this.index, 12 | this.scrollDirection = Axis.horizontal, 13 | this.scaleFactor = 0.3, 14 | this.style = FlPageViewTransformStyle.scale, 15 | }) : assert(scaleFactor > 0 && scaleFactor < 1); 16 | 17 | /// widget 18 | final Widget child; 19 | 20 | /// current index 21 | final int index; 22 | 23 | /// page controller 24 | final PageController controller; 25 | 26 | /// scroll direction 27 | final Axis scrollDirection; 28 | 29 | /// style 30 | final FlPageViewTransformStyle style; 31 | 32 | /// scale factor 33 | final double scaleFactor; 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return AnimatedBuilder( 38 | animation: controller, 39 | child: child, 40 | builder: (BuildContext context, child) { 41 | double scale = 1.0; 42 | double itemOffset = 0; 43 | final position = controller.position; 44 | double page = controller.initialPage.toDouble(); 45 | if (position.hasPixels && position.hasContentDimensions) { 46 | if (controller.page != null) page = controller.page!; 47 | itemOffset = page - index; 48 | } else { 49 | final storageContext = controller.position.context.storageContext; 50 | final previousSavedPosition = PageStorage.of(storageContext) 51 | .readState(storageContext) as double?; 52 | if (previousSavedPosition != null) { 53 | itemOffset = previousSavedPosition - index.toDouble(); 54 | } else { 55 | itemOffset = page.toDouble() - index.toDouble(); 56 | } 57 | } 58 | final ratio = (1 - (itemOffset.abs() * scaleFactor)).clamp(0.0, 1.0); 59 | scale = Curves.easeOut.transform(ratio); 60 | switch (style) { 61 | case FlPageViewTransformStyle.scale: 62 | return Transform.scale(scale: scale, child: child); 63 | case FlPageViewTransformStyle.zoom: 64 | Alignment alignment; 65 | final bool horizontal = scrollDirection == Axis.horizontal; 66 | if (itemOffset > 0) { 67 | alignment = 68 | horizontal ? Alignment.centerRight : Alignment.bottomCenter; 69 | } else { 70 | alignment = 71 | horizontal ? Alignment.centerLeft : Alignment.topCenter; 72 | } 73 | return Transform.scale( 74 | scale: scale, alignment: alignment, child: child); 75 | } 76 | }, 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/rating_stars.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_waya/src/extended_state.dart'; 4 | 5 | typedef RatingStarsChanged = void Function( 6 | double realStars, double selectedStars); 7 | 8 | typedef RatingStarsBuilder = Widget Function(bool selected); 9 | 10 | /// 评级星星 11 | class RatingStars extends StatefulWidget { 12 | const RatingStars({ 13 | super.key, 14 | required this.builder, 15 | this.starCount = 5, 16 | this.onChanged, 17 | this.starSize = const Size(20, 20), 18 | this.starSpacing = 4, 19 | this.value = 0.0, 20 | this.step = 0.5, 21 | this.rounded = false, 22 | this.minStars = 0, 23 | this.enable = true, 24 | this.followChanged = false, 25 | }); 26 | 27 | final RatingStarsBuilder builder; 28 | 29 | final int starCount; 30 | 31 | /// 初始数量(支持小数). 32 | final double value; 33 | 34 | /// 分阶, 范围0.01-1.0, 0.01表示任意星, 1.0表示整星星, 0.5表示半星, 范围内自定义. 35 | final double step; 36 | 37 | /// star size 38 | final Size starSize; 39 | 40 | /// spacing 间距. 41 | final double starSpacing; 42 | 43 | /// 最低分 44 | final double minStars; 45 | 46 | /// 是否开始滑动修改 47 | final bool enable; 48 | 49 | /// 数值发生变化的回调. 50 | final RatingStarsChanged? onChanged; 51 | 52 | /// 是否需要实时回调, 若开启, 拖动时会回调多次, 否则仅在用户操作结束后回调. 53 | final bool followChanged; 54 | 55 | /// 四舍五入 56 | /// 默认false: 举例: step=1, 实际选择2.4则结果为: 3. step=0.5, 实际选择2.2则结果为2.5.("进一法") 57 | /// 为true时: 举例: step=1, 实际选择2.4则结果为: 2. step=0.5, 实际选择2.2则结果为2.0.("四舍五入-取最近值") 58 | final bool rounded; 59 | 60 | @override 61 | State createState() => _RatingStarsState(); 62 | } 63 | 64 | class _RatingStarsState extends ExtendedState { 65 | late double _minStars; 66 | 67 | late double _currentStars; 68 | 69 | late double _step; 70 | 71 | @override 72 | void initState() { 73 | super.initState(); 74 | 75 | /// 0.01 <= step <= 1.0 76 | _step = min(1.0, widget.step); 77 | _step = max(0.01, widget.step); 78 | _minStars = min(widget.minStars, widget.starCount * 1.0); 79 | _currentStars = min(widget.value, widget.starCount * 1.0); 80 | _currentStars = max(widget.value, widget.minStars); 81 | setStars(_currentStars, false, false, false); 82 | } 83 | 84 | @override 85 | void didUpdateWidget(RatingStars oldWidget) { 86 | setStars(widget.value, false, false, false); 87 | super.didUpdateWidget(oldWidget); 88 | } 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | return Stack(children: [ 93 | widget.enable 94 | ? Listener( 95 | onPointerDown: (event) { 96 | calculateStars(event.localPosition.dx, false); 97 | }, 98 | onPointerMove: (event) { 99 | calculateStars(event.localPosition.dx, false); 100 | }, 101 | onPointerUp: (event) { 102 | calculateStars(event.localPosition.dx, true); 103 | }, 104 | child: getStars(widget.starCount, false)) 105 | : getStars(widget.starCount, false), 106 | IgnorePointer( 107 | child: ClipRect( 108 | clipper: _RatingStarsClipper(getWidth), 109 | child: getStars(widget.starCount, true))), 110 | ]); 111 | } 112 | 113 | /// 计算多少分 114 | void calculateStars(double width, bool needCallback) { 115 | var starIndex = 116 | (width / (widget.starSize.width + widget.starSpacing)).floor(); 117 | var locationX = min( 118 | width - starIndex * (widget.starSize.width + widget.starSpacing), 119 | widget.starSize.width); 120 | var selectedStars = starIndex + locationX / widget.starSize.width; 121 | setStars(selectedStars, true, true, needCallback); 122 | } 123 | 124 | /// 实际得分 125 | void setStars( 126 | double selectedStars, bool useStep, bool reload, bool needCallback) { 127 | var realStars = min(widget.starCount, selectedStars); 128 | realStars = max(_minStars, realStars); 129 | var i = realStars.floor(); 130 | if (useStep == true) { 131 | var decimalNumber = (realStars - i); 132 | int floor = (decimalNumber / _step).floor(); 133 | double remainder = decimalNumber % _step; 134 | if (widget.rounded == true) { 135 | realStars = 136 | i + floor * _step + ((remainder >= _step * 0.5) ? _step : 0); 137 | } else { 138 | realStars = i + floor * _step + ((remainder > 0.0) ? _step : 0); 139 | } 140 | } 141 | _currentStars = (realStars * 100).floor() / 100; 142 | if (reload == true) { 143 | if (mounted) setState(() {}); 144 | if (needCallback == false && widget.followChanged == false) return; 145 | if (widget.onChanged == null) return; 146 | widget.onChanged!(_currentStars, selectedStars); 147 | } 148 | } 149 | 150 | double get getWidth { 151 | var i = _currentStars.floor(); 152 | var width = (widget.starSize.width + widget.starSpacing) * i + 153 | (_currentStars - i) * widget.starSize.width; 154 | return width; 155 | } 156 | 157 | /// 获取要显示的所有星星 158 | Widget getStars(int count, bool selected) => Row( 159 | mainAxisSize: MainAxisSize.min, 160 | children: List.generate(max(count * 2 - 1, 0), (index) { 161 | if (index % 2 == 0) { 162 | return SizedBox.fromSize( 163 | size: widget.starSize, child: widget.builder(selected)); 164 | } 165 | return SizedBox( 166 | width: widget.starSpacing, height: widget.starSize.height); 167 | })); 168 | } 169 | 170 | class _RatingStarsClipper extends CustomClipper { 171 | _RatingStarsClipper(this.width); 172 | 173 | double width; 174 | 175 | @override 176 | Rect getClip(Size size) => Rect.fromLTRB(0, 0, width, size.height); 177 | 178 | @override 179 | bool shouldReclip(covariant CustomClipper oldClipper) { 180 | if (oldClipper is _RatingStarsClipper) { 181 | return oldClipper.width != width; 182 | } 183 | return false; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /lib/src/screen_adaptation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_waya/src/extended_state.dart'; 3 | 4 | typedef ScreenAdaptationChildBuilder = Widget Function( 5 | BuildContext context, bool scaled); 6 | 7 | /// none: 不做缩放 auto: 竖屏按宽缩放,横屏(宽 >= 高 * 1.1)不缩放 width: 按宽缩放 8 | enum ScreenAdaptationScaleType { none, auto, width } 9 | 10 | class ScreenAdaptation extends StatefulWidget { 11 | const ScreenAdaptation({ 12 | super.key, 13 | required this.designWidth, 14 | required this.builder, 15 | this.scaleType = ScreenAdaptationScaleType.auto, 16 | }); 17 | 18 | final double designWidth; 19 | final ScreenAdaptationChildBuilder builder; 20 | final ScreenAdaptationScaleType scaleType; 21 | 22 | @override 23 | State createState() => _ScreenAdaptationState(); 24 | } 25 | 26 | class _ScreenAdaptationState extends ExtendedState 27 | with WidgetsBindingObserver { 28 | @override 29 | void initState() { 30 | super.initState(); 31 | WidgetsBinding.instance.addObserver(this); 32 | } 33 | 34 | @override 35 | void dispose() { 36 | super.dispose(); 37 | WidgetsBinding.instance.removeObserver(this); 38 | } 39 | 40 | @override 41 | void didChangeMetrics() { 42 | super.didChangeMetrics(); 43 | if (mounted) setState(() {}); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | final view = View.of(context); 49 | if (view.physicalSize.isEmpty) { 50 | return ScreenAdaptationScope._( 51 | designWidth: widget.designWidth, 52 | scaleType: widget.scaleType, 53 | scaleRatio: 1, 54 | child: const SizedBox()); 55 | } 56 | final Size sceneSize = 57 | View.of(context).physicalSize / view.devicePixelRatio; 58 | if (widget.scaleType == ScreenAdaptationScaleType.none || 59 | (widget.scaleType == ScreenAdaptationScaleType.auto && 60 | sceneSize.width >= sceneSize.height * 1.1)) { 61 | return ScreenAdaptationScope._( 62 | designWidth: widget.designWidth, 63 | scaleType: widget.scaleType, 64 | scaleRatio: 1, 65 | child: Builder( 66 | builder: (BuildContext context) => 67 | widget.builder(context, false))); 68 | } 69 | 70 | final double scale = sceneSize.width / widget.designWidth; 71 | 72 | /// 如果filterQuality为null,则是渲染时直接应用transform,否则会先渲染,再对bitmap应用 73 | return FractionallySizedBox( 74 | widthFactor: 1 / scale, 75 | heightFactor: 1 / scale, 76 | child: Transform.scale( 77 | scale: scale, 78 | child: ScreenAdaptationScope._( 79 | designWidth: widget.designWidth, 80 | scaleType: widget.scaleType, 81 | scaleRatio: scale, 82 | child: widget.builder(context, true)))); 83 | } 84 | } 85 | 86 | class ScreenAdaptationScope extends InheritedWidget { 87 | ScreenAdaptationScope._({ 88 | required this.designWidth, 89 | required this.scaleType, 90 | required this.scaleRatio, 91 | required Widget child, 92 | }) : super(child: _MediaQueryDataProvider(child: child)); 93 | 94 | final double designWidth; 95 | final ScreenAdaptationScaleType scaleType; 96 | final double scaleRatio; 97 | 98 | static ScreenAdaptationScope of(BuildContext context) => 99 | context.dependOnInheritedWidgetOfExactType()!; 100 | 101 | static ScreenAdaptationScope? maybeOf(BuildContext context) => 102 | context.dependOnInheritedWidgetOfExactType(); 103 | 104 | // double toPhysicalWidth(double width) => 105 | // width * scaleRatio * window.devicePixelRatio; 106 | // 107 | // double toPhysicalHeight(double height) => 108 | // height * scaleRatio * window.devicePixelRatio; 109 | 110 | @override 111 | bool updateShouldNotify(covariant ScreenAdaptationScope oldWidget) => 112 | oldWidget.designWidth != designWidth || 113 | oldWidget.scaleType != scaleType || 114 | oldWidget.scaleRatio != scaleRatio; 115 | } 116 | 117 | class _MediaQueryDataProvider extends StatelessWidget { 118 | const _MediaQueryDataProvider({required this.child}); 119 | 120 | final Widget child; 121 | 122 | @override 123 | Widget build(BuildContext context) { 124 | final ScreenAdaptationScope data = ScreenAdaptationScope.of(context); 125 | final MediaQueryData parent = MediaQuery.of(context); 126 | return MediaQuery( 127 | data: parent.copyWith( 128 | size: parent.size / data.scaleRatio, 129 | devicePixelRatio: parent.devicePixelRatio * data.scaleRatio, 130 | padding: parent.padding / data.scaleRatio, 131 | viewPadding: parent.viewPadding / data.scaleRatio, 132 | viewInsets: parent.viewInsets / data.scaleRatio, 133 | systemGestureInsets: parent.systemGestureInsets / data.scaleRatio), 134 | child: child); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /lib/src/system_ui_overlay_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | class SystemUiOverlayStyleLight extends SystemUiOverlayStyle { 5 | const SystemUiOverlayStyleLight( 6 | {super.systemNavigationBarColor = Colors.transparent, 7 | super.systemNavigationBarDividerColor = Colors.transparent, 8 | super.statusBarColor = Colors.transparent, 9 | super.systemNavigationBarIconBrightness = Brightness.light, 10 | super.statusBarIconBrightness = Brightness.light, 11 | super.statusBarBrightness = Brightness.dark}); 12 | } 13 | 14 | class SystemUiOverlayStyleDark extends SystemUiOverlayStyle { 15 | const SystemUiOverlayStyleDark( 16 | {super.systemNavigationBarColor = Colors.transparent, 17 | super.systemNavigationBarDividerColor = Colors.transparent, 18 | super.statusBarColor = Colors.transparent, 19 | super.systemNavigationBarIconBrightness = Brightness.dark, 20 | super.statusBarIconBrightness = Brightness.dark, 21 | super.statusBarBrightness = Brightness.light}); 22 | } 23 | -------------------------------------------------------------------------------- /lib/src/tab_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// [TabBar]和[TabBarView] 4 | /// 外层添加 常用属性 5 | class TabBarMerge extends StatelessWidget { 6 | const TabBarMerge({ 7 | super.key, 8 | required this.tabBar, 9 | required this.tabBarView, 10 | this.reverse = false, 11 | this.height, 12 | this.width, 13 | this.among, 14 | this.header, 15 | this.footer, 16 | this.margin, 17 | this.padding, 18 | this.decoration, 19 | this.constraints, 20 | }); 21 | 22 | /// 使用 [TabBarBox] [TabBar] 23 | final Widget tabBar; 24 | final Widget tabBarView; 25 | 26 | /// 头部 27 | final Widget? header; 28 | 29 | /// [tabBar]和[tabBarView]中间层 30 | final Widget? among; 31 | 32 | /// 底部 33 | final Widget? footer; 34 | 35 | /// 作用于[tabView] 36 | final EdgeInsetsGeometry? margin; 37 | final EdgeInsetsGeometry? padding; 38 | final Decoration? decoration; 39 | final BoxConstraints? constraints; 40 | final double? width; 41 | final double? height; 42 | 43 | /// [tabBar],[tabView] 反转 44 | final bool reverse; 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | final List children = []; 49 | if (header != null) children.add(header!); 50 | children.add(reverse ? buildTabBarView : tabBar); 51 | if (among != null) children.add(among!); 52 | children.add(reverse ? tabBar : buildTabBarView); 53 | if (footer != null) children.add(footer!); 54 | return Column(children: children); 55 | } 56 | 57 | Widget get buildTabBarView => Expanded( 58 | child: Container( 59 | margin: margin, 60 | padding: padding, 61 | decoration: decoration, 62 | constraints: constraints, 63 | width: width, 64 | height: height, 65 | child: tabBarView)); 66 | } 67 | 68 | /// TabBarLevel 位置 69 | enum TabBarLevelPosition { right, left } 70 | 71 | class TabBarBox extends StatelessWidget { 72 | const TabBarBox({ 73 | super.key, 74 | required this.tabBar, 75 | this.levelPosition = TabBarLevelPosition.right, 76 | this.alignment, 77 | this.margin, 78 | this.padding, 79 | this.height, 80 | this.decoration, 81 | this.width, 82 | this.level, 83 | }); 84 | 85 | /// [TabBar] 86 | final TabBar tabBar; 87 | 88 | /// [TabBar] 位置 89 | final TabBarLevelPosition levelPosition; 90 | 91 | /// [TabBar] 水平左边或者右边的Widget 添加标签 92 | final Widget? level; 93 | 94 | /// 作用于整个[TabBar] 95 | final AlignmentGeometry? alignment; 96 | final EdgeInsetsGeometry? margin; 97 | final EdgeInsetsGeometry? padding; 98 | final double? height; 99 | final double? width; 100 | final Decoration? decoration; 101 | 102 | @override 103 | Widget build(BuildContext context) { 104 | Widget current = tabBar; 105 | if (level != null) { 106 | final List children = []; 107 | switch (levelPosition) { 108 | case TabBarLevelPosition.right: 109 | children.add(Expanded(child: current)); 110 | children.add(level!); 111 | break; 112 | case TabBarLevelPosition.left: 113 | children.add(level!); 114 | children.add(Expanded(child: current)); 115 | break; 116 | } 117 | current = Row(children: children); 118 | } 119 | return Container( 120 | height: height, 121 | margin: margin, 122 | padding: padding, 123 | width: width, 124 | alignment: alignment, 125 | decoration: decoration, 126 | child: current); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_waya 2 | description: The Flutter UI library contains multiple custom src,compatible with android,web,ios and MAC. 3 | version: 11.2.5 4 | repository: https://github.com/Wayaer/flutter_waya 5 | 6 | environment: 7 | sdk: '>=3.6.0 <4.0.0' 8 | flutter: '>=3.27.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_lints: ^5.0.0 --------------------------------------------------------------------------------