├── .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 | 
67 |
68 | Submenus
69 |
70 | 
71 |
72 | Context menus usable in text selection or any widget
73 |
74 | 
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 |
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 |
--------------------------------------------------------------------------------