├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── letsar │ │ │ │ │ └── ov │ │ │ │ │ └── example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── ephemeral │ │ │ ├── flutter_lldb_helper.py │ │ │ └── flutter_lldbinit │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ └── main.dart ├── pubspec.lock └── pubspec.yaml ├── lib ├── overflow_view.dart └── src │ ├── rendering │ └── overflow_view.dart │ └── widgets │ └── overflow_view.dart ├── packages └── images │ └── overflow_view.gif ├── pubspec.yaml └── test └── overflow_view_test.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 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | 77 | \.vscode/ 78 | 79 | pubspec.lock 80 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: bbfbf1770cca2da7c82e887e4e4af910034800b6 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.0 2 | ### Changed 3 | * Update Flutter constraints. 4 | * Update version of value_layout_builder. 5 | 6 | ### Fixed 7 | * Flutter 3.32 breaking changes issue. 8 | 9 | ## 0.4.0 10 | ### Changed 11 | * Update Flutter and Dart SDK constraints. 12 | * Update dependencies. 13 | 14 | ## 0.3.1 15 | ### Changed 16 | * Small formatting issues. 17 | 18 | ## 0.3.0 19 | ### Changed 20 | * Add support for null-safety. 21 | * Increase the minimum version of Flutter. 22 | * Increase the minimum version of dart sdk. 23 | 24 | ## 0.2.2 25 | ### Fixed 26 | * [Issue when rendering overflow indicator after having displayed it once.](https://github.com/letsar/overflow_view/issues/3) 27 | 28 | ## 0.2.1 29 | ### Changed 30 | * The parameter `spacing` can be negative to achieve a stacked effect. 31 | 32 | ## 0.2.0 33 | ### Added 34 | * An `OverflowView.flexible` constructor to let children determine their own size. 35 | 36 | ## 0.1.0 37 | * Initial Open Source release. 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Romain Rastel 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 | # overflow_view 2 | A widget displaying children in a line with an overflow indicator at the end if there is not enough space. 3 | 4 | [![Pub](https://img.shields.io/pub/v/overflow_view.svg)][pub] 5 | 6 | ## Features 7 | * Renders children horizontally or vertically. 8 | * Has an overflow indicator builder so that you can display a widget showing the number of elements not rendered. 9 | * Can either constrain the children to the size of the first child or let them have the size they want. 10 | * Children can overlap each other by setting a negative spacing. 11 | 12 | ![Overview][overview] 13 | 14 | ## Getting started 15 | 16 | In the `pubspec.yaml` of your flutter project, add the following dependency: 17 | 18 | ```yaml 19 | dependencies: 20 | ... 21 | overflow_view: 22 | ``` 23 | 24 | In your library add the following import: 25 | 26 | ```dart 27 | import 'package:overflow_view/overflow_view.dart'; 28 | ``` 29 | 30 | ## Usage 31 | 32 | ```dart 33 | OverflowView( 34 | // Either layout the children horizontally (the default) 35 | // or vertically. 36 | direction: Axis.horizontal, 37 | // The amount of space between children. 38 | spacing: 4, 39 | // The widgets to display until there is not enough space. 40 | children: [ 41 | for (int i = 0; i < _counter; i++) 42 | AvatarWidget( 43 | text: avatars[i].initials, 44 | color: avatars[i].color, 45 | ) 46 | ], 47 | // The overview indicator showed if there is not enough space for 48 | // all chidren. 49 | builder: (context, remaining) { 50 | // You can return any widget here. 51 | // You can either show a +n widget or a more complex widget 52 | // which can show a thumbnail of not rendered widgets. 53 | return AvatarWidget( 54 | text: '+$remaining', 55 | color: Colors.red, 56 | ); 57 | }, 58 | ) 59 | ``` 60 | 61 | ### Constructors 62 | 63 | There are two constuctors depending on what you want to do. 64 | 65 | The `OverflowView` constructor will constrain all children to have the same size as the first one. This can be used for an avatar list for example. 66 | 67 | The `OverflowView.flexible` constructor will let all children to determine their own size. This is less performant than the default one, but it's more flexible. This can be used for a menu bar for example. 68 | 69 | ## Sponsoring 70 | 71 | I'm working on my packages on my free-time, but I don't have as much time as I would. If this package or any other package I created is helping you, please consider to sponsor me. By doing so, I will prioritize your issues or your pull-requests before the others. 72 | 73 | ## Changelog 74 | 75 | Please see the [Changelog][changelog] page to know what's recently changed. 76 | 77 | ## Contributions 78 | 79 | Feel free to contribute to this project. 80 | 81 | If you find a bug or want a feature, but don't know how to fix/implement it, please fill an [issue][issue]. 82 | If you fixed a bug or implemented a feature, please send a [pull request][pr]. 83 | 84 | 85 | [pub]: https://pub.dartlang.org/packages/overflow_view 86 | [changelog]: https://github.com/letsar/overflow_view/blob/master/CHANGELOG.md 87 | [issue]: https://github.com/letsar/overflow_view/issues 88 | [pr]: https://github.com/letsar/overflow_view/pulls 89 | [overview]: https://raw.githubusercontent.com/letsar/overflow_view/master/packages/images/overflow_view.gif -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "2663184aa79047d0a33a14a3b607954f8fdd8730" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 17 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 18 | - platform: android 19 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 20 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 21 | - platform: ios 22 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 23 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 24 | - platform: linux 25 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 26 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 27 | - platform: macos 28 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 29 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 30 | - platform: web 31 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 32 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 33 | - platform: windows 34 | create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 35 | base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/to/reference-keystore 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 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.letsar.ov.example" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_1_8 15 | targetCompatibility = JavaVersion.VERSION_1_8 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_1_8 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "com.letsar.ov.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.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/letsar/ov/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.letsar.ov.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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -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.3-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return 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.1.0" apply false 22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/ephemeral/flutter_lldb_helper.py: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | import lldb 6 | 7 | def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): 8 | """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" 9 | base = frame.register["x0"].GetValueAsAddress() 10 | page_len = frame.register["x1"].GetValueAsUnsigned() 11 | 12 | # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the 13 | # first page to see if handled it correctly. This makes diagnosing 14 | # misconfiguration (e.g. missing breakpoint) easier. 15 | data = bytearray(page_len) 16 | data[0:8] = b'IHELPED!' 17 | 18 | error = lldb.SBError() 19 | frame.GetThread().GetProcess().WriteMemory(base, data, error) 20 | if not error.Success(): 21 | print(f'Failed to write into {base}[+{page_len}]', error) 22 | return 23 | 24 | def __lldb_init_module(debugger: lldb.SBDebugger, _): 25 | target = debugger.GetDummyTarget() 26 | # Caveat: must use BreakpointCreateByRegEx here and not 27 | # BreakpointCreateByName. For some reasons callback function does not 28 | # get carried over from dummy target for the later. 29 | bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") 30 | bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) 31 | bp.SetAutoContinue(True) 32 | print("-- LLDB integration loaded --") 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/ephemeral/flutter_lldbinit: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | command script import --relative-to-command-file flutter_lldb_helper.py 6 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 97 | ); 98 | path = Runner; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 97C146ED1CF9000F007C117D /* Runner */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 107 | buildPhases = ( 108 | 9740EEB61CF901F6004384FC /* Run Script */, 109 | 97C146EA1CF9000F007C117D /* Sources */, 110 | 97C146EB1CF9000F007C117D /* Frameworks */, 111 | 97C146EC1CF9000F007C117D /* Resources */, 112 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 114 | ); 115 | buildRules = ( 116 | ); 117 | dependencies = ( 118 | ); 119 | name = Runner; 120 | productName = Runner; 121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 122 | productType = "com.apple.product-type.application"; 123 | }; 124 | /* End PBXNativeTarget section */ 125 | 126 | /* Begin PBXProject section */ 127 | 97C146E61CF9000F007C117D /* Project object */ = { 128 | isa = PBXProject; 129 | attributes = { 130 | LastUpgradeCheck = 1510; 131 | ORGANIZATIONNAME = ""; 132 | TargetAttributes = { 133 | 97C146ED1CF9000F007C117D = { 134 | CreatedOnToolsVersion = 7.3.1; 135 | LastSwiftMigration = 1100; 136 | }; 137 | }; 138 | }; 139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 140 | compatibilityVersion = "Xcode 9.3"; 141 | developmentRegion = en; 142 | hasScannedForEncodings = 0; 143 | knownRegions = ( 144 | en, 145 | Base, 146 | ); 147 | mainGroup = 97C146E51CF9000F007C117D; 148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 149 | projectDirPath = ""; 150 | projectRoot = ""; 151 | targets = ( 152 | 97C146ED1CF9000F007C117D /* Runner */, 153 | ); 154 | }; 155 | /* End PBXProject section */ 156 | 157 | /* Begin PBXResourcesBuildPhase section */ 158 | 97C146EC1CF9000F007C117D /* Resources */ = { 159 | isa = PBXResourcesBuildPhase; 160 | buildActionMask = 2147483647; 161 | files = ( 162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 166 | ); 167 | runOnlyForDeploymentPostprocessing = 0; 168 | }; 169 | /* End PBXResourcesBuildPhase section */ 170 | 171 | /* Begin PBXShellScriptBuildPhase section */ 172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 173 | isa = PBXShellScriptBuildPhase; 174 | alwaysOutOfDate = 1; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | ); 178 | inputPaths = ( 179 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 180 | ); 181 | name = "Thin Binary"; 182 | outputPaths = ( 183 | ); 184 | runOnlyForDeploymentPostprocessing = 0; 185 | shellPath = /bin/sh; 186 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 187 | }; 188 | 9740EEB61CF901F6004384FC /* Run Script */ = { 189 | isa = PBXShellScriptBuildPhase; 190 | alwaysOutOfDate = 1; 191 | buildActionMask = 2147483647; 192 | files = ( 193 | ); 194 | inputPaths = ( 195 | ); 196 | name = "Run Script"; 197 | outputPaths = ( 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | shellPath = /bin/sh; 201 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 202 | }; 203 | /* End PBXShellScriptBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | 97C146EA1CF9000F007C117D /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 211 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXVariantGroup section */ 218 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 219 | isa = PBXVariantGroup; 220 | children = ( 221 | 97C146FB1CF9000F007C117D /* Base */, 222 | ); 223 | name = Main.storyboard; 224 | sourceTree = ""; 225 | }; 226 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 227 | isa = PBXVariantGroup; 228 | children = ( 229 | 97C147001CF9000F007C117D /* Base */, 230 | ); 231 | name = LaunchScreen.storyboard; 232 | sourceTree = ""; 233 | }; 234 | /* End PBXVariantGroup section */ 235 | 236 | /* Begin XCBuildConfiguration section */ 237 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_ANALYZER_NONNULL = YES; 242 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 243 | CLANG_CXX_LIBRARY = "libc++"; 244 | CLANG_ENABLE_MODULES = YES; 245 | CLANG_ENABLE_OBJC_ARC = YES; 246 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 247 | CLANG_WARN_BOOL_CONVERSION = YES; 248 | CLANG_WARN_COMMA = YES; 249 | CLANG_WARN_CONSTANT_CONVERSION = YES; 250 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 251 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 252 | CLANG_WARN_EMPTY_BODY = YES; 253 | CLANG_WARN_ENUM_CONVERSION = YES; 254 | CLANG_WARN_INFINITE_RECURSION = YES; 255 | CLANG_WARN_INT_CONVERSION = YES; 256 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 257 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 258 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 259 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 260 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 261 | CLANG_WARN_STRICT_PROTOTYPES = YES; 262 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 263 | CLANG_WARN_UNREACHABLE_CODE = YES; 264 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 265 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 266 | COPY_PHASE_STRIP = NO; 267 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 268 | ENABLE_NS_ASSERTIONS = NO; 269 | ENABLE_STRICT_OBJC_MSGSEND = YES; 270 | GCC_C_LANGUAGE_STANDARD = gnu99; 271 | GCC_NO_COMMON_BLOCKS = YES; 272 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 273 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 274 | GCC_WARN_UNDECLARED_SELECTOR = YES; 275 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 276 | GCC_WARN_UNUSED_FUNCTION = YES; 277 | GCC_WARN_UNUSED_VARIABLE = YES; 278 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 279 | MTL_ENABLE_DEBUG_INFO = NO; 280 | SDKROOT = iphoneos; 281 | SUPPORTED_PLATFORMS = iphoneos; 282 | TARGETED_DEVICE_FAMILY = "1,2"; 283 | VALIDATE_PRODUCT = YES; 284 | }; 285 | name = Profile; 286 | }; 287 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 288 | isa = XCBuildConfiguration; 289 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 290 | buildSettings = { 291 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 292 | CLANG_ENABLE_MODULES = YES; 293 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 294 | ENABLE_BITCODE = NO; 295 | FRAMEWORK_SEARCH_PATHS = ( 296 | "$(inherited)", 297 | "$(PROJECT_DIR)/Flutter", 298 | ); 299 | INFOPLIST_FILE = Runner/Info.plist; 300 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 301 | LIBRARY_SEARCH_PATHS = ( 302 | "$(inherited)", 303 | "$(PROJECT_DIR)/Flutter", 304 | ); 305 | PRODUCT_BUNDLE_IDENTIFIER = com.letsar.ov.example; 306 | PRODUCT_NAME = "$(TARGET_NAME)"; 307 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 308 | SWIFT_VERSION = 5.0; 309 | VERSIONING_SYSTEM = "apple-generic"; 310 | }; 311 | name = Profile; 312 | }; 313 | 97C147031CF9000F007C117D /* Debug */ = { 314 | isa = XCBuildConfiguration; 315 | buildSettings = { 316 | ALWAYS_SEARCH_USER_PATHS = NO; 317 | CLANG_ANALYZER_NONNULL = YES; 318 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 319 | CLANG_CXX_LIBRARY = "libc++"; 320 | CLANG_ENABLE_MODULES = YES; 321 | CLANG_ENABLE_OBJC_ARC = YES; 322 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 323 | CLANG_WARN_BOOL_CONVERSION = YES; 324 | CLANG_WARN_COMMA = YES; 325 | CLANG_WARN_CONSTANT_CONVERSION = YES; 326 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 327 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 328 | CLANG_WARN_EMPTY_BODY = YES; 329 | CLANG_WARN_ENUM_CONVERSION = YES; 330 | CLANG_WARN_INFINITE_RECURSION = YES; 331 | CLANG_WARN_INT_CONVERSION = YES; 332 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 333 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 334 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 335 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 336 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 337 | CLANG_WARN_STRICT_PROTOTYPES = YES; 338 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 339 | CLANG_WARN_UNREACHABLE_CODE = YES; 340 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 341 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 342 | COPY_PHASE_STRIP = NO; 343 | DEBUG_INFORMATION_FORMAT = dwarf; 344 | ENABLE_STRICT_OBJC_MSGSEND = YES; 345 | ENABLE_TESTABILITY = YES; 346 | GCC_C_LANGUAGE_STANDARD = gnu99; 347 | GCC_DYNAMIC_NO_PIC = NO; 348 | GCC_NO_COMMON_BLOCKS = YES; 349 | GCC_OPTIMIZATION_LEVEL = 0; 350 | GCC_PREPROCESSOR_DEFINITIONS = ( 351 | "DEBUG=1", 352 | "$(inherited)", 353 | ); 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNDECLARED_SELECTOR = YES; 357 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 358 | GCC_WARN_UNUSED_FUNCTION = YES; 359 | GCC_WARN_UNUSED_VARIABLE = YES; 360 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 361 | MTL_ENABLE_DEBUG_INFO = YES; 362 | ONLY_ACTIVE_ARCH = YES; 363 | SDKROOT = iphoneos; 364 | TARGETED_DEVICE_FAMILY = "1,2"; 365 | }; 366 | name = Debug; 367 | }; 368 | 97C147041CF9000F007C117D /* Release */ = { 369 | isa = XCBuildConfiguration; 370 | buildSettings = { 371 | ALWAYS_SEARCH_USER_PATHS = NO; 372 | CLANG_ANALYZER_NONNULL = YES; 373 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 374 | CLANG_CXX_LIBRARY = "libc++"; 375 | CLANG_ENABLE_MODULES = YES; 376 | CLANG_ENABLE_OBJC_ARC = YES; 377 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 378 | CLANG_WARN_BOOL_CONVERSION = YES; 379 | CLANG_WARN_COMMA = YES; 380 | CLANG_WARN_CONSTANT_CONVERSION = YES; 381 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 382 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 383 | CLANG_WARN_EMPTY_BODY = YES; 384 | CLANG_WARN_ENUM_CONVERSION = YES; 385 | CLANG_WARN_INFINITE_RECURSION = YES; 386 | CLANG_WARN_INT_CONVERSION = YES; 387 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 388 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 389 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 390 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 391 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 392 | CLANG_WARN_STRICT_PROTOTYPES = YES; 393 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 397 | COPY_PHASE_STRIP = NO; 398 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 399 | ENABLE_NS_ASSERTIONS = NO; 400 | ENABLE_STRICT_OBJC_MSGSEND = YES; 401 | GCC_C_LANGUAGE_STANDARD = gnu99; 402 | GCC_NO_COMMON_BLOCKS = YES; 403 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 404 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 405 | GCC_WARN_UNDECLARED_SELECTOR = YES; 406 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 407 | GCC_WARN_UNUSED_FUNCTION = YES; 408 | GCC_WARN_UNUSED_VARIABLE = YES; 409 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 410 | MTL_ENABLE_DEBUG_INFO = NO; 411 | SDKROOT = iphoneos; 412 | SUPPORTED_PLATFORMS = iphoneos; 413 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 414 | TARGETED_DEVICE_FAMILY = "1,2"; 415 | VALIDATE_PRODUCT = YES; 416 | }; 417 | name = Release; 418 | }; 419 | 97C147061CF9000F007C117D /* Debug */ = { 420 | isa = XCBuildConfiguration; 421 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 422 | buildSettings = { 423 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 424 | CLANG_ENABLE_MODULES = YES; 425 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 426 | ENABLE_BITCODE = NO; 427 | FRAMEWORK_SEARCH_PATHS = ( 428 | "$(inherited)", 429 | "$(PROJECT_DIR)/Flutter", 430 | ); 431 | INFOPLIST_FILE = Runner/Info.plist; 432 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 433 | LIBRARY_SEARCH_PATHS = ( 434 | "$(inherited)", 435 | "$(PROJECT_DIR)/Flutter", 436 | ); 437 | PRODUCT_BUNDLE_IDENTIFIER = com.letsar.ov.example; 438 | PRODUCT_NAME = "$(TARGET_NAME)"; 439 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 440 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 441 | SWIFT_VERSION = 5.0; 442 | VERSIONING_SYSTEM = "apple-generic"; 443 | }; 444 | name = Debug; 445 | }; 446 | 97C147071CF9000F007C117D /* Release */ = { 447 | isa = XCBuildConfiguration; 448 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 449 | buildSettings = { 450 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 451 | CLANG_ENABLE_MODULES = YES; 452 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 453 | ENABLE_BITCODE = NO; 454 | FRAMEWORK_SEARCH_PATHS = ( 455 | "$(inherited)", 456 | "$(PROJECT_DIR)/Flutter", 457 | ); 458 | INFOPLIST_FILE = Runner/Info.plist; 459 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 460 | LIBRARY_SEARCH_PATHS = ( 461 | "$(inherited)", 462 | "$(PROJECT_DIR)/Flutter", 463 | ); 464 | PRODUCT_BUNDLE_IDENTIFIER = com.letsar.ov.example; 465 | PRODUCT_NAME = "$(TARGET_NAME)"; 466 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 467 | SWIFT_VERSION = 5.0; 468 | VERSIONING_SYSTEM = "apple-generic"; 469 | }; 470 | name = Release; 471 | }; 472 | /* End XCBuildConfiguration section */ 473 | 474 | /* Begin XCConfigurationList section */ 475 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | 97C147031CF9000F007C117D /* Debug */, 479 | 97C147041CF9000F007C117D /* Release */, 480 | 249021D3217E4FDB00AE95B9 /* Profile */, 481 | ); 482 | defaultConfigurationIsVisible = 0; 483 | defaultConfigurationName = Release; 484 | }; 485 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 486 | isa = XCConfigurationList; 487 | buildConfigurations = ( 488 | 97C147061CF9000F007C117D /* Debug */, 489 | 97C147071CF9000F007C117D /* Release */, 490 | 249021D4217E4FDB00AE95B9 /* Profile */, 491 | ); 492 | defaultConfigurationIsVisible = 0; 493 | defaultConfigurationName = Release; 494 | }; 495 | /* End XCConfigurationList section */ 496 | }; 497 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 498 | } 499 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 57 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 76 | 78 | 84 | 85 | 86 | 87 | 89 | 90 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/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/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/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/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:overflow_view/overflow_view.dart'; 3 | 4 | void main() { 5 | runApp(MyApp()); 6 | } 7 | 8 | class MyApp extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | return MaterialApp( 12 | title: 'Flutter Demo', 13 | theme: ThemeData( 14 | primarySwatch: Colors.blue, 15 | visualDensity: VisualDensity.adaptivePlatformDensity, 16 | ), 17 | home: MyHomePage(title: 'Flutter Demo Home Page'), 18 | ); 19 | } 20 | } 21 | 22 | class MyHomePage extends StatefulWidget { 23 | MyHomePage({Key? key, required this.title}) : super(key: key); 24 | 25 | final String title; 26 | 27 | @override 28 | _MyHomePageState createState() => _MyHomePageState(); 29 | } 30 | 31 | class Avatar { 32 | const Avatar(this.initials, this.color); 33 | final String initials; 34 | final Color color; 35 | } 36 | 37 | const List avatars = [ 38 | Avatar('AD', Colors.green), 39 | Avatar('JG', Colors.pink), 40 | Avatar('DA', Colors.blue), 41 | Avatar('JA', Colors.black), 42 | Avatar('CB', Colors.amber), 43 | Avatar('RR', Colors.deepPurple), 44 | Avatar('JD', Colors.pink), 45 | Avatar('MB', Colors.amberAccent), 46 | Avatar('AA', Colors.blueAccent), 47 | Avatar('BA', Colors.tealAccent), 48 | Avatar('CR', Colors.yellow), 49 | ]; 50 | 51 | class _MyHomePageState extends State { 52 | int _counter = 1; 53 | double ratio = 1; 54 | 55 | void _incrementCounter() { 56 | setState(() { 57 | _counter = (_counter + 1).clamp(0, avatars.length - 1); 58 | }); 59 | } 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return Scaffold( 64 | appBar: AppBar( 65 | title: Text(widget.title), 66 | ), 67 | body: SafeArea( 68 | child: Padding( 69 | padding: const EdgeInsets.all(8.0), 70 | child: Column( 71 | crossAxisAlignment: CrossAxisAlignment.start, 72 | children: [ 73 | Text( 74 | 'People', 75 | style: TextStyle(fontSize: 20), 76 | ), 77 | SizedBox(height: 20), 78 | OverflowView.flexible( 79 | spacing: -40, 80 | children: [ 81 | for (int i = 0; i < _counter; i++) 82 | AvatarWidget( 83 | text: avatars[i].initials, 84 | color: avatars[i].color, 85 | ) 86 | ], 87 | builder: (context, remaining) { 88 | return AvatarWidget( 89 | text: '+$remaining', 90 | color: Colors.red, 91 | ); 92 | }, 93 | ), 94 | SizedBox(height: 20), 95 | FractionallySizedBox( 96 | widthFactor: ratio, 97 | child: CommandBar(), 98 | ), 99 | SizedBox(height: 20), 100 | Expanded( 101 | child: OverflowView( 102 | direction: Axis.vertical, 103 | spacing: 4, 104 | children: [ 105 | for (int i = 0; i < _counter; i++) 106 | AvatarWidget( 107 | text: avatars[i].initials, 108 | color: avatars[i].color, 109 | ) 110 | ], 111 | builder: (context, remaining) { 112 | return SizedBox( 113 | height: 80, 114 | width: 80, 115 | child: Stack( 116 | fit: StackFit.expand, 117 | children: [ 118 | if (remaining > 0) 119 | AvatarOverview( 120 | position: 0, 121 | remaining: remaining, 122 | counter: _counter, 123 | ), 124 | if (remaining > 1) 125 | AvatarOverview( 126 | position: 1, 127 | remaining: remaining, 128 | counter: _counter, 129 | ), 130 | if (remaining > 2) 131 | AvatarOverview( 132 | position: 2, 133 | remaining: remaining, 134 | counter: _counter, 135 | ), 136 | if (remaining > 3) 137 | AvatarOverview( 138 | position: 3, 139 | remaining: remaining, 140 | counter: _counter, 141 | ), 142 | Positioned.fill( 143 | child: Center( 144 | child: FractionallySizedBox( 145 | alignment: Alignment.center, 146 | widthFactor: 0.5, 147 | heightFactor: 0.5, 148 | child: FittedBox( 149 | child: AvatarWidget( 150 | text: '+$remaining', 151 | color: Colors.black.withOpacity(0.9), 152 | ), 153 | ), 154 | ), 155 | ), 156 | ), 157 | ], 158 | ), 159 | ); 160 | }, 161 | ), 162 | ), 163 | // Slider( 164 | // value: ratio, 165 | // min: 0, 166 | // max: 1, 167 | // divisions: 100, 168 | // onChanged: (value) { 169 | // setState(() { 170 | // ratio = value; 171 | // }); 172 | // }, 173 | // ), 174 | SizedBox(height: 40), 175 | ], 176 | ), 177 | ), 178 | ), 179 | floatingActionButton: FloatingActionButton( 180 | onPressed: _incrementCounter, 181 | tooltip: 'Increment', 182 | child: Icon(Icons.add), 183 | ), 184 | ); 185 | } 186 | } 187 | 188 | class AvatarOverview extends StatelessWidget { 189 | AvatarOverview({ 190 | Key? key, 191 | required int remaining, 192 | required int position, 193 | required int counter, 194 | }) : index = counter - remaining + position, 195 | alignment = _getAlignment(position), 196 | super(key: key); 197 | 198 | final int index; 199 | final Alignment alignment; 200 | 201 | @override 202 | Widget build(BuildContext context) { 203 | final Avatar avatar = avatars[index]; 204 | return FractionallySizedBox( 205 | key: ValueKey(index), 206 | alignment: alignment, 207 | widthFactor: 0.5, 208 | heightFactor: 0.5, 209 | child: FittedBox( 210 | child: AvatarWidget( 211 | text: avatar.initials, 212 | color: avatar.color, 213 | ), 214 | ), 215 | ); 216 | } 217 | 218 | static Alignment _getAlignment(int position) { 219 | switch (position) { 220 | case 0: 221 | return Alignment.topLeft; 222 | case 1: 223 | return Alignment.topRight; 224 | case 2: 225 | return Alignment.bottomLeft; 226 | default: 227 | return Alignment.bottomRight; 228 | } 229 | } 230 | } 231 | 232 | class AvatarWidget extends StatelessWidget { 233 | const AvatarWidget({ 234 | Key? key, 235 | required this.text, 236 | required this.color, 237 | }) : super(key: key); 238 | 239 | final String text; 240 | final Color color; 241 | 242 | @override 243 | Widget build(BuildContext context) { 244 | return CircleAvatar( 245 | radius: 40, 246 | backgroundColor: color, 247 | foregroundColor: Colors.white, 248 | child: Text( 249 | text, 250 | style: TextStyle(fontSize: 30), 251 | ), 252 | ); 253 | } 254 | } 255 | 256 | class CommandBar extends StatelessWidget { 257 | const CommandBar({ 258 | Key? key, 259 | }) : super(key: key); 260 | 261 | @override 262 | Widget build(BuildContext context) { 263 | final List commands = [ 264 | MenuItemData(id: 'a', label: 'File'), 265 | MenuItemData(id: 'b', icon: Icons.save, label: 'Save'), 266 | MenuItemData(id: 'c', label: 'Edit'), 267 | MenuItemData(id: 'd', label: 'View'), 268 | MenuItemData(id: 'e', icon: Icons.exit_to_app), 269 | MenuItemData(id: 'f', label: 'Long Command'), 270 | MenuItemData(id: 'f', label: 'Very Long Command'), 271 | MenuItemData(id: 'f', label: 'Very very Long Command'), 272 | MenuItemData(id: 'f', label: 'Help'), 273 | ]; 274 | 275 | return OverflowView.flexible( 276 | spacing: -4, 277 | children: [...commands.map((e) => _MenuItem(data: e))], 278 | builder: (context, remaining) { 279 | return PopupMenuButton( 280 | icon: Icon(Icons.menu), 281 | itemBuilder: (context) { 282 | return commands 283 | .skip(commands.length - remaining) 284 | .map((e) => PopupMenuItem( 285 | value: e.id, 286 | child: _MenuItem(data: e), 287 | )) 288 | .toList(); 289 | }, 290 | ); 291 | }, 292 | ); 293 | } 294 | } 295 | 296 | class MenuItemData { 297 | const MenuItemData({ 298 | required this.id, 299 | this.label, 300 | this.icon, 301 | }); 302 | 303 | final String id; 304 | final String? label; 305 | final IconData? icon; 306 | } 307 | 308 | class _MenuItem extends StatelessWidget { 309 | const _MenuItem({ 310 | Key? key, 311 | required this.data, 312 | }) : super(key: key); 313 | 314 | final MenuItemData data; 315 | 316 | @override 317 | Widget build(BuildContext context) { 318 | return TextButton( 319 | onPressed: () {}, 320 | child: Row( 321 | children: [ 322 | if (data.icon != null) Icon(data.icon), 323 | if (data.icon != null && data.label != null) SizedBox(width: 8), 324 | if (data.label != null) Text(data.label!), 325 | ], 326 | ), 327 | ); 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.13.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.2" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.4.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.2" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.19.1" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.3" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_test: 58 | dependency: "direct dev" 59 | description: flutter 60 | source: sdk 61 | version: "0.0.0" 62 | leak_tracker: 63 | dependency: transitive 64 | description: 65 | name: leak_tracker 66 | sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" 67 | url: "https://pub.dev" 68 | source: hosted 69 | version: "10.0.9" 70 | leak_tracker_flutter_testing: 71 | dependency: transitive 72 | description: 73 | name: leak_tracker_flutter_testing 74 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "3.0.9" 78 | leak_tracker_testing: 79 | dependency: transitive 80 | description: 81 | name: leak_tracker_testing 82 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "3.0.1" 86 | matcher: 87 | dependency: transitive 88 | description: 89 | name: matcher 90 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "0.12.17" 94 | material_color_utilities: 95 | dependency: transitive 96 | description: 97 | name: material_color_utilities 98 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "0.11.1" 102 | meta: 103 | dependency: transitive 104 | description: 105 | name: meta 106 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "1.16.0" 110 | overflow_view: 111 | dependency: "direct main" 112 | description: 113 | path: ".." 114 | relative: true 115 | source: path 116 | version: "0.5.0" 117 | path: 118 | dependency: transitive 119 | description: 120 | name: path 121 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" 122 | url: "https://pub.dev" 123 | source: hosted 124 | version: "1.9.1" 125 | sky_engine: 126 | dependency: transitive 127 | description: flutter 128 | source: sdk 129 | version: "0.0.0" 130 | source_span: 131 | dependency: transitive 132 | description: 133 | name: source_span 134 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" 135 | url: "https://pub.dev" 136 | source: hosted 137 | version: "1.10.1" 138 | stack_trace: 139 | dependency: transitive 140 | description: 141 | name: stack_trace 142 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" 143 | url: "https://pub.dev" 144 | source: hosted 145 | version: "1.12.1" 146 | stream_channel: 147 | dependency: transitive 148 | description: 149 | name: stream_channel 150 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" 151 | url: "https://pub.dev" 152 | source: hosted 153 | version: "2.1.4" 154 | string_scanner: 155 | dependency: transitive 156 | description: 157 | name: string_scanner 158 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" 159 | url: "https://pub.dev" 160 | source: hosted 161 | version: "1.4.1" 162 | term_glyph: 163 | dependency: transitive 164 | description: 165 | name: term_glyph 166 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" 167 | url: "https://pub.dev" 168 | source: hosted 169 | version: "1.2.2" 170 | test_api: 171 | dependency: transitive 172 | description: 173 | name: test_api 174 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd 175 | url: "https://pub.dev" 176 | source: hosted 177 | version: "0.7.4" 178 | value_layout_builder: 179 | dependency: transitive 180 | description: 181 | name: value_layout_builder 182 | sha256: ab4b7d98bac8cefeb9713154d43ee0477490183f5aa23bb4ffa5103d9bbf6275 183 | url: "https://pub.dev" 184 | source: hosted 185 | version: "0.5.0" 186 | vector_math: 187 | dependency: transitive 188 | description: 189 | name: vector_math 190 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 191 | url: "https://pub.dev" 192 | source: hosted 193 | version: "2.1.4" 194 | vm_service: 195 | dependency: transitive 196 | description: 197 | name: vm_service 198 | sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 199 | url: "https://pub.dev" 200 | source: hosted 201 | version: "15.0.0" 202 | sdks: 203 | dart: ">=3.7.0-0 <4.0.0" 204 | flutter: ">=3.32.0" 205 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.17.0 <4.0.0" 22 | flutter: ">= 3.0.0" 23 | 24 | dependencies: 25 | flutter: 26 | sdk: flutter 27 | overflow_view: 28 | path: ../ 29 | 30 | dev_dependencies: 31 | flutter_test: 32 | sdk: flutter 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://dart.dev/tools/pub/pubspec 36 | 37 | # The following section is specific to Flutter. 38 | flutter: 39 | 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | # assets: 47 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.dev/custom-fonts/#from-packages 75 | -------------------------------------------------------------------------------- /lib/overflow_view.dart: -------------------------------------------------------------------------------- 1 | library overflow_view; 2 | 3 | export 'src/widgets/overflow_view.dart'; 4 | -------------------------------------------------------------------------------- /lib/src/rendering/overflow_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/rendering.dart'; 2 | import 'package:value_layout_builder/value_layout_builder.dart'; 3 | 4 | import 'dart:math' as math; 5 | 6 | /// Parent data for use with [RenderOverflowView]. 7 | class OverflowViewParentData extends ContainerBoxParentData { 8 | bool? offstage; 9 | } 10 | 11 | enum OverflowViewLayoutBehavior { 12 | fixed, 13 | flexible, 14 | } 15 | 16 | class RenderOverflowView extends RenderBox 17 | with 18 | ContainerRenderObjectMixin, 19 | RenderBoxContainerDefaultsMixin { 20 | RenderOverflowView({ 21 | List? children, 22 | required Axis direction, 23 | required double spacing, 24 | required OverflowViewLayoutBehavior layoutBehavior, 25 | }) : assert(spacing > double.negativeInfinity && spacing < double.infinity), 26 | _direction = direction, 27 | _spacing = spacing, 28 | _layoutBehavior = layoutBehavior, 29 | _isHorizontal = direction == Axis.horizontal { 30 | addAll(children); 31 | } 32 | 33 | Axis get direction => _direction; 34 | Axis _direction; 35 | set direction(Axis value) { 36 | if (_direction != value) { 37 | _direction = value; 38 | _isHorizontal = direction == Axis.horizontal; 39 | markNeedsLayout(); 40 | } 41 | } 42 | 43 | double get spacing => _spacing; 44 | double _spacing; 45 | set spacing(double value) { 46 | assert(value > double.negativeInfinity && value < double.infinity); 47 | if (_spacing != value) { 48 | _spacing = value; 49 | markNeedsLayout(); 50 | } 51 | } 52 | 53 | OverflowViewLayoutBehavior get layoutBehavior => _layoutBehavior; 54 | OverflowViewLayoutBehavior _layoutBehavior; 55 | set layoutBehavior(OverflowViewLayoutBehavior value) { 56 | if (_layoutBehavior != value) { 57 | _layoutBehavior = value; 58 | markNeedsLayout(); 59 | } 60 | } 61 | 62 | bool _isHorizontal; 63 | @override 64 | void setupParentData(RenderBox child) { 65 | if (child.parentData is! OverflowViewParentData) 66 | child.parentData = OverflowViewParentData(); 67 | } 68 | 69 | double _getCrossSize(RenderBox child) { 70 | switch (_direction) { 71 | case Axis.horizontal: 72 | return child.size.height; 73 | case Axis.vertical: 74 | return child.size.width; 75 | } 76 | } 77 | 78 | double _getMainSize(RenderBox child) { 79 | switch (_direction) { 80 | case Axis.horizontal: 81 | return child.size.width; 82 | case Axis.vertical: 83 | return child.size.height; 84 | } 85 | } 86 | 87 | bool _hasOverflow = false; 88 | 89 | @override 90 | void performLayout() { 91 | _hasOverflow = false; 92 | assert(firstChild != null); 93 | resetOffstage(); 94 | if (layoutBehavior == OverflowViewLayoutBehavior.fixed) { 95 | performFixedLayout(); 96 | } else { 97 | performFlexibleLayout(); 98 | } 99 | } 100 | 101 | void resetOffstage() { 102 | visitChildren((child) { 103 | final OverflowViewParentData childParentData = 104 | child.parentData as OverflowViewParentData; 105 | childParentData.offstage = null; 106 | }); 107 | } 108 | 109 | void performFixedLayout() { 110 | RenderBox child = firstChild!; 111 | final BoxConstraints childConstraints = constraints.loosen(); 112 | final double maxExtent = 113 | _isHorizontal ? constraints.maxWidth : constraints.maxHeight; 114 | 115 | OverflowViewParentData childParentData = 116 | child.parentData as OverflowViewParentData; 117 | child.layout(childConstraints, parentUsesSize: true); 118 | final double childExtent = child.size.getMainExtent(direction); 119 | final double crossExtent = child.size.getCrossExtent(direction); 120 | final BoxConstraints otherChildConstraints = _isHorizontal 121 | ? childConstraints.tighten(width: childExtent, height: crossExtent) 122 | : childConstraints.tighten(height: childExtent, width: crossExtent); 123 | 124 | final double childStride = childExtent + spacing; 125 | Offset getChildOffset(int index) { 126 | final double mainAxisOffset = index * childStride; 127 | final double crossAxisOffset = 0; 128 | if (_isHorizontal) { 129 | return Offset(mainAxisOffset, crossAxisOffset); 130 | } else { 131 | return Offset(crossAxisOffset, mainAxisOffset); 132 | } 133 | } 134 | 135 | int onstageCount = 0; 136 | final int count = childCount - 1; 137 | final double requestedExtent = 138 | childExtent * (childCount - 1) + spacing * (childCount - 2); 139 | final int renderedChildCount = requestedExtent <= maxExtent 140 | ? count 141 | : (maxExtent + spacing) ~/ childStride - 1; 142 | final int unRenderedChildCount = count - renderedChildCount; 143 | if (renderedChildCount > 0) { 144 | childParentData.offstage = false; 145 | onstageCount++; 146 | } 147 | 148 | for (int i = 1; i < renderedChildCount; i++) { 149 | child = childParentData.nextSibling!; 150 | childParentData = child.parentData as OverflowViewParentData; 151 | child.layout(otherChildConstraints); 152 | childParentData.offset = getChildOffset(i); 153 | childParentData.offstage = false; 154 | onstageCount++; 155 | } 156 | 157 | while (child != lastChild) { 158 | child = childParentData.nextSibling!; 159 | childParentData = child.parentData as OverflowViewParentData; 160 | childParentData.offstage = true; 161 | } 162 | 163 | if (unRenderedChildCount > 0) { 164 | // We have to layout the overflow indicator. 165 | final RenderBox overflowIndicator = lastChild!; 166 | 167 | final BoxValueConstraints overflowIndicatorConstraints = 168 | BoxValueConstraints( 169 | value: unRenderedChildCount, 170 | constraints: otherChildConstraints, 171 | ); 172 | overflowIndicator.layout(overflowIndicatorConstraints); 173 | final OverflowViewParentData overflowIndicatorParentData = 174 | overflowIndicator.parentData as OverflowViewParentData; 175 | overflowIndicatorParentData.offset = getChildOffset(renderedChildCount); 176 | overflowIndicatorParentData.offstage = false; 177 | onstageCount++; 178 | } 179 | 180 | final double mainAxisExtent = onstageCount * childStride - spacing; 181 | final requestedSize = _isHorizontal 182 | ? Size(mainAxisExtent, crossExtent) 183 | : Size(crossExtent, mainAxisExtent); 184 | 185 | size = constraints.constrain(requestedSize); 186 | } 187 | 188 | void performFlexibleLayout() { 189 | RenderBox child = firstChild!; 190 | List renderBoxes = []; 191 | int unRenderedChildCount = childCount - 1; 192 | double availableExtent = 193 | _isHorizontal ? constraints.maxWidth : constraints.maxHeight; 194 | double offset = 0; 195 | final double maxCrossExtent = 196 | _isHorizontal ? constraints.maxHeight : constraints.maxWidth; 197 | 198 | final BoxConstraints childConstraints = _isHorizontal 199 | ? BoxConstraints.loose(Size(double.infinity, maxCrossExtent)) 200 | : BoxConstraints.loose(Size(maxCrossExtent, double.infinity)); 201 | 202 | bool showOverflowIndicator = false; 203 | while (child != lastChild) { 204 | final OverflowViewParentData childParentData = 205 | child.parentData as OverflowViewParentData; 206 | 207 | child.layout(childConstraints, parentUsesSize: true); 208 | 209 | final double childMainSize = _getMainSize(child); 210 | 211 | if (childMainSize <= availableExtent) { 212 | // We have room to paint this child. 213 | renderBoxes.add(child); 214 | childParentData.offstage = false; 215 | childParentData.offset = 216 | _isHorizontal ? Offset(offset, 0) : Offset(0, offset); 217 | 218 | final double childStride = spacing + childMainSize; 219 | offset += childStride; 220 | availableExtent -= childStride; 221 | unRenderedChildCount--; 222 | child = childParentData.nextSibling!; 223 | } else { 224 | // We have no room to paint any further child. 225 | showOverflowIndicator = true; 226 | break; 227 | } 228 | } 229 | 230 | if (showOverflowIndicator) { 231 | // We didn't layout all the children. 232 | final RenderBox overflowIndicator = lastChild!; 233 | final BoxValueConstraints overflowIndicatorConstraints = 234 | BoxValueConstraints( 235 | value: unRenderedChildCount, 236 | constraints: childConstraints, 237 | ); 238 | overflowIndicator.layout( 239 | overflowIndicatorConstraints, 240 | parentUsesSize: true, 241 | ); 242 | 243 | final double childMainSize = _getMainSize(overflowIndicator); 244 | 245 | // We need to remove the children that prevent the overflowIndicator 246 | // to paint. 247 | while (childMainSize > availableExtent && renderBoxes.isNotEmpty) { 248 | final RenderBox child = renderBoxes.removeLast(); 249 | final OverflowViewParentData childParentData = 250 | child.parentData as OverflowViewParentData; 251 | childParentData.offstage = true; 252 | final double childStride = _getMainSize(child) + spacing; 253 | 254 | availableExtent += childStride; 255 | unRenderedChildCount++; 256 | offset -= childStride; 257 | } 258 | 259 | if (childMainSize > availableExtent) { 260 | // We cannot paint any child because there is not enough space. 261 | _hasOverflow = true; 262 | } 263 | 264 | if (overflowIndicatorConstraints.value != unRenderedChildCount) { 265 | // The number of unrendered child changed, we have to layout the 266 | // indicator another time. 267 | overflowIndicator.layout( 268 | BoxValueConstraints( 269 | value: unRenderedChildCount, 270 | constraints: childConstraints, 271 | ), 272 | parentUsesSize: true, 273 | ); 274 | } 275 | 276 | renderBoxes.add(overflowIndicator); 277 | 278 | final OverflowViewParentData overflowIndicatorParentData = 279 | overflowIndicator.parentData as OverflowViewParentData; 280 | overflowIndicatorParentData.offset = 281 | _isHorizontal ? Offset(offset, 0) : Offset(0, offset); 282 | overflowIndicatorParentData.offstage = false; 283 | offset += childMainSize; 284 | } else { 285 | // We layout all children. We need to adjust the offset used to compute 286 | // the final size. 287 | offset -= spacing; 288 | 289 | // We need to layout the overflowIndicator because we may have already 290 | // laid it out with parentUsesSize: true before. 291 | // When unmounting a _LayoutBuilderElement, it calls markNeedsLayout 292 | // a last time, and can cause error. 293 | lastChild?.layout(BoxValueConstraints( 294 | value: unRenderedChildCount, 295 | constraints: childConstraints, 296 | )); 297 | 298 | // Because the overflow indicator will be paint outside of the screen, 299 | // we need to say that there is an overflow. 300 | _hasOverflow = true; 301 | } 302 | 303 | final double crossSize = renderBoxes.fold( 304 | 0, 305 | (previousValue, element) => math.max( 306 | previousValue, 307 | _getCrossSize(element), 308 | ), 309 | ); 310 | 311 | // By default we center all children in the cross-axis. 312 | for (final child in renderBoxes) { 313 | final double childCrossPosition = 314 | crossSize / 2.0 - _getCrossSize(child) / 2.0; 315 | final OverflowViewParentData childParentData = 316 | child.parentData as OverflowViewParentData; 317 | childParentData.offset = _isHorizontal 318 | ? Offset(childParentData.offset.dx, childCrossPosition) 319 | : Offset(childCrossPosition, childParentData.offset.dy); 320 | } 321 | 322 | Size idealSize; 323 | if (_isHorizontal) { 324 | idealSize = Size(offset, crossSize); 325 | } else { 326 | idealSize = Size(crossSize, offset); 327 | } 328 | 329 | size = constraints.constrain(idealSize); 330 | } 331 | 332 | void visitOnlyOnStageChildren(RenderObjectVisitor visitor) { 333 | visitChildren((child) { 334 | if (child.isOnstage) { 335 | visitor(child); 336 | } 337 | }); 338 | } 339 | 340 | @override 341 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { 342 | visitOnlyOnStageChildren(visitor); 343 | } 344 | 345 | @override 346 | void paint(PaintingContext context, Offset offset) { 347 | void paintChild(RenderObject child) { 348 | final OverflowViewParentData childParentData = 349 | child.parentData as OverflowViewParentData; 350 | if (childParentData.offstage == false) { 351 | context.paintChild(child, childParentData.offset + offset); 352 | } else { 353 | // We paint it outside the box. 354 | context.paintChild(child, size.bottomRight(Offset.zero)); 355 | } 356 | } 357 | 358 | void defaultPaint(PaintingContext context, Offset offset) { 359 | visitOnlyOnStageChildren(paintChild); 360 | } 361 | 362 | if (_hasOverflow) { 363 | context.pushClipRect( 364 | needsCompositing, 365 | offset, 366 | Offset.zero & size, 367 | defaultPaint, 368 | clipBehavior: Clip.hardEdge, 369 | ); 370 | } else { 371 | defaultPaint(context, offset); 372 | } 373 | } 374 | 375 | @override 376 | bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { 377 | // The x, y parameters have the top left of the node's box as the origin. 378 | visitOnlyOnStageChildren((renderObject) { 379 | final RenderBox child = renderObject as RenderBox; 380 | final OverflowViewParentData childParentData = 381 | child.parentData as OverflowViewParentData; 382 | result.addWithPaintOffset( 383 | offset: childParentData.offset, 384 | position: position, 385 | hitTest: (BoxHitTestResult result, Offset transformed) { 386 | assert(transformed == position - childParentData.offset); 387 | return child.hitTest(result, position: transformed); 388 | }, 389 | ); 390 | }); 391 | 392 | return false; 393 | } 394 | } 395 | 396 | extension on Size { 397 | double getMainExtent(Axis axis) { 398 | return axis == Axis.horizontal ? width : height; 399 | } 400 | 401 | double getCrossExtent(Axis axis) { 402 | return axis == Axis.horizontal ? height : width; 403 | } 404 | } 405 | 406 | extension RenderObjectExtensions on RenderObject { 407 | bool get isOnstage => 408 | (parentData as OverflowViewParentData).offstage == false; 409 | } 410 | -------------------------------------------------------------------------------- /lib/src/widgets/overflow_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:overflow_view/src/rendering/overflow_view.dart'; 3 | import 'package:value_layout_builder/value_layout_builder.dart'; 4 | 5 | /// Signature for a builder that creates an overflow indicator when there is not 6 | /// enough space to display all the children of an [OverflowView]. 7 | typedef OverflowIndicatorBuilder = Widget Function( 8 | BuildContext context, 9 | int remainingItemCount, 10 | ); 11 | 12 | /// A widget that displays its children in a one-dimensional array until there 13 | /// is no more room. If all the children don't fit in the available space, it 14 | /// displays an indicator at the end. 15 | /// 16 | /// All chidren will have the same size as the first child. 17 | class OverflowView extends MultiChildRenderObjectWidget { 18 | /// Creates an [OverflowView]. 19 | /// 20 | /// All children will have the same size has the first child. 21 | /// 22 | /// The [spacing] argument must also be positive and finite. 23 | OverflowView({ 24 | Key? key, 25 | required OverflowIndicatorBuilder builder, 26 | Axis direction = Axis.horizontal, 27 | required List children, 28 | double spacing = 0, 29 | }) : this._all( 30 | key: key, 31 | builder: builder, 32 | direction: direction, 33 | children: children, 34 | spacing: spacing, 35 | layoutBehavior: OverflowViewLayoutBehavior.fixed, 36 | ); 37 | 38 | /// Creates a flexible [OverflowView]. 39 | /// 40 | /// All children can have their own size. 41 | /// 42 | /// The [spacing] argument must also be positive and finite. 43 | OverflowView.flexible({ 44 | Key? key, 45 | required OverflowIndicatorBuilder builder, 46 | Axis direction = Axis.horizontal, 47 | required List children, 48 | double spacing = 0, 49 | }) : this._all( 50 | key: key, 51 | builder: builder, 52 | direction: direction, 53 | children: children, 54 | spacing: spacing, 55 | layoutBehavior: OverflowViewLayoutBehavior.flexible, 56 | ); 57 | 58 | OverflowView._all({ 59 | Key? key, 60 | required OverflowIndicatorBuilder builder, 61 | this.direction = Axis.horizontal, 62 | required List children, 63 | this.spacing = 0, 64 | required OverflowViewLayoutBehavior layoutBehavior, 65 | }) : assert(spacing > double.negativeInfinity && 66 | spacing < double.infinity), 67 | _layoutBehavior = layoutBehavior, 68 | super( 69 | key: key, 70 | children: [ 71 | ...children, 72 | ValueLayoutBuilder( 73 | builder: (context, constraints) { 74 | return builder(context, constraints.value); 75 | }, 76 | ), 77 | ], 78 | ); 79 | 80 | /// The direction to use as the main axis. 81 | /// 82 | /// For example, if [direction] is [Axis.horizontal], the default, the 83 | /// children are placed adjacent to one another as in a [Row]. 84 | final Axis direction; 85 | 86 | /// The amount of space between successive children. 87 | final double spacing; 88 | 89 | final OverflowViewLayoutBehavior _layoutBehavior; 90 | 91 | @override 92 | _OverflowViewElement createElement() { 93 | return _OverflowViewElement(this); 94 | } 95 | 96 | @override 97 | RenderOverflowView createRenderObject(BuildContext context) { 98 | return RenderOverflowView( 99 | direction: direction, 100 | spacing: spacing, 101 | layoutBehavior: _layoutBehavior, 102 | ); 103 | } 104 | 105 | @override 106 | void updateRenderObject( 107 | BuildContext context, 108 | RenderOverflowView renderObject, 109 | ) { 110 | renderObject 111 | ..direction = direction 112 | ..spacing = spacing 113 | ..layoutBehavior = _layoutBehavior; 114 | } 115 | } 116 | 117 | class _OverflowViewElement extends MultiChildRenderObjectElement { 118 | _OverflowViewElement(OverflowView widget) : super(widget); 119 | 120 | @override 121 | void debugVisitOnstageChildren(ElementVisitor visitor) { 122 | children.forEach((element) { 123 | if (element.renderObject?.isOnstage == true) { 124 | visitor(element); 125 | } 126 | }); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /packages/images/overflow_view.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/letsar/overflow_view/caefbcf69e828b7a7003778c4d36f68b01257c84/packages/images/overflow_view.gif -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: overflow_view 2 | description: A widget displaying children in a line with an overflow indicator at the end if there is not enough space. 3 | version: 0.5.0 4 | homepage: https://github.com/letsar/overflow_view 5 | 6 | environment: 7 | sdk: ">=2.17.0 <4.0.0" 8 | flutter: ">=3.32.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | value_layout_builder: ^0.5.0 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | -------------------------------------------------------------------------------- /test/overflow_view_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | import 'package:overflow_view/overflow_view.dart'; 5 | 6 | void main() { 7 | 8 | testWidgets( 9 | 'the overflow indicator is not built if there is enough room (except for flexible)', 10 | (tester) async { 11 | int buildCount = 0; 12 | await tester.pumpWidget( 13 | Center( 14 | child: SizedBox( 15 | width: 100, 16 | child: OverflowView( 17 | builder: (context, count) { 18 | buildCount++; 19 | return const SizedBox(); 20 | }, 21 | children: [const SizedBox(width: 100)], 22 | ), 23 | ), 24 | ), 25 | ); 26 | expect(buildCount, 0); 27 | 28 | await tester.pumpWidget( 29 | Center( 30 | child: SizedBox( 31 | width: 100, 32 | child: OverflowView.flexible( 33 | builder: (context, count) { 34 | buildCount++; 35 | return const SizedBox(); 36 | }, 37 | children: [const SizedBox(width: 100)], 38 | ), 39 | ), 40 | ), 41 | ); 42 | expect(buildCount, 1); 43 | }, 44 | ); 45 | 46 | testWidgets( 47 | 'the overflow indicator is built if there is not enough room', 48 | (tester) async { 49 | int buildCount = 0; 50 | late int remainingCount; 51 | await tester.pumpWidget( 52 | Center( 53 | child: SizedBox( 54 | width: 100, 55 | child: OverflowView( 56 | builder: (context, count) { 57 | buildCount++; 58 | remainingCount = count; 59 | return const SizedBox(); 60 | }, 61 | children: [ 62 | const SizedBox(width: 50), 63 | const SizedBox(width: 50), 64 | const SizedBox(width: 50), 65 | const SizedBox(width: 50), 66 | ], 67 | ), 68 | ), 69 | ), 70 | ); 71 | expect(buildCount, 1); 72 | expect(remainingCount, 3); 73 | 74 | buildCount = 0; 75 | 76 | await tester.pumpWidget( 77 | Center( 78 | child: SizedBox( 79 | width: 100, 80 | child: OverflowView.flexible( 81 | builder: (context, count) { 82 | buildCount++; 83 | remainingCount = count; 84 | return const SizedBox(width: 30); 85 | }, 86 | children: [ 87 | const SizedBox(width: 50), 88 | const SizedBox(width: 20), 89 | const SizedBox(width: 50), 90 | const SizedBox(width: 50), 91 | ], 92 | ), 93 | ), 94 | ), 95 | ); 96 | expect(buildCount, 1); 97 | expect(remainingCount, 2); 98 | }, 99 | ); 100 | 101 | testWidgets( 102 | 'children are layed out according to direction', 103 | (tester) async { 104 | await tester.pumpWidget( 105 | Directionality( 106 | textDirection: TextDirection.ltr, 107 | child: Align( 108 | alignment: Alignment.topLeft, 109 | child: SizedBox( 110 | height: 100, 111 | width: 100, 112 | child: OverflowView( 113 | builder: (context, count) { 114 | return const SizedBox(); 115 | }, 116 | children: [ 117 | const _Text('A'), 118 | const _Text('B'), 119 | ], 120 | ), 121 | ), 122 | ), 123 | ), 124 | ); 125 | 126 | expect(tester.getTopLeft(find.text('A')), const Offset(0, 0)); 127 | expect(tester.getTopLeft(find.text('B')), const Offset(50, 0)); 128 | 129 | await tester.pumpWidget( 130 | Directionality( 131 | textDirection: TextDirection.ltr, 132 | child: Align( 133 | alignment: Alignment.topLeft, 134 | child: SizedBox( 135 | height: 100, 136 | width: 100, 137 | child: OverflowView( 138 | direction: Axis.vertical, 139 | builder: (context, count) { 140 | return const SizedBox(); 141 | }, 142 | children: [ 143 | const _Text('A'), 144 | const _Text('B'), 145 | ], 146 | ), 147 | ), 148 | ), 149 | ), 150 | ); 151 | 152 | expect(tester.getTopLeft(find.text('A')), const Offset(0, 0)); 153 | expect(tester.getTopLeft(find.text('B')), const Offset(0, 50)); 154 | }, 155 | ); 156 | 157 | testWidgets( 158 | 'children are layed out according to direction and spacing', 159 | (tester) async { 160 | await tester.pumpWidget( 161 | Directionality( 162 | textDirection: TextDirection.ltr, 163 | child: Align( 164 | alignment: Alignment.topLeft, 165 | child: SizedBox( 166 | height: 110, 167 | width: 110, 168 | child: OverflowView( 169 | spacing: 10, 170 | builder: (context, count) { 171 | return const SizedBox(); 172 | }, 173 | children: [ 174 | const _Text('A'), 175 | const _Text('B'), 176 | ], 177 | ), 178 | ), 179 | ), 180 | ), 181 | ); 182 | 183 | expect(tester.getTopLeft(find.text('A')), const Offset(0, 0)); 184 | expect(tester.getTopLeft(find.text('B')), const Offset(60, 0)); 185 | 186 | await tester.pumpWidget( 187 | Directionality( 188 | textDirection: TextDirection.ltr, 189 | child: Align( 190 | alignment: Alignment.topLeft, 191 | child: SizedBox( 192 | height: 110, 193 | width: 110, 194 | child: OverflowView( 195 | spacing: 10, 196 | direction: Axis.vertical, 197 | builder: (context, count) { 198 | return const SizedBox(); 199 | }, 200 | children: [ 201 | const _Text('A'), 202 | const _Text('B'), 203 | ], 204 | ), 205 | ), 206 | ), 207 | ), 208 | ); 209 | 210 | expect(tester.getTopLeft(find.text('A')), const Offset(0, 0)); 211 | expect(tester.getTopLeft(find.text('B')), const Offset(0, 60)); 212 | 213 | await tester.pumpWidget( 214 | Directionality( 215 | textDirection: TextDirection.ltr, 216 | child: Align( 217 | alignment: Alignment.topLeft, 218 | child: SizedBox( 219 | height: 110, 220 | width: 110, 221 | child: OverflowView.flexible( 222 | spacing: 10, 223 | builder: (context, count) { 224 | return const SizedBox(); 225 | }, 226 | children: [ 227 | const _Text('A'), 228 | const _Text('B'), 229 | ], 230 | ), 231 | ), 232 | ), 233 | ), 234 | ); 235 | 236 | expect(tester.getTopLeft(find.text('A')), const Offset(0, 0)); 237 | expect(tester.getTopLeft(find.text('B')), const Offset(60, 0)); 238 | 239 | await tester.pumpWidget( 240 | Directionality( 241 | textDirection: TextDirection.ltr, 242 | child: Align( 243 | alignment: Alignment.topLeft, 244 | child: SizedBox( 245 | height: 110, 246 | width: 110, 247 | child: OverflowView.flexible( 248 | spacing: 10, 249 | direction: Axis.vertical, 250 | builder: (context, count) { 251 | return const SizedBox(); 252 | }, 253 | children: [ 254 | const _Text('A'), 255 | const _Text('B'), 256 | ], 257 | ), 258 | ), 259 | ), 260 | ), 261 | ); 262 | 263 | expect(tester.getTopLeft(find.text('A')), const Offset(0, 0)); 264 | expect(tester.getTopLeft(find.text('B')), const Offset(0, 60)); 265 | }, 266 | ); 267 | 268 | testWidgets( 269 | 'spacing can be negative for overlapping effect', 270 | (tester) async { 271 | await tester.pumpWidget( 272 | Directionality( 273 | textDirection: TextDirection.ltr, 274 | child: Align( 275 | alignment: Alignment.topLeft, 276 | child: SizedBox( 277 | height: 110, 278 | width: 110, 279 | child: OverflowView( 280 | spacing: -10, 281 | builder: (context, count) { 282 | return const SizedBox(); 283 | }, 284 | children: [ 285 | const _Text('A'), 286 | const _Text('B'), 287 | ], 288 | ), 289 | ), 290 | ), 291 | ), 292 | ); 293 | 294 | expect(tester.getTopLeft(find.text('A')), const Offset(0, 0)); 295 | expect(tester.getTopLeft(find.text('B')), const Offset(40, 0)); 296 | }, 297 | ); 298 | 299 | testWidgets( 300 | 'OverflowView.flexible should build builder twice if there is not enough room for it the first time', 301 | (tester) async { 302 | int buildCount = 0; 303 | await tester.pumpWidget( 304 | Directionality( 305 | textDirection: TextDirection.ltr, 306 | child: Align( 307 | alignment: Alignment.topLeft, 308 | child: SizedBox( 309 | height: 150, 310 | width: 150, 311 | child: OverflowView.flexible( 312 | builder: (context, count) { 313 | buildCount++; 314 | return const SizedBox( 315 | width: 100, 316 | ); 317 | }, 318 | children: [ 319 | const _Text('A'), 320 | const _Text('B'), 321 | const _Text('C'), 322 | const _Text('D'), 323 | ], 324 | ), 325 | ), 326 | ), 327 | ), 328 | ); 329 | 330 | expect(buildCount, 2); 331 | expect(find.text('A'), findsOneWidget); 332 | expect(find.text('B'), findsNothing); 333 | expect(find.text('C'), findsNothing); 334 | expect(find.text('E'), findsNothing); 335 | }, 336 | ); 337 | 338 | testWidgets( 339 | 'unmount successfully the overflow indicator once it has already been laid ' 340 | 'out with .flexible()', 341 | (tester) async { 342 | await tester.pumpWidget( 343 | Center( 344 | child: SizedBox( 345 | width: 100, 346 | child: OverflowView.flexible( 347 | builder: (context, count) { 348 | return const SizedBox(width: 30); 349 | }, 350 | children: [ 351 | const SizedBox(width: 50), 352 | const SizedBox(width: 20), 353 | const SizedBox(width: 50), 354 | const SizedBox(width: 50), 355 | ], 356 | ), 357 | ), 358 | ), 359 | ); 360 | 361 | await tester.pumpWidget( 362 | Center( 363 | child: SizedBox( 364 | width: 100, 365 | child: OverflowView.flexible( 366 | builder: (context, count) { 367 | return const SizedBox(width: 30); 368 | }, 369 | children: [ 370 | const SizedBox(width: 50), 371 | ], 372 | ), 373 | ), 374 | ), 375 | ); 376 | }, 377 | ); 378 | } 379 | 380 | class _Text extends StatelessWidget { 381 | const _Text( 382 | this.text, { 383 | Key? key, 384 | }) : super(key: key); 385 | 386 | final String text; 387 | 388 | @override 389 | Widget build(BuildContext context) { 390 | return SizedBox( 391 | height: 50, 392 | width: 50, 393 | child: Text(text), 394 | ); 395 | } 396 | } 397 | --------------------------------------------------------------------------------