├── example ├── ios │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── 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-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── .gitignore ├── .firebaserc ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── manifest.json │ └── index.html ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── 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 │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── macos │ └── Flutter │ │ ├── GeneratedPluginRegistrant.swift │ │ └── ephemeral │ │ ├── Flutter-Generated.xcconfig │ │ └── flutter_export_environment.sh ├── linux │ └── flutter │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake ├── firebase.json ├── pubspec.yaml ├── README.md ├── .gitignore ├── lib │ ├── list_example.dart │ └── main.dart ├── .metadata ├── pubspec.lock └── analysis_options.yaml ├── assets └── readme │ ├── highlight_example.png │ ├── package_profile.webp │ └── example_presentation.gif ├── lib ├── src │ ├── enums │ │ ├── arrow_direction.dart │ │ ├── popup_click_trigger_behavior.dart │ │ └── popup_dismiss_trigger_behavior.dart │ ├── typedefs │ │ ├── on_area_pressed.dart │ │ └── on_controller_created.dart │ ├── extensions │ │ └── context_extensions.dart │ ├── constants │ │ └── popup_constants.dart │ ├── themes │ │ ├── high_light_theme.dart │ │ ├── info_popup_arrow_theme.dart │ │ └── info_popup_content_theme.dart │ ├── painters │ │ ├── high_lighter.dart │ │ └── arrow_indicator_painter.dart │ ├── controllers │ │ └── info_popup_controller.dart │ ├── info_popup_widget.dart │ └── overlays │ │ └── overlay_entry_layout.dart └── info_popup.dart ├── .metadata ├── .github └── workflows │ └── stale.yml ├── .gitignore ├── pubspec.yaml ├── LICENSE ├── test └── info_popup_test.dart ├── CHANGELOG.md ├── analysis_options.yaml └── README.md /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "info-popup" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /assets/readme/highlight_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/assets/readme/highlight_example.png -------------------------------------------------------------------------------- /assets/readme/package_profile.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/assets/readme/package_profile.webp -------------------------------------------------------------------------------- /assets/readme/example_presentation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/assets/readme/example_presentation.gif -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/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/SalihCanBinboga/info_popup/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SalihCanBinboga/info_popup/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/enums/arrow_direction.dart: -------------------------------------------------------------------------------- 1 | /// [ArrowDirection] is used to specify the direction of the arrow. 2 | enum ArrowDirection { 3 | /// The arrow is pointing up. 4 | up, 5 | 6 | /// The arrow is pointing down. 7 | down, 8 | } 9 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | 9 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 10 | } 11 | -------------------------------------------------------------------------------- /lib/src/typedefs/on_area_pressed.dart: -------------------------------------------------------------------------------- 1 | import 'package:info_popup/src/controllers/info_popup_controller.dart'; 2 | 3 | /// [OnAreaPressed] is called when the tap area is pressed. 4 | typedef OnAreaPressed = void Function(InfoPopupController controller); 5 | -------------------------------------------------------------------------------- /lib/src/typedefs/on_controller_created.dart: -------------------------------------------------------------------------------- 1 | import 'package:info_popup/info_popup.dart'; 2 | 3 | /// [OnControllerCreated] is called when the [InfoPopupController] is created. 4 | typedef OnControllerCreated = void Function(InfoPopupController controller); 5 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void fl_register_plugins(FlPluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /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-7.4-all.zip 6 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build/web", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ], 9 | "rewrites": [ 10 | { 11 | "source": "**", 12 | "destination": "/index.html" 13 | } 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.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: 5464c5bac742001448fe4fc0597be939379f88ea 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: Info Popup Example 3 | publish_to: 'none' 4 | version: 1.0.0+2 5 | 6 | environment: 7 | sdk: '>=2.17.3 <3.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | info_popup: 13 | path: ../ 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | flutter: 20 | uses-material-design: true 21 | -------------------------------------------------------------------------------- /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/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 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 | -------------------------------------------------------------------------------- /lib/src/enums/popup_click_trigger_behavior.dart: -------------------------------------------------------------------------------- 1 | /// [PopupClickTriggerBehavior] is used to specify the trigger behavior of the info popup. 2 | enum PopupClickTriggerBehavior { 3 | /// [onTap] is used to trigger the popup when the user taps on the child widget. 4 | onTap, 5 | 6 | /// [onLongPress] is used to trigger the popup when the user long presses on the child widget. 7 | onLongPress, 8 | 9 | /// [none] is used to disable the trigger behavior of the popup. 10 | none, 11 | } 12 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Close Stale Issues 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | close_stale_issues: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Close Stale Issues 12 | uses: actions/stale@v9.0.0 13 | with: 14 | stale-issue-message: 'This issue has been automatically closed due to inactivity. Please feel free to reopen it if you are still experiencing this problem.' 15 | days-before-stale: 14 16 | days-before-close: 7 17 | stale-issue-label: 'stale' -------------------------------------------------------------------------------- /example/macos/Flutter/ephemeral/Flutter-Generated.xcconfig: -------------------------------------------------------------------------------- 1 | // This is a generated file; do not edit or check into version control. 2 | FLUTTER_ROOT=/Users/salihcanbinboga/development/sdk/fvm/versions/3.13.6 3 | FLUTTER_APPLICATION_PATH=/Users/salihcanbinboga/development/flutter_projects/personal/case/info_popup/example 4 | COCOAPODS_PARALLEL_CODE_SIGN=true 5 | FLUTTER_BUILD_DIR=build 6 | FLUTTER_BUILD_NAME=1.0.0 7 | FLUTTER_BUILD_NUMBER=2 8 | DART_OBFUSCATION=false 9 | TRACK_WIDGET_CREATION=true 10 | TREE_SHAKE_ICONS=false 11 | PACKAGE_CONFIG=.dart_tool/package_config.json 12 | -------------------------------------------------------------------------------- /lib/src/enums/popup_dismiss_trigger_behavior.dart: -------------------------------------------------------------------------------- 1 | /// [PopupDismissTriggerBehavior] is used to specify the trigger behavior of the info popup. 2 | enum PopupDismissTriggerBehavior { 3 | /// [onTapContent] is used to dismiss the popup when the content is tapped. 4 | onTapContent, 5 | 6 | /// [onTapArea] is used to dismiss the popup when the area outside the popup is tapped. 7 | onTapArea, 8 | 9 | /// [anyWhere] is used to dismiss the popup when anywhere is tapped. 10 | anyWhere, 11 | 12 | /// [manuel] is used to dismiss the popup manually. 13 | manuel, 14 | } 15 | -------------------------------------------------------------------------------- /lib/info_popup.dart: -------------------------------------------------------------------------------- 1 | library info_popup; 2 | 3 | export 'src/constants/popup_constants.dart'; 4 | export 'src/controllers/info_popup_controller.dart'; 5 | export 'src/enums/arrow_direction.dart'; 6 | export 'src/enums/popup_click_trigger_behavior.dart'; 7 | export 'src/enums/popup_dismiss_trigger_behavior.dart'; 8 | export 'src/info_popup_widget.dart'; 9 | export 'src/themes/high_light_theme.dart'; 10 | export 'src/themes/info_popup_arrow_theme.dart'; 11 | export 'src/themes/info_popup_content_theme.dart'; 12 | export 'src/typedefs/on_area_pressed.dart'; 13 | export 'src/typedefs/on_controller_created.dart'; 14 | -------------------------------------------------------------------------------- /lib/src/extensions/context_extensions.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// [BuildContextExtensions] is a class that contains extensions for [BuildContext] 4 | extension BuildContextExtensions on BuildContext { 5 | /// Returns the [MediaQueryData] from the closest instance that encloses the given context. 6 | MediaQueryData get mediaQuery => MediaQuery.of(this); 7 | 8 | /// Return the [screenWidth] of the screen 9 | double get screenWidth => mediaQuery.size.width; 10 | 11 | /// Return the [screenHeight] of the screen 12 | double get screenHeight => mediaQuery.size.height; 13 | } 14 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | Info Popup Example 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://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /.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 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 25 | /pubspec.lock 26 | **/doc/api/ 27 | .dart_tool/ 28 | .packages 29 | build/ 30 | -------------------------------------------------------------------------------- /example/macos/Flutter/ephemeral/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/salihcanbinboga/development/sdk/fvm/versions/3.13.6" 4 | export "FLUTTER_APPLICATION_PATH=/Users/salihcanbinboga/development/flutter_projects/personal/case/info_popup/example" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "FLUTTER_BUILD_NAME=1.0.0" 8 | export "FLUTTER_BUILD_NUMBER=2" 9 | export "DART_OBFUSCATION=false" 10 | export "TRACK_WIDGET_CREATION=true" 11 | export "TREE_SHAKE_ICONS=false" 12 | export "PACKAGE_CONFIG=.dart_tool/package_config.json" 13 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: info_popup 2 | description: The simple way to show the user some information on your selected widget. 3 | version: 4.3.2 4 | maintainer: Salih Can Binboga 5 | homepage: https://github.com/salihcanbinboga/info_popup 6 | repository: https://github.com/salihcanbinboga/info_popup 7 | issue_tracker: https://github.com/salihcanbinboga/info_popup/issues 8 | screenshots: 9 | - description: "Info Popup Logo" 10 | path: assets/readme/package_profile.webp 11 | topics: 12 | - popup 13 | - modal 14 | - dialog 15 | - alert 16 | - tooltip 17 | 18 | environment: 19 | sdk: '>=2.18.4 <4.0.0' 20 | flutter: '>=2.0.0' 21 | 22 | dependencies: 23 | flutter: 24 | sdk: flutter 25 | 26 | dev_dependencies: 27 | example: 28 | path: example 29 | flutter_lints: ^1.0.0 30 | flutter_test: 31 | sdk: flutter 32 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /lib/src/constants/popup_constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// [PopupConstants] is some constants used in the popup. 4 | mixin PopupConstants { 5 | /// [defaultArrowSize] is used to create a default arrow size. 6 | static const Size defaultArrowSize = Size(15, 10); 7 | 8 | /// [defaultInfoTextStyle] is used to create a default info text style. 9 | static const TextStyle defaultInfoTextStyle = TextStyle( 10 | color: Colors.black, 11 | fontSize: 14.0, 12 | ); 13 | 14 | /// [defaultContentBorderRadius] is used to create a default content border radius. 15 | static const BorderRadius defaultContentBorderRadius = 16 | BorderRadius.all(Radius.circular(8)); 17 | 18 | /// [defaultAreaBackgroundColor] is used to create a default area background color. 19 | static const Color defaultAreaBackgroundColor = Colors.transparent; 20 | } 21 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | /.firebase/ 46 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "Info Popup Example", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /example/lib/list_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:info_popup/info_popup.dart'; 3 | 4 | class ListExample extends StatelessWidget { 5 | const ListExample({Key? key}) : super(key: key); 6 | 7 | @override 8 | Widget build(BuildContext context) { 9 | return Scaffold( 10 | appBar: AppBar( 11 | title: const Text('List Example'), 12 | ), 13 | body: SizedBox( 14 | width: MediaQuery.of(context).size.width, 15 | child: ListView.builder( 16 | itemCount: 120, 17 | cacheExtent: 10000, 18 | itemBuilder: (_, int index) { 19 | return ListTile( 20 | title: Text('Item $index'), 21 | leading: const InfoPopupWidget( 22 | contentTitle: 'Lorem ipsum dolor sit amet', 23 | dismissTriggerBehavior: 24 | PopupDismissTriggerBehavior.onTapContent, 25 | child: Icon(Icons.info), 26 | ), 27 | ); 28 | }, 29 | ), 30 | ), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Salih Can Binboga 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. -------------------------------------------------------------------------------- /lib/src/themes/high_light_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart' 2 | show BorderRadius, Color, Colors, EdgeInsets; 3 | 4 | /// The [HighLightTheme] class is used to customize the highlighter. 5 | class HighLightTheme { 6 | /// Creates a [HighLightTheme]. 7 | HighLightTheme({ 8 | required this.backgroundColor, 9 | this.padding = EdgeInsets.zero, 10 | this.radius = BorderRadius.zero, 11 | }) : assert( 12 | backgroundColor != Colors.transparent, 13 | '\n\nThe backgroundColor can not be transparent. \n' 14 | 'Description: The backgroundColor can not be transparent. ' 15 | 'Please use another color. \n \n' 16 | 'For example: Colors.black.withOpacity(.5) \n', 17 | ); 18 | 19 | /// Default [HighLightTheme]. 20 | factory HighLightTheme.defaultTheme() => HighLightTheme( 21 | backgroundColor: Colors.black.withOpacity(.5), 22 | ); 23 | 24 | /// The [radius] of the highlighter. 25 | final BorderRadius radius; 26 | 27 | /// The [padding] of the highlighter. 28 | final EdgeInsets padding; 29 | 30 | /// The [backgroundColor] of background area color. 31 | final Color backgroundColor; 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/painters/high_lighter.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: use_late_for_private_fields_and_variables 2 | 3 | part of '../controllers/info_popup_controller.dart'; 4 | 5 | class _HighLighter extends CustomClipper { 6 | _HighLighter({ 7 | this.area = Rect.zero, 8 | this.radius = BorderRadius.zero, 9 | this.padding = EdgeInsets.zero, 10 | }); 11 | 12 | final Rect area; 13 | final BorderRadius radius; 14 | final EdgeInsets padding; 15 | 16 | @override 17 | Path getClip(Size size) { 18 | final Rect rect = Rect.fromLTRB( 19 | area.left - padding.left, 20 | area.top - padding.top, 21 | area.right + padding.right, 22 | area.bottom + padding.bottom, 23 | ); 24 | 25 | return Path() 26 | ..fillType = PathFillType.evenOdd 27 | ..addRect(Offset.zero & size) 28 | ..addRRect( 29 | RRect.fromRectAndCorners( 30 | rect, 31 | topLeft: radius.topLeft, 32 | topRight: radius.topRight, 33 | bottomLeft: radius.bottomLeft, 34 | bottomRight: radius.bottomRight, 35 | ), 36 | ); 37 | } 38 | 39 | @override 40 | bool shouldReclip(covariant CustomClipper oldClipper) => 41 | oldClipper is _HighLighter && 42 | (radius != oldClipper.radius || area != oldClipper.area); 43 | } 44 | -------------------------------------------------------------------------------- /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. 5 | 6 | version: 7 | revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 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: ffccd96b62ee8cec7740dab303538c5fc26ac543 17 | base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 18 | - platform: android 19 | create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 20 | base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 21 | - platform: ios 22 | create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 23 | base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 24 | - platform: web 25 | create_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 26 | base_revision: ffccd96b62ee8cec7740dab303538c5fc26ac543 27 | 28 | # User provided section 29 | 30 | # List of Local paths (relative to this file) that should be 31 | # ignored by the migrate tool. 32 | # 33 | # Files that are not part of the templates will be ignored by default. 34 | unmanaged_files: 35 | - 'lib/main.dart' 36 | - 'ios/Runner.xcodeproj/project.pbxproj' 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/src/painters/arrow_indicator_painter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:info_popup/src/enums/arrow_direction.dart'; 3 | 4 | /// [ArrowIndicatorPainter] is custom painter for drawing a triangle indicator for popup 5 | /// to point specific widget 6 | class ArrowIndicatorPainter extends CustomPainter { 7 | /// Creates a [ArrowIndicatorPainter] 8 | const ArrowIndicatorPainter({ 9 | required this.arrowDirection, 10 | this.arrowColor = Colors.black, 11 | }); 12 | 13 | /// [arrowDirection] is the direction of the arrow 14 | final ArrowDirection arrowDirection; 15 | 16 | /// [arrowColor] is the color of the arrow 17 | final Color arrowColor; 18 | 19 | /// Draws the triangle of specific [size] on [canvas] 20 | @override 21 | void paint(Canvas canvas, Size size) { 22 | final Path arrowPath = Path(); 23 | final Paint arrowPaint = Paint(); 24 | 25 | arrowPaint.strokeWidth = 2.0; 26 | arrowPaint.color = arrowColor; 27 | arrowPaint.style = PaintingStyle.fill; 28 | 29 | switch (arrowDirection) { 30 | case ArrowDirection.up: 31 | arrowPath.moveTo(size.width / 2.0, 0.0); 32 | arrowPath.lineTo(0.0, size.height + 1); 33 | arrowPath.lineTo(size.width, size.height + 1); 34 | break; 35 | case ArrowDirection.down: 36 | arrowPath.moveTo(0.0, -1.0); 37 | arrowPath.lineTo(size.width, -1.0); 38 | arrowPath.lineTo(size.width / 2.0, size.height); 39 | break; 40 | } 41 | 42 | canvas.drawPath(arrowPath, arrowPaint); 43 | } 44 | 45 | /// [shouldRepaint] is called when the [CustomPainter] is asked to paint again. 46 | @override 47 | bool shouldRepaint(CustomPainter customPainter) => true; 48 | } 49 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDisplayName 8 | Example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | example 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(FLUTTER_BUILD_NUMBER) 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/src/themes/info_popup_arrow_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart' show Color, Colors, CustomPainter, Size; 2 | import 'package:info_popup/src/constants/popup_constants.dart'; 3 | import 'package:info_popup/src/enums/arrow_direction.dart'; 4 | 5 | /// [InfoPopupArrowTheme] is used to customize the arrow of the popup. 6 | class InfoPopupArrowTheme { 7 | /// [InfoPopupArrowTheme] is creates a [InfoPopupArrowTheme] constructor. 8 | /// [arrowDirection] is used to customize the direction of the arrow. 9 | /// [arrowSize] is used to customize the size of the arrow. 10 | /// [arrowGap] is used to customize the gap between the arrow and the content. 11 | /// [arrowPainter] is used to customize the painter of the arrow. 12 | const InfoPopupArrowTheme({ 13 | this.arrowSize = PopupConstants.defaultArrowSize, 14 | this.arrowDirection = ArrowDirection.up, 15 | this.color = Colors.black, 16 | this.arrowPainter, 17 | this.enabledAutoArrowDirection = true, 18 | }); 19 | 20 | /// The size [arrowSize] of the arrow indicator. 21 | final Size arrowSize; 22 | 23 | /// The [arrowDirection] of the arrow indicator. 24 | final ArrowDirection arrowDirection; 25 | 26 | /// The [color] of the arrow indicator. 27 | final Color color; 28 | 29 | /// The [arrowPainter] is used to draw the arrow indicator. 30 | final CustomPainter? arrowPainter; 31 | 32 | /// The [enabledAutoArrowDirection] is used to enable the auto arrow direction. 33 | final bool enabledAutoArrowDirection; 34 | 35 | /// [copyWith] is used to copy the [InfoPopupArrowTheme] with new values. 36 | InfoPopupArrowTheme copyWith({ 37 | Size? arrowSize, 38 | ArrowDirection? arrowDirection, 39 | Color? color, 40 | CustomPainter? arrowPainter, 41 | }) { 42 | return InfoPopupArrowTheme( 43 | arrowSize: arrowSize ?? this.arrowSize, 44 | arrowDirection: arrowDirection ?? this.arrowDirection, 45 | color: color ?? this.color, 46 | arrowPainter: arrowPainter ?? this.arrowPainter, 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | example 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.example" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/src/themes/info_popup_content_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart' 2 | show 3 | BorderRadius, 4 | Color, 5 | Colors, 6 | EdgeInsets, 7 | EdgeInsetsGeometry, 8 | TextAlign, 9 | TextStyle; 10 | import 'package:info_popup/src/constants/popup_constants.dart'; 11 | 12 | /// [InfoPopupContentTheme] is used to customize the content of the popup. 13 | class InfoPopupContentTheme { 14 | /// [InfoPopupContentTheme] creates a theme for the content of the popup. 15 | /// [infoTextStyle] is used to customize the text style of the content. 16 | /// [infoTextAlign] is used to customize the text align of the content. 17 | /// [infoContainerBackgroundColor] is used to customize the background color of the content. 18 | /// [contentPadding] is used to customize the padding of the content. 19 | /// [contentBorderRadius] is used to customize the border radius of the content. 20 | const InfoPopupContentTheme({ 21 | this.infoTextStyle = PopupConstants.defaultInfoTextStyle, 22 | this.infoTextAlign = TextAlign.center, 23 | this.infoContainerBackgroundColor = Colors.white, 24 | this.contentPadding = const EdgeInsets.all(8.0), 25 | this.contentBorderRadius = PopupConstants.defaultContentBorderRadius, 26 | }); 27 | 28 | /// The [infoTextStyle] of the info text. 29 | final TextStyle infoTextStyle; 30 | 31 | /// The [infoTextAlign] of the info text. 32 | final TextAlign infoTextAlign; 33 | 34 | /// The [infoContainerBackgroundColor] of the info container color. 35 | final Color infoContainerBackgroundColor; 36 | 37 | /// The [padding] of the info container. 38 | final EdgeInsetsGeometry contentPadding; 39 | 40 | /// The [borderRadius] of the info container. 41 | final BorderRadius contentBorderRadius; 42 | 43 | /// [copyWith] is used to copy the [InfoPopupContentTheme] with new values. 44 | InfoPopupContentTheme copyWith({ 45 | TextStyle? infoTextStyle, 46 | TextAlign? infoTextAlign, 47 | Color? infoContainerBackgroundColor, 48 | EdgeInsetsGeometry? contentPadding, 49 | BorderRadius? contentBorderRadius, 50 | }) { 51 | return InfoPopupContentTheme( 52 | infoTextStyle: infoTextStyle ?? this.infoTextStyle, 53 | infoTextAlign: infoTextAlign ?? this.infoTextAlign, 54 | infoContainerBackgroundColor: 55 | infoContainerBackgroundColor ?? this.infoContainerBackgroundColor, 56 | contentPadding: contentPadding ?? this.contentPadding, 57 | contentBorderRadius: contentBorderRadius ?? this.contentBorderRadius, 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/info_popup_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/main.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | void main() { 6 | testWidgets( 7 | 'MyApp has 4 text widgets', 8 | (WidgetTester tester) async { 9 | await tester.pumpWidget(const MyApp()); 10 | await tester.pumpAndSettle(const Duration(seconds: 2)); 11 | 12 | final Finder widgets = find.byType(Text); 13 | 14 | expect(widgets, findsNWidgets(4)); 15 | }, 16 | ); 17 | 18 | testWidgets( 19 | 'MyApp has normal info popup widget', 20 | (WidgetTester tester) async { 21 | await tester.pumpWidget(const MyApp()); 22 | await tester.pumpAndSettle(const Duration(seconds: 2)); 23 | 24 | await safeTapByKey(tester, infoPopupTextExampleKey); 25 | 26 | await tester.pumpAndSettle(const Duration(seconds: 2)); 27 | 28 | expect(find.text(infoPopupTextExampleText), findsOneWidget); 29 | }, 30 | ); 31 | 32 | testWidgets( 33 | 'MyApp has info popup widget with custom widget', 34 | (WidgetTester tester) async { 35 | await tester.pumpWidget(const MyApp()); 36 | await tester.pumpAndSettle(const Duration(seconds: 2)); 37 | 38 | await safeTapByKey(tester, infoPopupCustomExampleKey); 39 | await tester.pumpAndSettle(const Duration(seconds: 2)); 40 | 41 | final Finder widgets = find.text(infoPopupCustomExampleText); 42 | 43 | expect(widgets, findsOneWidget); 44 | }, 45 | ); 46 | 47 | testWidgets( 48 | 'MyApp has info popup widget with long text', 49 | (WidgetTester tester) async { 50 | await tester.pumpWidget(const MyApp()); 51 | await tester.pumpAndSettle(const Duration(seconds: 2)); 52 | 53 | await safeTapByKey(tester, infoPopupLongTextExampleKey); 54 | await tester.pumpAndSettle(const Duration(seconds: 2)); 55 | 56 | final Finder widgets = find.text(infoPopupLongTextExampleText); 57 | 58 | expect(widgets, findsOneWidget); 59 | }, 60 | ); 61 | 62 | testWidgets( 63 | 'MyApp has info popup widget that has gap', 64 | (WidgetTester tester) async { 65 | await tester.pumpWidget(const MyApp()); 66 | await tester.pumpAndSettle(const Duration(seconds: 2)); 67 | 68 | await safeTapByKey(tester, infoPopupArrowGapExampleKey); 69 | await tester.pumpAndSettle(const Duration(seconds: 2)); 70 | 71 | final Finder widgets = find.text(infoPopupArrowGapExampleText); 72 | 73 | expect(widgets, findsOneWidget); 74 | }, 75 | ); 76 | 77 | // TODO(salihcanbinboga): 27.09.2022 18:48 - Arrow Example Info Widget Test 78 | // TODO(salihcanbinboga): 27.09.2022 18:48 - Arrow Directional Gap Widget Test 79 | // TODO(salihcanbinboga): 27.09.2022 18:48 - Info Widget Screen Overflow Test 80 | } 81 | 82 | Future safeTapByKey(WidgetTester tester, Key key) async { 83 | await tester.ensureVisible(find.byKey(key)); 84 | await tester.pumpAndSettle(const Duration(seconds: 2)); 85 | await tester.tap(find.byKey(key)); 86 | } 87 | -------------------------------------------------------------------------------- /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.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 4.3.2 2 | 3 | - chore: Update package icon on pub.dev 4 | 5 | ## 4.3.1 6 | 7 | - fix: The BoxConstraints error was addressed by adding extra validation. 8 | 9 | ## 4.3.0 10 | 11 | - feat: Added a structure that prevents other gestures when the showroom is active. 12 | - feat: You can now disable the content constraints using the [enabledAutomaticConstraint] parameter. (default: true) 13 | - feat: Arrow direction now adjusts automatically. You can manage it using the [enabledAutoArrowDirection] parameter. (default: true) 14 | 15 | ## 4.1.0 16 | 17 | - feat: Added click behaviour to `InfoPopupWidget` (default: `PopupClickTriggerBehavior.onTap`) 18 | 19 | ## 4.0.0 20 | 21 | - fix: Issue where InfoPopupWidget was not updating when a state inside it changed, whether it was active or closed, has been resolved. 22 | - fix: Problem of closing when clicked while active on top of InfoPopupWidget and automatically reopening has been fixed. 23 | 24 | #### Breaking Change: 25 | The customContent prop should now be a function that returns a Widget instead of directly accepting a Widget. 26 | 27 | Previous usage: `customContent: Widget` 28 | 29 | New usage: `customContent: () => Widget` 30 | 31 | This change means that the `customContent` prop must now be a function, requiring adjustments in the existing codebase. 32 | 33 | ## 3.0.6 34 | 35 | - refactor: Reformat code 36 | 37 | ## 3.0.5 38 | 39 | - chore: Update package topics. 40 | 41 | ## 3.0.4 42 | 43 | - fix: Highlight theme usable in `InfoPopupWidget` now 44 | 45 | ## 3.0.3 46 | 47 | - feat: Version upgrade 48 | 49 | ## 3.0.1 50 | 51 | - feat: Added `enableLog` prop to `InfoPopupWidget` 52 | - doc: Added package profile logo. 53 | 54 | ## 3.0.0 55 | 56 | - fix: This fixes the issue of the InfoPopupWidget not updating when the popup content or a property of the InfoPopupWidget changes. 57 | - feat: Upgrade Flutter SDK to 3.7.1 58 | - doc: The readme.md file has been updated. 59 | 60 | ## 2.4.0 61 | 62 | - feature: Added automatic popup dismiss 63 | - feature: Added new dismiss trigger values (`anyWhere`, `manuel`) 64 | 65 | ## 2.3.0 66 | 67 | - feature: **Highlight** feature is now available! 68 | - feature: Improve for popup horizontal alignment calculation 69 | - refactor: Refactored the whole OverlayEntryLayout (Major Update Reason) 70 | - refactor: Improve popup opening animation performance 71 | 72 | ## 2.2.0 73 | 74 | - feature: Added `contentMaxWidth` prop to `InfoPopupWidget` 75 | 76 | ## 2.1.3 77 | 78 | - doc: Update README.md 79 | 80 | ## 2.1.2 81 | 82 | - doc: Deployed to hosting to test an example project 83 | - You can test now! [Info Popup](https://info-popup.web.app/#/ "Info Popup") 84 | 85 | ## 2.1.0 86 | 87 | - feature: Added `indicatorOffset` prop to `InfoPopupWidget` 88 | 89 | ## 2.0.0 90 | 91 | - feature: Added dismiss behavior to info popup 92 | - feature: Support for inside list items! 93 | - feature: Added `contentOffset` prop 94 | - refactor: Some argument names have been changed (major version bump) 95 | - fix: Fixed a bug where the popup would not be dismissed when the user tapped outside of it 96 | - fix: Bottom sheet offset is now calculated correctly in web 97 | - deprecated: Removed `infoText` & `infoWidget` props 98 | - deprecated: Removed `arrowGap` & `arrowAlignment` props 99 | 100 | ## 1.4.0 101 | 102 | - refactor: Improved calculating above and below the target widget remaining area 103 | - refactor: `onControllerCreated` now optional 104 | - refactor: `infoText` & `infoWidget` now deprecated, use `customTitle` & `customContent` instead 105 | - feature: Add popup trigger mode `PopupTriggerBehavior` (default: `PopupTriggerBehavior.onTap`) 106 | - refactor: Some code refactoring and name changes 107 | - feature: Calculate dynamic popup position based on the target widget position 108 | - feature: Add content SafeArea to indicator 109 | 110 | ## 1.3.0 111 | 112 | - fix: Content overflow on device with small screen 113 | - feature: Added arrow alignment option by target 114 | 115 | ## 1.2.0 116 | 117 | - feature: Added custom indicator painter support 118 | 119 | ## 1.1.0 120 | 121 | - feature: Added arrow gap support according to the arrow direction 122 | - doc: Updated example project 123 | 124 | ## 1.0.10 125 | 126 | - doc: update planned task to README.md 127 | 128 | ## 1.0.9 129 | 130 | - doc: add planned task to README.md 131 | 132 | ## 1.0.8 133 | 134 | - doc: GIF size reduced 135 | - doc: README.md updated 136 | 137 | ## 1.0.7 138 | 139 | - doc: GIF speed updated 140 | 141 | ## 1.0.6 142 | 143 | - test: Added some simple widget tests 144 | - refactor: Disabled automatic showing info popup structure in example 145 | 146 | ## 1.0.5 147 | 148 | - Update README.md 149 | - fix: Info overlay layout done catching 150 | 151 | ## 1.0.4 152 | 153 | - Update README.md 154 | 155 | ## 1.0.3 156 | 157 | - Update README.md 158 | 159 | ## 1.0.2 160 | 161 | - Update README.md 162 | 163 | ## 1.0.1 164 | 165 | - Update README.md 166 | - Resize presentation GIF 167 | 168 | ## 1.0.0 169 | 170 | - Initial release -------------------------------------------------------------------------------- /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: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.17.2" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.1" 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 | info_popup: 63 | dependency: "direct main" 64 | description: 65 | path: ".." 66 | relative: true 67 | source: path 68 | version: "4.0.0" 69 | matcher: 70 | dependency: transitive 71 | description: 72 | name: matcher 73 | sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" 74 | url: "https://pub.dev" 75 | source: hosted 76 | version: "0.12.16" 77 | material_color_utilities: 78 | dependency: transitive 79 | description: 80 | name: material_color_utilities 81 | sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" 82 | url: "https://pub.dev" 83 | source: hosted 84 | version: "0.5.0" 85 | meta: 86 | dependency: transitive 87 | description: 88 | name: meta 89 | sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" 90 | url: "https://pub.dev" 91 | source: hosted 92 | version: "1.9.1" 93 | path: 94 | dependency: transitive 95 | description: 96 | name: path 97 | sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" 98 | url: "https://pub.dev" 99 | source: hosted 100 | version: "1.8.3" 101 | sky_engine: 102 | dependency: transitive 103 | description: flutter 104 | source: sdk 105 | version: "0.0.99" 106 | source_span: 107 | dependency: transitive 108 | description: 109 | name: source_span 110 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 111 | url: "https://pub.dev" 112 | source: hosted 113 | version: "1.10.0" 114 | stack_trace: 115 | dependency: transitive 116 | description: 117 | name: stack_trace 118 | sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 119 | url: "https://pub.dev" 120 | source: hosted 121 | version: "1.11.0" 122 | stream_channel: 123 | dependency: transitive 124 | description: 125 | name: stream_channel 126 | sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" 127 | url: "https://pub.dev" 128 | source: hosted 129 | version: "2.1.1" 130 | string_scanner: 131 | dependency: transitive 132 | description: 133 | name: string_scanner 134 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 135 | url: "https://pub.dev" 136 | source: hosted 137 | version: "1.2.0" 138 | term_glyph: 139 | dependency: transitive 140 | description: 141 | name: term_glyph 142 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 143 | url: "https://pub.dev" 144 | source: hosted 145 | version: "1.2.1" 146 | test_api: 147 | dependency: transitive 148 | description: 149 | name: test_api 150 | sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" 151 | url: "https://pub.dev" 152 | source: hosted 153 | version: "0.6.0" 154 | vector_math: 155 | dependency: transitive 156 | description: 157 | name: vector_math 158 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 159 | url: "https://pub.dev" 160 | source: hosted 161 | version: "2.1.4" 162 | web: 163 | dependency: transitive 164 | description: 165 | name: web 166 | sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 167 | url: "https://pub.dev" 168 | source: hosted 169 | version: "0.1.4-beta" 170 | sdks: 171 | dart: ">=3.1.0-185.0.dev <4.0.0" 172 | flutter: ">=2.0.0" 173 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | errors: 3 | missing_required_param: warning 4 | missing_return: warning 5 | todo: ignore 6 | deprecated_member_use_from_same_package: ignore 7 | sdk_version_async_exported_from_core: ignore 8 | unnecessary_null_comparison: ignore 9 | always_require_non_null_named_parameters: false 10 | exclude: 11 | - '**/*.g.dart' 12 | - 'lib/src/generated/*.dart' 13 | - '**/*.mocks.dart' 14 | language: 15 | strict-casts: true 16 | strict-raw-types: true 17 | 18 | linter: 19 | rules: 20 | - always_declare_return_types 21 | - always_put_control_body_on_new_line 22 | - always_require_non_null_named_parameters 23 | - always_specify_types 24 | - annotate_overrides 25 | - avoid_bool_literals_in_conditional_expressions 26 | - avoid_classes_with_only_static_members 27 | - avoid_double_and_int_checks 28 | - avoid_empty_else 29 | - avoid_equals_and_hash_code_on_mutable_classes 30 | - avoid_escaping_inner_quotes 31 | - avoid_field_initializers_in_const_classes 32 | - avoid_function_literals_in_foreach_calls 33 | - avoid_init_to_null 34 | - avoid_js_rounded_ints 35 | - avoid_null_checks_in_equality_operators 36 | - avoid_redundant_argument_values 37 | - avoid_relative_lib_imports 38 | - avoid_renaming_method_parameters 39 | - avoid_return_types_on_setters 40 | - avoid_returning_null_for_future 41 | - avoid_returning_null_for_void 42 | - avoid_setters_without_getters 43 | - avoid_shadowing_type_parameters 44 | - avoid_single_cascade_in_expression_statements 45 | - avoid_slow_async_io 46 | - avoid_type_to_string 47 | - avoid_types_as_parameter_names 48 | - avoid_unnecessary_containers 49 | - avoid_unused_constructor_parameters 50 | - avoid_void_async 51 | - await_only_futures 52 | - camel_case_extensions 53 | - camel_case_types 54 | - cancel_subscriptions 55 | - cast_nullable_to_non_nullable 56 | - control_flow_in_finally 57 | - deprecated_consistency 58 | - directives_ordering 59 | - empty_catches 60 | - empty_constructor_bodies 61 | - empty_statements 62 | - eol_at_end_of_file 63 | - exhaustive_cases 64 | - file_names 65 | - flutter_style_todos 66 | - hash_and_equals 67 | - implementation_imports 68 | 69 | - leading_newlines_in_multiline_strings 70 | - library_names 71 | - library_prefixes 72 | - library_private_types_in_public_api 73 | - missing_whitespace_between_adjacent_strings 74 | - no_adjacent_strings_in_list 75 | - no_duplicate_case_values 76 | - no_leading_underscores_for_library_prefixes 77 | - no_logic_in_create_state 78 | - non_constant_identifier_names 79 | - noop_primitive_operations 80 | - null_check_on_nullable_type_parameter 81 | - null_closures 82 | - overridden_fields 83 | - package_api_docs 84 | - package_names 85 | - package_prefixed_library_names 86 | - prefer_adjacent_string_concatenation 87 | - prefer_asserts_in_initializer_lists 88 | - prefer_collection_literals 89 | - prefer_conditional_assignment 90 | - prefer_const_constructors 91 | - prefer_const_constructors_in_immutables 92 | - prefer_const_declarations 93 | - prefer_const_literals_to_create_immutables 94 | - prefer_contains 95 | - prefer_final_fields 96 | - prefer_final_in_for_each 97 | - prefer_final_locals 98 | - prefer_for_elements_to_map_fromIterable 99 | - prefer_foreach 100 | - prefer_function_declarations_over_variables 101 | - prefer_generic_function_type_aliases 102 | - prefer_if_elements_to_conditional_expressions 103 | - prefer_if_null_operators 104 | - prefer_initializing_formals 105 | - prefer_inlined_adds 106 | - prefer_interpolation_to_compose_strings 107 | - prefer_is_empty 108 | - prefer_is_not_empty 109 | - prefer_is_not_operator 110 | - prefer_iterable_whereType 111 | - prefer_null_aware_operators 112 | - prefer_single_quotes 113 | - prefer_spread_collections 114 | - prefer_typing_uninitialized_variables 115 | - prefer_void_to_null 116 | - provide_deprecation_message 117 | - recursive_getters 118 | - secure_pubspec_urls 119 | - slash_for_doc_comments 120 | - sort_child_properties_last 121 | - sort_constructors_first 122 | - sort_unnamed_constructors_first 123 | - test_types_in_equals 124 | - throw_in_finally 125 | - tighten_type_of_initializing_formals 126 | - type_init_formals 127 | - unnecessary_brace_in_string_interps 128 | - unnecessary_const 129 | - unnecessary_constructor_name 130 | - unnecessary_getters_setters 131 | - unnecessary_late 132 | - unnecessary_new 133 | - unnecessary_null_aware_assignments 134 | - unnecessary_null_checks 135 | - unnecessary_null_in_if_null_operators 136 | - unnecessary_nullable_for_final_variable_declarations 137 | - unnecessary_overrides 138 | - unnecessary_parenthesis 139 | - unnecessary_statements 140 | - unnecessary_string_escapes 141 | - unnecessary_string_interpolations 142 | - unnecessary_this 143 | - unrelated_type_equality_checks 144 | - unsafe_html 145 | - use_full_hex_values_for_flutter_colors 146 | - use_function_type_syntax_for_parameters 147 | - use_if_null_to_convert_nulls_to_bools 148 | - use_is_even_rather_than_modulo 149 | - use_key_in_widget_constructors 150 | - use_late_for_private_fields_and_variables 151 | - use_raw_strings 152 | - use_rethrow_when_possible 153 | - use_setters_to_change_properties 154 | - valid_regexps 155 | - void_checks 156 | - no_runtimeType_toString 157 | - public_member_api_docs 158 | - sort_pub_dependencies -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | analyzer: 2 | language: 3 | strict-casts: true 4 | strict-raw-types: true 5 | errors: 6 | deprecated_member_use_from_same_package: ignore 7 | unnecessary_null_comparison: ignore 8 | exclude: 9 | - "build/**" 10 | 11 | linter: 12 | rules: 13 | # This list is derived from the list of all available lints located at 14 | # https://github.com/dart-lang/linter/blob/master/example/all.yaml 15 | - always_declare_return_types 16 | - always_specify_types 17 | - always_put_control_body_on_new_line 18 | - always_require_non_null_named_parameters 19 | - annotate_overrides 20 | - avoid_bool_literals_in_conditional_expressions 21 | - avoid_classes_with_only_static_members 22 | - avoid_double_and_int_checks 23 | - avoid_dynamic_calls 24 | - avoid_empty_else 25 | - avoid_equals_and_hash_code_on_mutable_classes 26 | - avoid_escaping_inner_quotes 27 | - avoid_field_initializers_in_const_classes 28 | - avoid_function_literals_in_foreach_calls 29 | - avoid_implementing_value_types 30 | - avoid_init_to_null 31 | - avoid_js_rounded_ints 32 | - avoid_null_checks_in_equality_operators 33 | - avoid_print 34 | - avoid_redundant_argument_values 35 | - avoid_relative_lib_imports 36 | - avoid_renaming_method_parameters 37 | - avoid_return_types_on_setters 38 | - avoid_returning_null 39 | - avoid_returning_null_for_future 40 | - avoid_returning_null_for_void 41 | - avoid_setters_without_getters 42 | - avoid_shadowing_type_parameters 43 | - avoid_single_cascade_in_expression_statements 44 | - avoid_slow_async_io 45 | - avoid_type_to_string 46 | - avoid_types_as_parameter_names 47 | - avoid_unnecessary_containers 48 | - avoid_unused_constructor_parameters 49 | - avoid_void_async 50 | - await_only_futures 51 | - camel_case_extensions 52 | - camel_case_types 53 | - cancel_subscriptions 54 | - cast_nullable_to_non_nullable 55 | - conditional_uri_does_not_exist 56 | - control_flow_in_finally 57 | - curly_braces_in_flow_control_structures 58 | - depend_on_referenced_packages 59 | - deprecated_consistency 60 | - directives_ordering 61 | - empty_catches 62 | - empty_constructor_bodies 63 | - empty_statements 64 | - eol_at_end_of_file 65 | - exhaustive_cases 66 | - file_names 67 | - hash_and_equals 68 | - implementation_imports 69 | - library_names 70 | - library_prefixes 71 | - library_private_types_in_public_api 72 | - missing_whitespace_between_adjacent_strings 73 | - no_default_cases 74 | - no_duplicate_case_values 75 | - no_leading_underscores_for_library_prefixes 76 | - no_leading_underscores_for_local_identifiers 77 | - no_logic_in_create_state 78 | - non_constant_identifier_names 79 | - noop_primitive_operations 80 | - null_check_on_nullable_type_parameter 81 | - null_closures 82 | - only_throw_errors 83 | - overridden_fields 84 | - package_api_docs 85 | - package_names 86 | - package_prefixed_library_names 87 | - prefer_adjacent_string_concatenation 88 | - prefer_asserts_in_initializer_lists 89 | - prefer_collection_literals 90 | - prefer_conditional_assignment 91 | - prefer_const_constructors 92 | - prefer_const_constructors_in_immutables 93 | - prefer_const_declarations 94 | - prefer_const_literals_to_create_immutables 95 | - prefer_contains 96 | - prefer_equal_for_default_values 97 | - prefer_final_fields 98 | - prefer_final_in_for_each 99 | - prefer_final_locals 100 | - prefer_for_elements_to_map_fromIterable 101 | - prefer_foreach 102 | - prefer_function_declarations_over_variables 103 | - prefer_generic_function_type_aliases 104 | - prefer_if_elements_to_conditional_expressions 105 | - prefer_if_null_operators 106 | - prefer_initializing_formals 107 | - prefer_inlined_adds 108 | - prefer_interpolation_to_compose_strings 109 | - prefer_is_empty 110 | - prefer_is_not_empty 111 | - prefer_is_not_operator 112 | - prefer_iterable_whereType 113 | - prefer_null_aware_operators 114 | - prefer_relative_imports 115 | - prefer_single_quotes 116 | - prefer_spread_collections 117 | - prefer_typing_uninitialized_variables 118 | - prefer_void_to_null 119 | - provide_deprecation_message 120 | - recursive_getters 121 | - secure_pubspec_urls 122 | - sized_box_for_whitespace 123 | - slash_for_doc_comments 124 | - sort_child_properties_last 125 | - sort_constructors_first 126 | - sort_unnamed_constructors_first 127 | - test_types_in_equals 128 | - throw_in_finally 129 | - tighten_type_of_initializing_formals 130 | - type_init_formals 131 | - unnecessary_await_in_return 132 | - unnecessary_brace_in_string_interps 133 | - unnecessary_const 134 | - unnecessary_constructor_name 135 | - unnecessary_getters_setters 136 | - unnecessary_late 137 | - unnecessary_new 138 | - unnecessary_null_aware_assignments 139 | - unnecessary_null_checks 140 | - unnecessary_null_in_if_null_operators 141 | - unnecessary_nullable_for_final_variable_declarations 142 | - unnecessary_overrides 143 | - unnecessary_parenthesis 144 | - unnecessary_statements 145 | - unnecessary_string_escapes 146 | - unnecessary_string_interpolations 147 | - unnecessary_this 148 | - unrelated_type_equality_checks 149 | - unsafe_html 150 | - use_build_context_synchronously 151 | - use_full_hex_values_for_flutter_colors 152 | - use_function_type_syntax_for_parameters 153 | - use_if_null_to_convert_nulls_to_bools 154 | - use_is_even_rather_than_modulo 155 | - use_key_in_widget_constructors 156 | - use_late_for_private_fields_and_variables 157 | - use_named_constants 158 | - use_raw_strings 159 | - use_rethrow_when_possible 160 | - use_setters_to_change_properties 161 | - use_test_throws_matchers 162 | - valid_regexps 163 | - void_checks -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | The `info_popup` package allows you to easily show a **simple**, **customizable** popup on your wrapped widget. The **highlight feature**, which can be activated at will, helps draw the user's attention to the desired location. 4 | 5 | You can test it now on the [Info Popup preview page](https://info-popup.web.app/#/ "Info Popup"). 6 | Note that the website experience may be different. 7 | 8 | ## Features 9 | 10 | - Display a ***customizable*** popup on your wrapped widget 11 | - Activate the ***highlight*** feature to draw the user's attention to the desired location 12 | - Fully customize the content of the popup 13 | - Add margins to the popup from ***any side*** 14 | 15 | ![MainPresentation](assets/readme/example_presentation.gif) 16 | ![HighlightExample](assets/readme/highlight_example.png) 17 | 18 | ## Getting Started 19 | 20 | To use this package, add `info_popup` as a dependency in your `pubspec.yaml` file. 21 | 22 | ```yaml 23 | dependencies: 24 | info_popup: ^3.0.0 25 | ``` 26 | 27 | Alternatively, you can add it to your project by running the following commands in your terminal: 28 | 29 | with Dart: 30 | 31 | ```shell 32 | $ dart pub add info_popup 33 | ``` 34 | 35 | with Flutter: 36 | 37 | ```shell 38 | $ flutter pub add info_popup 39 | ``` 40 | 41 | ## Usage 42 | 43 | To show a popup, wrap the widget that you want to display the popup on with the `InfoPopupWidget` widget. All you have to do is wrap it in the widget you want to show information with the `InfoPopupWidget` widget. With the `InfoPopupController`, you can customize it as you wish, and turn it on and off. 44 | 45 | ```dart 46 | import 'package:info_popup/info_popup.dart'; 47 | ``` 48 | 49 | ```dart 50 | InfoPopupWidget( 51 | contentTitle: 'Info Popup Details', 52 | child: Icon( 53 | Icons.info, 54 | color: Colors.pink, 55 | ), 56 | ), 57 | ``` 58 | 59 | ## Example 60 | 61 | This is a normal info text displayed using the `InfoPopupWidget` widget with several optional parameters. 62 | 63 | ```dart 64 | InfoPopupWidget( 65 | contentTitle: 'Info Popup Details', 66 | arrowTheme: InfoPopupArrowTheme( 67 | color: Colors.pink, 68 | arrowDirection: ArrowDirection.up, 69 | ), 70 | contentTheme: InfoPopupContentTheme( 71 | infoContainerBackgroundColor: Colors.black, 72 | infoTextStyle: TextStyle(color: Colors.white), 73 | contentPadding: const EdgeInsets.all(8), 74 | contentBorderRadius: BorderRadius.all(Radius.circular(10)), 75 | infoTextAlign: TextAlign.center, 76 | ), 77 | dismissTriggerBehavior: PopupDismissTriggerBehavior.onTapArea, 78 | areaBackgroundColor: Colors.transparent, 79 | indicatorOffset: Offset.zero, 80 | contentOffset: Offset.zero, 81 | onControllerCreated: (controller) { 82 | print('Info Popup Controller Created'); 83 | }, 84 | onAreaPressed: (InfoPopupController controller) { 85 | print('Area Pressed'); 86 | }, 87 | infoPopupDismissed: () { 88 | print('Info Popup Dismissed'); 89 | }, 90 | onLayoutMounted: (Size size) { 91 | print('Info Popup Layout Mounted'); 92 | }, 93 | child: Icon( 94 | Icons.info, 95 | color: Colors.pink, 96 | ), 97 | ), 98 | ``` 99 | "This is a ***custom popup*** example created using the `InfoPopupWidget` widget. 100 | 101 | ##### Custom Popup Widget 102 | ```dart 103 | InfoPopupWidget( 104 | customContent: Container( 105 | decoration: BoxDecoration( 106 | color: Colors.blueGrey, 107 | borderRadius: BorderRadius.circular(10), 108 | ), 109 | padding: const EdgeInsets.all(10), 110 | child: Column( 111 | children: const [ 112 | TextField( 113 | decoration: InputDecoration( 114 | hintText: 'Enter your name', 115 | hintStyle: TextStyle(color: Colors.white), 116 | enabledBorder: OutlineInputBorder( 117 | borderSide: BorderSide(color: Colors.white), 118 | ), 119 | ), 120 | ), 121 | SizedBox(height: 10), 122 | Center( 123 | child: Text( 124 | 'Example of Info Popup inside a Bottom Sheet', 125 | style: TextStyle( 126 | color: Colors.white, 127 | ), 128 | ), 129 | ), 130 | ], 131 | ), 132 | ), 133 | arrowTheme: const InfoPopupArrowTheme( 134 | color: Colors.pink, 135 | arrowDirection: ArrowDirection.up, 136 | ), 137 | dismissTriggerBehavior: PopupDismissTriggerBehavior.onTapArea, 138 | areaBackgroundColor: Colors.transparent, 139 | indicatorOffset: Offset.zero, 140 | contentOffset: Offset.zero, 141 | onControllerCreated: (controller) { 142 | print('Info Popup Controller Created'); 143 | }, 144 | onAreaPressed: (InfoPopupController controller) { 145 | print('Area Pressed'); 146 | }, 147 | infoPopupDismissed: () { 148 | print('Info Popup Dismissed'); 149 | }, 150 | onLayoutMounted: (Size size) { 151 | print('Info Popup Layout Mounted'); 152 | }, 153 | child: Icon( 154 | Icons.info, 155 | color: Colors.pink, 156 | ), 157 | ), 158 | ``` 159 | 160 | 161 | ## Conclusion 162 | 163 | The info_popup package provides a simple and effective way to show a customizable popup on your wrapped widget. With the highlight feature, you can draw the user's attention to the desired location. 164 | 165 | ## License 166 | 167 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/SalihCanBinboga/info_popup/blob/master/LICENSE "LICENSE") file for details. 168 | -------------------------------------------------------------------------------- /lib/src/controllers/info_popup_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | import 'package:info_popup/info_popup.dart'; 5 | import 'package:info_popup/src/extensions/context_extensions.dart'; 6 | 7 | import '../painters/arrow_indicator_painter.dart'; 8 | 9 | part '../overlays/overlay_entry_layout.dart'; 10 | 11 | part '../painters/high_lighter.dart'; 12 | 13 | /// Popup manager for the InfoPopup widget. 14 | /// [InfoPopupController] is used to show and dismiss the popup. 15 | class InfoPopupController { 16 | /// Creates a [InfoPopupController] widget. 17 | InfoPopupController({ 18 | required this.context, 19 | required RenderBox targetRenderBox, 20 | required this.areaBackgroundColor, 21 | required this.arrowTheme, 22 | required this.contentTheme, 23 | required this.layerLink, 24 | required this.dismissTriggerBehavior, 25 | required this.enableHighlight, 26 | required this.highLightTheme, 27 | required this.enabledAutomaticConstraint, 28 | this.infoPopupDismissed, 29 | this.contentTitle, 30 | this.customContent, 31 | this.onAreaPressed, 32 | this.onLayoutMounted, 33 | this.contentOffset = Offset.zero, 34 | this.indicatorOffset = Offset.zero, 35 | this.contentMaxWidth, 36 | }) : _targetRenderBox = targetRenderBox; 37 | 38 | /// The [layerLink] is the layer link of the popup. 39 | final LayerLink layerLink; 40 | 41 | /// The context of the widget. 42 | final BuildContext context; 43 | 44 | /// The [_targetRenderBox] is the render box of the info text. 45 | RenderBox _targetRenderBox; 46 | 47 | /// The [infoPopupDismissed] is the callback function when the popup is dismissed. 48 | final VoidCallback? infoPopupDismissed; 49 | 50 | /// The [contentTitle] to show in the popup. 51 | final String? contentTitle; 52 | 53 | /// The [customContent] is the widget that will be custom shown in the popup. 54 | final Widget? Function()? customContent; 55 | 56 | /// The [areaBackgroundColor] is the background color of the area that 57 | final Color areaBackgroundColor; 58 | 59 | /// [arrowTheme] is the arrow theme of the popup. 60 | final InfoPopupArrowTheme arrowTheme; 61 | 62 | /// [contentTheme] is the content theme of the popup. 63 | final InfoPopupContentTheme contentTheme; 64 | 65 | /// [onAreaPressed] Called when the area outside the popup is pressed. 66 | final OnAreaPressed? onAreaPressed; 67 | 68 | /// [onLayoutMounted] Called when the info layout is mounted. 69 | final Function(Size size)? onLayoutMounted; 70 | 71 | /// The [contentOffset] is the offset of the content. 72 | final Offset contentOffset; 73 | 74 | /// The [indicatorOffset] is the offset of the indicator. 75 | final Offset indicatorOffset; 76 | 77 | /// The [dismissTriggerBehavior] is the dismissing behavior of the popup. 78 | final PopupDismissTriggerBehavior dismissTriggerBehavior; 79 | 80 | /// The [_infoPopupOverlayEntry] is the overlay entry of the popup. 81 | OverlayEntry? _infoPopupOverlayEntry; 82 | 83 | /// The [infoPopupContainerSize] is the size of the popup. 84 | Size? infoPopupContainerSize; 85 | 86 | /// [contentMaxWidth] is the max width of the content that is shown. 87 | /// If the [contentMaxWidth] is null, the max width will be eighty percent 88 | /// of the screen. 89 | final double? contentMaxWidth; 90 | 91 | /// The [enableHighlight] is the boolean value that indicates whether the 92 | /// highlight is enabled or not. 93 | final bool enableHighlight; 94 | 95 | /// The [highLightTheme] is the theme of the highlight. Can customize the 96 | /// highlight border radius and the padding. 97 | final HighLightTheme highLightTheme; 98 | 99 | /// The [enabledAutomaticConstraint] is the boolean value that indicates 100 | /// whether the popup will be constrained automatically or not. 101 | final bool enabledAutomaticConstraint; 102 | 103 | /// The [show] method is used to show the popup. 104 | void show() { 105 | _infoPopupOverlayEntry = OverlayEntry( 106 | builder: (_) { 107 | return OverlayInfoPopup( 108 | targetRenderBox: _targetRenderBox, 109 | contentTitle: contentTitle, 110 | layerLink: layerLink, 111 | customContent: customContent, 112 | areaBackgroundColor: areaBackgroundColor, 113 | indicatorTheme: arrowTheme, 114 | contentTheme: contentTheme, 115 | contentOffset: contentOffset, 116 | indicatorOffset: indicatorOffset, 117 | enableHighlight: enableHighlight, 118 | highlightTheme: highLightTheme, 119 | dismissTriggerBehavior: dismissTriggerBehavior, 120 | contentMaxWidth: contentMaxWidth, 121 | enabledAutomaticConstraint: enabledAutomaticConstraint, 122 | hideOverlay: dismissInfoPopup, 123 | onLayoutMounted: (Size size) { 124 | Future.delayed( 125 | const Duration(milliseconds: 30), 126 | () { 127 | infoPopupContainerSize = size; 128 | _infoPopupOverlayEntry?.markNeedsBuild(); 129 | 130 | if (onLayoutMounted != null) { 131 | onLayoutMounted!.call(size); 132 | } 133 | }, 134 | ); 135 | }, 136 | onAreaPressed: () { 137 | if (onAreaPressed != null) { 138 | onAreaPressed!.call(this); 139 | return; 140 | } 141 | 142 | dismissInfoPopup(); 143 | }, 144 | ); 145 | }, 146 | ); 147 | 148 | Overlay.of(context).insert(_infoPopupOverlayEntry!); 149 | } 150 | 151 | /// The [isShowing] method is used to check if the popup is showing. 152 | bool get isShowing => _infoPopupOverlayEntry != null; 153 | 154 | /// [dismissInfoPopup] is used to dismiss the popup. 155 | void dismissInfoPopup() { 156 | if (_infoPopupOverlayEntry != null) { 157 | _infoPopupOverlayEntry!.remove(); 158 | _infoPopupOverlayEntry = null; 159 | 160 | if (infoPopupDismissed != null) { 161 | infoPopupDismissed!.call(); 162 | } 163 | } 164 | } 165 | 166 | /// [updateInfoPopupTargetRenderBox] is used to update the render box of the info text. 167 | void updateInfoPopupTargetRenderBox(RenderBox renderBox) { 168 | _targetRenderBox = renderBox; 169 | _infoPopupOverlayEntry?.markNeedsBuild(); 170 | } 171 | 172 | /// [updateContent] is used to update the custom content of the popup. 173 | void updateContent() { 174 | Future.microtask( 175 | () { 176 | _infoPopupOverlayEntry?.markNeedsBuild(); 177 | }, 178 | ); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:info_popup/info_popup.dart'; 3 | 4 | import 'list_example.dart'; 5 | 6 | void main() => runApp(const MyApp()); 7 | 8 | const Key infoPopupTextExampleKey = Key('info_popup_text_example'); 9 | const String infoPopupTextExampleText = 'This is a popup'; 10 | 11 | const Key infoPopupCustomExampleKey = Key('info_popup_custom_example'); 12 | const String infoPopupCustomExampleText = 'This is a custom widget'; 13 | 14 | const Key infoPopupLongTextExampleKey = Key('info_popup_long_text_example'); 15 | const String infoPopupLongTextExampleText = ''' 16 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Enim lobortis scelerisque fermentum dui faucibus in ornare quam viverra. Consectetur adipiscing elit ut aliquam purus sit. Nisl vel pretium lectus quam. Et odio pellentesque diam volutpat commodo. Diam vulputate ut pharetra sit amet aliquam id diam maecenas. Malesuada fames ac turpis egestas. Et sollicitudin ac orci phasellus egestas tellus rutrum. Pretium lectus quam id leo in. Semper risus in hendrerit gravida. Nullam ac tortor vitae purus faucibus ornare suspendisse sed. Non tellus orci ac auctor. Quis risus sed vulputate odio ut enim blandit. 17 | \n 18 | Nullam eget felis eget nunc lobortis mattis aliquam faucibus purus. Aenean et tortor at risus viverra adipiscing at in. Augue eget arcu dictum varius duis at consectetur. Est pellentesque elit ullamcorper dignissim cras. At consectetur lorem donec massa sapien faucibus et. Sit amet venenatis urna cursus eget. Dignissim cras tincidunt lobortis feugiat vivamus. Eget arcu dictum varius duis at. Aenean pharetra magna ac placerat. Enim nec dui nunc mattis enim ut tellus elementum. Laoreet suspendisse interdum consectetur libero. Tellus mauris a diam maecenas sed enim. Tortor posuere ac ut consequat semper viverra nam libero. Tellus molestie nunc non blandit massa. 19 | '''; 20 | 21 | const Key infoPopupArrowGapExampleKey = Key('info_popup_arrow_gap_example'); 22 | const String infoPopupArrowGapExampleText = infoPopupLongTextExampleText; 23 | 24 | class MyApp extends StatelessWidget { 25 | const MyApp({super.key}); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return const MaterialApp( 30 | home: InfoPopupPage(), 31 | ); 32 | } 33 | } 34 | 35 | class InfoPopupPage extends StatefulWidget { 36 | const InfoPopupPage({super.key}); 37 | 38 | @override 39 | State createState() => _InfoPopupPageState(); 40 | } 41 | 42 | class _InfoPopupPageState extends State { 43 | @override 44 | Widget build(BuildContext context) { 45 | final Size size = MediaQuery.sizeOf(context); 46 | 47 | return Scaffold( 48 | body: Align( 49 | child: SingleChildScrollView( 50 | child: Column( 51 | mainAxisAlignment: MainAxisAlignment.center, 52 | children: [ 53 | const InfoPopupWidget( 54 | arrowTheme: InfoPopupArrowTheme( 55 | arrowDirection: ArrowDirection.down, 56 | color: Colors.pink, 57 | ), 58 | contentTitle: infoPopupTextExampleText, 59 | child: Text('Info Popup Info Text Example'), 60 | ), 61 | const SizedBox(height: 30), 62 | InfoPopupWidget( 63 | arrowTheme: const InfoPopupArrowTheme( 64 | color: Colors.black87, 65 | arrowDirection: ArrowDirection.down, 66 | ), 67 | customContent: () => Container( 68 | width: size.width * .8, 69 | decoration: BoxDecoration( 70 | color: Colors.blueGrey, 71 | borderRadius: BorderRadius.circular(10), 72 | ), 73 | padding: const EdgeInsets.all(10), 74 | child: const Column( 75 | children: [ 76 | TextField( 77 | decoration: InputDecoration( 78 | hintText: 'Enter your name', 79 | hintStyle: TextStyle(color: Colors.white), 80 | enabledBorder: OutlineInputBorder( 81 | borderSide: BorderSide(color: Colors.white), 82 | ), 83 | ), 84 | ), 85 | SizedBox(height: 10), 86 | Center( 87 | child: Text( 88 | infoPopupCustomExampleText, 89 | style: TextStyle( 90 | color: Colors.white, 91 | ), 92 | ), 93 | ), 94 | ], 95 | ), 96 | ), 97 | child: const Text('Info Popup Custom Widget Example'), 98 | ), 99 | const SizedBox(height: 30), 100 | const InfoPopupWidget( 101 | arrowTheme: InfoPopupArrowTheme( 102 | color: Colors.pink, 103 | ), 104 | contentTitle: infoPopupLongTextExampleText, 105 | child: Text('Info Popup Long Info Text Example'), 106 | ), 107 | const SizedBox(height: 30), 108 | GestureDetector( 109 | onTap: () { 110 | showModalBottomSheet( 111 | context: context, 112 | builder: (_) { 113 | return Container( 114 | color: Colors.white, 115 | height: MediaQuery.of(context).size.height * .5, 116 | padding: const EdgeInsets.all(20), 117 | child: const Column( 118 | mainAxisAlignment: MainAxisAlignment.center, 119 | crossAxisAlignment: CrossAxisAlignment.start, 120 | children: [ 121 | Row( 122 | children: [ 123 | InfoPopupWidget( 124 | contentTitle: infoPopupLongTextExampleText, 125 | child: Icon( 126 | Icons.info, 127 | color: Colors.pink, 128 | ), 129 | ), 130 | SizedBox(width: 8), 131 | Text('Info Popup Inside Bottom Sheet Example'), 132 | ], 133 | ), 134 | ], 135 | ), 136 | ); 137 | }, 138 | ); 139 | }, 140 | behavior: HitTestBehavior.translucent, 141 | child: const Text('Bottom Sheet Inside Example'), 142 | ), 143 | const SizedBox(height: 30), 144 | const InfoPopupWidget( 145 | contentTitle: 'Info Popup Icon Examplee', 146 | arrowTheme: InfoPopupArrowTheme( 147 | color: Colors.pink, 148 | ), 149 | child: Icon( 150 | Icons.info, 151 | color: Colors.pink, 152 | ), 153 | ), 154 | const SizedBox(height: 30), 155 | const InfoPopupWidget( 156 | contentOffset: Offset(0, 30), 157 | contentTitle: infoPopupArrowGapExampleText, 158 | child: Text('Info Popup Arrow Gap Example'), 159 | ), 160 | const SizedBox(height: 30), 161 | GestureDetector( 162 | onTap: () { 163 | Navigator.of(context).push( 164 | MaterialPageRoute( 165 | builder: (_) { 166 | return const ListExample(); 167 | }, 168 | ), 169 | ); 170 | }, 171 | behavior: HitTestBehavior.translucent, 172 | child: const Text('List Example'), 173 | ), 174 | const SizedBox(height: 30), 175 | const InfoPopupWidget( 176 | enableHighlight: true, 177 | contentTitle: 'This is a HighLighted Info Popup', 178 | child: Text('HighLighted Info Popup Example'), 179 | ), 180 | ], 181 | ), 182 | ), 183 | ), 184 | ); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /lib/src/info_popup_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | import 'package:flutter/scheduler.dart'; 5 | import 'package:info_popup/info_popup.dart'; 6 | 7 | /// A widget that shows a popup with text. 8 | class InfoPopupWidget extends StatefulWidget { 9 | /// Creates a [InfoPopupWidget] widget. 10 | const InfoPopupWidget({ 11 | required this.child, 12 | this.onControllerCreated, 13 | this.infoPopupDismissed, 14 | this.contentTitle, 15 | this.customContent, 16 | this.areaBackgroundColor, 17 | this.arrowTheme, 18 | this.contentTheme, 19 | this.onAreaPressed, 20 | this.onLayoutMounted, 21 | this.dismissTriggerBehavior = PopupDismissTriggerBehavior.onTapArea, 22 | this.popupClickTriggerBehavior = PopupClickTriggerBehavior.onTap, 23 | this.contentOffset, 24 | this.indicatorOffset, 25 | this.contentMaxWidth, 26 | this.enableHighlight = false, 27 | this.highLightTheme, 28 | this.enableLog = false, 29 | this.enabledAutomaticConstraint = true, 30 | super.key, 31 | }) : assert(customContent == null || contentTitle == null, 32 | 'You can not use both customContent and contentTitle at the same time.'); 33 | 34 | /// The [child] of the [InfoPopupWidget]. 35 | final Widget child; 36 | 37 | /// [onControllerCreated] is called when the [InfoPopupController] is created. 38 | final OnControllerCreated? onControllerCreated; 39 | 40 | /// The [infoPopupDismissed] is the callback function when the popup is dismissed. 41 | final VoidCallback? infoPopupDismissed; 42 | 43 | /// The [contentTitle] to show in the popup. 44 | final String? contentTitle; 45 | 46 | /// The [customContent] is the widget that will be custom shown in the popup. 47 | final Widget? Function()? customContent; 48 | 49 | /// The [areaBackgroundColor] is the background color of the area that 50 | final Color? areaBackgroundColor; 51 | 52 | /// [arrowTheme] is the arrow theme of the popup. 53 | final InfoPopupArrowTheme? arrowTheme; 54 | 55 | /// [contentTheme] is the content theme of the popup. 56 | final InfoPopupContentTheme? contentTheme; 57 | 58 | /// [onAreaPressed] Called when the area outside the popup is pressed. 59 | final OnAreaPressed? onAreaPressed; 60 | 61 | /// [onLayoutMounted] Called when the info layout is mounted. 62 | final Function(Size size)? onLayoutMounted; 63 | 64 | /// The [dismissTriggerBehavior] is the showing behavior of the popup. 65 | final PopupDismissTriggerBehavior dismissTriggerBehavior; 66 | 67 | /// The [popupClickTriggerBehavior] is the click behavior of the popup. 68 | final PopupClickTriggerBehavior popupClickTriggerBehavior; 69 | 70 | /// The [contentOffset] is the offset of the content.. 71 | final Offset? contentOffset; 72 | 73 | /// The [indicatorOffset] is the offset of the indicator. 74 | final Offset? indicatorOffset; 75 | 76 | /// [contentMaxWidth] is the max width of the content that is shown. 77 | /// If the [contentMaxWidth] is null, the max width will be eighty percent 78 | /// of the screen. 79 | final double? contentMaxWidth; 80 | 81 | /// The [enableHighlight] is the boolean value that indicates whether the 82 | /// highlight is enabled or not. 83 | final bool enableHighlight; 84 | 85 | /// The [enableLog] is the boolean value that indicates whether the 86 | /// log is enabled or not. 87 | /// 88 | /// If the [enableLog] is true, the log will be shown in the console. 89 | final bool enableLog; 90 | 91 | /// The [highLightTheme] is the theme of the highlight. Can customize the 92 | /// highlight border radius and the padding. 93 | final HighLightTheme? highLightTheme; 94 | 95 | /// The [enabledAutomaticConstraint] is the boolean value that indicates 96 | /// whether the popup will be constrained automatically or not. 97 | final bool enabledAutomaticConstraint; 98 | 99 | @override 100 | State createState() => _InfoPopupWidgetState(); 101 | } 102 | 103 | class _InfoPopupWidgetState extends State { 104 | final GlobalKey> _infoPopupTargetKey = GlobalKey(); 105 | InfoPopupController? _infoPopupController; 106 | bool _isControllerInitialized = false; 107 | final LayerLink _layerLink = LayerLink(); 108 | 109 | @override 110 | void dispose() { 111 | if (_infoPopupController != null && _infoPopupController!.isShowing) { 112 | _infoPopupController!.dismissInfoPopup(); 113 | } 114 | super.dispose(); 115 | } 116 | 117 | bool get _isMouseRegionPermitted { 118 | final bool mouseIsConnected = 119 | RendererBinding.instance.mouseTracker.mouseIsConnected; 120 | 121 | if (!mouseIsConnected || !_isControllerInitialized) { 122 | return false; 123 | } 124 | 125 | return true; 126 | } 127 | 128 | @override 129 | void didUpdateWidget(InfoPopupWidget oldWidget) { 130 | if (widget.customContent != null && _infoPopupController != null) { 131 | _infoPopupController!.updateContent(); 132 | } else if (oldWidget.contentTitle != widget.contentTitle || 133 | oldWidget.areaBackgroundColor != widget.areaBackgroundColor || 134 | oldWidget.arrowTheme != widget.arrowTheme || 135 | oldWidget.contentTheme != widget.contentTheme || 136 | oldWidget.contentOffset != widget.contentOffset || 137 | oldWidget.indicatorOffset != widget.indicatorOffset || 138 | oldWidget.contentMaxWidth != widget.contentMaxWidth || 139 | oldWidget.enableHighlight != widget.enableHighlight || 140 | oldWidget.highLightTheme != widget.highLightTheme || 141 | oldWidget.dismissTriggerBehavior != widget.dismissTriggerBehavior) { 142 | if (_infoPopupController != null || _isControllerInitialized) { 143 | _infoPopupController!.dismissInfoPopup(); 144 | _infoPopupController = null; 145 | _isControllerInitialized = false; 146 | } 147 | setState(() {}); 148 | } 149 | 150 | super.didUpdateWidget(oldWidget); 151 | } 152 | 153 | @override 154 | Widget build(BuildContext context) { 155 | SchedulerBinding.instance.addPostFrameCallback((_) => _updateRenderBox()); 156 | return MouseRegion( 157 | onHover: (PointerHoverEvent event) { 158 | if (_isMouseRegionPermitted && !_infoPopupController!.isShowing) { 159 | _infoPopupController!.show(); 160 | } 161 | }, 162 | onExit: (PointerExitEvent event) { 163 | if (widget.dismissTriggerBehavior == 164 | PopupDismissTriggerBehavior.manuel) { 165 | return; 166 | } 167 | 168 | if (_isMouseRegionPermitted && _infoPopupController!.isShowing) { 169 | _infoPopupController!.dismissInfoPopup(); 170 | } 171 | }, 172 | child: GestureDetector( 173 | onTap: _behaviour(), 174 | onLongPressEnd: _onLongPressEnd, 175 | behavior: HitTestBehavior.translucent, 176 | child: CompositedTransformTarget( 177 | link: _layerLink, 178 | child: Container( 179 | key: _infoPopupTargetKey, 180 | child: widget.child, 181 | ), 182 | ), 183 | ), 184 | ); 185 | } 186 | 187 | Function()? _behaviour() { 188 | // ignore: use_if_null_to_convert_nulls_to_bools 189 | if (_infoPopupController?.isShowing == true) { 190 | return null; 191 | } 192 | 193 | if (widget.popupClickTriggerBehavior == PopupClickTriggerBehavior.none) { 194 | return null; 195 | } 196 | 197 | return () { 198 | if (_infoPopupController != null && !_infoPopupController!.isShowing) { 199 | _infoPopupController!.show(); 200 | } 201 | }; 202 | } 203 | 204 | Future _updateRenderBox() async { 205 | final BuildContext? context = _infoPopupTargetKey.currentContext; 206 | 207 | if (!mounted || context == null) { 208 | return; 209 | } 210 | 211 | final RenderBox? renderBox = context.findRenderObject() as RenderBox?; 212 | 213 | if (renderBox == null) { 214 | return; 215 | } 216 | 217 | _infoPopupController = _infoPopupController ??= InfoPopupController( 218 | context: context, 219 | targetRenderBox: renderBox, 220 | layerLink: _layerLink, 221 | contentTitle: widget.contentTitle, 222 | customContent: widget.customContent, 223 | areaBackgroundColor: widget.areaBackgroundColor ?? 224 | PopupConstants.defaultAreaBackgroundColor, 225 | arrowTheme: widget.arrowTheme ?? const InfoPopupArrowTheme(), 226 | contentTheme: widget.contentTheme ?? const InfoPopupContentTheme(), 227 | onAreaPressed: widget.onAreaPressed, 228 | enabledAutomaticConstraint: widget.enabledAutomaticConstraint, 229 | onLayoutMounted: (Size size) { 230 | setState(() { 231 | widget.onLayoutMounted?.call(size); 232 | }); 233 | }, 234 | dismissTriggerBehavior: widget.dismissTriggerBehavior, 235 | infoPopupDismissed: () { 236 | setState(() { 237 | widget.infoPopupDismissed?.call(); 238 | }); 239 | }, 240 | contentOffset: widget.contentOffset ?? const Offset(0, 0), 241 | indicatorOffset: widget.indicatorOffset ?? const Offset(0, 0), 242 | contentMaxWidth: widget.contentMaxWidth, 243 | enableHighlight: widget.enableHighlight, 244 | highLightTheme: widget.highLightTheme ?? HighLightTheme.defaultTheme(), 245 | ); 246 | 247 | if (!_isControllerInitialized && widget.onControllerCreated != null) { 248 | widget.onControllerCreated!.call(_infoPopupController!); 249 | } 250 | 251 | _infoPopupController!.updateInfoPopupTargetRenderBox(renderBox); 252 | 253 | _isControllerInitialized = true; 254 | } 255 | 256 | Function? _onLongPressEnd(LongPressEndDetails details) { 257 | if (widget.popupClickTriggerBehavior != 258 | PopupClickTriggerBehavior.onLongPress) { 259 | return null; 260 | } 261 | 262 | return () { 263 | if (_infoPopupController != null && !_infoPopupController!.isShowing) { 264 | _infoPopupController!.show(); 265 | } 266 | }; 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /lib/src/overlays/overlay_entry_layout.dart: -------------------------------------------------------------------------------- 1 | part of '../controllers/info_popup_controller.dart'; 2 | 3 | /// [InfoPopup] is a widget that shows a popup with a text and an arrow indicator. 4 | class OverlayInfoPopup extends StatefulWidget { 5 | /// Creates a [InfoPopup] widget. 6 | const OverlayInfoPopup({ 7 | required LayerLink layerLink, 8 | required RenderBox targetRenderBox, 9 | required Color areaBackgroundColor, 10 | required InfoPopupArrowTheme indicatorTheme, 11 | required InfoPopupContentTheme contentTheme, 12 | required VoidCallback onAreaPressed, 13 | required Function(Size size) onLayoutMounted, 14 | required Offset contentOffset, 15 | required Offset indicatorOffset, 16 | required PopupDismissTriggerBehavior dismissTriggerBehavior, 17 | required bool enableHighlight, 18 | required HighLightTheme highlightTheme, 19 | required VoidCallback hideOverlay, 20 | required bool enabledAutomaticConstraint, 21 | Widget? Function()? customContent, 22 | String? contentTitle, 23 | double? contentMaxWidth, 24 | super.key, 25 | }) : _layerLink = layerLink, 26 | _targetRenderBox = targetRenderBox, 27 | _areaBackgroundColor = areaBackgroundColor, 28 | _indicatorTheme = indicatorTheme, 29 | _contentTheme = contentTheme, 30 | _onAreaPressed = onAreaPressed, 31 | _onLayoutMounted = onLayoutMounted, 32 | _contentOffset = contentOffset, 33 | _indicatorOffset = indicatorOffset, 34 | _dismissTriggerBehavior = dismissTriggerBehavior, 35 | _customContent = customContent, 36 | _contentTitle = contentTitle, 37 | _contentMaxWidth = contentMaxWidth, 38 | _enableHighlight = enableHighlight, 39 | _highLightTheme = highlightTheme, 40 | _enabledAutomaticConstraint = enabledAutomaticConstraint, 41 | _hideOverlay = hideOverlay; 42 | 43 | final LayerLink _layerLink; 44 | final RenderBox _targetRenderBox; 45 | final Widget? Function()? _customContent; 46 | final String? _contentTitle; 47 | final Color _areaBackgroundColor; 48 | final InfoPopupArrowTheme _indicatorTheme; 49 | final InfoPopupContentTheme _contentTheme; 50 | final VoidCallback _onAreaPressed; 51 | final Function(Size size) _onLayoutMounted; 52 | final Offset _contentOffset; 53 | final Offset _indicatorOffset; 54 | final PopupDismissTriggerBehavior _dismissTriggerBehavior; 55 | final double? _contentMaxWidth; 56 | final bool _enableHighlight; 57 | final HighLightTheme _highLightTheme; 58 | final VoidCallback _hideOverlay; 59 | final bool _enabledAutomaticConstraint; 60 | 61 | @override 62 | State createState() => _OverlayInfoPopupState(); 63 | } 64 | 65 | class _OverlayInfoPopupState extends State { 66 | final GlobalKey _bodyKey = GlobalKey(); 67 | 68 | @override 69 | void initState() { 70 | GestureBinding.instance.pointerRouter.addGlobalRoute(_handlePointerEvent); 71 | WidgetsBinding.instance.addPostFrameCallback( 72 | (_) { 73 | _updateContentLayoutSize(); 74 | }, 75 | ); 76 | super.initState(); 77 | } 78 | 79 | bool _isPointListenerDisposed = false; 80 | 81 | void _handlePointerEvent(PointerEvent event) { 82 | if (!mounted) { 83 | return; 84 | } 85 | 86 | final bool mouseIsConnected = 87 | RendererBinding.instance.mouseTracker.mouseIsConnected; 88 | 89 | if (mouseIsConnected) { 90 | GestureBinding.instance.pointerRouter.removeGlobalRoute( 91 | _handlePointerEvent, 92 | ); 93 | _isPointListenerDisposed = true; 94 | return; 95 | } 96 | 97 | final BuildContext? bodyContext = _bodyKey.currentContext; 98 | 99 | if (bodyContext == null) { 100 | return; 101 | } 102 | 103 | final RenderBox? bodyRenderBox = 104 | bodyContext.findRenderObject() as RenderBox?; 105 | 106 | if (bodyRenderBox == null) { 107 | return; 108 | } 109 | 110 | final Offset clickPosition = event.position; 111 | final Offset contentPosition = bodyRenderBox.localToGlobal(Offset.zero); 112 | 113 | switch (widget._dismissTriggerBehavior) { 114 | case PopupDismissTriggerBehavior.onTapContent: 115 | if (clickPosition.dx >= contentPosition.dx && 116 | clickPosition.dx <= contentPosition.dx + contentSize.width && 117 | clickPosition.dy >= contentPosition.dy && 118 | clickPosition.dy <= contentPosition.dy + contentSize.height) { 119 | widget._hideOverlay(); 120 | } 121 | break; 122 | case PopupDismissTriggerBehavior.onTapArea: 123 | if (!(clickPosition.dx >= contentPosition.dx && 124 | clickPosition.dx <= contentPosition.dx + contentSize.width && 125 | clickPosition.dy >= contentPosition.dy && 126 | clickPosition.dy <= contentPosition.dy + contentSize.height)) { 127 | widget._onAreaPressed(); 128 | } 129 | break; 130 | case PopupDismissTriggerBehavior.anyWhere: 131 | widget._hideOverlay(); 132 | break; 133 | case PopupDismissTriggerBehavior.manuel: 134 | // do nothing 135 | break; 136 | } 137 | } 138 | 139 | @override 140 | void dispose() { 141 | if (!_isPointListenerDisposed) { 142 | GestureBinding.instance.pointerRouter.removeGlobalRoute( 143 | _handlePointerEvent, 144 | ); 145 | } 146 | 147 | super.dispose(); 148 | } 149 | 150 | ArrowDirection? _overridenArrowDirection; 151 | 152 | ArrowDirection get arrowDirection { 153 | if (_overridenArrowDirection != null) { 154 | return _overridenArrowDirection!; 155 | } 156 | 157 | return widget._indicatorTheme.arrowDirection; 158 | } 159 | 160 | Offset get _indicatorOffset { 161 | final double indicatorWidth = widget._indicatorTheme.arrowSize.width; 162 | switch (arrowDirection) { 163 | case ArrowDirection.up: 164 | return Offset( 165 | _targetWidgetRect.width / 2 - indicatorWidth / 2, 166 | _targetWidgetRect.height, 167 | ) + 168 | widget._indicatorOffset + 169 | _highlightOffset; 170 | case ArrowDirection.down: 171 | return Offset( 172 | _targetWidgetRect.width / 2 - indicatorWidth / 2, 173 | -widget._indicatorTheme.arrowSize.height, 174 | ) + 175 | widget._indicatorOffset + 176 | _highlightOffset; 177 | } 178 | } 179 | 180 | Offset get _highlightOffset { 181 | double highlightVerticalGap = 0; 182 | 183 | if (widget._enableHighlight) { 184 | highlightVerticalGap = widget._highLightTheme.padding.bottom; 185 | } 186 | 187 | switch (arrowDirection) { 188 | case ArrowDirection.up: 189 | return Offset(0, highlightVerticalGap); 190 | case ArrowDirection.down: 191 | return Offset(0, -highlightVerticalGap); 192 | } 193 | } 194 | 195 | Offset get _bodyOffset { 196 | Offset targetCenterOffset = Offset.zero; 197 | 198 | final double contentWidth = contentSize.width; 199 | final double contentHeight = contentSize.height; 200 | final double targetWidth = _targetWidgetRect.width; 201 | final double targetHeight = _targetWidgetRect.height; 202 | final double contentDxCenter = targetWidth / 2 - contentWidth / 2; 203 | 204 | switch (arrowDirection) { 205 | case ArrowDirection.up: 206 | targetCenterOffset = Offset( 207 | contentDxCenter, 208 | targetHeight + widget._indicatorTheme.arrowSize.height, 209 | ); 210 | break; 211 | case ArrowDirection.down: 212 | targetCenterOffset = Offset( 213 | contentDxCenter, 214 | -(contentHeight + widget._indicatorTheme.arrowSize.height), 215 | ); 216 | break; 217 | } 218 | 219 | targetCenterOffset += _highlightOffset; 220 | 221 | final double contentLeft = contentDxCenter + _targetOffset.dx; 222 | final double contentRight = contentLeft + contentWidth; 223 | final double screenWidth = context.screenWidth; 224 | 225 | if (contentLeft < 0) { 226 | targetCenterOffset += Offset(-contentLeft, 0); 227 | } else if (contentRight > screenWidth) { 228 | targetCenterOffset += Offset(screenWidth - contentRight, 0); 229 | } 230 | 231 | return targetCenterOffset + widget._contentOffset; 232 | } 233 | 234 | Size? _contentSize; 235 | 236 | Size get contentSize { 237 | if (!_isLayoutMounted) { 238 | return widget._targetRenderBox.size; 239 | } else { 240 | return _contentSize!; 241 | } 242 | } 243 | 244 | @override 245 | void didUpdateWidget(covariant OverlayInfoPopup oldWidget) { 246 | _updateContentLayoutSize(); 247 | _contentMaxHeight; 248 | super.didUpdateWidget(oldWidget); 249 | } 250 | 251 | @override 252 | void didChangeDependencies() { 253 | _updateContentLayoutSize(); 254 | _contentMaxHeight; 255 | super.didChangeDependencies(); 256 | } 257 | 258 | bool _isLayoutMounted = false; 259 | 260 | void _updateContentLayoutSize() { 261 | Future.microtask( 262 | () { 263 | Future.delayed( 264 | const Duration(milliseconds: 50), 265 | () { 266 | if (!mounted) { 267 | return; 268 | } 269 | 270 | if (_bodyKey.currentContext == null) { 271 | return; 272 | } 273 | 274 | final RenderBox? renderBox = 275 | _bodyKey.currentContext!.findRenderObject() as RenderBox?; 276 | 277 | if (renderBox == null) { 278 | return; 279 | } 280 | 281 | final Size size = renderBox.size; 282 | 283 | if (size != _contentSize) { 284 | setState( 285 | () { 286 | _contentSize = size; 287 | 288 | widget._onLayoutMounted(size); 289 | _isLayoutMounted = true; 290 | }, 291 | ); 292 | } 293 | }, 294 | ); 295 | }, 296 | ); 297 | } 298 | 299 | Rect get _targetWidgetRect { 300 | if (!widget._targetRenderBox.attached) { 301 | return Rect.zero; 302 | } 303 | 304 | final Offset offset = widget._targetRenderBox.localToGlobal(Offset.zero); 305 | 306 | return Rect.fromLTWH( 307 | offset.dx, 308 | offset.dy, 309 | widget._targetRenderBox.size.width, 310 | widget._targetRenderBox.size.height, 311 | ); 312 | } 313 | 314 | double get _contentMaxWidth { 315 | if (widget._contentMaxWidth == null) { 316 | return context.screenWidth * .8; 317 | } else { 318 | return widget._contentMaxWidth!; 319 | } 320 | } 321 | 322 | double get _contentMaxHeight { 323 | const int padding = 16; 324 | final double screenHeight = context.screenHeight; 325 | final double bottomPadding = context.mediaQuery.padding.bottom; 326 | final double topPadding = context.mediaQuery.padding.top; 327 | final double targetWidgetTopPosition = _targetWidgetRect.top; 328 | final double contentHeight = _contentSize?.height ?? 0; 329 | final bool isArrowDirectionOverriden = _overridenArrowDirection != null; 330 | 331 | switch (arrowDirection) { 332 | case ArrowDirection.up: 333 | final double belowSpace = screenHeight - 334 | targetWidgetTopPosition - 335 | _targetWidgetRect.height - 336 | padding - 337 | bottomPadding; 338 | 339 | if (!widget._indicatorTheme.enabledAutoArrowDirection) { 340 | return belowSpace; 341 | } 342 | 343 | if ((belowSpace - contentHeight) > 0 || isArrowDirectionOverriden) { 344 | return belowSpace; 345 | } else { 346 | _setIndicatorDirection(ArrowDirection.down); 347 | return 0; 348 | } 349 | case ArrowDirection.down: 350 | final double aboveSpace = targetWidgetTopPosition - topPadding; 351 | 352 | if (!widget._indicatorTheme.enabledAutoArrowDirection) { 353 | return aboveSpace; 354 | } 355 | 356 | if ((aboveSpace - contentHeight) > 0 || isArrowDirectionOverriden) { 357 | return aboveSpace; 358 | } else { 359 | _setIndicatorDirection(ArrowDirection.up); 360 | return 0; 361 | } 362 | } 363 | } 364 | 365 | Offset get _areaOffset { 366 | if (widget._enableHighlight) { 367 | return Offset(-_targetWidgetRect.left, -_targetWidgetRect.top); 368 | } 369 | 370 | switch (widget._dismissTriggerBehavior) { 371 | case PopupDismissTriggerBehavior.onTapContent: 372 | return _bodyOffset; 373 | case PopupDismissTriggerBehavior.onTapArea: 374 | case PopupDismissTriggerBehavior.anyWhere: 375 | case PopupDismissTriggerBehavior.manuel: 376 | return Offset(-_targetWidgetRect.left, -_targetWidgetRect.top); 377 | } 378 | } 379 | 380 | Offset get _targetOffset { 381 | if (!widget._targetRenderBox.attached) { 382 | return Offset.zero; 383 | } 384 | 385 | return widget._targetRenderBox.localToGlobal(Offset.zero); 386 | } 387 | 388 | bool get _dismissBehaviorIsOnTapContent => 389 | widget._dismissTriggerBehavior == 390 | PopupDismissTriggerBehavior.onTapContent; 391 | 392 | @override 393 | Widget build(BuildContext context) { 394 | _contentMaxHeight; 395 | return ClipPath( 396 | clipper: widget._enableHighlight 397 | ? _HighLighter( 398 | area: Rect.fromLTWH( 399 | _targetWidgetRect.left, 400 | _targetWidgetRect.top, 401 | _targetWidgetRect.width, 402 | _targetWidgetRect.height, 403 | ), 404 | padding: widget._highLightTheme.padding, 405 | radius: widget._highLightTheme.radius, 406 | ) 407 | : null, 408 | child: Align( 409 | child: CompositedTransformFollower( 410 | link: widget._layerLink, 411 | showWhenUnlinked: false, 412 | offset: _areaOffset, 413 | child: Material( 414 | color: widget._enableHighlight 415 | ? widget._highLightTheme.backgroundColor 416 | : widget._areaBackgroundColor, 417 | type: (!widget._enableHighlight && 418 | widget._areaBackgroundColor == Colors.transparent) 419 | ? MaterialType.transparency 420 | : MaterialType.canvas, 421 | child: SizedBox( 422 | height: 423 | _dismissBehaviorIsOnTapContent ? null : context.screenHeight, 424 | width: 425 | _dismissBehaviorIsOnTapContent ? null : context.screenWidth, 426 | child: Column( 427 | children: [ 428 | CompositedTransformFollower( 429 | link: widget._layerLink, 430 | showWhenUnlinked: false, 431 | offset: _indicatorOffset, 432 | child: AnimatedScale( 433 | scale: _isLayoutMounted ? 1.0 : 0.0, 434 | duration: const Duration(milliseconds: 50), 435 | alignment: Alignment.topCenter, 436 | child: CustomPaint( 437 | size: widget._indicatorTheme.arrowSize, 438 | painter: widget._indicatorTheme.arrowPainter ?? 439 | ArrowIndicatorPainter( 440 | arrowDirection: arrowDirection, 441 | arrowColor: widget._indicatorTheme.color, 442 | ), 443 | ), 444 | ), 445 | ), 446 | CompositedTransformFollower( 447 | link: widget._layerLink, 448 | showWhenUnlinked: false, 449 | offset: _bodyOffset, 450 | child: AnimatedScale( 451 | scale: _isLayoutMounted ? 1.0 : 0.0, 452 | duration: const Duration(milliseconds: 50), 453 | alignment: Alignment.topCenter, 454 | child: Builder(builder: (BuildContext context) { 455 | final Container content = Container( 456 | key: _bodyKey, 457 | decoration: widget._customContent != null 458 | ? null 459 | : BoxDecoration( 460 | color: widget._contentTheme 461 | .infoContainerBackgroundColor, 462 | borderRadius: 463 | widget._contentTheme.contentBorderRadius, 464 | boxShadow: const [ 465 | BoxShadow( 466 | color: Color(0xFF808080), 467 | blurRadius: 1.0, 468 | ), 469 | ], 470 | ), 471 | padding: widget._customContent != null 472 | ? null 473 | : widget._contentTheme.contentPadding, 474 | child: SingleChildScrollView( 475 | child: widget._customContent == null 476 | ? Text( 477 | widget._contentTitle ?? '', 478 | style: widget._contentTheme.infoTextStyle, 479 | textAlign: 480 | widget._contentTheme.infoTextAlign, 481 | ) 482 | : widget._customContent!(), 483 | ), 484 | ); 485 | 486 | if (!widget._enabledAutomaticConstraint) { 487 | return content; 488 | } 489 | 490 | if (_contentMaxHeight < 0) { 491 | return content; 492 | } 493 | 494 | return ConstrainedBox( 495 | constraints: BoxConstraints( 496 | maxWidth: _contentMaxWidth, 497 | maxHeight: _contentMaxHeight, 498 | ), 499 | child: content, 500 | ); 501 | }), 502 | ), 503 | ) 504 | ], 505 | ), 506 | ), 507 | ), 508 | ), 509 | ), 510 | ); 511 | } 512 | 513 | void _setIndicatorDirection(ArrowDirection newDirection) { 514 | if (!widget._indicatorTheme.enabledAutoArrowDirection) { 515 | return; 516 | } 517 | 518 | setState(() { 519 | _overridenArrowDirection = newDirection; 520 | }); 521 | } 522 | } 523 | -------------------------------------------------------------------------------- /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 = 1430; 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 = 11.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 | DEVELOPMENT_TEAM = J5QH55CXD8; 295 | ENABLE_BITCODE = NO; 296 | INFOPLIST_FILE = Runner/Info.plist; 297 | LD_RUNPATH_SEARCH_PATHS = ( 298 | "$(inherited)", 299 | "@executable_path/Frameworks", 300 | ); 301 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 302 | PRODUCT_NAME = "$(TARGET_NAME)"; 303 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 304 | SWIFT_VERSION = 5.0; 305 | VERSIONING_SYSTEM = "apple-generic"; 306 | }; 307 | name = Profile; 308 | }; 309 | 97C147031CF9000F007C117D /* Debug */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | ALWAYS_SEARCH_USER_PATHS = NO; 313 | CLANG_ANALYZER_NONNULL = YES; 314 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 315 | CLANG_CXX_LIBRARY = "libc++"; 316 | CLANG_ENABLE_MODULES = YES; 317 | CLANG_ENABLE_OBJC_ARC = YES; 318 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 319 | CLANG_WARN_BOOL_CONVERSION = YES; 320 | CLANG_WARN_COMMA = YES; 321 | CLANG_WARN_CONSTANT_CONVERSION = YES; 322 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 323 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 324 | CLANG_WARN_EMPTY_BODY = YES; 325 | CLANG_WARN_ENUM_CONVERSION = YES; 326 | CLANG_WARN_INFINITE_RECURSION = YES; 327 | CLANG_WARN_INT_CONVERSION = YES; 328 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 330 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 332 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 333 | CLANG_WARN_STRICT_PROTOTYPES = YES; 334 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 335 | CLANG_WARN_UNREACHABLE_CODE = YES; 336 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 337 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 338 | COPY_PHASE_STRIP = NO; 339 | DEBUG_INFORMATION_FORMAT = dwarf; 340 | ENABLE_STRICT_OBJC_MSGSEND = YES; 341 | ENABLE_TESTABILITY = YES; 342 | GCC_C_LANGUAGE_STANDARD = gnu99; 343 | GCC_DYNAMIC_NO_PIC = NO; 344 | GCC_NO_COMMON_BLOCKS = YES; 345 | GCC_OPTIMIZATION_LEVEL = 0; 346 | GCC_PREPROCESSOR_DEFINITIONS = ( 347 | "DEBUG=1", 348 | "$(inherited)", 349 | ); 350 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 351 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 352 | GCC_WARN_UNDECLARED_SELECTOR = YES; 353 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 354 | GCC_WARN_UNUSED_FUNCTION = YES; 355 | GCC_WARN_UNUSED_VARIABLE = YES; 356 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 357 | MTL_ENABLE_DEBUG_INFO = YES; 358 | ONLY_ACTIVE_ARCH = YES; 359 | SDKROOT = iphoneos; 360 | TARGETED_DEVICE_FAMILY = "1,2"; 361 | }; 362 | name = Debug; 363 | }; 364 | 97C147041CF9000F007C117D /* Release */ = { 365 | isa = XCBuildConfiguration; 366 | buildSettings = { 367 | ALWAYS_SEARCH_USER_PATHS = NO; 368 | CLANG_ANALYZER_NONNULL = YES; 369 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 370 | CLANG_CXX_LIBRARY = "libc++"; 371 | CLANG_ENABLE_MODULES = YES; 372 | CLANG_ENABLE_OBJC_ARC = YES; 373 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 374 | CLANG_WARN_BOOL_CONVERSION = YES; 375 | CLANG_WARN_COMMA = YES; 376 | CLANG_WARN_CONSTANT_CONVERSION = YES; 377 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 378 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 379 | CLANG_WARN_EMPTY_BODY = YES; 380 | CLANG_WARN_ENUM_CONVERSION = YES; 381 | CLANG_WARN_INFINITE_RECURSION = YES; 382 | CLANG_WARN_INT_CONVERSION = YES; 383 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 384 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 385 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 387 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 388 | CLANG_WARN_STRICT_PROTOTYPES = YES; 389 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 390 | CLANG_WARN_UNREACHABLE_CODE = YES; 391 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 392 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 393 | COPY_PHASE_STRIP = NO; 394 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 395 | ENABLE_NS_ASSERTIONS = NO; 396 | ENABLE_STRICT_OBJC_MSGSEND = YES; 397 | GCC_C_LANGUAGE_STANDARD = gnu99; 398 | GCC_NO_COMMON_BLOCKS = YES; 399 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 400 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 401 | GCC_WARN_UNDECLARED_SELECTOR = YES; 402 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 403 | GCC_WARN_UNUSED_FUNCTION = YES; 404 | GCC_WARN_UNUSED_VARIABLE = YES; 405 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 406 | MTL_ENABLE_DEBUG_INFO = NO; 407 | SDKROOT = iphoneos; 408 | SUPPORTED_PLATFORMS = iphoneos; 409 | SWIFT_COMPILATION_MODE = wholemodule; 410 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 411 | TARGETED_DEVICE_FAMILY = "1,2"; 412 | VALIDATE_PRODUCT = YES; 413 | }; 414 | name = Release; 415 | }; 416 | 97C147061CF9000F007C117D /* Debug */ = { 417 | isa = XCBuildConfiguration; 418 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 419 | buildSettings = { 420 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 421 | CLANG_ENABLE_MODULES = YES; 422 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 423 | DEVELOPMENT_TEAM = J5QH55CXD8; 424 | ENABLE_BITCODE = NO; 425 | INFOPLIST_FILE = Runner/Info.plist; 426 | LD_RUNPATH_SEARCH_PATHS = ( 427 | "$(inherited)", 428 | "@executable_path/Frameworks", 429 | ); 430 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 431 | PRODUCT_NAME = "$(TARGET_NAME)"; 432 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 433 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 434 | SWIFT_VERSION = 5.0; 435 | VERSIONING_SYSTEM = "apple-generic"; 436 | }; 437 | name = Debug; 438 | }; 439 | 97C147071CF9000F007C117D /* Release */ = { 440 | isa = XCBuildConfiguration; 441 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 442 | buildSettings = { 443 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 444 | CLANG_ENABLE_MODULES = YES; 445 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 446 | DEVELOPMENT_TEAM = J5QH55CXD8; 447 | ENABLE_BITCODE = NO; 448 | INFOPLIST_FILE = Runner/Info.plist; 449 | LD_RUNPATH_SEARCH_PATHS = ( 450 | "$(inherited)", 451 | "@executable_path/Frameworks", 452 | ); 453 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 456 | SWIFT_VERSION = 5.0; 457 | VERSIONING_SYSTEM = "apple-generic"; 458 | }; 459 | name = Release; 460 | }; 461 | /* End XCBuildConfiguration section */ 462 | 463 | /* Begin XCConfigurationList section */ 464 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 465 | isa = XCConfigurationList; 466 | buildConfigurations = ( 467 | 97C147031CF9000F007C117D /* Debug */, 468 | 97C147041CF9000F007C117D /* Release */, 469 | 249021D3217E4FDB00AE95B9 /* Profile */, 470 | ); 471 | defaultConfigurationIsVisible = 0; 472 | defaultConfigurationName = Release; 473 | }; 474 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 475 | isa = XCConfigurationList; 476 | buildConfigurations = ( 477 | 97C147061CF9000F007C117D /* Debug */, 478 | 97C147071CF9000F007C117D /* Release */, 479 | 249021D4217E4FDB00AE95B9 /* Profile */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | defaultConfigurationName = Release; 483 | }; 484 | /* End XCConfigurationList section */ 485 | }; 486 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 487 | } 488 | --------------------------------------------------------------------------------