├── .gitattributes ├── .gitignore ├── .metadata ├── .vscode └── settings.json ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── selection_controls_example │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── main.dart └── src │ ├── cupertino │ ├── cupertino_selection_controllers.dart │ └── cupertino_selection_handle.dart │ ├── material │ ├── material_selection_controllers.dart │ └── material_selection_handle.dart │ ├── selection_toolbar_controller.dart │ └── text_selection_controls.dart ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ └── app_icon_64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart └── web ├── favicon.png ├── icons ├── Icon-192.png └── Icon-512.png ├── index.html └── manifest.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | -------------------------------------------------------------------------------- /.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: 1a9ea39ad795b5076a075f7616ea434598f6777a 8 | channel: master 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dart.flutterSdkPath": "/Users/jaimeblasco/Documents/GitHub/flutter", 3 | "dart.flutterSdkPaths": [ 4 | "/Users/jaimeblasco/Documents/GitHub/flutter/bin/" 5 | ] 6 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # selection_controls_example 2 | 3 | A repo that experiments with the missing features related to the text selection controls and context menus 4 | 5 | ```dart 6 | final menu = CupertinoSelectionToolbar( 7 | theme: CupertinoThemeData(), 8 | actions: [ 9 | ContextMenuItem.sublist(title: Text('Format'), children: [ 10 | ContextMenuItem( 11 | title: Icon(Icons.brush, size: 18), 12 | onPressed: (menuController) { 13 | final selection = menuController.textSelectionController?.selection; 14 | highlightSelection(selection); 15 | return true; // Return true to close the menu when pressed 16 | }, 17 | ), 18 | ContextMenuItem( 19 | title: Icon(Icons.format_bold, size: 18), 20 | onPressed: (menuController) { 21 | final selection = menuController.textSelectionController?.selection; 22 | boldSelection(selection); 23 | return true; 24 | }, 25 | ), 26 | ]), 27 | TextSelectionContextMenuItem.copy(), // Default text selection items 28 | TextSelectionContextMenuItem.selectAll(), 29 | ContextMenuItem( 30 | title: Text('Search'), 31 | onPressed: (menuController) { 32 | return true; 33 | }, 34 | ), 35 | ContextMenuItem( 36 | title: Text('Share'), 37 | onPressed: (menuController) { 38 | return true; 39 | }, 40 | ), 41 | ], 42 | ); 43 | ``` 44 | 45 | To use for text inside a TextField, SelectableText, TextFormField, CupertinoTextField: 46 | ```dart 47 | TextField( 48 | selectionControls: DefaultTextSelectionControls( 49 | handle: CupertinoTextSelectionHandle(color: Colors.black), 50 | toolbar: menu, 51 | ), 52 | ), 53 | ``` 54 | 55 | To display it when tapped on any widget. 56 | 57 | ```dart 58 | ContextMenuButton( 59 | child: Icon(Icons.menu), 60 | menu: menu, 61 | ); 62 | ``` 63 | 64 | Custom items in text selection toolbar 65 | 66 | ![](https://user-images.githubusercontent.com/19904063/94375942-7d1f2200-0117-11eb-8e87-e5ca6e07b613.gif) 67 | 68 | Submenus 69 | 70 | ![](https://user-images.githubusercontent.com/19904063/95842879-5f70d000-0d47-11eb-9ce4-a2d3466ca050.gif) 71 | 72 | Context menus usable in text selection or any widget 73 | 74 | ![](https://user-images.githubusercontent.com/19904063/96102386-c375ce80-0ed6-11eb-9078-539e079dc3cd.gif) 75 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.selection_controls_example" 42 | minSdkVersion 16 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/selection_controls_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.selection_controls_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 9.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 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 = 1020; 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 | buildActionMask = 2147483647; 175 | files = ( 176 | ); 177 | inputPaths = ( 178 | ); 179 | name = "Thin Binary"; 180 | outputPaths = ( 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | shellPath = /bin/sh; 184 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 185 | }; 186 | 9740EEB61CF901F6004384FC /* Run Script */ = { 187 | isa = PBXShellScriptBuildPhase; 188 | buildActionMask = 2147483647; 189 | files = ( 190 | ); 191 | inputPaths = ( 192 | ); 193 | name = "Run Script"; 194 | outputPaths = ( 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | shellPath = /bin/sh; 198 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 199 | }; 200 | /* End PBXShellScriptBuildPhase section */ 201 | 202 | /* Begin PBXSourcesBuildPhase section */ 203 | 97C146EA1CF9000F007C117D /* Sources */ = { 204 | isa = PBXSourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 208 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | /* End PBXSourcesBuildPhase section */ 213 | 214 | /* Begin PBXVariantGroup section */ 215 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 216 | isa = PBXVariantGroup; 217 | children = ( 218 | 97C146FB1CF9000F007C117D /* Base */, 219 | ); 220 | name = Main.storyboard; 221 | sourceTree = ""; 222 | }; 223 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C147001CF9000F007C117D /* Base */, 227 | ); 228 | name = LaunchScreen.storyboard; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXVariantGroup section */ 232 | 233 | /* Begin XCBuildConfiguration section */ 234 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 235 | isa = XCBuildConfiguration; 236 | buildSettings = { 237 | ALWAYS_SEARCH_USER_PATHS = NO; 238 | CLANG_ANALYZER_NONNULL = YES; 239 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 240 | CLANG_CXX_LIBRARY = "libc++"; 241 | CLANG_ENABLE_MODULES = YES; 242 | CLANG_ENABLE_OBJC_ARC = YES; 243 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 244 | CLANG_WARN_BOOL_CONVERSION = YES; 245 | CLANG_WARN_COMMA = YES; 246 | CLANG_WARN_CONSTANT_CONVERSION = YES; 247 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 248 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 249 | CLANG_WARN_EMPTY_BODY = YES; 250 | CLANG_WARN_ENUM_CONVERSION = YES; 251 | CLANG_WARN_INFINITE_RECURSION = YES; 252 | CLANG_WARN_INT_CONVERSION = YES; 253 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 254 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 255 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 256 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 257 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 258 | CLANG_WARN_STRICT_PROTOTYPES = YES; 259 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 260 | CLANG_WARN_UNREACHABLE_CODE = YES; 261 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 262 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 263 | COPY_PHASE_STRIP = NO; 264 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 265 | ENABLE_NS_ASSERTIONS = NO; 266 | ENABLE_STRICT_OBJC_MSGSEND = YES; 267 | GCC_C_LANGUAGE_STANDARD = gnu99; 268 | GCC_NO_COMMON_BLOCKS = YES; 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 276 | MTL_ENABLE_DEBUG_INFO = NO; 277 | SDKROOT = iphoneos; 278 | SUPPORTED_PLATFORMS = iphoneos; 279 | TARGETED_DEVICE_FAMILY = "1,2"; 280 | VALIDATE_PRODUCT = YES; 281 | }; 282 | name = Profile; 283 | }; 284 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 285 | isa = XCBuildConfiguration; 286 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 287 | buildSettings = { 288 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 289 | CLANG_ENABLE_MODULES = YES; 290 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 291 | ENABLE_BITCODE = NO; 292 | FRAMEWORK_SEARCH_PATHS = ( 293 | "$(inherited)", 294 | "$(PROJECT_DIR)/Flutter", 295 | ); 296 | INFOPLIST_FILE = Runner/Info.plist; 297 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 298 | LIBRARY_SEARCH_PATHS = ( 299 | "$(inherited)", 300 | "$(PROJECT_DIR)/Flutter", 301 | ); 302 | PRODUCT_BUNDLE_IDENTIFIER = com.example.selectionControlsExample; 303 | PRODUCT_NAME = "$(TARGET_NAME)"; 304 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 305 | SWIFT_VERSION = 5.0; 306 | VERSIONING_SYSTEM = "apple-generic"; 307 | }; 308 | name = Profile; 309 | }; 310 | 97C147031CF9000F007C117D /* Debug */ = { 311 | isa = XCBuildConfiguration; 312 | buildSettings = { 313 | ALWAYS_SEARCH_USER_PATHS = NO; 314 | CLANG_ANALYZER_NONNULL = YES; 315 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 316 | CLANG_CXX_LIBRARY = "libc++"; 317 | CLANG_ENABLE_MODULES = YES; 318 | CLANG_ENABLE_OBJC_ARC = YES; 319 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 320 | CLANG_WARN_BOOL_CONVERSION = YES; 321 | CLANG_WARN_COMMA = YES; 322 | CLANG_WARN_CONSTANT_CONVERSION = YES; 323 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 324 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 325 | CLANG_WARN_EMPTY_BODY = YES; 326 | CLANG_WARN_ENUM_CONVERSION = YES; 327 | CLANG_WARN_INFINITE_RECURSION = YES; 328 | CLANG_WARN_INT_CONVERSION = YES; 329 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 330 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 331 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 332 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 333 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 334 | CLANG_WARN_STRICT_PROTOTYPES = YES; 335 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 336 | CLANG_WARN_UNREACHABLE_CODE = YES; 337 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 338 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 339 | COPY_PHASE_STRIP = NO; 340 | DEBUG_INFORMATION_FORMAT = dwarf; 341 | ENABLE_STRICT_OBJC_MSGSEND = YES; 342 | ENABLE_TESTABILITY = YES; 343 | GCC_C_LANGUAGE_STANDARD = gnu99; 344 | GCC_DYNAMIC_NO_PIC = NO; 345 | GCC_NO_COMMON_BLOCKS = YES; 346 | GCC_OPTIMIZATION_LEVEL = 0; 347 | GCC_PREPROCESSOR_DEFINITIONS = ( 348 | "DEBUG=1", 349 | "$(inherited)", 350 | ); 351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 353 | GCC_WARN_UNDECLARED_SELECTOR = YES; 354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 355 | GCC_WARN_UNUSED_FUNCTION = YES; 356 | GCC_WARN_UNUSED_VARIABLE = YES; 357 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 358 | MTL_ENABLE_DEBUG_INFO = YES; 359 | ONLY_ACTIVE_ARCH = YES; 360 | SDKROOT = iphoneos; 361 | TARGETED_DEVICE_FAMILY = "1,2"; 362 | }; 363 | name = Debug; 364 | }; 365 | 97C147041CF9000F007C117D /* Release */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | ALWAYS_SEARCH_USER_PATHS = NO; 369 | CLANG_ANALYZER_NONNULL = YES; 370 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 371 | CLANG_CXX_LIBRARY = "libc++"; 372 | CLANG_ENABLE_MODULES = YES; 373 | CLANG_ENABLE_OBJC_ARC = YES; 374 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 375 | CLANG_WARN_BOOL_CONVERSION = YES; 376 | CLANG_WARN_COMMA = YES; 377 | CLANG_WARN_CONSTANT_CONVERSION = YES; 378 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 379 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 380 | CLANG_WARN_EMPTY_BODY = YES; 381 | CLANG_WARN_ENUM_CONVERSION = YES; 382 | CLANG_WARN_INFINITE_RECURSION = YES; 383 | CLANG_WARN_INT_CONVERSION = YES; 384 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 385 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 386 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 387 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 388 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 389 | CLANG_WARN_STRICT_PROTOTYPES = YES; 390 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 391 | CLANG_WARN_UNREACHABLE_CODE = YES; 392 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 393 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 394 | COPY_PHASE_STRIP = NO; 395 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 396 | ENABLE_NS_ASSERTIONS = NO; 397 | ENABLE_STRICT_OBJC_MSGSEND = YES; 398 | GCC_C_LANGUAGE_STANDARD = gnu99; 399 | GCC_NO_COMMON_BLOCKS = YES; 400 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 401 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 402 | GCC_WARN_UNDECLARED_SELECTOR = YES; 403 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 404 | GCC_WARN_UNUSED_FUNCTION = YES; 405 | GCC_WARN_UNUSED_VARIABLE = YES; 406 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 407 | MTL_ENABLE_DEBUG_INFO = NO; 408 | SDKROOT = iphoneos; 409 | SUPPORTED_PLATFORMS = iphoneos; 410 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 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 | ENABLE_BITCODE = NO; 424 | FRAMEWORK_SEARCH_PATHS = ( 425 | "$(inherited)", 426 | "$(PROJECT_DIR)/Flutter", 427 | ); 428 | INFOPLIST_FILE = Runner/Info.plist; 429 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 430 | LIBRARY_SEARCH_PATHS = ( 431 | "$(inherited)", 432 | "$(PROJECT_DIR)/Flutter", 433 | ); 434 | PRODUCT_BUNDLE_IDENTIFIER = com.example.selectionControlsExample; 435 | PRODUCT_NAME = "$(TARGET_NAME)"; 436 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 437 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 438 | SWIFT_VERSION = 5.0; 439 | VERSIONING_SYSTEM = "apple-generic"; 440 | }; 441 | name = Debug; 442 | }; 443 | 97C147071CF9000F007C117D /* Release */ = { 444 | isa = XCBuildConfiguration; 445 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 446 | buildSettings = { 447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 448 | CLANG_ENABLE_MODULES = YES; 449 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 450 | ENABLE_BITCODE = NO; 451 | FRAMEWORK_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "$(PROJECT_DIR)/Flutter", 454 | ); 455 | INFOPLIST_FILE = Runner/Info.plist; 456 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 457 | LIBRARY_SEARCH_PATHS = ( 458 | "$(inherited)", 459 | "$(PROJECT_DIR)/Flutter", 460 | ); 461 | PRODUCT_BUNDLE_IDENTIFIER = com.example.selectionControlsExample; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 464 | SWIFT_VERSION = 5.0; 465 | VERSIONING_SYSTEM = "apple-generic"; 466 | }; 467 | name = Release; 468 | }; 469 | /* End XCBuildConfiguration section */ 470 | 471 | /* Begin XCConfigurationList section */ 472 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 473 | isa = XCConfigurationList; 474 | buildConfigurations = ( 475 | 97C147031CF9000F007C117D /* Debug */, 476 | 97C147041CF9000F007C117D /* Release */, 477 | 249021D3217E4FDB00AE95B9 /* Profile */, 478 | ); 479 | defaultConfigurationIsVisible = 0; 480 | defaultConfigurationName = Release; 481 | }; 482 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 483 | isa = XCConfigurationList; 484 | buildConfigurations = ( 485 | 97C147061CF9000F007C117D /* Debug */, 486 | 97C147071CF9000F007C117D /* Release */, 487 | 249021D4217E4FDB00AE95B9 /* Profile */, 488 | ); 489 | defaultConfigurationIsVisible = 0; 490 | defaultConfigurationName = Release; 491 | }; 492 | /* End XCConfigurationList section */ 493 | }; 494 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 495 | } 496 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | selection_controls_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:selection_controls_example/src/cupertino/cupertino_selection_controllers.dart'; 4 | import 'package:selection_controls_example/src/cupertino/cupertino_selection_handle.dart'; 5 | import 'package:selection_controls_example/src/material/material_selection_controllers.dart'; 6 | import 'package:selection_controls_example/src/text_selection_controls.dart'; 7 | 8 | void main() { 9 | runApp(MyApp()); 10 | } 11 | 12 | class MyApp extends StatelessWidget { 13 | // This widget is the root of your application. 14 | @override 15 | Widget build(BuildContext context) { 16 | return MaterialApp( 17 | title: 'Flutter Demo', 18 | theme: ThemeData( 19 | textSelectionTheme: TextSelectionThemeData( 20 | selectionColor: Colors.grey[300], 21 | ), 22 | accentColor: Colors.amber, 23 | 24 | // This is the theme of your application. 25 | // 26 | // Try running your application with "flutter run". You'll see the 27 | // application has a blue toolbar. Then, without quitting the app, try 28 | // changing the primarySwatch below to Colors.green and then invoke 29 | // "hot reload" (press "r" in the console where you ran "flutter run", 30 | // or simply save your changes to "hot reload" in a Flutter IDE). 31 | // Notice that the counter didn't reset back to zero; the application 32 | // is not restarted. 33 | primarySwatch: Colors.grey, 34 | // This makes the visual density adapt to the platform that you run 35 | // the app on. For desktop platforms, the controls will be smaller and 36 | // closer together (more dense) than on mobile platforms. 37 | visualDensity: VisualDensity.adaptivePlatformDensity, 38 | ), 39 | home: HomePage(), 40 | debugShowCheckedModeBanner: false, 41 | ); 42 | } 43 | } 44 | 45 | class HomePage extends StatelessWidget { 46 | @override 47 | Widget build(BuildContext context) { 48 | return Scaffold( 49 | body: Padding( 50 | padding: EdgeInsets.all(20), 51 | child: RichTextEditor(text: text), 52 | )); 53 | } 54 | } 55 | 56 | class RichTextEditor extends StatefulWidget { 57 | RichTextEditor({Key key, this.text}) : super(key: key); 58 | 59 | final String text; 60 | 61 | @override 62 | _RichTextEditorState createState() => _RichTextEditorState(); 63 | } 64 | 65 | class RichTextSection { 66 | final TextStyle style; 67 | final TextSelection selection; 68 | 69 | RichTextSection({this.selection, this.style}); 70 | } 71 | 72 | class RichTextController extends ChangeNotifier { 73 | final String _sourceText; 74 | 75 | RichTextController(this._sourceText) 76 | : sections = [ 77 | RichTextSection( 78 | selection: 79 | TextSelection(baseOffset: 0, extentOffset: _sourceText.length), 80 | ) 81 | ]; 82 | 83 | List sections; 84 | 85 | void setStyle(TextSelection selection, TextStyle style) { 86 | final previousSelection = RichTextSection( 87 | selection: 88 | TextSelection(baseOffset: 0, extentOffset: selection.baseOffset)); 89 | final newSelection = RichTextSection(selection: selection, style: style); 90 | final endSelection = RichTextSection( 91 | selection: TextSelection( 92 | baseOffset: selection.extentOffset, extentOffset: _sourceText.length), 93 | ); 94 | sections = [ 95 | previousSelection, 96 | newSelection, 97 | endSelection, 98 | ]; 99 | notifyListeners(); 100 | } 101 | 102 | TextSpan get textSpan => TextSpan( 103 | children: [ 104 | for (final section in sections) 105 | TextSpan( 106 | text: section.selection.textInside(_sourceText), 107 | style: section.style, 108 | ) 109 | ], 110 | ); 111 | } 112 | 113 | enum ToolbarExample { cupertino, material, custom } 114 | 115 | extension on ToolbarExample { 116 | String get title { 117 | return { 118 | ToolbarExample.cupertino: 'Cupertino', 119 | ToolbarExample.material: 'Material', 120 | ToolbarExample.custom: 'Custom', 121 | }[this]; 122 | } 123 | } 124 | 125 | class _RichTextEditorState extends State { 126 | RichTextController controller; 127 | 128 | @override 129 | void initState() { 130 | controller = RichTextController(widget.text); 131 | controller.addListener(updateRichText); 132 | super.initState(); 133 | } 134 | 135 | updateRichText() { 136 | setState(() { 137 | // Notify rich text changes 138 | }); 139 | } 140 | 141 | @override 142 | void didUpdateWidget(RichTextEditor oldWidget) { 143 | super.didUpdateWidget(oldWidget); 144 | 145 | if (widget.text != oldWidget.text) { 146 | controller.removeListener(updateRichText); 147 | controller.dispose(); 148 | controller = RichTextController(widget.text); 149 | controller.addListener(updateRichText); 150 | } 151 | } 152 | 153 | @override 154 | void dispose() { 155 | controller.removeListener(updateRichText); 156 | controller.dispose(); 157 | super.dispose(); 158 | } 159 | 160 | ToolbarExample example = ToolbarExample.material; 161 | 162 | @override 163 | Widget build(BuildContext context) { 164 | final items = [ 165 | TextSelectionToolbarItem( 166 | title: Icon(Icons.brush, size: 18), 167 | onPressed: (selectionController) { 168 | final selection = selectionController.selection; 169 | controller.setStyle( 170 | selection, 171 | TextStyle( 172 | backgroundColor: Color(0xffd4ff32), 173 | color: Colors.black, 174 | ), 175 | ); 176 | }, 177 | ), 178 | TextSelectionToolbarItem( 179 | title: Icon( 180 | Icons.format_bold, 181 | size: 18, 182 | ), 183 | onPressed: (selectionController) { 184 | final selection = selectionController.selection; 185 | controller.setStyle( 186 | selection, 187 | TextStyle(fontWeight: FontWeight.bold), 188 | ); 189 | }, 190 | ), 191 | TextSelectionToolbarItem( 192 | title: Icon( 193 | Icons.format_italic, 194 | size: 18, 195 | ), 196 | onPressed: (selectionController) { 197 | final selection = selectionController.selection; 198 | controller.setStyle( 199 | selection, 200 | TextStyle(fontStyle: FontStyle.italic), 201 | ); 202 | }, 203 | ), 204 | TextSelectionToolbarItem.copy(), 205 | TextSelectionToolbarItem.paste() 206 | ]; 207 | return Scaffold( 208 | body: Center( 209 | child: SafeArea( 210 | bottom: false, 211 | child: Stack( 212 | children: [ 213 | SelectableText.rich( 214 | controller.textSpan, 215 | cursorColor: Colors.black, 216 | style: TextStyle(color: Colors.grey[700], fontSize: 16), 217 | toolbarOptions: ToolbarOptions(copy: true), 218 | textSelectionControls: DefaultTextSelectionControls( 219 | handle: CupertinoTextSelectionHandle(color: Colors.black), 220 | toolbar: toolbar(items), 221 | ), 222 | ), 223 | Positioned( 224 | left: 20, 225 | bottom: MediaQuery.of(context).padding.top + 20, 226 | right: 20, 227 | child: CupertinoSlidingSegmentedControl( 228 | backgroundColor: Colors.grey[100], 229 | groupValue: example, 230 | onValueChanged: (value) { 231 | setState(() { 232 | example = value; 233 | }); 234 | }, 235 | children: Map.fromEntries( 236 | ToolbarExample.values.map((e) => MapEntry( 237 | e, 238 | Text(e.title), 239 | )), 240 | ), 241 | ), 242 | ) 243 | ], 244 | ), 245 | ), 246 | ), 247 | ); 248 | } 249 | 250 | TextSelectionToolbar toolbar(List items) { 251 | switch (example) { 252 | case ToolbarExample.cupertino: 253 | return CupertinoSelectionToolbar( 254 | items: items, 255 | theme: CupertinoThemeData(), 256 | ); 257 | 258 | case ToolbarExample.material: 259 | return MaterialSelectionToolbar( 260 | items: items, 261 | ); 262 | 263 | case ToolbarExample.custom: 264 | return MaterialSelectionToolbar( 265 | backgroundColor: Colors.black, 266 | shape: BeveledRectangleBorder( 267 | borderRadius: BorderRadius.circular(12), side: BorderSide()), 268 | theme: ThemeData.dark(), 269 | items: items, 270 | ); 271 | } 272 | return null; 273 | } 274 | } 275 | 276 | final text = ''' 277 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce sit amet semper leo, sed consequat ante. Etiam eu elit fermentum, pulvinar velit ut, lobortis velit. Cras et tellus vestibulum, tincidunt dui eu, pretium est. Aliquam erat volutpat. Suspendisse dictum diam purus, nec dapibus urna vestibulum vitae. Sed vitae ultricies ligula. Aliquam hendrerit lorem ultricies, pulvinar elit sit amet, semper sapien. Morbi non pellentesque libero. Ut vehicula nisl in enim maximus elementum. Integer scelerisque nisl quis sapien dignissim aliquam. 278 | 279 | Proin a tincidunt metus. Ut scelerisque hendrerit leo, at eleifend elit ornare in. Morbi tristique neque ipsum, nec consectetur neque auctor nec. Fusce tellus elit, molestie sed urna sed, placerat sagittis magna. Donec non semper enim. Aenean sollicitudin blandit diam, sit amet ullamcorper nulla consequat nec. Etiam in hendrerit ex. Mauris semper nulla id sollicitudin porttitor. 280 | 281 | Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras suscipit vel mi sollicitudin tempus. Sed quis libero dapibus urna condimentum porta. Aenean id enim at nisi consequat pulvinar. Mauris vel ex non diam auctor venenatis id a ante. Cras molestie sagittis ex ut finibus. Mauris efficitur, velit eleifend ultricies pharetra, enim lacus scelerisque metus, eget auctor ipsum turpis eu sapien. Duis varius vulputate sem, sed porta neque. Sed gravida congue orci ut hendrerit. Donec ligula tellus, aliquet aliquam lectus et, sodales molestie justo. 282 | 283 | Nullam iaculis dolor a enim pulvinar iaculis. Fusce in tempor odio. Quisque fringilla mattis tellus. Nulla tincidunt sodales diam ornare lobortis. Morbi ac dolor lacus. Vivamus pellentesque, nisl sed finibus tempus, neque magna hendrerit eros, ac sodales ligula libero quis magna. Nulla facilisi. Duis tristique ullamcorper nisi, sed congue est aliquam sit amet. Morbi sem eros, consequat ac metus ut, vehicula mollis lectus. Donec non velit sit amet elit fringilla varius quis quis magna. Nullam felis magna, varius nec luctus vel, lobortis ac quam. Nulla aliquet risus non orci auctor, gravida tristique nunc tempus. Aenean ultrices sapien tortor. 284 | 285 | In eget ipsum blandit dui consectetur sodales. Integer dignissim libero at eleifend semper. Cras non viverra leo. Vestibulum elit tortor, malesuada eget tempor vel, sodales nec ipsum. Integer accumsan sapien vel tortor efficitur venenatis. Vivamus ultrices mauris eu odio dignissim, quis facilisis nulla semper. Curabitur congue molestie ante. Nulla facilisi. Nunc quis posuere lectus, non iaculis nulla. Vestibulum dignissim, odio id venenatis placerat, ante est posuere erat, non interdum eros justo at mi. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. 286 | '''; 287 | -------------------------------------------------------------------------------- /lib/src/cupertino/cupertino_selection_handle.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import '../text_selection_controls.dart'; 4 | import 'dart:math' as math; 5 | 6 | // Read off from the output on iOS 12. This color does not vary with the 7 | // application's theme color. 8 | const double _kSelectionHandleOverlap = 1.5; 9 | // Extracted from https://developer.apple.com/design/resources/. 10 | const double _kSelectionHandleRadius = 6; 11 | 12 | /// Draws a single text selection handle with a bar and a ball. 13 | class _TextSelectionHandlePainter extends CustomPainter { 14 | const _TextSelectionHandlePainter(this.color); 15 | 16 | final Color color; 17 | 18 | @override 19 | void paint(Canvas canvas, Size size) { 20 | const double halfStrokeWidth = 1.0; 21 | final Paint paint = Paint()..color = color; 22 | final Rect circle = Rect.fromCircle( 23 | center: const Offset(_kSelectionHandleRadius, _kSelectionHandleRadius), 24 | radius: _kSelectionHandleRadius, 25 | ); 26 | final Rect line = Rect.fromPoints( 27 | const Offset( 28 | _kSelectionHandleRadius - halfStrokeWidth, 29 | 2 * _kSelectionHandleRadius - _kSelectionHandleOverlap, 30 | ), 31 | Offset(_kSelectionHandleRadius + halfStrokeWidth, size.height), 32 | ); 33 | final Path path = Path() 34 | ..addOval(circle) 35 | // Draw line so it slightly overlaps the circle. 36 | ..addRect(line); 37 | canvas.drawPath(path, paint); 38 | } 39 | 40 | @override 41 | bool shouldRepaint(_TextSelectionHandlePainter oldPainter) => 42 | color != oldPainter.color; 43 | } 44 | 45 | class CupertinoTextSelectionHandle extends TextSelectionHandle { 46 | final Color color; 47 | 48 | const CupertinoTextSelectionHandle({this.color}); 49 | 50 | /// Returns the size of the Cupertino handle. 51 | @override 52 | Size getHandleSize(double textLineHeight) { 53 | return Size( 54 | _kSelectionHandleRadius * 2, 55 | textLineHeight + _kSelectionHandleRadius * 2 - _kSelectionHandleOverlap, 56 | ); 57 | } 58 | 59 | /// Builder for iOS text selection edges. 60 | @override 61 | Widget buildHandle(BuildContext context, TextSelectionHandleType type, 62 | double textLineHeight) { 63 | // We want a size that's a vertical line the height of the text plus a 18.0 64 | // padding in every direction that will constitute the selection drag area. 65 | final Size desiredSize = getHandleSize(textLineHeight); 66 | 67 | final Widget handle = SizedBox.fromSize( 68 | size: desiredSize, 69 | child: CustomPaint( 70 | painter: _TextSelectionHandlePainter( 71 | color ?? CupertinoTheme.of(context).primaryColor), 72 | ), 73 | ); 74 | 75 | // [buildHandle]'s widget is positioned at the selection cursor's bottom 76 | // baseline. We transform the handle such that the SizedBox is superimposed 77 | // on top of the text selection endpoints. 78 | switch (type) { 79 | case TextSelectionHandleType.left: 80 | return handle; 81 | case TextSelectionHandleType.right: 82 | // Right handle is a vertical mirror of the left. 83 | return Transform( 84 | transform: Matrix4.identity() 85 | ..translate(desiredSize.width / 2, desiredSize.height / 2) 86 | ..rotateZ(math.pi) 87 | ..translate(-desiredSize.width / 2, -desiredSize.height / 2), 88 | child: handle, 89 | ); 90 | // iOS doesn't draw anything for collapsed selections. 91 | case TextSelectionHandleType.collapsed: 92 | return const SizedBox(); 93 | } 94 | assert(type != null); 95 | return null; 96 | } 97 | 98 | /// Gets anchor for cupertino-style text selection handles. 99 | /// 100 | /// See [TextSelectionControls.getHandleAnchor]. 101 | @override 102 | Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) { 103 | final Size handleSize = getHandleSize(textLineHeight); 104 | switch (type) { 105 | // The circle is at the top for the left handle, and the anchor point is 106 | // all the way at the bottom of the line. 107 | case TextSelectionHandleType.left: 108 | return Offset( 109 | handleSize.width / 2, 110 | handleSize.height, 111 | ); 112 | // The right handle is vertically flipped, and the anchor point is near 113 | // the top of the circle to give slight overlap. 114 | case TextSelectionHandleType.right: 115 | return Offset( 116 | handleSize.width / 2, 117 | handleSize.height - 118 | 2 * _kSelectionHandleRadius + 119 | _kSelectionHandleOverlap, 120 | ); 121 | // A collapsed handle anchors itself so that it's centered. 122 | default: 123 | return Offset( 124 | handleSize.width / 2, 125 | textLineHeight + (handleSize.height - textLineHeight) / 2, 126 | ); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /lib/src/material/material_selection_controllers.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'dart:math' as math; 4 | 5 | import '../selection_toolbar_controller.dart'; 6 | import '../text_selection_controls.dart'; 7 | 8 | const double _kHandleSize = 22.0; 9 | 10 | // Minimal padding from all edges of the selection toolbar to all edges of the 11 | // viewport. 12 | const double _kToolbarScreenPadding = 8.0; 13 | const double _kToolbarHeight = 44.0; 14 | // Padding when positioning toolbar below selection. 15 | const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0; 16 | const double _kToolbarContentDistance = 8.0; 17 | 18 | /// Manages a copy/paste text selection toolbar. 19 | class _TextSelectionToolbar extends StatefulWidget { 20 | const _TextSelectionToolbar({ 21 | Key key, 22 | this.isAbove, 23 | this.items = const [], 24 | this.backgroundColor, 25 | this.elevation, 26 | this.clipBehavior, 27 | this.shape, 28 | this.theme, 29 | }) : super(key: key); 30 | 31 | final List items; 32 | 33 | // When true, the toolbar fits above its anchor and will be positioned there. 34 | final bool isAbove; 35 | 36 | final Color backgroundColor; 37 | final double elevation; 38 | 39 | final Clip clipBehavior; 40 | final ShapeBorder shape; 41 | final ThemeData theme; 42 | 43 | @override 44 | _TextSelectionToolbarState createState() => _TextSelectionToolbarState(); 45 | } 46 | 47 | class _TextSelectionToolbarState extends State<_TextSelectionToolbar> 48 | with TickerProviderStateMixin { 49 | // Whether or not the overflow menu is open. When it is closed, the menu 50 | // items that don't overflow are shown. When it is open, only the overflowing 51 | // menu items are shown. 52 | bool _overflowOpen = false; 53 | 54 | // The key for _TextSelectionToolbarContainer. 55 | UniqueKey _containerKey = UniqueKey(); 56 | 57 | // Close the menu and reset layout calculations, as in when the menu has 58 | // changed and saved values are no longer relevant. This should be called in 59 | // setState or another context where a rebuild is happening. 60 | void _reset() { 61 | // Change _TextSelectionToolbarContainer's key when the menu changes in 62 | // order to cause it to rebuild. This lets it recalculate its 63 | // saved width for the new set of children, and it prevents AnimatedSize 64 | // from animating the size change. 65 | _containerKey = UniqueKey(); 66 | // If the menu items change, make sure the overflow menu is closed. This 67 | // prevents an empty overflow menu. 68 | _overflowOpen = false; 69 | } 70 | 71 | @override 72 | void didUpdateWidget(_TextSelectionToolbar oldWidget) { 73 | super.didUpdateWidget(oldWidget); 74 | if (widget.items != oldWidget.items) { 75 | _reset(); 76 | } 77 | } 78 | 79 | Widget _getItem( 80 | TextSelectionToolbarItem itemData, bool isFirst, bool isLast) { 81 | assert(isFirst != null); 82 | assert(isLast != null); 83 | return itemData.buildItem(context, isFirst, isLast, TextSelectionToolbarItem.materialBuilder); 84 | } 85 | 86 | @override 87 | Widget build(BuildContext context) { 88 | final MaterialLocalizations localizations = 89 | MaterialLocalizations.of(context); 90 | 91 | final items = [ 92 | ...widget.items.where((element) => element.enabled(context)) 93 | ]; 94 | if (items.isEmpty) { 95 | return const SizedBox(width: 0.0, height: 0.0); 96 | } 97 | 98 | return _TextSelectionToolbarContainer( 99 | key: _containerKey, 100 | overflowOpen: _overflowOpen, 101 | child: AnimatedSize( 102 | vsync: this, 103 | // This duration was eyeballed on a Pixel 2 emulator running Android 104 | // API 28. 105 | duration: const Duration(milliseconds: 140), 106 | child: Material( 107 | // This value was eyeballed to match the native text selection menu on 108 | // a Pixel 2 running Android 10. 109 | borderRadius: widget.shape == null ? const BorderRadius.all(Radius.circular(7.0)) : null, 110 | shape: widget.shape, 111 | clipBehavior: widget.clipBehavior ?? Clip.antiAlias, 112 | elevation: widget.elevation ?? 1.0, 113 | color: widget.backgroundColor, 114 | type: MaterialType.card, 115 | child: _TextSelectionToolbarItems( 116 | isAbove: widget.isAbove, 117 | overflowOpen: _overflowOpen, 118 | children: [ 119 | // The navButton that shows and hides the overflow menu is the 120 | // first child. 121 | Material( 122 | type: MaterialType.card, 123 | child: IconButton( 124 | // TODO(justinmc): This should be an AnimatedIcon, but 125 | // AnimatedIcons doesn't yet support arrow_back to more_vert. 126 | // https://github.com/flutter/flutter/issues/51209 127 | icon: 128 | Icon(_overflowOpen ? Icons.arrow_back : Icons.more_vert), 129 | onPressed: () { 130 | setState(() { 131 | _overflowOpen = !_overflowOpen; 132 | }); 133 | }, 134 | tooltip: _overflowOpen 135 | ? localizations.backButtonTooltip 136 | : localizations.moreButtonTooltip, 137 | ), 138 | ), 139 | for (int i = 0; i < items.length; i++) 140 | _getItem(items[i], i == 0, i == items.length - 1), 141 | ], 142 | ), 143 | ), 144 | ), 145 | ); 146 | } 147 | } 148 | 149 | // When the overflow menu is open, it tries to align its right edge to the right 150 | // edge of the closed menu. This widget handles this effect by measuring and 151 | // maintaining the width of the closed menu and aligning the child to the right. 152 | class _TextSelectionToolbarContainer extends SingleChildRenderObjectWidget { 153 | const _TextSelectionToolbarContainer({ 154 | Key key, 155 | @required Widget child, 156 | @required this.overflowOpen, 157 | }) : assert(child != null), 158 | assert(overflowOpen != null), 159 | super(key: key, child: child); 160 | 161 | final bool overflowOpen; 162 | 163 | @override 164 | _TextSelectionToolbarContainerRenderBox createRenderObject( 165 | BuildContext context) { 166 | return _TextSelectionToolbarContainerRenderBox(overflowOpen: overflowOpen); 167 | } 168 | 169 | @override 170 | void updateRenderObject(BuildContext context, 171 | _TextSelectionToolbarContainerRenderBox renderObject) { 172 | renderObject.overflowOpen = overflowOpen; 173 | } 174 | } 175 | 176 | class _TextSelectionToolbarContainerRenderBox extends RenderProxyBox { 177 | _TextSelectionToolbarContainerRenderBox({ 178 | @required bool overflowOpen, 179 | }) : assert(overflowOpen != null), 180 | _overflowOpen = overflowOpen, 181 | super(); 182 | 183 | // The width of the menu when it was closed. This is used to achieve the 184 | // behavior where the open menu aligns its right edge to the closed menu's 185 | // right edge. 186 | double _closedWidth; 187 | 188 | bool _overflowOpen; 189 | bool get overflowOpen => _overflowOpen; 190 | set overflowOpen(bool value) { 191 | if (value == overflowOpen) { 192 | return; 193 | } 194 | _overflowOpen = value; 195 | markNeedsLayout(); 196 | } 197 | 198 | @override 199 | void performLayout() { 200 | child.layout(constraints.loosen(), parentUsesSize: true); 201 | 202 | // Save the width when the menu is closed. If the menu changes, this width 203 | // is invalid, so it's important that this RenderBox be recreated in that 204 | // case. Currently, this is achieved by providing a new key to 205 | // _TextSelectionToolbarContainer. 206 | if (!overflowOpen && _closedWidth == null) { 207 | _closedWidth = child.size.width; 208 | } 209 | 210 | size = constraints.constrain(Size( 211 | // If the open menu is wider than the closed menu, just use its own width 212 | // and don't worry about aligning the right edges. 213 | // _closedWidth is used even when the menu is closed to allow it to 214 | // animate its size while keeping the same right alignment. 215 | _closedWidth == null || child.size.width > _closedWidth 216 | ? child.size.width 217 | : _closedWidth, 218 | child.size.height, 219 | )); 220 | 221 | final ToolbarItemsParentData childParentData = 222 | child.parentData as ToolbarItemsParentData; 223 | childParentData.offset = Offset( 224 | size.width - child.size.width, 225 | 0.0, 226 | ); 227 | } 228 | 229 | // Paint at the offset set in the parent data. 230 | @override 231 | void paint(PaintingContext context, Offset offset) { 232 | final ToolbarItemsParentData childParentData = 233 | child.parentData as ToolbarItemsParentData; 234 | context.paintChild(child, childParentData.offset + offset); 235 | } 236 | 237 | // Include the parent data offset in the hit test. 238 | @override 239 | bool hitTestChildren(BoxHitTestResult result, {Offset position}) { 240 | // The x, y parameters have the top left of the node's box as the origin. 241 | final ToolbarItemsParentData childParentData = 242 | child.parentData as ToolbarItemsParentData; 243 | return result.addWithPaintOffset( 244 | offset: childParentData.offset, 245 | position: position, 246 | hitTest: (BoxHitTestResult result, Offset transformed) { 247 | assert(transformed == position - childParentData.offset); 248 | return child.hitTest(result, position: transformed); 249 | }, 250 | ); 251 | } 252 | 253 | @override 254 | void setupParentData(RenderBox child) { 255 | if (child.parentData is! ToolbarItemsParentData) { 256 | child.parentData = ToolbarItemsParentData(); 257 | } 258 | } 259 | 260 | @override 261 | void applyPaintTransform(RenderObject child, Matrix4 transform) { 262 | final ToolbarItemsParentData childParentData = 263 | child.parentData as ToolbarItemsParentData; 264 | transform.translate(childParentData.offset.dx, childParentData.offset.dy); 265 | super.applyPaintTransform(child, transform); 266 | } 267 | } 268 | 269 | // Renders the menu items in the correct positions in the menu and its overflow 270 | // submenu based on calculating which item would first overflow. 271 | class _TextSelectionToolbarItems extends MultiChildRenderObjectWidget { 272 | _TextSelectionToolbarItems({ 273 | Key key, 274 | @required this.isAbove, 275 | @required this.overflowOpen, 276 | @required List children, 277 | }) : assert(children != null), 278 | assert(isAbove != null), 279 | assert(overflowOpen != null), 280 | super(key: key, children: children); 281 | 282 | final bool isAbove; 283 | final bool overflowOpen; 284 | 285 | @override 286 | _TextSelectionToolbarItemsRenderBox createRenderObject(BuildContext context) { 287 | return _TextSelectionToolbarItemsRenderBox( 288 | isAbove: isAbove, 289 | overflowOpen: overflowOpen, 290 | ); 291 | } 292 | 293 | @override 294 | void updateRenderObject( 295 | BuildContext context, _TextSelectionToolbarItemsRenderBox renderObject) { 296 | renderObject 297 | ..isAbove = isAbove 298 | ..overflowOpen = overflowOpen; 299 | } 300 | 301 | @override 302 | _TextSelectionToolbarItemsElement createElement() => 303 | _TextSelectionToolbarItemsElement(this); 304 | } 305 | 306 | class _TextSelectionToolbarItemsElement extends MultiChildRenderObjectElement { 307 | _TextSelectionToolbarItemsElement( 308 | MultiChildRenderObjectWidget widget, 309 | ) : super(widget); 310 | 311 | static bool _shouldPaint(Element child) { 312 | return (child.renderObject.parentData as ToolbarItemsParentData) 313 | .shouldPaint; 314 | } 315 | 316 | @override 317 | void debugVisitOnstageChildren(ElementVisitor visitor) { 318 | children.where(_shouldPaint).forEach(visitor); 319 | } 320 | } 321 | 322 | class _TextSelectionToolbarItemsRenderBox extends RenderBox 323 | with ContainerRenderObjectMixin { 324 | _TextSelectionToolbarItemsRenderBox({ 325 | @required bool isAbove, 326 | @required bool overflowOpen, 327 | }) : assert(overflowOpen != null), 328 | assert(isAbove != null), 329 | _isAbove = isAbove, 330 | _overflowOpen = overflowOpen, 331 | super(); 332 | 333 | // The index of the last item that doesn't overflow. 334 | int _lastIndexThatFits = -1; 335 | 336 | bool _isAbove; 337 | bool get isAbove => _isAbove; 338 | set isAbove(bool value) { 339 | if (value == isAbove) { 340 | return; 341 | } 342 | _isAbove = value; 343 | markNeedsLayout(); 344 | } 345 | 346 | bool _overflowOpen; 347 | bool get overflowOpen => _overflowOpen; 348 | set overflowOpen(bool value) { 349 | if (value == overflowOpen) { 350 | return; 351 | } 352 | _overflowOpen = value; 353 | markNeedsLayout(); 354 | } 355 | 356 | // Layout the necessary children, and figure out where the children first 357 | // overflow, if at all. 358 | void _layoutChildren() { 359 | // When overflow is not open, the toolbar is always a specific height. 360 | final BoxConstraints sizedConstraints = _overflowOpen 361 | ? constraints 362 | : BoxConstraints.loose(Size( 363 | constraints.maxWidth, 364 | _kToolbarHeight, 365 | )); 366 | 367 | int i = -1; 368 | double width = 0.0; 369 | visitChildren((RenderObject renderObjectChild) { 370 | i++; 371 | 372 | // No need to layout children inside the overflow menu when it's closed. 373 | // The opposite is not true. It is necessary to layout the children that 374 | // don't overflow when the overflow menu is open in order to calculate 375 | // _lastIndexThatFits. 376 | if (_lastIndexThatFits != -1 && !overflowOpen) { 377 | return; 378 | } 379 | 380 | final RenderBox child = renderObjectChild as RenderBox; 381 | child.layout(sizedConstraints.loosen(), parentUsesSize: true); 382 | width += child.size.width; 383 | 384 | if (width > sizedConstraints.maxWidth && _lastIndexThatFits == -1) { 385 | _lastIndexThatFits = i - 1; 386 | } 387 | }); 388 | 389 | // If the last child overflows, but only because of the width of the 390 | // overflow button, then just show it and hide the overflow button. 391 | final RenderBox navButton = firstChild; 392 | if (_lastIndexThatFits != -1 && 393 | _lastIndexThatFits == childCount - 2 && 394 | width - navButton.size.width <= sizedConstraints.maxWidth) { 395 | _lastIndexThatFits = -1; 396 | } 397 | } 398 | 399 | // Returns true when the child should be painted, false otherwise. 400 | bool _shouldPaintChild(RenderObject renderObjectChild, int index) { 401 | // Paint the navButton when there is overflow. 402 | if (renderObjectChild == firstChild) { 403 | return _lastIndexThatFits != -1; 404 | } 405 | 406 | // If there is no overflow, all children besides the navButton are painted. 407 | if (_lastIndexThatFits == -1) { 408 | return true; 409 | } 410 | 411 | // When there is overflow, paint if the child is in the part of the menu 412 | // that is currently open. Overflowing children are painted when the 413 | // overflow menu is open, and the children that fit are painted when the 414 | // overflow menu is closed. 415 | return (index > _lastIndexThatFits) == overflowOpen; 416 | } 417 | 418 | // Decide which children will be pained and set their shouldPaint, and set the 419 | // offset that painted children will be placed at. 420 | void _placeChildren() { 421 | int i = -1; 422 | Size nextSize = const Size(0.0, 0.0); 423 | double fitWidth = 0.0; 424 | final RenderBox navButton = firstChild; 425 | double overflowHeight = 426 | overflowOpen && !isAbove ? navButton.size.height : 0.0; 427 | visitChildren((RenderObject renderObjectChild) { 428 | i++; 429 | 430 | final RenderBox child = renderObjectChild as RenderBox; 431 | final ToolbarItemsParentData childParentData = 432 | child.parentData as ToolbarItemsParentData; 433 | 434 | // Handle placing the navigation button after iterating all children. 435 | if (renderObjectChild == navButton) { 436 | return; 437 | } 438 | 439 | // There is no need to place children that won't be painted. 440 | if (!_shouldPaintChild(renderObjectChild, i)) { 441 | childParentData.shouldPaint = false; 442 | return; 443 | } 444 | childParentData.shouldPaint = true; 445 | 446 | if (!overflowOpen) { 447 | childParentData.offset = Offset(fitWidth, 0.0); 448 | fitWidth += child.size.width; 449 | nextSize = Size( 450 | fitWidth, 451 | math.max(child.size.height, nextSize.height), 452 | ); 453 | } else { 454 | childParentData.offset = Offset(0.0, overflowHeight); 455 | overflowHeight += child.size.height; 456 | nextSize = Size( 457 | math.max(child.size.width, nextSize.width), 458 | overflowHeight, 459 | ); 460 | } 461 | }); 462 | 463 | // Place the navigation button if needed. 464 | final ToolbarItemsParentData navButtonParentData = 465 | navButton.parentData as ToolbarItemsParentData; 466 | if (_shouldPaintChild(firstChild, 0)) { 467 | navButtonParentData.shouldPaint = true; 468 | if (overflowOpen) { 469 | navButtonParentData.offset = 470 | isAbove ? Offset(0.0, overflowHeight) : Offset.zero; 471 | nextSize = Size( 472 | nextSize.width, 473 | isAbove ? nextSize.height + navButton.size.height : nextSize.height, 474 | ); 475 | } else { 476 | navButtonParentData.offset = Offset(fitWidth, 0.0); 477 | nextSize = Size(nextSize.width + navButton.size.width, nextSize.height); 478 | } 479 | } else { 480 | navButtonParentData.shouldPaint = false; 481 | } 482 | 483 | size = nextSize; 484 | } 485 | 486 | @override 487 | void performLayout() { 488 | _lastIndexThatFits = -1; 489 | if (firstChild == null) { 490 | performResize(); 491 | return; 492 | } 493 | 494 | _layoutChildren(); 495 | _placeChildren(); 496 | } 497 | 498 | @override 499 | void paint(PaintingContext context, Offset offset) { 500 | visitChildren((RenderObject renderObjectChild) { 501 | final RenderBox child = renderObjectChild as RenderBox; 502 | final ToolbarItemsParentData childParentData = 503 | child.parentData as ToolbarItemsParentData; 504 | if (!childParentData.shouldPaint) { 505 | return; 506 | } 507 | 508 | context.paintChild(child, childParentData.offset + offset); 509 | }); 510 | } 511 | 512 | @override 513 | void setupParentData(RenderBox child) { 514 | if (child.parentData is! ToolbarItemsParentData) { 515 | child.parentData = ToolbarItemsParentData(); 516 | } 517 | } 518 | 519 | @override 520 | bool hitTestChildren(BoxHitTestResult result, {Offset position}) { 521 | // The x, y parameters have the top left of the node's box as the origin. 522 | RenderBox child = lastChild; 523 | while (child != null) { 524 | final ToolbarItemsParentData childParentData = 525 | child.parentData as ToolbarItemsParentData; 526 | 527 | // Don't hit test children aren't shown. 528 | if (!childParentData.shouldPaint) { 529 | child = childParentData.previousSibling; 530 | continue; 531 | } 532 | 533 | final bool isHit = result.addWithPaintOffset( 534 | offset: childParentData.offset, 535 | position: position, 536 | hitTest: (BoxHitTestResult result, Offset transformed) { 537 | assert(transformed == position - childParentData.offset); 538 | return child.hitTest(result, position: transformed); 539 | }, 540 | ); 541 | if (isHit) return true; 542 | child = childParentData.previousSibling; 543 | } 544 | return false; 545 | } 546 | 547 | // Visit only the children that should be painted. 548 | @override 549 | void visitChildrenForSemantics(RenderObjectVisitor visitor) { 550 | visitChildren((RenderObject renderObjectChild) { 551 | final RenderBox child = renderObjectChild as RenderBox; 552 | final ToolbarItemsParentData childParentData = 553 | child.parentData as ToolbarItemsParentData; 554 | if (childParentData.shouldPaint) { 555 | visitor(renderObjectChild); 556 | } 557 | }); 558 | } 559 | } 560 | 561 | /// Centers the toolbar around the given anchor, ensuring that it remains on 562 | /// screen. 563 | class _TextSelectionToolbarLayout extends SingleChildLayoutDelegate { 564 | _TextSelectionToolbarLayout(this.anchor, this.upperBounds, this.fitsAbove); 565 | 566 | /// Anchor position of the toolbar in global coordinates. 567 | final Offset anchor; 568 | 569 | /// The upper-most valid y value for the anchor. 570 | final double upperBounds; 571 | 572 | /// Whether the closed toolbar fits above the anchor position. 573 | /// 574 | /// If the closed toolbar doesn't fit, then the menu is rendered below the 575 | /// anchor position. It should never happen that the toolbar extends below the 576 | /// padded bottom of the screen. 577 | /// 578 | /// If the closed toolbar does fit but it doesn't fit when the overflow menu 579 | /// is open, then the toolbar is still rendered above the anchor position. It 580 | /// then grows downward, overlapping the selection. 581 | final bool fitsAbove; 582 | 583 | // Return the value that centers width as closely as possible to position 584 | // while fitting inside of min and max. 585 | static double _centerOn( 586 | double position, double width, double min, double max) { 587 | // If it overflows on the left, put it as far left as possible. 588 | if (position - width / 2.0 < min) { 589 | return min; 590 | } 591 | 592 | // If it overflows on the right, put it as far right as possible. 593 | if (position + width / 2.0 > max) { 594 | return max - width; 595 | } 596 | 597 | // Otherwise it fits while perfectly centered. 598 | return position - width / 2.0; 599 | } 600 | 601 | @override 602 | BoxConstraints getConstraintsForChild(BoxConstraints constraints) { 603 | return constraints.loosen(); 604 | } 605 | 606 | @override 607 | Offset getPositionForChild(Size size, Size childSize) { 608 | return Offset( 609 | _centerOn( 610 | anchor.dx, 611 | childSize.width, 612 | _kToolbarScreenPadding, 613 | size.width - _kToolbarScreenPadding, 614 | ), 615 | fitsAbove 616 | ? math.max(upperBounds, anchor.dy - childSize.height) 617 | : anchor.dy, 618 | ); 619 | } 620 | 621 | @override 622 | bool shouldRelayout(_TextSelectionToolbarLayout oldDelegate) { 623 | return anchor != oldDelegate.anchor; 624 | } 625 | } 626 | 627 | class MaterialSelectionToolbar extends TextSelectionToolbar { 628 | final List items; 629 | 630 | final Color backgroundColor; 631 | final double elevation; 632 | 633 | final Clip clipBehavior; 634 | final ShapeBorder shape; 635 | final ThemeData theme; 636 | 637 | MaterialSelectionToolbar({ 638 | @required this.items, 639 | this.backgroundColor, 640 | this.elevation, 641 | this.clipBehavior, 642 | this.shape, 643 | this.theme, 644 | }) : assert(items != null), 645 | super(items: items); 646 | 647 | /// Builder for material-style copy/paste text selection toolbar. 648 | @override 649 | Widget buildToolbar( 650 | BuildContext context, 651 | Rect globalEditableRegion, 652 | double textLineHeight, 653 | Offset selectionMidpoint, 654 | List endpoints, 655 | TextSelectionDelegate delegate, 656 | ClipboardStatusNotifier clipboardStatus, 657 | ) { 658 | assert(debugCheckHasMediaQuery(context)); 659 | assert(debugCheckHasMaterialLocalizations(context)); 660 | final MediaQueryData mediaQuery = MediaQuery.of(context); 661 | 662 | // The toolbar should appear below the TextField when there is not enough 663 | // space above the TextField to show it. 664 | 665 | final toolbarScreenPadding = _kToolbarScreenPadding; 666 | final toolbarHeight = _kToolbarHeight; 667 | final toolbarContentDistance = _kToolbarContentDistance; 668 | final toolbarContentDistanceBelow = _kToolbarContentDistanceBelow; 669 | 670 | final Offset startTextSelectionPoint = endpoints.first.point; 671 | final Offset endTextSelectionPoint = endpoints.last.point; 672 | 673 | final double closedToolbarHeightNeeded = 674 | toolbarScreenPadding + toolbarHeight + toolbarContentDistance; 675 | 676 | final double availableHeight = globalEditableRegion.top + 677 | startTextSelectionPoint.dy - 678 | textLineHeight - 679 | mediaQuery.padding.top; 680 | 681 | final bool fitsAbove = closedToolbarHeightNeeded <= availableHeight; 682 | final Offset anchor = Offset( 683 | globalEditableRegion.left + selectionMidpoint.dx, 684 | fitsAbove 685 | ? globalEditableRegion.top + 686 | startTextSelectionPoint.dy - 687 | textLineHeight - 688 | toolbarContentDistance 689 | : globalEditableRegion.top + 690 | endTextSelectionPoint.dy + 691 | toolbarContentDistanceBelow, 692 | ); 693 | 694 | final child = Stack( 695 | children: [ 696 | CustomSingleChildLayout( 697 | delegate: _TextSelectionToolbarLayout( 698 | anchor, 699 | toolbarScreenPadding + mediaQuery.padding.top, 700 | fitsAbove, 701 | ), 702 | child: _TextSelectionToolbar( 703 | items: items, 704 | isAbove: fitsAbove, 705 | shape: shape, 706 | backgroundColor: backgroundColor, 707 | elevation: elevation, 708 | clipBehavior: clipBehavior, 709 | ), 710 | ) 711 | ], 712 | ); 713 | 714 | if (theme != null) { 715 | return Theme(data: theme, child: child); 716 | } else { 717 | return child; 718 | } 719 | } 720 | } 721 | -------------------------------------------------------------------------------- /lib/src/material/material_selection_handle.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'dart:math' as math; 5 | 6 | import '../text_selection_controls.dart'; 7 | 8 | const double _kHandleSize = 22.0; 9 | 10 | /// Draws a single text selection handle which points up and to the left. 11 | class _TextSelectionHandlePainter extends CustomPainter { 12 | _TextSelectionHandlePainter({this.color}); 13 | 14 | final Color color; 15 | 16 | @override 17 | void paint(Canvas canvas, Size size) { 18 | final Paint paint = Paint()..color = color; 19 | final double radius = size.width / 2.0; 20 | final Rect circle = 21 | Rect.fromCircle(center: Offset(radius, radius), radius: radius); 22 | final Rect point = Rect.fromLTWH(0.0, 0.0, radius, radius); 23 | final Path path = Path() 24 | ..addOval(circle) 25 | ..addRect(point); 26 | canvas.drawPath(path, paint); 27 | } 28 | 29 | @override 30 | bool shouldRepaint(_TextSelectionHandlePainter oldPainter) { 31 | return color != oldPainter.color; 32 | } 33 | } 34 | 35 | 36 | class MaterialSelectionHandle extends TextSelectionHandle { 37 | const MaterialSelectionHandle(); 38 | 39 | /// Returns the size of the Material handle. 40 | @override 41 | Size getHandleSize(double textLineHeight) => 42 | const Size(_kHandleSize, _kHandleSize); 43 | 44 | /// Builder for material-style text selection handles. 45 | @override 46 | Widget buildHandle( 47 | BuildContext context, TextSelectionHandleType type, double textHeight) { 48 | final ThemeData theme = Theme.of(context); 49 | final Color handleColor = theme.useTextSelectionTheme 50 | ? TextSelectionTheme.of(context).selectionHandleColor ?? 51 | theme.colorScheme.primary 52 | : theme.textSelectionHandleColor; 53 | final Widget handle = SizedBox( 54 | width: _kHandleSize, 55 | height: _kHandleSize, 56 | child: CustomPaint( 57 | painter: _TextSelectionHandlePainter( 58 | color: handleColor, 59 | ), 60 | ), 61 | ); 62 | 63 | // [handle] is a circle, with a rectangle in the top left quadrant of that 64 | // circle (an onion pointing to 10:30). We rotate [handle] to point 65 | // straight up or up-right depending on the handle type. 66 | switch (type) { 67 | case TextSelectionHandleType.left: // points up-right 68 | return Transform.rotate( 69 | angle: math.pi / 2.0, 70 | child: handle, 71 | ); 72 | case TextSelectionHandleType.right: // points up-left 73 | return handle; 74 | case TextSelectionHandleType.collapsed: // points up 75 | return Transform.rotate( 76 | angle: math.pi / 4.0, 77 | child: handle, 78 | ); 79 | } 80 | assert(type != null); 81 | return null; 82 | } 83 | 84 | /// Gets anchor for material-style text selection handles. 85 | /// 86 | /// See [TextSelectionControls.getHandleAnchor]. 87 | @override 88 | Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) { 89 | switch (type) { 90 | case TextSelectionHandleType.left: 91 | return const Offset(_kHandleSize, 0); 92 | case TextSelectionHandleType.right: 93 | return Offset.zero; 94 | default: 95 | return const Offset(_kHandleSize / 2, -4); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /lib/src/selection_toolbar_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | import 'package:selection_controls_example/src/text_selection_controls.dart'; 4 | 5 | 6 | typedef _TextSelectionToolbarBuilder = Widget Function( 7 | List items); 8 | 9 | class TextSelectionToolbarController extends StatelessWidget { 10 | final TextSelectionDelegate delegate; 11 | final ClipboardStatusNotifier clipboardStatus; 12 | final WidgetBuilder builder; 13 | 14 | final VoidCallback cut; 15 | final VoidCallback copy; 16 | final VoidCallback paste; 17 | final VoidCallback selectAll; 18 | 19 | static _DefaultTextSelectionOptionsScope of(BuildContext context) => context 20 | .dependOnInheritedWidgetOfExactType<_DefaultTextSelectionOptionsScope>(); 21 | 22 | const TextSelectionToolbarController({ 23 | Key key, 24 | this.builder, 25 | this.delegate, 26 | this.clipboardStatus, 27 | this.cut, 28 | this.copy, 29 | this.paste, 30 | this.selectAll, 31 | }) : super(key: key); 32 | 33 | @override 34 | Widget build(BuildContext context) { 35 | return _ClipboardToolbarItemsHandler( 36 | clipboardStatus: clipboardStatus, 37 | handleCopy: copy, 38 | handleCut: cut, 39 | handlePaste: paste, 40 | handleSelectAll: selectAll, 41 | builder: (defaultItems) { 42 | return _DefaultTextSelectionOptionsScope( 43 | clipboardStatus: clipboardStatus, 44 | delegate: delegate, 45 | cut: cut, 46 | copy: copy, 47 | paste: paste, 48 | selectAll: selectAll, 49 | defaultItems: defaultItems, 50 | child: builder(context), 51 | ); 52 | }); 53 | } 54 | } 55 | 56 | typedef SelectionToolbarCallback = Function(_DefaultTextSelectionOptionsScope controller); 57 | 58 | /// Manages a copy/paste text selection toolbar. 59 | class _ClipboardToolbarItemsHandler extends StatefulWidget { 60 | const _ClipboardToolbarItemsHandler({ 61 | this.clipboardStatus, 62 | Key key, 63 | this.handleCut, 64 | this.handleCopy, 65 | this.handlePaste, 66 | this.handleSelectAll, 67 | this.builder, 68 | }) : super(key: key); 69 | 70 | final ClipboardStatusNotifier clipboardStatus; 71 | final VoidCallback handleCut; 72 | final VoidCallback handleCopy; 73 | final VoidCallback handlePaste; 74 | final VoidCallback handleSelectAll; 75 | 76 | final _TextSelectionToolbarBuilder builder; 77 | 78 | @override 79 | _ClipboardToolbarItemsHandlerState createState() => 80 | _ClipboardToolbarItemsHandlerState(); 81 | } 82 | 83 | class _ClipboardToolbarItemsHandlerState 84 | extends State<_ClipboardToolbarItemsHandler> with TickerProviderStateMixin { 85 | ClipboardStatusNotifier _clipboardStatus; 86 | 87 | void _onChangedClipboardStatus() { 88 | setState(() { 89 | // Inform the widget that the value of clipboardStatus has changed. 90 | }); 91 | } 92 | 93 | @override 94 | void initState() { 95 | super.initState(); 96 | _clipboardStatus = widget.clipboardStatus ?? ClipboardStatusNotifier(); 97 | _clipboardStatus.addListener(_onChangedClipboardStatus); 98 | _clipboardStatus.update(); 99 | } 100 | 101 | @override 102 | void didUpdateWidget(_ClipboardToolbarItemsHandler oldWidget) { 103 | super.didUpdateWidget(oldWidget); 104 | 105 | if (oldWidget.clipboardStatus == null && widget.clipboardStatus != null) { 106 | _clipboardStatus.removeListener(_onChangedClipboardStatus); 107 | _clipboardStatus.dispose(); 108 | _clipboardStatus = widget.clipboardStatus; 109 | } else if (oldWidget.clipboardStatus != null) { 110 | if (widget.clipboardStatus == null) { 111 | _clipboardStatus = ClipboardStatusNotifier(); 112 | _clipboardStatus.addListener(_onChangedClipboardStatus); 113 | oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus); 114 | } else if (widget.clipboardStatus != oldWidget.clipboardStatus) { 115 | _clipboardStatus = widget.clipboardStatus; 116 | _clipboardStatus.addListener(_onChangedClipboardStatus); 117 | oldWidget.clipboardStatus.removeListener(_onChangedClipboardStatus); 118 | } 119 | } 120 | if (widget.handlePaste != null) { 121 | _clipboardStatus.update(); 122 | } 123 | } 124 | 125 | @override 126 | void dispose() { 127 | super.dispose(); 128 | // When used in an Overlay, this can be disposed after its creator has 129 | // already disposed _clipboardStatus. 130 | if (!_clipboardStatus.disposed) { 131 | _clipboardStatus.removeListener(_onChangedClipboardStatus); 132 | if (widget.clipboardStatus == null) { 133 | _clipboardStatus.dispose(); 134 | } 135 | } 136 | } 137 | 138 | @override 139 | Widget build(BuildContext context) { 140 | // Don't render the menu until the state of the clipboard is known. 141 | if (widget.handlePaste != null && 142 | _clipboardStatus.value == ClipboardStatus.unknown) { 143 | return const SizedBox(width: 0.0, height: 0.0); 144 | } 145 | 146 | final MaterialLocalizations localizations = 147 | MaterialLocalizations.of(context); 148 | final List itemDatas = [ 149 | if (widget.handleCut != null) 150 | TextSelectionToolbarItem( 151 | onPressed: (_) =>widget.handleCut(), 152 | title: Text(localizations.cutButtonLabel), 153 | ), 154 | if (widget.handleCopy != null) 155 | TextSelectionToolbarItem( 156 | onPressed: (_) => widget.handleCopy(), 157 | title: Text(localizations.copyButtonLabel)), 158 | if (widget.handlePaste != null && 159 | _clipboardStatus.value == ClipboardStatus.pasteable) 160 | TextSelectionToolbarItem( 161 | onPressed: (_) => widget.handlePaste(), 162 | title: Text(localizations.pasteButtonLabel)), 163 | if (widget.handleSelectAll != null) 164 | TextSelectionToolbarItem( 165 | onPressed: (_) => widget.handleSelectAll(), 166 | title: Text(localizations.selectAllButtonLabel)), 167 | ]; 168 | return widget.builder(itemDatas); 169 | } 170 | } 171 | 172 | class _DefaultTextSelectionOptionsScope extends InheritedWidget { 173 | final ClipboardStatusNotifier clipboardStatus; 174 | final TextSelectionDelegate delegate; 175 | 176 | final VoidCallback cut; 177 | final VoidCallback copy; 178 | final VoidCallback paste; 179 | final VoidCallback selectAll; 180 | 181 | bool get canCut => cut != null; 182 | bool get canCopy => copy != null; 183 | bool get canPaste => 184 | paste != null && clipboardStatus.value == ClipboardStatus.pasteable; 185 | bool get canSelectAll => selectAll != null; 186 | 187 | TextSelection get selection => delegate.textEditingValue.selection; 188 | 189 | final Widget child; 190 | final List defaultItems; 191 | 192 | _DefaultTextSelectionOptionsScope({ 193 | this.clipboardStatus, 194 | this.delegate, 195 | this.cut, 196 | this.copy, 197 | this.paste, 198 | this.selectAll, 199 | this.child, 200 | this.defaultItems, 201 | }) : super(child: child); 202 | 203 | @override 204 | bool updateShouldNotify( 205 | covariant _DefaultTextSelectionOptionsScope oldWidget) { 206 | return cut != oldWidget.cut || 207 | copy != oldWidget.copy || 208 | paste != oldWidget.paste || 209 | selectAll != oldWidget.selectAll || 210 | clipboardStatus != oldWidget.clipboardStatus || 211 | defaultItems.length != defaultItems.length; 212 | } 213 | 214 | void hide() { 215 | delegate.hideToolbar(); 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /lib/src/text_selection_controls.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | import 'package:selection_controls_example/src/cupertino/cupertino_selection_handle.dart'; 5 | import 'package:selection_controls_example/src/selection_toolbar_controller.dart'; 6 | import 'dart:math' as math; 7 | 8 | import 'material/material_selection_handle.dart'; 9 | 10 | @immutable 11 | abstract class TextSelectionHandle { 12 | const TextSelectionHandle(); 13 | 14 | Size getHandleSize(double textLineHeight); 15 | 16 | Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight); 17 | 18 | Widget buildHandle( 19 | BuildContext context, 20 | TextSelectionHandleType type, 21 | double textHeight, 22 | ); 23 | } 24 | 25 | @immutable 26 | abstract class TextSelectionToolbar { 27 | final List items; 28 | 29 | TextSelectionToolbar({@required this.items}); 30 | 31 | Widget buildToolbar( 32 | BuildContext context, 33 | Rect globalEditableRegion, 34 | double textLineHeight, 35 | Offset selectionMidpoint, 36 | List endpoints, 37 | TextSelectionDelegate delegate, 38 | ClipboardStatusNotifier clipboardStatus, 39 | ); 40 | } 41 | 42 | typedef TextSelectionToolbarItemBuilder = Widget Function( 43 | BuildContext context, 44 | SelectionToolbarCallback onPressed, 45 | Widget title, 46 | bool isFirst, 47 | bool isLast, 48 | ); 49 | 50 | // Intermediate data used for building menu items with the _getItems method. 51 | @immutable 52 | class TextSelectionToolbarItem { 53 | final TextSelectionToolbarItemBuilder builder; 54 | final Widget _child; 55 | 56 | const TextSelectionToolbarItem({ 57 | @required this.onPressed, 58 | @required this.title, 59 | this.builder, 60 | }) : assert(title != null), 61 | _child = null, 62 | copy = false, 63 | paste = false, 64 | cut = false, 65 | selectAll = false; 66 | 67 | const TextSelectionToolbarItem.custom({ 68 | @required Widget child, 69 | }) : _child = child, 70 | onPressed = null, 71 | builder = null, 72 | title = null, 73 | copy = false, 74 | paste = false, 75 | cut = false, 76 | selectAll = false; 77 | 78 | const TextSelectionToolbarItem.cut({this.builder}) 79 | : onPressed = null, 80 | title = null, 81 | _child = null, 82 | copy = false, 83 | paste = false, 84 | cut = true, 85 | selectAll = false; 86 | 87 | const TextSelectionToolbarItem.copy({this.builder}) 88 | : onPressed = null, 89 | title = null, 90 | _child = null, 91 | copy = true, 92 | paste = false, 93 | cut = false, 94 | selectAll = false; 95 | 96 | const TextSelectionToolbarItem.paste({this.builder}) 97 | : onPressed = null, 98 | title = null, 99 | _child = null, 100 | copy = false, 101 | paste = true, 102 | cut = false, 103 | selectAll = false; 104 | 105 | const TextSelectionToolbarItem.selectAll({this.builder}) 106 | : onPressed = null, 107 | title = null, 108 | _child = null, 109 | copy = false, 110 | paste = false, 111 | cut = false, 112 | selectAll = true; 113 | 114 | final SelectionToolbarCallback onPressed; 115 | final Widget title; 116 | 117 | final bool copy; 118 | final bool paste; 119 | final bool cut; 120 | final bool selectAll; 121 | 122 | bool enabled(BuildContext context) { 123 | final defaultController = TextSelectionToolbarController.of(context); 124 | if (cut) 125 | return defaultController.canCut; 126 | else if (copy) 127 | return defaultController.canCopy; 128 | else if (paste) 129 | return defaultController.canPaste; 130 | else if (selectAll) 131 | return defaultController.canSelectAll; 132 | else 133 | return onPressed != null || _child != null; 134 | } 135 | 136 | Widget buildItem(BuildContext context, bool isFirst, bool isLast, 137 | TextSelectionToolbarItemBuilder defaultBuilder) { 138 | if (_child != null) return _child; 139 | 140 | SelectionToolbarCallback onPressed = this.onPressed; 141 | Widget label = this.title; 142 | final MaterialLocalizations localizations = 143 | MaterialLocalizations.of(context); 144 | final defaultController = TextSelectionToolbarController.of(context); 145 | 146 | if (cut) { 147 | onPressed = (_) => defaultController.cut(); 148 | label = Text(localizations.cutButtonLabel); 149 | } 150 | if (copy) { 151 | onPressed = (_) => defaultController.copy(); 152 | label = Text(localizations.copyButtonLabel); 153 | } 154 | if (paste) { 155 | onPressed = (_) => defaultController.paste(); 156 | label = Text(localizations.pasteButtonLabel); 157 | } 158 | if (selectAll) { 159 | onPressed = (_) => defaultController.selectAll(); 160 | label = Text(localizations.selectAllButtonLabel); 161 | } 162 | assert(label != null); 163 | final builder = this.builder ?? defaultBuilder; 164 | return builder(context, onPressed, label, isFirst, isLast); 165 | } 166 | 167 | static TextSelectionToolbarItemBuilder materialBuilder = 168 | (BuildContext context, SelectionToolbarCallback onPressed, Widget label, 169 | bool isFirst, bool isLast) { 170 | return ButtonTheme.fromButtonThemeData( 171 | data: ButtonTheme.of(context).copyWith( 172 | height: kMinInteractiveDimension, 173 | minWidth: kMinInteractiveDimension, 174 | ), 175 | child: FlatButton( 176 | onPressed: onPressed != null 177 | ? () { 178 | final controller = TextSelectionToolbarController.of(context); 179 | controller.hide(); 180 | onPressed(controller); 181 | } 182 | : null, 183 | padding: EdgeInsets.only( 184 | // These values were eyeballed to match the native text selection menu 185 | // on a Pixel 2 running Android 10. 186 | left: 9.5 + (isFirst ? 5.0 : 0.0), 187 | right: 9.5 + (isLast ? 5.0 : 0.0), 188 | ), 189 | shape: Border.all(width: 0.0, color: Colors.transparent), 190 | child: label, 191 | ), 192 | ); 193 | }; 194 | } 195 | 196 | class DefaultTextSelectionControls extends TextSelectionControls { 197 | final TextSelectionHandle handle; 198 | final TextSelectionToolbar toolbar; 199 | 200 | DefaultTextSelectionControls({ 201 | this.toolbar, 202 | @required this.handle, 203 | }) : assert(handle != null); 204 | 205 | DefaultTextSelectionControls.material({ 206 | this.toolbar, 207 | this.handle = const MaterialSelectionHandle(), 208 | }) : assert(handle != null); 209 | 210 | DefaultTextSelectionControls.cupertino({ 211 | this.toolbar, 212 | this.handle = const CupertinoTextSelectionHandle(), 213 | }) : assert(handle != null); 214 | 215 | DefaultTextSelectionControls.withPlatformHandle({ 216 | this.toolbar, 217 | BuildContext context, 218 | }) : handle = defaultHandle(context); 219 | 220 | static TextSelectionHandle defaultHandle(BuildContext context) { 221 | final platform = Theme.of(context).platform; 222 | switch (platform) { 223 | case TargetPlatform.iOS: 224 | case TargetPlatform.macOS: 225 | return CupertinoTextSelectionHandle(); 226 | case TargetPlatform.android: 227 | case TargetPlatform.fuchsia: 228 | case TargetPlatform.linux: 229 | case TargetPlatform.windows: 230 | return MaterialSelectionHandle(); 231 | } 232 | return null; 233 | } 234 | 235 | /// Returns the size of the Material handle. 236 | @override 237 | Size getHandleSize(double textLineHeight) => 238 | handle.getHandleSize(textLineHeight); 239 | 240 | /// Builder for material-style text selection handles. 241 | @override 242 | Widget buildHandle( 243 | BuildContext context, TextSelectionHandleType type, double textHeight) { 244 | return handle.buildHandle(context, type, textHeight); 245 | } 246 | 247 | /// Gets anchor for material-style text selection handles. 248 | /// 249 | /// See [TextSelectionControls.getHandleAnchor]. 250 | @override 251 | Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight) { 252 | return handle.getHandleAnchor(type, textLineHeight); 253 | } 254 | 255 | @override 256 | bool canSelectAll(TextSelectionDelegate delegate) { 257 | // Android allows SelectAll when selection is not collapsed, unless 258 | // everything has already been selected. 259 | final TextEditingValue value = delegate.textEditingValue; 260 | return delegate.selectAllEnabled && 261 | value.text.isNotEmpty && 262 | !(value.selection.start == 0 && 263 | value.selection.end == value.text.length); 264 | } 265 | 266 | @override 267 | Widget buildToolbar( 268 | BuildContext context, 269 | Rect globalEditableRegion, 270 | double textLineHeight, 271 | Offset position, 272 | List endpoints, 273 | TextSelectionDelegate delegate, 274 | ClipboardStatusNotifier clipboardStatus) { 275 | return TextSelectionToolbarController( 276 | clipboardStatus: clipboardStatus, 277 | delegate: delegate, 278 | cut: canCut(delegate) ? () => handleCut(delegate) : null, 279 | copy: canCopy(delegate) 280 | ? () => handleCopy(delegate, clipboardStatus) 281 | : null, 282 | paste: canPaste(delegate) ? () => handlePaste(delegate) : null, 283 | selectAll: 284 | canSelectAll(delegate) ? () => handleSelectAll(delegate) : null, 285 | builder: (context) { 286 | return this.toolbar.buildToolbar( 287 | context, 288 | globalEditableRegion, 289 | textLineHeight, 290 | position, 291 | endpoints, 292 | delegate, 293 | clipboardStatus, 294 | ); 295 | }, 296 | ); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "ephemeral/Flutter-Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 51; 7 | objects = { 8 | 9 | /* Begin PBXAggregateTarget section */ 10 | 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { 11 | isa = PBXAggregateTarget; 12 | buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; 13 | buildPhases = ( 14 | 33CC111E2044C6BF0003C045 /* ShellScript */, 15 | ); 16 | dependencies = ( 17 | ); 18 | name = "Flutter Assemble"; 19 | productName = FLX; 20 | }; 21 | /* End PBXAggregateTarget section */ 22 | 23 | /* Begin PBXBuildFile section */ 24 | 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 25 | 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 26 | 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 27 | 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 28 | 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 29 | 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; }; 30 | 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 31 | D73912F022F37F9E000D13A0 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; }; 32 | D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 33 | /* End PBXBuildFile section */ 34 | 35 | /* Begin PBXContainerItemProxy section */ 36 | 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = 33CC10E52044A3C60003C045 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = 33CC111A2044C6BA0003C045; 41 | remoteInfo = FLX; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXCopyFilesBuildPhase section */ 46 | 33CC110E2044A8840003C045 /* Bundle Framework */ = { 47 | isa = PBXCopyFilesBuildPhase; 48 | buildActionMask = 2147483647; 49 | dstPath = ""; 50 | dstSubfolderSpec = 10; 51 | files = ( 52 | D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */, 53 | 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */, 54 | ); 55 | name = "Bundle Framework"; 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXCopyFilesBuildPhase section */ 59 | 60 | /* Begin PBXFileReference section */ 61 | 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 62 | 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 63 | 33CC10ED2044A3C60003C045 /* selection_controls_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "selection_controls_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 65 | 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 66 | 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 67 | 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; 68 | 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; 69 | 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 70 | 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 71 | 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; 72 | 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FlutterMacOS.framework; path = Flutter/ephemeral/FlutterMacOS.framework; sourceTree = SOURCE_ROOT; }; 73 | 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 74 | 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 75 | 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; 76 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 77 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; 78 | D73912EF22F37F9E000D13A0 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/ephemeral/App.framework; sourceTree = SOURCE_ROOT; }; 79 | /* End PBXFileReference section */ 80 | 81 | /* Begin PBXFrameworksBuildPhase section */ 82 | 33CC10EA2044A3C60003C045 /* Frameworks */ = { 83 | isa = PBXFrameworksBuildPhase; 84 | buildActionMask = 2147483647; 85 | files = ( 86 | D73912F022F37F9E000D13A0 /* App.framework in Frameworks */, 87 | 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */, 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | /* End PBXFrameworksBuildPhase section */ 92 | 93 | /* Begin PBXGroup section */ 94 | 33BA886A226E78AF003329D5 /* Configs */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 33E5194F232828860026EE4D /* AppInfo.xcconfig */, 98 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 99 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 100 | 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, 101 | ); 102 | path = Configs; 103 | sourceTree = ""; 104 | }; 105 | 33CC10E42044A3C60003C045 = { 106 | isa = PBXGroup; 107 | children = ( 108 | 33FAB671232836740065AC1E /* Runner */, 109 | 33CEB47122A05771004F2AC0 /* Flutter */, 110 | 33CC10EE2044A3C60003C045 /* Products */, 111 | D73912EC22F37F3D000D13A0 /* Frameworks */, 112 | ); 113 | sourceTree = ""; 114 | }; 115 | 33CC10EE2044A3C60003C045 /* Products */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 33CC10ED2044A3C60003C045 /* selection_controls_example.app */, 119 | ); 120 | name = Products; 121 | sourceTree = ""; 122 | }; 123 | 33CC11242044D66E0003C045 /* Resources */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 33CC10F22044A3C60003C045 /* Assets.xcassets */, 127 | 33CC10F42044A3C60003C045 /* MainMenu.xib */, 128 | 33CC10F72044A3C60003C045 /* Info.plist */, 129 | ); 130 | name = Resources; 131 | path = ..; 132 | sourceTree = ""; 133 | }; 134 | 33CEB47122A05771004F2AC0 /* Flutter */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 138 | 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 139 | 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 140 | 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, 141 | D73912EF22F37F9E000D13A0 /* App.framework */, 142 | 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */, 143 | ); 144 | path = Flutter; 145 | sourceTree = ""; 146 | }; 147 | 33FAB671232836740065AC1E /* Runner */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 151 | 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 152 | 33E51913231747F40026EE4D /* DebugProfile.entitlements */, 153 | 33E51914231749380026EE4D /* Release.entitlements */, 154 | 33CC11242044D66E0003C045 /* Resources */, 155 | 33BA886A226E78AF003329D5 /* Configs */, 156 | ); 157 | path = Runner; 158 | sourceTree = ""; 159 | }; 160 | D73912EC22F37F3D000D13A0 /* Frameworks */ = { 161 | isa = PBXGroup; 162 | children = ( 163 | ); 164 | name = Frameworks; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXGroup section */ 168 | 169 | /* Begin PBXNativeTarget section */ 170 | 33CC10EC2044A3C60003C045 /* Runner */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; 173 | buildPhases = ( 174 | 33CC10E92044A3C60003C045 /* Sources */, 175 | 33CC10EA2044A3C60003C045 /* Frameworks */, 176 | 33CC10EB2044A3C60003C045 /* Resources */, 177 | 33CC110E2044A8840003C045 /* Bundle Framework */, 178 | 3399D490228B24CF009A79C7 /* ShellScript */, 179 | ); 180 | buildRules = ( 181 | ); 182 | dependencies = ( 183 | 33CC11202044C79F0003C045 /* PBXTargetDependency */, 184 | ); 185 | name = Runner; 186 | productName = Runner; 187 | productReference = 33CC10ED2044A3C60003C045 /* selection_controls_example.app */; 188 | productType = "com.apple.product-type.application"; 189 | }; 190 | /* End PBXNativeTarget section */ 191 | 192 | /* Begin PBXProject section */ 193 | 33CC10E52044A3C60003C045 /* Project object */ = { 194 | isa = PBXProject; 195 | attributes = { 196 | LastSwiftUpdateCheck = 0920; 197 | LastUpgradeCheck = 0930; 198 | ORGANIZATIONNAME = "The Flutter Authors"; 199 | TargetAttributes = { 200 | 33CC10EC2044A3C60003C045 = { 201 | CreatedOnToolsVersion = 9.2; 202 | LastSwiftMigration = 1100; 203 | ProvisioningStyle = Automatic; 204 | SystemCapabilities = { 205 | com.apple.Sandbox = { 206 | enabled = 1; 207 | }; 208 | }; 209 | }; 210 | 33CC111A2044C6BA0003C045 = { 211 | CreatedOnToolsVersion = 9.2; 212 | ProvisioningStyle = Manual; 213 | }; 214 | }; 215 | }; 216 | buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; 217 | compatibilityVersion = "Xcode 8.0"; 218 | developmentRegion = en; 219 | hasScannedForEncodings = 0; 220 | knownRegions = ( 221 | en, 222 | Base, 223 | ); 224 | mainGroup = 33CC10E42044A3C60003C045; 225 | productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; 226 | projectDirPath = ""; 227 | projectRoot = ""; 228 | targets = ( 229 | 33CC10EC2044A3C60003C045 /* Runner */, 230 | 33CC111A2044C6BA0003C045 /* Flutter Assemble */, 231 | ); 232 | }; 233 | /* End PBXProject section */ 234 | 235 | /* Begin PBXResourcesBuildPhase section */ 236 | 33CC10EB2044A3C60003C045 /* Resources */ = { 237 | isa = PBXResourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, 241 | 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | }; 245 | /* End PBXResourcesBuildPhase section */ 246 | 247 | /* Begin PBXShellScriptBuildPhase section */ 248 | 3399D490228B24CF009A79C7 /* ShellScript */ = { 249 | isa = PBXShellScriptBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | ); 253 | inputFileListPaths = ( 254 | ); 255 | inputPaths = ( 256 | ); 257 | outputFileListPaths = ( 258 | ); 259 | outputPaths = ( 260 | ); 261 | runOnlyForDeploymentPostprocessing = 0; 262 | shellPath = /bin/sh; 263 | shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n"; 264 | }; 265 | 33CC111E2044C6BF0003C045 /* ShellScript */ = { 266 | isa = PBXShellScriptBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | inputFileListPaths = ( 271 | Flutter/ephemeral/FlutterInputs.xcfilelist, 272 | ); 273 | inputPaths = ( 274 | Flutter/ephemeral/tripwire, 275 | ); 276 | outputFileListPaths = ( 277 | Flutter/ephemeral/FlutterOutputs.xcfilelist, 278 | ); 279 | outputPaths = ( 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | shellPath = /bin/sh; 283 | shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; 284 | }; 285 | /* End PBXShellScriptBuildPhase section */ 286 | 287 | /* Begin PBXSourcesBuildPhase section */ 288 | 33CC10E92044A3C60003C045 /* Sources */ = { 289 | isa = PBXSourcesBuildPhase; 290 | buildActionMask = 2147483647; 291 | files = ( 292 | 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, 293 | 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, 294 | 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, 295 | ); 296 | runOnlyForDeploymentPostprocessing = 0; 297 | }; 298 | /* End PBXSourcesBuildPhase section */ 299 | 300 | /* Begin PBXTargetDependency section */ 301 | 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { 302 | isa = PBXTargetDependency; 303 | target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; 304 | targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; 305 | }; 306 | /* End PBXTargetDependency section */ 307 | 308 | /* Begin PBXVariantGroup section */ 309 | 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { 310 | isa = PBXVariantGroup; 311 | children = ( 312 | 33CC10F52044A3C60003C045 /* Base */, 313 | ); 314 | name = MainMenu.xib; 315 | path = Runner; 316 | sourceTree = ""; 317 | }; 318 | /* End PBXVariantGroup section */ 319 | 320 | /* Begin XCBuildConfiguration section */ 321 | 338D0CE9231458BD00FA5F75 /* Profile */ = { 322 | isa = XCBuildConfiguration; 323 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 324 | buildSettings = { 325 | ALWAYS_SEARCH_USER_PATHS = NO; 326 | CLANG_ANALYZER_NONNULL = YES; 327 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 328 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 329 | CLANG_CXX_LIBRARY = "libc++"; 330 | CLANG_ENABLE_MODULES = YES; 331 | CLANG_ENABLE_OBJC_ARC = YES; 332 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 333 | CLANG_WARN_BOOL_CONVERSION = YES; 334 | CLANG_WARN_CONSTANT_CONVERSION = YES; 335 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 338 | CLANG_WARN_EMPTY_BODY = YES; 339 | CLANG_WARN_ENUM_CONVERSION = YES; 340 | CLANG_WARN_INFINITE_RECURSION = YES; 341 | CLANG_WARN_INT_CONVERSION = YES; 342 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 345 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 346 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 347 | CODE_SIGN_IDENTITY = "-"; 348 | COPY_PHASE_STRIP = NO; 349 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 350 | ENABLE_NS_ASSERTIONS = NO; 351 | ENABLE_STRICT_OBJC_MSGSEND = YES; 352 | GCC_C_LANGUAGE_STANDARD = gnu11; 353 | GCC_NO_COMMON_BLOCKS = YES; 354 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 355 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 356 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 357 | GCC_WARN_UNUSED_FUNCTION = YES; 358 | GCC_WARN_UNUSED_VARIABLE = YES; 359 | MACOSX_DEPLOYMENT_TARGET = 10.11; 360 | MTL_ENABLE_DEBUG_INFO = NO; 361 | SDKROOT = macosx; 362 | SWIFT_COMPILATION_MODE = wholemodule; 363 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 364 | }; 365 | name = Profile; 366 | }; 367 | 338D0CEA231458BD00FA5F75 /* Profile */ = { 368 | isa = XCBuildConfiguration; 369 | baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; 370 | buildSettings = { 371 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 372 | CLANG_ENABLE_MODULES = YES; 373 | CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; 374 | CODE_SIGN_STYLE = Automatic; 375 | COMBINE_HIDPI_IMAGES = YES; 376 | FRAMEWORK_SEARCH_PATHS = ( 377 | "$(inherited)", 378 | "$(PROJECT_DIR)/Flutter/ephemeral", 379 | ); 380 | INFOPLIST_FILE = Runner/Info.plist; 381 | LD_RUNPATH_SEARCH_PATHS = ( 382 | "$(inherited)", 383 | "@executable_path/../Frameworks", 384 | ); 385 | PROVISIONING_PROFILE_SPECIFIER = ""; 386 | SWIFT_VERSION = 5.0; 387 | }; 388 | name = Profile; 389 | }; 390 | 338D0CEB231458BD00FA5F75 /* Profile */ = { 391 | isa = XCBuildConfiguration; 392 | buildSettings = { 393 | CODE_SIGN_STYLE = Manual; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | }; 396 | name = Profile; 397 | }; 398 | 33CC10F92044A3C60003C045 /* Debug */ = { 399 | isa = XCBuildConfiguration; 400 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 401 | buildSettings = { 402 | ALWAYS_SEARCH_USER_PATHS = NO; 403 | CLANG_ANALYZER_NONNULL = YES; 404 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 405 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 406 | CLANG_CXX_LIBRARY = "libc++"; 407 | CLANG_ENABLE_MODULES = YES; 408 | CLANG_ENABLE_OBJC_ARC = YES; 409 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 410 | CLANG_WARN_BOOL_CONVERSION = YES; 411 | CLANG_WARN_CONSTANT_CONVERSION = YES; 412 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 413 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 414 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 415 | CLANG_WARN_EMPTY_BODY = YES; 416 | CLANG_WARN_ENUM_CONVERSION = YES; 417 | CLANG_WARN_INFINITE_RECURSION = YES; 418 | CLANG_WARN_INT_CONVERSION = YES; 419 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 420 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 421 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 422 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 423 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 424 | CODE_SIGN_IDENTITY = "-"; 425 | COPY_PHASE_STRIP = NO; 426 | DEBUG_INFORMATION_FORMAT = dwarf; 427 | ENABLE_STRICT_OBJC_MSGSEND = YES; 428 | ENABLE_TESTABILITY = YES; 429 | GCC_C_LANGUAGE_STANDARD = gnu11; 430 | GCC_DYNAMIC_NO_PIC = NO; 431 | GCC_NO_COMMON_BLOCKS = YES; 432 | GCC_OPTIMIZATION_LEVEL = 0; 433 | GCC_PREPROCESSOR_DEFINITIONS = ( 434 | "DEBUG=1", 435 | "$(inherited)", 436 | ); 437 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 438 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 439 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 440 | GCC_WARN_UNUSED_FUNCTION = YES; 441 | GCC_WARN_UNUSED_VARIABLE = YES; 442 | MACOSX_DEPLOYMENT_TARGET = 10.11; 443 | MTL_ENABLE_DEBUG_INFO = YES; 444 | ONLY_ACTIVE_ARCH = YES; 445 | SDKROOT = macosx; 446 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 447 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 448 | }; 449 | name = Debug; 450 | }; 451 | 33CC10FA2044A3C60003C045 /* Release */ = { 452 | isa = XCBuildConfiguration; 453 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 454 | buildSettings = { 455 | ALWAYS_SEARCH_USER_PATHS = NO; 456 | CLANG_ANALYZER_NONNULL = YES; 457 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 458 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 459 | CLANG_CXX_LIBRARY = "libc++"; 460 | CLANG_ENABLE_MODULES = YES; 461 | CLANG_ENABLE_OBJC_ARC = YES; 462 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 463 | CLANG_WARN_BOOL_CONVERSION = YES; 464 | CLANG_WARN_CONSTANT_CONVERSION = YES; 465 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 466 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 467 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 468 | CLANG_WARN_EMPTY_BODY = YES; 469 | CLANG_WARN_ENUM_CONVERSION = YES; 470 | CLANG_WARN_INFINITE_RECURSION = YES; 471 | CLANG_WARN_INT_CONVERSION = YES; 472 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 473 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 474 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 475 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 476 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 477 | CODE_SIGN_IDENTITY = "-"; 478 | COPY_PHASE_STRIP = NO; 479 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 480 | ENABLE_NS_ASSERTIONS = NO; 481 | ENABLE_STRICT_OBJC_MSGSEND = YES; 482 | GCC_C_LANGUAGE_STANDARD = gnu11; 483 | GCC_NO_COMMON_BLOCKS = YES; 484 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 485 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 486 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 487 | GCC_WARN_UNUSED_FUNCTION = YES; 488 | GCC_WARN_UNUSED_VARIABLE = YES; 489 | MACOSX_DEPLOYMENT_TARGET = 10.11; 490 | MTL_ENABLE_DEBUG_INFO = NO; 491 | SDKROOT = macosx; 492 | SWIFT_COMPILATION_MODE = wholemodule; 493 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 494 | }; 495 | name = Release; 496 | }; 497 | 33CC10FC2044A3C60003C045 /* Debug */ = { 498 | isa = XCBuildConfiguration; 499 | baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; 500 | buildSettings = { 501 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 502 | CLANG_ENABLE_MODULES = YES; 503 | CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; 504 | CODE_SIGN_STYLE = Automatic; 505 | COMBINE_HIDPI_IMAGES = YES; 506 | FRAMEWORK_SEARCH_PATHS = ( 507 | "$(inherited)", 508 | "$(PROJECT_DIR)/Flutter/ephemeral", 509 | ); 510 | INFOPLIST_FILE = Runner/Info.plist; 511 | LD_RUNPATH_SEARCH_PATHS = ( 512 | "$(inherited)", 513 | "@executable_path/../Frameworks", 514 | ); 515 | PROVISIONING_PROFILE_SPECIFIER = ""; 516 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 517 | SWIFT_VERSION = 5.0; 518 | }; 519 | name = Debug; 520 | }; 521 | 33CC10FD2044A3C60003C045 /* Release */ = { 522 | isa = XCBuildConfiguration; 523 | baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; 524 | buildSettings = { 525 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 526 | CLANG_ENABLE_MODULES = YES; 527 | CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; 528 | CODE_SIGN_STYLE = Automatic; 529 | COMBINE_HIDPI_IMAGES = YES; 530 | FRAMEWORK_SEARCH_PATHS = ( 531 | "$(inherited)", 532 | "$(PROJECT_DIR)/Flutter/ephemeral", 533 | ); 534 | INFOPLIST_FILE = Runner/Info.plist; 535 | LD_RUNPATH_SEARCH_PATHS = ( 536 | "$(inherited)", 537 | "@executable_path/../Frameworks", 538 | ); 539 | PROVISIONING_PROFILE_SPECIFIER = ""; 540 | SWIFT_VERSION = 5.0; 541 | }; 542 | name = Release; 543 | }; 544 | 33CC111C2044C6BA0003C045 /* Debug */ = { 545 | isa = XCBuildConfiguration; 546 | buildSettings = { 547 | CODE_SIGN_STYLE = Manual; 548 | PRODUCT_NAME = "$(TARGET_NAME)"; 549 | }; 550 | name = Debug; 551 | }; 552 | 33CC111D2044C6BA0003C045 /* Release */ = { 553 | isa = XCBuildConfiguration; 554 | buildSettings = { 555 | CODE_SIGN_STYLE = Automatic; 556 | PRODUCT_NAME = "$(TARGET_NAME)"; 557 | }; 558 | name = Release; 559 | }; 560 | /* End XCBuildConfiguration section */ 561 | 562 | /* Begin XCConfigurationList section */ 563 | 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { 564 | isa = XCConfigurationList; 565 | buildConfigurations = ( 566 | 33CC10F92044A3C60003C045 /* Debug */, 567 | 33CC10FA2044A3C60003C045 /* Release */, 568 | 338D0CE9231458BD00FA5F75 /* Profile */, 569 | ); 570 | defaultConfigurationIsVisible = 0; 571 | defaultConfigurationName = Release; 572 | }; 573 | 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { 574 | isa = XCConfigurationList; 575 | buildConfigurations = ( 576 | 33CC10FC2044A3C60003C045 /* Debug */, 577 | 33CC10FD2044A3C60003C045 /* Release */, 578 | 338D0CEA231458BD00FA5F75 /* Profile */, 579 | ); 580 | defaultConfigurationIsVisible = 0; 581 | defaultConfigurationName = Release; 582 | }; 583 | 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { 584 | isa = XCConfigurationList; 585 | buildConfigurations = ( 586 | 33CC111C2044C6BA0003C045 /* Debug */, 587 | 33CC111D2044C6BA0003C045 /* Release */, 588 | 338D0CEB231458BD00FA5F75 /* Profile */, 589 | ); 590 | defaultConfigurationIsVisible = 0; 591 | defaultConfigurationName = Release; 592 | }; 593 | /* End XCConfigurationList section */ 594 | }; 595 | rootObject = 33CC10E52044A3C60003C045 /* Project object */; 596 | } 597 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 49 | 50 | 51 | 52 | 53 | 54 | 64 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 83 | 85 | 91 | 92 | 93 | 94 | 96 | 97 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = selection_controls_example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.selectionControlsExample 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2020 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.5.0-nullsafety" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0-nullsafety" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0-nullsafety.2" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0-nullsafety" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0-nullsafety" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0-nullsafety.2" 46 | cupertino_icons: 47 | dependency: "direct main" 48 | description: 49 | name: cupertino_icons 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.0.0" 53 | fake_async: 54 | dependency: transitive 55 | description: 56 | name: fake_async 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.1.0-nullsafety" 60 | flutter: 61 | dependency: "direct main" 62 | description: flutter 63 | source: sdk 64 | version: "0.0.0" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | matcher: 71 | dependency: transitive 72 | description: 73 | name: matcher 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "0.12.10-nullsafety" 77 | meta: 78 | dependency: "direct main" 79 | description: 80 | name: meta 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.3.0-nullsafety.2" 84 | path: 85 | dependency: transitive 86 | description: 87 | name: path 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.8.0-nullsafety" 91 | sky_engine: 92 | dependency: transitive 93 | description: flutter 94 | source: sdk 95 | version: "0.0.99" 96 | source_span: 97 | dependency: transitive 98 | description: 99 | name: source_span 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.8.0-nullsafety.2" 103 | stack_trace: 104 | dependency: transitive 105 | description: 106 | name: stack_trace 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.10.0-nullsafety" 110 | stream_channel: 111 | dependency: transitive 112 | description: 113 | name: stream_channel 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "2.1.0-nullsafety" 117 | string_scanner: 118 | dependency: transitive 119 | description: 120 | name: string_scanner 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.1.0-nullsafety" 124 | term_glyph: 125 | dependency: transitive 126 | description: 127 | name: term_glyph 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.2.0-nullsafety" 131 | test_api: 132 | dependency: transitive 133 | description: 134 | name: test_api 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "0.2.19-nullsafety" 138 | typed_data: 139 | dependency: transitive 140 | description: 141 | name: typed_data 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "1.3.0-nullsafety.2" 145 | vector_math: 146 | dependency: transitive 147 | description: 148 | name: vector_math 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "2.1.0-nullsafety.2" 152 | sdks: 153 | dart: ">=2.10.0-0.0.dev <2.10.0" 154 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: selection_controls_example 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | version: 1.0.0+1 19 | 20 | environment: 21 | sdk: ">=2.7.0 <3.0.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^1.0.0 31 | meta: 32 | 33 | dev_dependencies: 34 | flutter_test: 35 | sdk: flutter 36 | 37 | # For information on the generic Dart part of this file, see the 38 | # following page: https://dart.dev/tools/pub/pubspec 39 | 40 | # The following section is specific to Flutter. 41 | flutter: 42 | 43 | # The following line ensures that the Material Icons font is 44 | # included with your application, so that you can use the icons in 45 | # the material Icons class. 46 | uses-material-design: true 47 | 48 | # To add assets to your application, add an assets section, like this: 49 | # assets: 50 | # - images/a_dot_burr.jpeg 51 | # - images/a_dot_ham.jpeg 52 | 53 | # An image asset can refer to one or more resolution-specific "variants", see 54 | # https://flutter.dev/assets-and-images/#resolution-aware. 55 | 56 | # For details regarding adding assets from package dependencies, see 57 | # https://flutter.dev/assets-and-images/#from-packages 58 | 59 | # To add custom fonts to your application, add a fonts section here, 60 | # in this "flutter" section. Each entry in this list should have a 61 | # "family" key with the font family name, and a "fonts" key with a 62 | # list giving the asset and other descriptors for the font. For 63 | # example: 64 | # fonts: 65 | # - family: Schyler 66 | # fonts: 67 | # - asset: fonts/Schyler-Regular.ttf 68 | # - asset: fonts/Schyler-Italic.ttf 69 | # style: italic 70 | # - family: Trajan Pro 71 | # fonts: 72 | # - asset: fonts/TrajanPro.ttf 73 | # - asset: fonts/TrajanPro_Bold.ttf 74 | # weight: 700 75 | # 76 | # For details regarding fonts from package dependencies, 77 | # see https://flutter.dev/custom-fonts/#from-packages 78 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:selection_controls_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesblasco/selection_controls_example/0cdbea76534acdf4bff8553202283ac25d76ecdd/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | selection_controls_example 18 | 19 | 20 | 21 | 24 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "selection_controls_example", 3 | "short_name": "selection_controls_example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | --------------------------------------------------------------------------------