Repository: GeekAbdelouahed/flutter-reaction-button Branch: master Commit: dbab28e9f441 Files: 68 Total size: 96.1 KB Directory structure: gitextract_8tbpjqev/ ├── .github/ │ └── workflows/ │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── example/ │ ├── .gitignore │ ├── .metadata │ ├── README.md │ ├── analysis_options.yaml │ ├── android/ │ │ ├── .gitignore │ │ ├── .project │ │ ├── .settings/ │ │ │ └── org.eclipse.buildship.core.prefs │ │ ├── app/ │ │ │ ├── build.gradle │ │ │ └── src/ │ │ │ ├── debug/ │ │ │ │ └── AndroidManifest.xml │ │ │ ├── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── kotlin/ │ │ │ │ │ └── abdelouahedmedjoudja/ │ │ │ │ │ ├── example/ │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ │ └── flutter_reaction_button_test/ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── res/ │ │ │ │ ├── drawable/ │ │ │ │ │ └── launch_background.xml │ │ │ │ └── values/ │ │ │ │ └── styles.xml │ │ │ └── profile/ │ │ │ └── AndroidManifest.xml │ │ ├── build.gradle │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ └── settings.gradle │ ├── ios/ │ │ ├── .gitignore │ │ ├── Flutter/ │ │ │ ├── AppFrameworkInfo.plist │ │ │ ├── Debug.xcconfig │ │ │ └── Release.xcconfig │ │ ├── Runner/ │ │ │ ├── AppDelegate.swift │ │ │ ├── Assets.xcassets/ │ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ │ └── Contents.json │ │ │ │ └── LaunchImage.imageset/ │ │ │ │ ├── Contents.json │ │ │ │ └── README.md │ │ │ ├── Base.lproj/ │ │ │ │ ├── LaunchScreen.storyboard │ │ │ │ └── Main.storyboard │ │ │ ├── Info.plist │ │ │ └── Runner-Bridging-Header.h │ │ ├── Runner.xcodeproj/ │ │ │ ├── project.pbxproj │ │ │ ├── project.xcworkspace/ │ │ │ │ ├── contents.xcworkspacedata │ │ │ │ └── xcshareddata/ │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ │ └── xcshareddata/ │ │ │ └── xcschemes/ │ │ │ └── Runner.xcscheme │ │ └── Runner.xcworkspace/ │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata/ │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ ├── lib/ │ │ ├── data.dart │ │ ├── image.dart │ │ ├── main.dart │ │ └── post.dart │ ├── pubspec.yaml │ └── web/ │ ├── index.html │ └── manifest.json ├── flutter_reaction_button.iml ├── lib/ │ ├── flutter_reaction_button.dart │ └── src/ │ ├── common/ │ │ ├── position.dart │ │ └── position_notifier.dart │ ├── enums/ │ │ ├── box.dart │ │ └── reaction.dart │ ├── extensions/ │ │ └── key.dart │ ├── models/ │ │ └── reaction.dart │ └── widgets/ │ ├── reaction_button.dart │ ├── reactions_box.dart │ └── reactions_box_item.dart ├── pubspec.yaml └── test/ ├── main.dart └── reaction_button_test.dart ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: push: tags: - 'v[0-9]+.[0-9]+.[0-9]+*' jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Install Flutter uses: subosito/flutter-action@v2.10.0 - name: Install dependencies run: flutter pub get - name: Analyze run: flutter analyze - name: Run tests run: flutter test - name: Format code run: dart format --fix . - name: Check Publish Warnings run: dart pub publish --dry-run - name: Publish uses: k-paxian/dart-package-publisher@v1.5.1 with: credentialJson: ${{ secrets.CREDENTIAL_JSON }} flutter: true skipTests: true ================================================ FILE: .github/workflows/test.yml ================================================ name: Test on: [push] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 - run: flutter pub get - name: Analyze project source run: dart analyze - name: Format code run: dart format --fix . - name: Run tests run: flutter test ================================================ FILE: .gitignore ================================================ #Flutter versions manager .fvm/ .idea/ .vscode/ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .packages .pub-cache/ .pub/ build/ # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/local.properties **/android/**/GeneratedPluginRegistrant.java # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages !flutter_reaction_button.iml ================================================ FILE: .metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: b712a172f9694745f50505c93340883493b505e5 channel: stable project_type: package ================================================ FILE: CHANGELOG.md ================================================ ## 3.0.0+3 * Fix reactions box overflow ## 3.0.0+2 * Minor updates ## 3.0.0+1 * Update README ## 3.0.0 - **BREAKING CHANGE**: - `FlutterReactionButton` and `FlutterReactionButtonToggle` removed - `itemSize` is required in `ReactionButton` - `onReactionChanged` callback updated - `VerticalPosition` and `HorizontalPosition` removed - Adds `child` to `ReactionButton` - Animated `ReactionsBox` ## 2.0.2 * Pipeline setup ## 2.0.1+1 * Minor bugs fixed. Thanks to [Kyle Venn](https://github.com/kvenn) ## 2.0.1 * Adds ReactionContainer * Bugs fix and code improvements, Thanks to [Felix Gabler](https://github.com/felixgabler) ## 2.0.0+3 * Upgrade flutter version ## 2.0.0+2 * Fix Offscreen Issue. Thanks to [rlee1990](https://github.com/rlee1990) ## 2.0.0+1 * Dragging improvement ## 2.0.0 * Change `FlutterReactionButton` to `ReactionButton` * Change `FlutterReactionButtonCheck` to `ReactionButtonToggle` * Change `Function(Reaction, int, bool) onReactionChanged` to `Function(T?, bool) onReactionChanged` * Change `Function(Reaction, int) onReactionChanged` to `Function(T?) onReactionChanged` * Dynamic scale depending on the hover position * Dynamic horizontal alignment * Scrollable position * Minor bugs fixed ## 1.0.8 * Added support null safety ## 1.0.7+3 * Deprecated Reaction id ## 1.0.7+2 * Improvement box flow ## 1.0.7 * Remove reaction id * Clean code ## 1.0.6+2 * Minor bugs fixed ## 1.0.6+1 * Minor bugs fixed ## 1.0.6 * Reaction title ## 1.0.5 * Items spacing * Box padding ## 1.0.4 * Reactions box alignment ## 1.0.3 * Change state externally ## 1.0.2 * Code improvements ## 1.0.1 * Define reaction by id. * Alternative to popup menu. * Enable/disable reaction click. ## 1.0.0 * Adds ripple effect. ## 0.1.3 * Selected rection index. ## 0.1.2+1 * Adds Example. ## 0.1.2 * From plugin to packages. ## 0.1.1 * Reactions List @required. ## 0.1.0 * Update Description pubspec.yaml. ## 0.0.1 * Describe initial release. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Abdelouahed Medjoudja Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![pub package](https://img.shields.io/pub/v/flutter_reaction_button.svg)](https://pub.dartlang.org/packages/flutter_reaction_button) # Flutter reaction button Flutter Reaction Button is a customizable Flutter package that allows you to easily create interactive buttons with reaction emojis, similar to Facebook's iconic reaction buttons. ## Screenshot ![App Screenshot](https://raw.githubusercontent.com/GeekAbdelouahed/flutter-reaction-button/master/images/flutter_reaction_button_preview.png) ## Installation ```yaml # pubspec.yaml dependencies: flutter: sdk: flutter flutter_reaction_button: ``` ## Usage ```dart import 'package:flutter_reaction_button/flutter_reaction_button.dart'; ReactionButton( onReactionChanged: (Reaction? reaction) { debugPrint('Selected value: ${reaction?.value}'); }, reactions: >[ Reaction( value: 'like', icon: widget, ), Reaction( value: 'love', icon: widget, ), ... ], initialReaction: Reaction( value: 'like', icon: widget, ), selectedReaction: Reaction( value: 'like_fill', icon: widget, ), ) ``` ## License ```legal MIT License Copyright (c) 2023 Abdelouahed Medjoudja Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` ================================================ FILE: analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: example/.gitignore ================================================ #Flutter versions manager .fvm/ # Miscellaneous *.class *.log *.pyc *.swp .DS_Store .atom/ .buildlog/ .history .svn/ # IntelliJ related *.iml *.ipr *.iws .idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line # is commented out by default. #.vscode/ # Flutter/Dart/Pub related **/doc/api/ .dart_tool/ .flutter-plugins .packages .pub-cache/ .pub/ /build/ # Android related **/android/**/gradle-wrapper.jar **/android/.gradle **/android/captures/ **/android/gradlew **/android/gradlew.bat **/android/local.properties **/android/**/GeneratedPluginRegistrant.java # iOS/XCode related **/ios/**/*.mode1v3 **/ios/**/*.mode2v3 **/ios/**/*.moved-aside **/ios/**/*.pbxuser **/ios/**/*.perspectivev3 **/ios/**/*sync/ **/ios/**/.sconsign.dblite **/ios/**/.tags* **/ios/**/.vagrant/ **/ios/**/DerivedData/ **/ios/**/Icon? **/ios/**/Pods/ **/ios/**/.symlinks/ **/ios/**/profile **/ios/**/xcuserdata **/ios/.generated/ **/ios/Flutter/App.framework **/ios/Flutter/Flutter.framework **/ios/Flutter/Generated.xcconfig **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !**/ios/**/default.mode1v3 !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages ================================================ FILE: example/.metadata ================================================ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited. version: revision: b712a172f9694745f50505c93340883493b505e5 channel: stable project_type: app ================================================ FILE: example/README.md ================================================ # flutter_reaction_button_example ReactionButton: ```dart ReactionButton( onReactionChanged: (Reaction? reaction) { debugPrint('Selected value: ${reaction?.value}'); }, reactions: >[ Reaction( value: 'like', icon: widget, ), Reaction( value: 'love', icon: widget, ), ... ], initialReaction: Reaction( value: 'like', icon: widget, ), selectedReaction: Reaction( value: 'like_fill', icon: widget, ), ) ``` ReactionButton: ```dart ReactionButton( toggle: false, onReactionChanged: (Reaction? reaction) { debugPrint('Selected language: ${reaction?.value}'); }, reactions: >[ Reaction( value: 'en', icon: widget, ), Reaction( value: 'ar', icon: widget, ), ... ], initialReaction: Reaction( value: null, icon: Icon(Icons.language), ), ) ``` ================================================ FILE: example/analysis_options.yaml ================================================ # This file configures the analyzer, which statically analyzes Dart code to # check for errors, warnings, and lints. # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be # invoked from the command line by running `flutter analyze`. # The following line activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml linter: # The lint rules applied to this project can be customized in the # section below to disable rules from the `package:flutter_lints/flutter.yaml` # included above or to enable additional rules. A list of all available lints # and their documentation is published at # https://dart-lang.github.io/linter/lints/index.html. # # Instead of disabling a lint rule for the entire project in the # section below, it can also be suppressed for a single line of code # or a specific dart file by using the `// ignore: name_of_lint` and # `// ignore_for_file: name_of_lint` syntax on the line or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # Additional information about this file can be found at # https://dart.dev/guides/language/analysis-options ================================================ FILE: example/android/.gitignore ================================================ gradle-wrapper.jar /.gradle /captures/ /gradlew /gradlew.bat /local.properties GeneratedPluginRegistrant.java # Remember to never publicly share your keystore. # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app key.properties ================================================ FILE: example/android/.project ================================================ android Project android created by Buildship. org.eclipse.buildship.core.gradleprojectbuilder org.eclipse.buildship.core.gradleprojectnature ================================================ FILE: example/android/.settings/org.eclipse.buildship.core.prefs ================================================ connection.project.dir= eclipse.preferences.version=1 ================================================ FILE: example/android/app/build.gradle ================================================ def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { localPropertiesFile.withReader('UTF-8') { reader -> localProperties.load(reader) } } def flutterRoot = localProperties.getProperty('flutter.sdk') if (flutterRoot == null) { throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' } def flutterVersionName = localProperties.getProperty('flutter.versionName') if (flutterVersionName == null) { flutterVersionName = '1.0' } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion 31 sourceSets { main.java.srcDirs += 'src/main/kotlin' } lintOptions { disable 'InvalidPackage' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "abdelouahedmedjoudja.flutter_reaction_button_test" minSdkVersion 21 targetSdkVersion 31 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.debug } } } flutter { source '../..' } dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' } ================================================ FILE: example/android/app/src/debug/AndroidManifest.xml ================================================ ================================================ FILE: example/android/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: example/android/app/src/main/kotlin/abdelouahedmedjoudja/example/MainActivity.kt ================================================ package abdelouahedmedjoudja.example import io.flutter.embedding.android.FlutterActivity class MainActivity : FlutterActivity() { } ================================================ FILE: example/android/app/src/main/kotlin/abdelouahedmedjoudja/flutter_reaction_button_test/MainActivity.kt ================================================ package abdelouahedmedjoudja.flutter_reaction_button_test import io.flutter.embedding.android.FlutterActivity class MainActivity : FlutterActivity() { } ================================================ FILE: example/android/app/src/main/res/drawable/launch_background.xml ================================================ ================================================ FILE: example/android/app/src/main/res/values/styles.xml ================================================ ================================================ FILE: example/android/app/src/profile/AndroidManifest.xml ================================================ ================================================ FILE: example/android/build.gradle ================================================ buildscript { ext.kotlin_version = '1.6.21' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } allprojects { repositories { google() jcenter() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } subprojects { project.evaluationDependsOn(':app') } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: example/android/gradle/wrapper/gradle-wrapper.properties ================================================ #Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip ================================================ FILE: example/android/gradle.properties ================================================ android.enableJetifier=true android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M android.enableR8=true ================================================ FILE: example/android/settings.gradle ================================================ include ':app' def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() def plugins = new Properties() def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') if (pluginsFile.exists()) { pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } } plugins.each { name, path -> def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() include ":$name" project(":$name").projectDir = pluginDirectory } ================================================ FILE: example/ios/.gitignore ================================================ *.mode1v3 *.mode2v3 *.moved-aside *.pbxuser *.perspectivev3 **/*sync/ .sconsign.dblite .tags* **/.vagrant/ **/DerivedData/ Icon? **/Pods/ **/.symlinks/ profile xcuserdata **/.generated/ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ ServiceDefinitions.json Runner/GeneratedPluginRegistrant.* # Exceptions to above rules. !default.mode1v3 !default.mode2v3 !default.pbxuser !default.perspectivev3 ================================================ FILE: example/ios/Flutter/AppFrameworkInfo.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable App CFBundleIdentifier io.flutter.flutter.app CFBundleInfoDictionaryVersion 6.0 CFBundleName App CFBundlePackageType FMWK CFBundleShortVersionString 1.0 CFBundleSignature ???? CFBundleVersion 1.0 MinimumOSVersion 11.0 ================================================ FILE: example/ios/Flutter/Debug.xcconfig ================================================ #include "Generated.xcconfig" ================================================ FILE: example/ios/Flutter/Release.xcconfig ================================================ #include "Generated.xcconfig" ================================================ FILE: example/ios/Runner/AppDelegate.swift ================================================ import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? ) -> Bool { GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ================================================ FILE: example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images": [ { "size": "20x20", "idiom": "iphone", "filename": "Icon-App-20x20@2x.png", "scale": "2x" }, { "size": "20x20", "idiom": "iphone", "filename": "Icon-App-20x20@3x.png", "scale": "3x" }, { "size": "29x29", "idiom": "iphone", "filename": "Icon-App-29x29@1x.png", "scale": "1x" }, { "size": "29x29", "idiom": "iphone", "filename": "Icon-App-29x29@2x.png", "scale": "2x" }, { "size": "29x29", "idiom": "iphone", "filename": "Icon-App-29x29@3x.png", "scale": "3x" }, { "size": "40x40", "idiom": "iphone", "filename": "Icon-App-40x40@2x.png", "scale": "2x" }, { "size": "40x40", "idiom": "iphone", "filename": "Icon-App-40x40@3x.png", "scale": "3x" }, { "size": "60x60", "idiom": "iphone", "filename": "Icon-App-60x60@2x.png", "scale": "2x" }, { "size": "60x60", "idiom": "iphone", "filename": "Icon-App-60x60@3x.png", "scale": "3x" }, { "size": "20x20", "idiom": "ipad", "filename": "Icon-App-20x20@1x.png", "scale": "1x" }, { "size": "20x20", "idiom": "ipad", "filename": "Icon-App-20x20@2x.png", "scale": "2x" }, { "size": "29x29", "idiom": "ipad", "filename": "Icon-App-29x29@1x.png", "scale": "1x" }, { "size": "29x29", "idiom": "ipad", "filename": "Icon-App-29x29@2x.png", "scale": "2x" }, { "size": "40x40", "idiom": "ipad", "filename": "Icon-App-40x40@1x.png", "scale": "1x" }, { "size": "40x40", "idiom": "ipad", "filename": "Icon-App-40x40@2x.png", "scale": "2x" }, { "size": "76x76", "idiom": "ipad", "filename": "Icon-App-76x76@1x.png", "scale": "1x" }, { "size": "76x76", "idiom": "ipad", "filename": "Icon-App-76x76@2x.png", "scale": "2x" }, { "size": "83.5x83.5", "idiom": "ipad", "filename": "Icon-App-83.5x83.5@2x.png", "scale": "2x" }, { "size": "1024x1024", "idiom": "ios-marketing", "filename": "Icon-App-1024x1024@1x.png", "scale": "1x" } ], "info": { "version": 1, "author": "xcode" } } ================================================ FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json ================================================ { "images": [ { "idiom": "universal", "filename": "LaunchImage.png", "scale": "1x" }, { "idiom": "universal", "filename": "LaunchImage@2x.png", "scale": "2x" }, { "idiom": "universal", "filename": "LaunchImage@3x.png", "scale": "3x" } ], "info": { "version": 1, "author": "xcode" } } ================================================ FILE: example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md ================================================ # Launch Screen Assets You can customize the launch screen with your own desired assets by replacing the image files in this directory. 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. ================================================ FILE: example/ios/Runner/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: example/ios/Runner/Base.lproj/Main.storyboard ================================================ ================================================ FILE: example/ios/Runner/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName flutter_reaction_button_test CFBundlePackageType APPL CFBundleShortVersionString $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIViewControllerBasedStatusBarAppearance CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents ================================================ FILE: example/ios/Runner/Runner-Bridging-Header.h ================================================ #import "GeneratedPluginRegistrant.h" ================================================ FILE: example/ios/Runner.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, ); name = Flutter; sourceTree = ""; }; 97C146E51CF9000F007C117D = { isa = PBXGroup; children = ( 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, ); name = Products; sourceTree = ""; }; 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; 97C146F11CF9000F007C117D /* Supporting Files */ = { isa = PBXGroup; children = ( ); name = "Supporting Files"; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, ); buildRules = ( ); dependencies = ( ); name = Runner; productName = Runner; productReference = 97C146EE1CF9000F007C117D /* Runner.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1430; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 0910; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "Run Script"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 97C146FB1CF9000F007C117D /* Base */, ); name = Main.storyboard; sourceTree = ""; }; 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 97C147001CF9000F007C117D /* Base */, ); name = LaunchScreen.storyboard; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Profile; }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 6YQ9LWSA59; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = abdelouahedmedjoudja.flutterReactionButtonTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 6YQ9LWSA59; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = abdelouahedmedjoudja.flutterReactionButtonTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 6YQ9LWSA59; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); PRODUCT_BUNDLE_IDENTIFIER = abdelouahedmedjoudja.flutterReactionButtonTest; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_SWIFT3_OBJC_INFERENCE = On; SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 97C146E61CF9000F007C117D /* Project object */; } ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme ================================================ ================================================ FILE: example/ios/Runner.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ================================================ IDEDidComputeMac32BitWarning ================================================ FILE: example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings ================================================ PreviewsEnabled ================================================ FILE: example/lib/data.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; final List> flagsReactions = [ Reaction( value: 'en', previewIcon: _buildFlagPreviewIcon( 'assets/images/united-kingdom-round.png', 'English', ), icon: _buildFlagIcon( 'assets/images/united-kingdom.png', ), ), Reaction( value: 'ar', previewIcon: _buildFlagPreviewIcon( 'assets/images/algeria-round.png', 'Arabic', ), icon: _buildFlagIcon( 'assets/images/algeria.png', ), ), Reaction( value: 'gr', previewIcon: _buildFlagPreviewIcon( 'assets/images/germany-round.png', 'German', ), icon: _buildFlagIcon( 'assets/images/germany.png', ), ), Reaction( value: 'sp', previewIcon: _buildFlagPreviewIcon( 'assets/images/spain-round.png', 'Spanish', ), icon: _buildFlagIcon( 'assets/images/spain.png', ), ), Reaction( value: 'ch', previewIcon: _buildFlagPreviewIcon( 'assets/images/china-round.png', 'Chinese', ), icon: _buildFlagIcon( 'assets/images/china.png', ), ), ]; const defaultInitialReaction = Reaction( value: null, icon: Text( 'No reaction', ), ); final List> reactions = [ Reaction( value: 'Happy', title: _buildEmojiTitle( 'Happy', ), previewIcon: _buildEmojiPreviewIcon( 'assets/images/happy.png', ), icon: _buildReactionsIcon( 'assets/images/happy.png', const Text( 'Happy', style: TextStyle( color: Color(0XFF3b5998), ), ), ), ), Reaction( value: 'Angry', title: _buildEmojiTitle( 'Angry', ), previewIcon: _buildEmojiPreviewIcon( 'assets/images/angry.png', ), icon: _buildReactionsIcon( 'assets/images/angry.png', const Text( 'Angry', style: TextStyle( color: Color(0XFFed5168), ), ), ), ), Reaction( value: 'In love', title: _buildEmojiTitle( 'In love', ), previewIcon: _buildEmojiPreviewIcon( 'assets/images/in-love.png', ), icon: _buildReactionsIcon( 'assets/images/in-love.png', const Text( 'In love', style: TextStyle( color: Color(0XFFffda6b), ), ), ), ), Reaction( value: 'Sad', title: _buildEmojiTitle( 'Sad', ), previewIcon: _buildEmojiPreviewIcon( 'assets/images/sad.png', ), icon: _buildReactionsIcon( 'assets/images/sad.png', const Text( 'Sad', style: TextStyle( color: Color(0XFFffda6b), ), ), ), ), Reaction( value: 'Surprised', title: _buildEmojiTitle( 'Surprised', ), previewIcon: _buildEmojiPreviewIcon( 'assets/images/surprised.png', ), icon: _buildReactionsIcon( 'assets/images/surprised.png', const Text( 'Surprised', style: TextStyle( color: Color(0XFFffda6b), ), ), ), ), Reaction( value: 'Mad', title: _buildEmojiTitle( 'Mad', ), previewIcon: _buildEmojiPreviewIcon( 'assets/images/mad.png', ), icon: _buildReactionsIcon( 'assets/images/mad.png', const Text( 'Mad', style: TextStyle( color: Color(0XFFf05766), ), ), ), ), ]; Widget _buildFlagPreviewIcon(String path, String text) { return Column( mainAxisSize: MainAxisSize.min, children: [ Text( text, style: const TextStyle( fontSize: 10, fontWeight: FontWeight.w300, color: Colors.white, ), ), const SizedBox(height: 7.5), Image.asset(path, height: 30), ], ); } Widget _buildEmojiTitle(String title) { return Container( margin: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.black.withOpacity(.75), borderRadius: BorderRadius.circular(15), ), child: Text( title, style: const TextStyle( color: Colors.white, fontSize: 8, fontWeight: FontWeight.bold, ), ), ); } Widget _buildEmojiPreviewIcon(String path) { return Image.asset(path); } Widget _buildFlagIcon(String path) { return Image.asset(path); } Widget _buildReactionsIcon(String path, Text text) { return Container( color: Colors.transparent, child: Row( children: [ Image.asset(path, height: 20), const SizedBox(width: 5), text, ], ), ); } ================================================ FILE: example/lib/image.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; class ImageWidget extends StatefulWidget { const ImageWidget({ Key? key, required this.imgPath, required this.reactions, }) : super(key: key); final String imgPath; final List> reactions; @override State createState() => _ImageWidgetState(); } class _ImageWidgetState extends State { Reaction? _selectedReaction; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), child: AspectRatio( aspectRatio: 2, child: Stack( children: [ Card( margin: EdgeInsets.zero, elevation: 2, clipBehavior: Clip.antiAlias, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.all( Radius.circular(5), ), ), child: ReactionButton( onReactionChanged: (Reaction? reaction) { setState(() { _selectedReaction = reaction; }); debugPrint('Selected value: ${reaction?.value}'); }, itemSize: const Size.square(40), reactions: widget.reactions, child: Image.asset( widget.imgPath, width: double.infinity, fit: BoxFit.cover, ), ), ), PositionedDirectional( end: 5, bottom: 5, child: Container( height: 30, width: 30, decoration: BoxDecoration( color: Colors.black.withOpacity(.35), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(.3), offset: const Offset(0, 3), blurRadius: 3, ) ], ), child: _selectedReaction?.previewIcon, ), ), ], ), ), ); } } ================================================ FILE: example/lib/main.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; import 'package:flutter_reaction_button_test/data.dart' as data; import 'package:flutter_reaction_button_test/image.dart'; import 'package:flutter_reaction_button_test/post.dart'; void main() { runApp( const MyApp(), ); } class MyApp extends StatelessWidget { const MyApp({ super.key, }); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Reaction Button', home: Scaffold( backgroundColor: Colors.grey[200], appBar: AppBar( title: const Text('Flutter Reaction Button'), actions: [ Builder( builder: (context) { return SizedBox.square( dimension: 30, child: ReactionButton( toggle: false, direction: ReactionsBoxAlignment.rtl, onReactionChanged: (Reaction? reaction) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Selected language: ${reaction?.value}'), ), ); }, reactions: data.flagsReactions, placeholder: const Reaction( value: null, icon: Icon( Icons.language, ), ), boxColor: Colors.black.withOpacity(0.5), boxRadius: 10, itemsSpacing: 20, itemSize: const Size(40, 60), ), ); }, ), const SizedBox(width: 10), ], ), body: SingleChildScrollView( padding: const EdgeInsets.symmetric(vertical: 5), child: Column( children: [ PostWidget( title: 'image 1', imgPath: 'assets/images/img1.jpg', reactions: data.reactions, ), ImageWidget( imgPath: 'assets/images/img1.jpg', reactions: data.reactions, ), PostWidget( title: 'image 2', imgPath: 'assets/images/img2.jpg', reactions: data.reactions, ), ImageWidget( imgPath: 'assets/images/img2.jpg', reactions: data.reactions, ), PostWidget( title: 'image 3', imgPath: 'assets/images/img3.jpg', reactions: data.reactions, ), ImageWidget( imgPath: 'assets/images/img3.jpg', reactions: data.reactions, ), PostWidget( title: 'image 4', imgPath: 'assets/images/img4.jpg', reactions: data.reactions, ), ImageWidget( imgPath: 'assets/images/img4.jpg', reactions: data.reactions, ), PostWidget( title: 'image 5', imgPath: 'assets/images/img5.jpg', reactions: data.reactions, ), ImageWidget( imgPath: 'assets/images/img5.jpg', reactions: data.reactions, ), const SafeArea( child: SizedBox(), ), ], ), ), ), ); } } ================================================ FILE: example/lib/post.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; import 'package:flutter_reaction_button_test/data.dart' as data; class PostWidget extends StatelessWidget { const PostWidget({ Key? key, required this.title, required this.imgPath, required this.reactions, }) : super(key: key); final String title; final String imgPath; final List> reactions; @override Widget build(BuildContext context) { return Card( margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), elevation: 2, child: ClipRRect( borderRadius: const BorderRadius.all( Radius.circular(5), ), child: Column( children: [ AspectRatio( aspectRatio: 2, child: Image.asset( imgPath, width: double.infinity, fit: BoxFit.cover, ), ), Container( height: 55, padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ReactionButton( itemSize: const Size.square(40), onReactionChanged: (Reaction? reaction) { debugPrint('Selected value: ${reaction?.value}'); }, reactions: reactions, placeholder: data.defaultInitialReaction, selectedReaction: reactions.first, ), Row( children: [ Icon( Icons.message, size: 16, color: Colors.grey[400], ), const SizedBox(width: 5), Text( 'Comment', style: TextStyle( fontSize: 12, color: Colors.grey[600], fontWeight: FontWeight.w600, ), ), ], ), Row( children: [ Icon( Icons.share, size: 16, color: Colors.grey[400], ), const SizedBox(width: 5), Text( 'Share', style: TextStyle( fontSize: 14, color: Colors.grey[600], fontWeight: FontWeight.w600, ), ), ], ) ], ), ), ], ), ), ); } } ================================================ FILE: example/pubspec.yaml ================================================ name: flutter_reaction_button_test description: A new Flutter application. publish_to: none version: 1.0.0+1 environment: sdk: '>=3.0.0 <4.0.0' dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.5 flutter_reaction_button: path: ../ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.1 flutter: uses-material-design: true assets: - assets/images/ ================================================ FILE: example/web/index.html ================================================ example ================================================ FILE: example/web/manifest.json ================================================ { "name": "example", "short_name": "example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", "description": "A new Flutter project.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ { "src": "icons/Icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: flutter_reaction_button.iml ================================================ ================================================ FILE: lib/flutter_reaction_button.dart ================================================ library flutter_reaction_button; export 'src/enums/box.dart'; export 'src/models/reaction.dart'; export 'src/widgets/reaction_button.dart'; ================================================ FILE: lib/src/common/position.dart ================================================ import 'package:flutter/cupertino.dart'; class PositionData { const PositionData({ required this.offset, this.isBoxHovered = false, }); final Offset offset; final bool isBoxHovered; const PositionData.init() : offset = Offset.zero, isBoxHovered = false; PositionData copyWith({ Offset? offset, bool? isBoxHovered, }) { return PositionData( offset: offset ?? this.offset, isBoxHovered: isBoxHovered ?? this.isBoxHovered, ); } } ================================================ FILE: lib/src/common/position_notifier.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/src/common/position.dart'; class PositionNotifier extends ValueNotifier { PositionNotifier() : super(const PositionData.init()); } ================================================ FILE: lib/src/enums/box.dart ================================================ enum ReactionsBoxAlignment { ltr, rtl, } ================================================ FILE: lib/src/enums/reaction.dart ================================================ enum ReactionType { button, container, } ================================================ FILE: lib/src/extensions/key.dart ================================================ import 'package:flutter/widgets.dart'; extension GlobalKeyExtensions on GlobalKey { Offset get offset { final RenderBox renderBox = currentContext!.findRenderObject() as RenderBox; return renderBox.localToGlobal(Offset.zero); } } ================================================ FILE: lib/src/models/reaction.dart ================================================ import 'package:flutter/material.dart'; class Reaction { const Reaction({ required this.value, required this.icon, Widget? previewIcon, this.title, }) : previewIcon = previewIcon ?? icon; /// Widget showing as button after selecting preview Icon from box appear. final Widget icon; /// Widget showing when reactions box appear. /// /// If it's null will replace by [icon]. final Widget previewIcon; /// Widget that describes the action that will occur when the button is pressed. /// ///This widget is displayed when the user hover on the button. final Widget? title; final T? value; @override bool operator ==(Object? other) { return other is Reaction && icon == other.icon && icon.key == other.icon.key && previewIcon == other.previewIcon && previewIcon.key == other.previewIcon.key && title == other.title && title?.key == other.title?.key; } @override int get hashCode { return Object.hash(icon, previewIcon, title); } } ================================================ FILE: lib/src/widgets/reaction_button.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; import 'package:flutter_reaction_button/src/enums/reaction.dart'; import 'package:flutter_reaction_button/src/extensions/key.dart'; import 'package:flutter_reaction_button/src/widgets/reactions_box.dart'; class ReactionButton extends StatefulWidget { const ReactionButton({ super.key, required this.onReactionChanged, required this.reactions, this.placeholder, this.selectedReaction, this.boxColor = Colors.white, this.boxElevation = 5, this.boxRadius = 50, this.isChecked = false, this.itemsSpacing = 8, this.itemScale = .3, required this.itemSize, this.animateBox = true, this.toggle = true, this.boxPadding = const EdgeInsets.all(4), this.boxAnimationDuration = const Duration(milliseconds: 200), this.itemAnimationDuration = const Duration(milliseconds: 100), this.hoverDuration = const Duration(milliseconds: 400), this.child, this.direction = ReactionsBoxAlignment.ltr, }) : _type = child != null ? ReactionType.container : ReactionType.button; /// This triggers when reaction button value changed. final ValueChanged?> onReactionChanged; /// Default widget if no reaction selected final Reaction? placeholder; /// Selected reaction button when tap on widget final Reaction? selectedReaction; final List?> reactions; /// Reactions box color [default = white] final Color boxColor; /// Reactions box elevation [default = 5] final double boxElevation; /// Reactions box radius [default = 50] final double boxRadius; /// Reactions box visibility duration [default = 200 milliseconds] final Duration boxAnimationDuration; /// Flag for pre-set reactions if true @link selectedReaction will be /// displayed else @link initialReaction will be displayed [default = false] final bool isChecked; /// Reactions box padding [default = const EdgeInsets.all(0)] final EdgeInsetsGeometry boxPadding; /// Spacing between the reaction items final double itemsSpacing; /// Scale ratio when item hovered [default = 0.3] final double itemScale; /// Animation duration while moving [default = const Duration(milliseconds: 100)] final Duration itemAnimationDuration; final Size itemSize; final bool animateBox; final bool toggle; final Widget? child; final Duration hoverDuration; final ReactionsBoxAlignment direction; final ReactionType _type; @override State> createState() => _ReactionButtonState(); } class _ReactionButtonState extends State> { final GlobalKey _globalKey = GlobalKey(); late Reaction? _selectedReaction = _isChecked ? widget.selectedReaction : widget.placeholder; late bool _isChecked = widget.isChecked; bool get _isContainer => widget._type == ReactionType.container; OverlayEntry? _overlayEntry; void _updateReaction(Reaction? reaction) { _isChecked = reaction != widget.placeholder; widget.onReactionChanged.call(reaction); setState(() { _selectedReaction = reaction; }); } void _onCheck() { _isChecked = !_isChecked; _updateReaction( _isChecked ? widget.selectedReaction ?? widget.reactions.first : widget.placeholder, ); } void _onShowReactionsBox([Offset? offset]) { _overlayEntry = OverlayEntry( builder: (context) { return ReactionsBox( offset: offset ?? _globalKey.offset, itemSize: widget.itemSize, reactions: widget.reactions, color: widget.boxColor, elevation: widget.boxElevation, radius: widget.boxRadius, boxDuration: widget.boxAnimationDuration, boxPadding: widget.boxPadding, itemSpace: widget.itemsSpacing, itemScale: widget.itemScale, itemScaleDuration: widget.itemAnimationDuration, animateBox: widget.animateBox, direction: widget.direction, onReactionSelected: (reaction) { _updateReaction(reaction); _disposeOverlayEntry(); }, onClose: () { _disposeOverlayEntry(); }, ); }, ); Overlay.of(context).insert(_overlayEntry!); } void _disposeOverlayEntry() { _overlayEntry ?..remove() ..dispose(); _overlayEntry = null; } @override void dispose() { _disposeOverlayEntry(); super.dispose(); } @override Widget build(BuildContext context) { final Widget? child = _isContainer ? widget.child : (_selectedReaction ?? widget.reactions.first)!.icon; return GestureDetector( key: _globalKey, onTap: () { if (widget.toggle) { _onCheck(); } else { _onShowReactionsBox(); } }, onLongPressStart: (details) { if (widget.toggle) { _onShowReactionsBox(_isContainer ? details.globalPosition : null); } }, child: child, ); } } ================================================ FILE: lib/src/widgets/reactions_box.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; import 'package:flutter_reaction_button/src/common/position.dart'; import 'package:flutter_reaction_button/src/common/position_notifier.dart'; import 'package:flutter_reaction_button/src/widgets/reactions_box_item.dart'; class ReactionsBox extends StatefulWidget { const ReactionsBox({ super.key, required this.offset, required this.itemSize, required this.reactions, required this.color, required this.elevation, required this.radius, required this.boxDuration, required this.boxPadding, required this.itemSpace, required this.itemScale, required this.itemScaleDuration, required this.onReactionSelected, required this.onClose, required this.animateBox, this.direction = ReactionsBoxAlignment.ltr, }) : assert(itemScale > 0.0 && itemScale < 1); final Offset offset; final Size itemSize; final List?> reactions; final Color color; final double elevation; final double radius; final Duration boxDuration; final EdgeInsetsGeometry boxPadding; final double itemSpace; final double itemScale; final Duration itemScaleDuration; final ValueChanged?> onReactionSelected; final VoidCallback onClose; final bool animateBox; final ReactionsBoxAlignment direction; @override State> createState() => _ReactionsBoxState(); } class _ReactionsBoxState extends State> with SingleTickerProviderStateMixin { final PositionNotifier _positionNotifier = PositionNotifier(); late final AnimationController _boxAnimationController = AnimationController( vsync: this, duration: widget.animateBox ? widget.boxDuration : Duration.zero, ); late final Animation _animation; double get _boxHeight => widget.itemSize.height + widget.boxPadding.vertical; double get _boxWidth => (widget.itemSize.width * widget.reactions.length) + (widget.itemSpace * (widget.reactions.length - 1)) + widget.boxPadding.horizontal; bool get _isWidthOverflow => widget.direction == ReactionsBoxAlignment.ltr ? widget.offset.dx + _boxWidth > MediaQuery.sizeOf(context).width : widget.offset.dx - _boxWidth < 0; bool get _shouldStartFromBottom => widget.offset.dy - _boxHeight - widget.boxPadding.vertical < 0; void _checkIsOffsetOutsideBox(Offset offset) { final Rect boxRect = Rect.fromLTWH(0, 0, _boxWidth, _boxHeight); if (!boxRect.contains(offset)) { widget.onClose(); } } @override void initState() { super.initState(); final Tween tween = IntTween(begin: 0, end: widget.reactions.length); _animation = tween.animate(_boxAnimationController); _boxAnimationController.forward(); } @override void dispose() { _positionNotifier.dispose(); _boxAnimationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: _positionNotifier, builder: (context, fingerPosition, child) { final bool isBoxHovered = fingerPosition?.isBoxHovered ?? false; final double boxScale = 1 - (widget.itemScale / widget.reactions.length); final double widthOverflow; if (_isWidthOverflow) { widthOverflow = widget.direction == ReactionsBoxAlignment.ltr ? widget.offset.dx + _boxWidth - MediaQuery.sizeOf(context).width : _boxWidth; } else { widthOverflow = 0; } final double start = widget.direction == ReactionsBoxAlignment.ltr ? widget.offset.dx - widthOverflow : widget.offset.dx - _boxWidth + widthOverflow; final double top = widget.offset.dy - widget.boxPadding.vertical + (_shouldStartFromBottom ? 1 : -1) * widget.itemSize.height; return Stack( children: [ Positioned.fill( child: Listener( onPointerDown: (_) { widget.onClose(); }, child: Container( key: const ValueKey('outside'), color: Colors.transparent, ), ), ), PositionedDirectional( start: start, top: top, child: Listener( onPointerDown: (point) { _positionNotifier.value = PositionData( offset: point.localPosition, isBoxHovered: true, ); }, onPointerMove: (point) { _positionNotifier.value = _positionNotifier.value.copyWith( offset: point.localPosition, isBoxHovered: true, ); }, onPointerUp: (point) { _positionNotifier.value = _positionNotifier.value.copyWith( isBoxHovered: false, ); _checkIsOffsetOutsideBox(point.localPosition); }, onPointerCancel: (point) { _positionNotifier.value = _positionNotifier.value.copyWith( isBoxHovered: false, ); _checkIsOffsetOutsideBox(point.localPosition); }, child: Transform.scale( scale: isBoxHovered ? boxScale : 1, child: child!, ), ), ), ], ); }, child: Material( color: widget.color, elevation: widget.elevation, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( widget.radius, ), ), child: Container( width: _boxWidth, height: _boxHeight, padding: widget.boxPadding, child: Row( children: [ for (int index = 0; index < widget.reactions.length; index++) ...[ if (index > 0) ...{ SizedBox( width: widget.itemSpace, ), }, AnimatedBuilder( animation: _animation, builder: (context, child) { return AnimatedScale( duration: widget.boxDuration, scale: _animation.value > index ? 1 : 0, child: child, ); }, child: ReactionsBoxItem( index: index, fingerPositionNotifier: _positionNotifier, reaction: widget.reactions[index]!, size: widget.itemSize, scale: widget.itemScale, space: widget.itemSpace, animationDuration: widget.itemScaleDuration, onReactionSelected: (reaction) { widget.onReactionSelected(reaction); }, ), ), ], ], ), ), ), ); } } ================================================ FILE: lib/src/widgets/reactions_box_item.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; import 'package:flutter_reaction_button/src/common/position_notifier.dart'; class ReactionsBoxItem extends StatefulWidget { const ReactionsBoxItem({ super.key, required this.reaction, required this.onReactionSelected, required this.scale, required this.index, required this.size, required this.space, required this.animationDuration, required this.fingerPositionNotifier, }); final Reaction reaction; final ValueChanged?> onReactionSelected; final Duration animationDuration; final PositionNotifier fingerPositionNotifier; final double scale; final int index; final Size size; final double space; @override State> createState() => _ReactionsBoxItemState(); } class _ReactionsBoxItemState extends State> with SingleTickerProviderStateMixin { late final AnimationController _animationController = AnimationController( vsync: this, upperBound: widget.scale, duration: widget.animationDuration, reverseDuration: widget.animationDuration, ); void _listener() { final Offset fingerOffset = widget.fingerPositionNotifier.value.offset; final Offset topLeft = Offset((widget.size.width + widget.space) * widget.index, 0); final Offset bottomRight = Offset( (widget.size.width + widget.space) * (widget.index + 1), widget.size.height, ); final Rect rect = Rect.fromPoints(topLeft, bottomRight); final bool selected = rect.contains(fingerOffset); if (selected) { final bool isBoxHovered = widget.fingerPositionNotifier.value.isBoxHovered; if (!isBoxHovered) { widget.onReactionSelected(widget.reaction); } _animationController.forward(); } else { _animationController.reverse(); } } @override void initState() { super.initState(); widget.fingerPositionNotifier.addListener(_listener); } @override void dispose() { widget.fingerPositionNotifier.removeListener(_listener); _animationController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return AnimatedBuilder( animation: _animationController, builder: (context, child) { final bool showTitle = _animationController.value == _animationController.upperBound && widget.reaction.title != null; return Stack( clipBehavior: Clip.none, alignment: Alignment.center, children: [ Transform.scale( scale: 1 + _animationController.value, child: SizedBox.fromSize( size: widget.size, child: widget.reaction.previewIcon, ), ), if (widget.reaction.title != null) ...{ Positioned( bottom: widget.size.height * (1 + (_animationController.value / 2)), child: AnimatedOpacity( opacity: showTitle ? 1 : 0, duration: widget.animationDuration, child: widget.reaction.title!, ), ) }, ], ); }, ); } } ================================================ FILE: pubspec.yaml ================================================ name: flutter_reaction_button description: Flutter Reaction Button is a customizable Flutter package that allows you to easily create interactive buttons with reaction emojis, similar to Facebook's iconic reaction buttons. homepage: https://github.com/GeekAbdelouahed/flutter-reaction-button repository: https://github.com/GeekAbdelouahed/flutter-reaction-button.git version: 3.0.0+3 environment: sdk: '>=3.0.0 <4.0.0' dependencies: flutter: sdk: flutter dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.1 flutter: ================================================ FILE: test/main.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; const Reaction _placeHolder = Reaction( value: 0, icon: Icon(Icons.sunny_snowing), ); const List _reactions = [ Reaction( value: 1, icon: Icon(Icons.ac_unit), ), Reaction( value: 2, icon: Icon(Icons.sunny), ), Reaction( value: 3, icon: Icon(Icons.wind_power), ), ]; class MyApp extends StatelessWidget { const MyApp({ super.key, this.toggle = true, }); final bool toggle; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: SingleChildScrollView( padding: const EdgeInsets.symmetric(vertical: 5), child: Column( children: List.generate( 10, (index) { return ReactionButton( toggle: toggle, onReactionChanged: (reaction) {}, reactions: _reactions, placeholder: _placeHolder, itemSize: const Size.square(24), ); }, ), ), ), ), ); } } ================================================ FILE: test/reaction_button_test.dart ================================================ import 'package:flutter/material.dart'; import 'package:flutter_reaction_button/flutter_reaction_button.dart'; import 'package:flutter_reaction_button/src/widgets/reactions_box.dart'; import 'package:flutter_reaction_button/src/widgets/reactions_box_item.dart'; import 'package:flutter_test/flutter_test.dart'; import 'main.dart'; void main() { group( 'Reaction button with toggle = true', () { testWidgets( 'Expect to select initial reaction ' 'when tap on reaction button', (tester) async { await tester.pumpWidget(const MyApp()); final Finder reaction = find.byType(ReactionButton).first; await tester.tap(reaction); await tester.pump(); final Finder initialReaction = find.byIcon(Icons.ac_unit); expect(initialReaction, findsOneWidget); }, ); testWidgets( 'Expect to show reactions box ' 'when long press on reaction button', (tester) async { await tester.pumpWidget(const MyApp()); final Finder reaction = find.byType(ReactionButton).first; await tester.longPress(reaction); await tester.pumpAndSettle(); final Finder reactionsBox = find.byType(ReactionsBox); expect(reactionsBox, findsOneWidget); }, ); testWidgets( 'Expect reactions box to closed successfully ' 'when tap outside', (tester) async { await tester.pumpWidget(const MyApp()); final Finder reaction = find.byType(ReactionButton).first; await tester.longPress(reaction); await tester.pumpAndSettle(); final Finder outsideWidget = find.byKey(const ValueKey('outside')); await tester.tap(outsideWidget); await tester.pumpAndSettle(); final Finder lastReactionIcon = find.byType(ReactionsBox); expect(lastReactionIcon, findsNothing); }, ); testWidgets( 'Expect to select last reaction from reactions box ' 'when long press on reaction button and reactions box is visible', (tester) async { await tester.pumpWidget(const MyApp()); final Finder reaction = find.byType(ReactionButton).first; await tester.longPress(reaction); await tester.pumpAndSettle(); final Finder lastReaction = find.byType(ReactionsBoxItem).last; await tester.tap(lastReaction); await tester.pumpAndSettle(); final Finder lastReactionIcon = find.byIcon(Icons.wind_power); expect(lastReactionIcon, findsOneWidget); }, ); }, ); group( 'Reaction button with toggle = false', () { testWidgets( 'Expect to show reactions box ' 'when tap on reaction button', (tester) async { await tester.pumpWidget(const MyApp(toggle: false)); final Finder reaction = find.byType(ReactionButton).first; await tester.tap(reaction); await tester.pumpAndSettle(); final Finder reactionsBox = find.byType(ReactionsBox); expect(reactionsBox, findsOneWidget); }, ); testWidgets( 'Expect reactions box to closed successfully ' 'when tap outside', (tester) async { await tester.pumpWidget(const MyApp(toggle: false)); final Finder reaction = find.byType(ReactionButton).first; await tester.tap(reaction); await tester.pumpAndSettle(); final Finder outsideWidget = find.byKey(const ValueKey('outside')); await tester.tap(outsideWidget); await tester.pumpAndSettle(); final Finder lastReactionIcon = find.byType(ReactionsBox); expect(lastReactionIcon, findsNothing); }, ); testWidgets( 'Expect to select last reaction from reactions box ' 'when long press on reaction button and reactions box is visible', (tester) async { await tester.pumpWidget(const MyApp(toggle: false)); final Finder reaction = find.byType(ReactionButton).first; await tester.tap(reaction); await tester.pumpAndSettle(); final Finder lastReaction = find.byType(ReactionsBoxItem).last; await tester.tap(lastReaction); await tester.pumpAndSettle(); final Finder lastReactionIcon = find.byIcon(Icons.wind_power); expect(lastReactionIcon, findsOneWidget); }, ); }, ); }