diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml
index 0f7d96719..41ead442b 100644
--- a/.github/dependabot.yaml
+++ b/.github/dependabot.yaml
@@ -141,6 +141,10 @@ updates:
directory: "simplistic_calculator/"
schedule:
interval: "daily"
+ - package-ecosystem: "pub"
+ directory: "simplistic_editor/"
+ schedule:
+ interval: "daily"
- package-ecosystem: "pub"
directory: "testing_app/"
schedule:
diff --git a/simplistic_editor/.gitignore b/simplistic_editor/.gitignore
new file mode 100644
index 000000000..a8e938c08
--- /dev/null
+++ b/simplistic_editor/.gitignore
@@ -0,0 +1,47 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Web related
+lib/generated_plugin_registrant.dart
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
diff --git a/simplistic_editor/.metadata b/simplistic_editor/.metadata
new file mode 100644
index 000000000..d90406e39
--- /dev/null
+++ b/simplistic_editor/.metadata
@@ -0,0 +1,36 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled.
+
+version:
+ revision: 14a9b4a7512c9bc40d0417672a87276bd48df2ad
+ channel: master
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 14a9b4a7512c9bc40d0417672a87276bd48df2ad
+ base_revision: 14a9b4a7512c9bc40d0417672a87276bd48df2ad
+ - platform: linux
+ create_revision: 14a9b4a7512c9bc40d0417672a87276bd48df2ad
+ base_revision: 14a9b4a7512c9bc40d0417672a87276bd48df2ad
+ - platform: macos
+ create_revision: 14a9b4a7512c9bc40d0417672a87276bd48df2ad
+ base_revision: 14a9b4a7512c9bc40d0417672a87276bd48df2ad
+ - platform: windows
+ create_revision: 14a9b4a7512c9bc40d0417672a87276bd48df2ad
+ base_revision: 14a9b4a7512c9bc40d0417672a87276bd48df2ad
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/simplistic_editor/README.md b/simplistic_editor/README.md
new file mode 100644
index 000000000..ad72dc7e7
--- /dev/null
+++ b/simplistic_editor/README.md
@@ -0,0 +1,96 @@
+# Simplistic Editor
+This sample text editor showcases the use of TextEditingDeltas and a DeltaTextInputClient to expand
+and contract styled ranges of text. For more information visit https://api.flutter.dev/flutter/services/TextEditingDelta-class.html.
+
+https://user-images.githubusercontent.com/948037/166981868-0529e328-18e7-48de-9245-524b91c63c0c.mov
+
+# Structure
+## Visualization Layer
+The layer that showcases the `TextEditingDelta` history in a ListView. These widgets are all unique to this sample
+and are unlikely to contain much logic the typical user will need.
+
+
+
+### `TextEditingDeltaHistoryManager`
+An inherited widget that handles the state of the text editing delta history that sits below the input field
+in a `ListView`. This widget contains the list of `TextEditingDelta`s, and the callback needed to update the list
+when a delta is received from the platform, as well as when the framework reports a delta. Deltas
+can be reported by the framework when the selection is changed as a result of a gesture such as tapping or dragging,
+when the `TextEditingValue` is updated as a result of a copy/paste, and when the `TextEditingValue` is updated by an `Intent` -> `Action`.
+
+This widget is primarily used to wrap the `BasicTextField`, so the `BasicTextInputClient`, which is lower in the tree, can
+update the history of `TextEditingDelta`s as they are reported by the platform and framework.
+
+### `TextEditingDeltaView`
+A widget that represents the content of a `TextEditingDelta`. A list of `TextEditingDeltaView`s sits below
+the input field showcasing a history of `TextEditingDelta`s that have occurred on that input field. A
+`TextEditingDeltaView` varies in color depending on the type of `TextEditingDelta`. `TextEditingDeltaInsertion`s
+are green, `TextEditingDeltaDeletion`s are red, `TextEditingDeltaReplacement`s are yellow, and `TextEditingDeltaNonTextUpdate`s are blue.
+
+## Replacements Layer
+The layer that handles the styling of the input field, including expanding and contracting the styled ranges based
+on the deltas that are received from the platform, and the handling of the state of the styling toggle button toolbar.
+This layer contains a mixture of logic unique to this sample and helpful for developers expecting to consume the
+`TextEditingDelta` APIs.
+
+
+
+### `ToggleButtonsStateManager`
+An inherited widget that handles the state of the styling toggle button toolbar that sits on the top
+of the input field. This toolbar includes three buttons: Bold, Italic, and Underline. This widget contains
+the state of the `ToggleButtons`, and the callbacks needed to update the state when the selection has changed
+or when the toggle buttons have been pressed.
+
+This widget wraps the `ToggleButtons` so it may access the state of the toggle buttons and update that state
+when they have been pressed. It also wraps the `BasicTextField`, so that the `BasicTextInputClient`, which is lower
+in the tree may access the callback necessary to update the toggle button state when the selection has changed.
+
+### `TextEditingInlineSpanReplacement`
+A data structure that represents a replacement, with a range, and a generator that produces
+the desired `InlineSpan`. The generator should return a `TextSpan` with the desired styling, and the
+range should be the target range for that styling in the current `TextEditingValue`. This structure also
+contains an expand property which dictates if the replacement should continue to expand from the back edge.
+For example say we have "Hello |world|", where "world" is covered by a replacement that bolds the text. If
+the expand property is true, typing text at the back edge of "world|" will expand the range and make any text
+typed also bold. If it is false then the text typed would not be bold. Additionally, this structure contains
+methods to update the replacement for each subclass of `TextEditingDelta` and a method to remove a section of
+the replacement range.
+
+### `ReplacementTextEditingController`
+A `TextEditingController` that manages a list of `TextEditingInlineSpanReplacement`s, that insert custom `InlineSpan`s
+in place of matched `TextRange`s. The controller syncs the replacement ranges based on the type of `TextEditingDelta`
+it receives from the `BasicTextInputClient`, managing any overlapping ranges accordingly.
+
+This controller also contains convenience methods used by the styling toggle buttons toolbar to un-style
+certain ranges, disable the expand property of a replacement, and get the common replacements at a selection
+to determine the current toggle button state.
+
+## Text Input Layer
+The layer that defines the appearance of a text input field, handles the text input received from
+the platform, and mutations done by the framework through gestures and keyboard shortcuts. These classes begin to
+demonstrate the types of logic developers may need if they wish to interact with `TextEditingDelta`s.
+
+
+
+### `BasicTextField`
+A basic text field that defines the appearance, and the selection gestures of a basic text input client.
+These gestures call on methods in `RenderEditable` to mutate the `TextEditingValue` through the `TextSelectionDelegate`,
+which, in this case, is a the `BasicTextInputClient`.
+
+This widget wraps the `BasicTextInputClient` to define its appearance such as borders, and selection overlay
+appearance based on the platform.
+
+### `BasicTextInputClient`
+A `DeltaTextInputClient`, a `TextInputClient` that receives `TextEditingDelta`s from the platform instead of the
+entire `TextEditingValue`. It is responsible for sending/receiving information from the framework to the platforms text input
+plugin, and vice-versa. A list of `TextEditingDelta`s is received from the platform and can be handled through
+the method `updateEditingValueWithDeltas`. When the framework makes a change to the `TextEditingValue`, the updated value
+is sent through the `TextInputConnection.setEditingState` method.
+
+A `TextSelectionDelegate` that handles the manipulation of selection through toolbar or shortcut keys.
+
+An `Actions` widget is used to handle certain hardware keyboard shortcuts, such as the backspace key to delete text, and
+the left and right arrows keys to move the selection.
+
+The `RenderObject`, `RenderEditable` (`_Editable`), is used at the leaf node of the `BasicTextInputClient` to render the `TextSpan`s given
+by the `ReplacementTextEditingController`.
diff --git a/simplistic_editor/analysis_options.yaml b/simplistic_editor/analysis_options.yaml
new file mode 100644
index 000000000..5e2133eb6
--- /dev/null
+++ b/simplistic_editor/analysis_options.yaml
@@ -0,0 +1 @@
+include: ../analysis_options.yaml
diff --git a/simplistic_editor/android/.gitignore b/simplistic_editor/android/.gitignore
new file mode 100644
index 000000000..6f568019d
--- /dev/null
+++ b/simplistic_editor/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/simplistic_editor/android/app/build.gradle b/simplistic_editor/android/app/build.gradle
new file mode 100644
index 000000000..4420a33cd
--- /dev/null
+++ b/simplistic_editor/android/app/build.gradle
@@ -0,0 +1,71 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+ throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+ compileSdkVersion flutter.compileSdkVersion
+ ndkVersion flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "com.example.simplistic_editor"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
+ minSdkVersion flutter.minSdkVersion
+ targetSdkVersion flutter.targetSdkVersion
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+}
diff --git a/simplistic_editor/android/app/src/debug/AndroidManifest.xml b/simplistic_editor/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 000000000..5d254a145
--- /dev/null
+++ b/simplistic_editor/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/simplistic_editor/android/app/src/main/AndroidManifest.xml b/simplistic_editor/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..931d4ed34
--- /dev/null
+++ b/simplistic_editor/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/simplistic_editor/android/app/src/main/kotlin/com/example/simplistic_editor/MainActivity.kt b/simplistic_editor/android/app/src/main/kotlin/com/example/simplistic_editor/MainActivity.kt
new file mode 100644
index 000000000..1f33959a1
--- /dev/null
+++ b/simplistic_editor/android/app/src/main/kotlin/com/example/simplistic_editor/MainActivity.kt
@@ -0,0 +1,6 @@
+package com.example.simplistic_editor
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity() {
+}
diff --git a/simplistic_editor/android/app/src/main/res/drawable-v21/launch_background.xml b/simplistic_editor/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 000000000..f74085f3f
--- /dev/null
+++ b/simplistic_editor/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/simplistic_editor/android/app/src/main/res/drawable/launch_background.xml b/simplistic_editor/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 000000000..304732f88
--- /dev/null
+++ b/simplistic_editor/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/simplistic_editor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/simplistic_editor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..db77bb4b7
Binary files /dev/null and b/simplistic_editor/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/simplistic_editor/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/simplistic_editor/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..17987b79b
Binary files /dev/null and b/simplistic_editor/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/simplistic_editor/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/simplistic_editor/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..09d439148
Binary files /dev/null and b/simplistic_editor/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/simplistic_editor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/simplistic_editor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..d5f1c8d34
Binary files /dev/null and b/simplistic_editor/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/simplistic_editor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/simplistic_editor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..4d6372eeb
Binary files /dev/null and b/simplistic_editor/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/simplistic_editor/android/app/src/main/res/values-night/styles.xml b/simplistic_editor/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 000000000..06952be74
--- /dev/null
+++ b/simplistic_editor/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/simplistic_editor/android/app/src/main/res/values/styles.xml b/simplistic_editor/android/app/src/main/res/values/styles.xml
new file mode 100644
index 000000000..cb1ef8805
--- /dev/null
+++ b/simplistic_editor/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/simplistic_editor/android/app/src/profile/AndroidManifest.xml b/simplistic_editor/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 000000000..5d254a145
--- /dev/null
+++ b/simplistic_editor/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,8 @@
+
+
+
+
diff --git a/simplistic_editor/android/build.gradle b/simplistic_editor/android/build.gradle
new file mode 100644
index 000000000..83ae22004
--- /dev/null
+++ b/simplistic_editor/android/build.gradle
@@ -0,0 +1,31 @@
+buildscript {
+ ext.kotlin_version = '1.6.10'
+ repositories {
+ google()
+ mavenCentral()
+ }
+
+ dependencies {
+ classpath 'com.android.tools.build:gradle:7.1.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/simplistic_editor/android/gradle.properties b/simplistic_editor/android/gradle.properties
new file mode 100644
index 000000000..94adc3a3f
--- /dev/null
+++ b/simplistic_editor/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/simplistic_editor/android/gradle/wrapper/gradle-wrapper.properties b/simplistic_editor/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..cc5527d78
--- /dev/null
+++ b/simplistic_editor/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
diff --git a/simplistic_editor/android/settings.gradle b/simplistic_editor/android/settings.gradle
new file mode 100644
index 000000000..44e62bcf0
--- /dev/null
+++ b/simplistic_editor/android/settings.gradle
@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
diff --git a/simplistic_editor/ios/.gitignore b/simplistic_editor/ios/.gitignore
new file mode 100644
index 000000000..7a7f9873a
--- /dev/null
+++ b/simplistic_editor/ios/.gitignore
@@ -0,0 +1,34 @@
+**/dgph
+*.mode1v3
+*.mode2v3
+*.moved-aside
+*.pbxuser
+*.perspectivev3
+**/*sync/
+.sconsign.dblite
+.tags*
+**/.vagrant/
+**/DerivedData/
+Icon?
+**/Pods/
+**/.symlinks/
+profile
+xcuserdata
+**/.generated/
+Flutter/App.framework
+Flutter/Flutter.framework
+Flutter/Flutter.podspec
+Flutter/Generated.xcconfig
+Flutter/ephemeral/
+Flutter/app.flx
+Flutter/app.zip
+Flutter/flutter_assets/
+Flutter/flutter_export_environment.sh
+ServiceDefinitions.json
+Runner/GeneratedPluginRegistrant.*
+
+# Exceptions to above rules.
+!default.mode1v3
+!default.mode2v3
+!default.pbxuser
+!default.perspectivev3
diff --git a/simplistic_editor/ios/Flutter/AppFrameworkInfo.plist b/simplistic_editor/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 000000000..9625e105d
--- /dev/null
+++ b/simplistic_editor/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ en
+ CFBundleExecutable
+ App
+ CFBundleIdentifier
+ io.flutter.flutter.app
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ App
+ CFBundlePackageType
+ FMWK
+ CFBundleShortVersionString
+ 1.0
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ 1.0
+ MinimumOSVersion
+ 11.0
+
+
diff --git a/simplistic_editor/ios/Flutter/Debug.xcconfig b/simplistic_editor/ios/Flutter/Debug.xcconfig
new file mode 100644
index 000000000..592ceee85
--- /dev/null
+++ b/simplistic_editor/ios/Flutter/Debug.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/simplistic_editor/ios/Flutter/Release.xcconfig b/simplistic_editor/ios/Flutter/Release.xcconfig
new file mode 100644
index 000000000..592ceee85
--- /dev/null
+++ b/simplistic_editor/ios/Flutter/Release.xcconfig
@@ -0,0 +1 @@
+#include "Generated.xcconfig"
diff --git a/simplistic_editor/ios/Runner.xcodeproj/project.pbxproj b/simplistic_editor/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 000000000..27a743915
--- /dev/null
+++ b/simplistic_editor/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,484 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 50;
+ objects = {
+
+/* Begin PBXBuildFile section */
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
+ 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
+ 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
+ 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 97C146EB1CF9000F007C117D /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 9740EEB11CF90186004384FC /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 9740EEB31CF90195004384FC /* Generated.xcconfig */,
+ );
+ name = Flutter;
+ sourceTree = "";
+ };
+ 97C146E51CF9000F007C117D = {
+ isa = PBXGroup;
+ children = (
+ 9740EEB11CF90186004384FC /* Flutter */,
+ 97C146F01CF9000F007C117D /* Runner */,
+ 97C146EF1CF9000F007C117D /* Products */,
+ );
+ sourceTree = "";
+ };
+ 97C146EF1CF9000F007C117D /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146EE1CF9000F007C117D /* Runner.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 97C146F01CF9000F007C117D /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 97C146FA1CF9000F007C117D /* Main.storyboard */,
+ 97C146FD1CF9000F007C117D /* Assets.xcassets */,
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+ 97C147021CF9000F007C117D /* Info.plist */,
+ 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+ 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
+ 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 97C146ED1CF9000F007C117D /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ 9740EEB61CF901F6004384FC /* Run Script */,
+ 97C146EA1CF9000F007C117D /* Sources */,
+ 97C146EB1CF9000F007C117D /* Frameworks */,
+ 97C146EC1CF9000F007C117D /* Resources */,
+ 9705A1C41CF9048500538489 /* Embed Frameworks */,
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 97C146E61CF9000F007C117D /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastUpgradeCheck = 1300;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 97C146ED1CF9000F007C117D = {
+ CreatedOnToolsVersion = 7.3.1;
+ LastSwiftMigration = 1100;
+ };
+ };
+ };
+ buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 97C146E51CF9000F007C117D;
+ productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 97C146ED1CF9000F007C117D /* Runner */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 97C146EC1CF9000F007C117D /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+ 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+ 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Thin Binary";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+ };
+ 9740EEB61CF901F6004384FC /* Run Script */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputPaths = (
+ );
+ name = "Run Script";
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 97C146EA1CF9000F007C117D /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
+ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXVariantGroup section */
+ 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C146FB1CF9000F007C117D /* Base */,
+ );
+ name = Main.storyboard;
+ sourceTree = "";
+ };
+ 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 97C147001CF9000F007C117D /* Base */,
+ );
+ name = LaunchScreen.storyboard;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 249021D3217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Profile;
+ };
+ 249021D4217E4FDB00AE95B9 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = 4DZ44AM6DD;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.simplisticEditor;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Profile;
+ };
+ 97C147031CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = iphoneos;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Debug;
+ };
+ 97C147041CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_COMMA = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_STRICT_PROTOTYPES = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CLANG_WARN_UNREACHABLE_CODE = YES;
+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu99;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNDECLARED_SELECTOR = YES;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = iphoneos;
+ SUPPORTED_PLATFORMS = iphoneos;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ VALIDATE_PRODUCT = YES;
+ };
+ name = Release;
+ };
+ 97C147061CF9000F007C117D /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = 4DZ44AM6DD;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.simplisticEditor;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Debug;
+ };
+ 97C147071CF9000F007C117D /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
+ DEVELOPMENT_TEAM = 4DZ44AM6DD;
+ ENABLE_BITCODE = NO;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ );
+ PRODUCT_BUNDLE_IDENTIFIER = com.example.simplisticEditor;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+ SWIFT_VERSION = 5.0;
+ VERSIONING_SYSTEM = "apple-generic";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147031CF9000F007C117D /* Debug */,
+ 97C147041CF9000F007C117D /* Release */,
+ 249021D3217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 97C147061CF9000F007C117D /* Debug */,
+ 97C147071CF9000F007C117D /* Release */,
+ 249021D4217E4FDB00AE95B9 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/simplistic_editor/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/simplistic_editor/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..919434a62
--- /dev/null
+++ b/simplistic_editor/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/simplistic_editor/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/simplistic_editor/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/simplistic_editor/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/simplistic_editor/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/simplistic_editor/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000..f9b0d7c5e
--- /dev/null
+++ b/simplistic_editor/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/simplistic_editor/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/simplistic_editor/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 000000000..c87d15a33
--- /dev/null
+++ b/simplistic_editor/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/simplistic_editor/ios/Runner.xcworkspace/contents.xcworkspacedata b/simplistic_editor/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 000000000..1d526a16e
--- /dev/null
+++ b/simplistic_editor/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/simplistic_editor/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/simplistic_editor/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 000000000..18d981003
--- /dev/null
+++ b/simplistic_editor/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/simplistic_editor/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/simplistic_editor/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
new file mode 100644
index 000000000..f9b0d7c5e
--- /dev/null
+++ b/simplistic_editor/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
@@ -0,0 +1,8 @@
+
+
+
+
+ PreviewsEnabled
+
+
+
diff --git a/simplistic_editor/ios/Runner/AppDelegate.swift b/simplistic_editor/ios/Runner/AppDelegate.swift
new file mode 100644
index 000000000..70693e4a8
--- /dev/null
+++ b/simplistic_editor/ios/Runner/AppDelegate.swift
@@ -0,0 +1,13 @@
+import UIKit
+import Flutter
+
+@UIApplicationMain
+@objc class AppDelegate: FlutterAppDelegate {
+ override func application(
+ _ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
+ ) -> Bool {
+ GeneratedPluginRegistrant.register(with: self)
+ return super.application(application, didFinishLaunchingWithOptions: launchOptions)
+ }
+}
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 000000000..d36b1fab2
--- /dev/null
+++ b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+ "images" : [
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-20x20@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-29x29@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-40x40@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "60x60",
+ "idiom" : "iphone",
+ "filename" : "Icon-App-60x60@3x.png",
+ "scale" : "3x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "20x20",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-20x20@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "29x29",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-29x29@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "40x40",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-40x40@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@1x.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "76x76",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-76x76@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "83.5x83.5",
+ "idiom" : "ipad",
+ "filename" : "Icon-App-83.5x83.5@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "1024x1024",
+ "idiom" : "ios-marketing",
+ "filename" : "Icon-App-1024x1024@1x.png",
+ "scale" : "1x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 000000000..dc9ada472
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 000000000..28c6bf030
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 000000000..2ccbfd967
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 000000000..f091b6b0b
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 000000000..4cde12118
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 000000000..d0ef06e7e
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 000000000..dcdc2306c
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 000000000..2ccbfd967
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 000000000..c8f9ed8f5
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 000000000..a6d6b8609
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 000000000..a6d6b8609
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 000000000..75b2d164a
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 000000000..c4df70d39
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 000000000..6a84f41e1
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 000000000..d0e1f5853
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 000000000..0bedcf2fd
--- /dev/null
+++ b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 000000000..9da19eaca
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 000000000..9da19eaca
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 000000000..9da19eaca
Binary files /dev/null and b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ
diff --git a/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 000000000..89c2725b7
--- /dev/null
+++ b/simplistic_editor/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/simplistic_editor/ios/Runner/Base.lproj/LaunchScreen.storyboard b/simplistic_editor/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 000000000..f2e259c7c
--- /dev/null
+++ b/simplistic_editor/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/simplistic_editor/ios/Runner/Base.lproj/Main.storyboard b/simplistic_editor/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 000000000..f3c28516f
--- /dev/null
+++ b/simplistic_editor/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/simplistic_editor/ios/Runner/Info.plist b/simplistic_editor/ios/Runner/Info.plist
new file mode 100644
index 000000000..ae093d282
--- /dev/null
+++ b/simplistic_editor/ios/Runner/Info.plist
@@ -0,0 +1,51 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Simplistic Editor
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ simplistic_editor
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+
diff --git a/simplistic_editor/ios/Runner/Runner-Bridging-Header.h b/simplistic_editor/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 000000000..308a2a560
--- /dev/null
+++ b/simplistic_editor/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/simplistic_editor/lib/basic_text_field.dart b/simplistic_editor/lib/basic_text_field.dart
new file mode 100644
index 000000000..03819f757
--- /dev/null
+++ b/simplistic_editor/lib/basic_text_field.dart
@@ -0,0 +1,163 @@
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+
+import 'basic_text_input_client.dart';
+
+/// A basic text field. Defines the appearance of a basic text input client.
+class BasicTextField extends StatefulWidget {
+ const BasicTextField({
+ Key? key,
+ required this.controller,
+ required this.style,
+ required this.focusNode,
+ }) : super(key: key);
+
+ final TextEditingController controller;
+ final TextStyle style;
+ final FocusNode focusNode;
+
+ @override
+ State createState() => _BasicTextFieldState();
+}
+
+class _BasicTextFieldState extends State {
+ final GlobalKey textInputClientKey = GlobalKey();
+ BasicTextInputClientState? get _textInputClient => textInputClientKey.currentState;
+ RenderEditable get _renderEditable => _textInputClient!.renderEditable;
+
+ // For text selection gestures.
+ // The viewport offset pixels of the [RenderEditable] at the last drag start.
+ double _dragStartViewportOffset = 0.0;
+ late DragStartDetails _startDetails;
+
+ // For text selection.
+ TextSelectionControls? _textSelectionControls;
+ bool _showSelectionHandles = false;
+
+ bool _shouldShowSelectionHandles(SelectionChangedCause? cause) {
+ // When the text field is activated by something that doesn't trigger the
+ // selection overlay, we shouldn't show the handles either.
+ if (cause == SelectionChangedCause.keyboard) {
+ return false;
+ }
+
+ if (cause == SelectionChangedCause.longPress
+ || cause == SelectionChangedCause.scribble) {
+ return true;
+ }
+
+ if (widget.controller.text.isNotEmpty) {
+ return true;
+ }
+
+ return false;
+ }
+
+ void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
+ final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause);
+ if (willShowSelectionHandles != _showSelectionHandles) {
+ setState(() {
+ _showSelectionHandles = willShowSelectionHandles;
+ });
+ }
+ }
+
+ void _onDragUpdate(DragUpdateDetails details) {
+ final Offset startOffset = _renderEditable.maxLines == 1
+ ? Offset(_renderEditable.offset.pixels - _dragStartViewportOffset, 0.0)
+ : Offset(0.0, _renderEditable.offset.pixels - _dragStartViewportOffset);
+
+ _renderEditable.selectPositionAt(
+ from: _startDetails.globalPosition - startOffset,
+ to: details.globalPosition,
+ cause: SelectionChangedCause.drag,
+ );
+ }
+
+ void _onDragStart(DragStartDetails details) {
+ _startDetails = details;
+ _dragStartViewportOffset = _renderEditable.offset.pixels;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ switch (Theme.of(this.context).platform) {
+ case TargetPlatform.iOS:
+ _textSelectionControls = cupertinoTextSelectionControls;
+ break;
+ case TargetPlatform.macOS:
+ _textSelectionControls = cupertinoDesktopTextSelectionControls;
+ break;
+ case TargetPlatform.android:
+ case TargetPlatform.fuchsia:
+ _textSelectionControls = materialTextSelectionControls;
+ break;
+ case TargetPlatform.linux:
+ _textSelectionControls = desktopTextSelectionControls;
+ break;
+ case TargetPlatform.windows:
+ _textSelectionControls = desktopTextSelectionControls;
+ break;
+ }
+
+ return FocusTrapArea(
+ focusNode: widget.focusNode,
+ child: GestureDetector(
+ behavior: HitTestBehavior.translucent,
+ onPanStart: (dragStartDetails) => _onDragStart(dragStartDetails),
+ onPanUpdate: (dragUpdateDetails) => _onDragUpdate(dragUpdateDetails),
+ onTap: () {
+ _textInputClient!.requestKeyboard();
+ },
+ onTapDown: (tapDownDetails) {
+ _renderEditable.handleTapDown(tapDownDetails);
+ _renderEditable.selectPosition(cause: SelectionChangedCause.tap);
+ },
+ onLongPressMoveUpdate: (longPressMoveUpdateDetails) {
+ switch (Theme.of(this.context).platform) {
+ case TargetPlatform.iOS:
+ case TargetPlatform.macOS:
+ _renderEditable.selectPositionAt(
+ from: longPressMoveUpdateDetails.globalPosition,
+ cause: SelectionChangedCause.longPress,
+ );
+ break;
+ case TargetPlatform.android:
+ case TargetPlatform.fuchsia:
+ case TargetPlatform.linux:
+ case TargetPlatform.windows:
+ _renderEditable.selectWordsInRange(
+ from: longPressMoveUpdateDetails.globalPosition - longPressMoveUpdateDetails.offsetFromOrigin,
+ to: longPressMoveUpdateDetails.globalPosition,
+ cause: SelectionChangedCause.longPress,
+ );
+ break;
+ }
+ },
+ onLongPressEnd: (longPressEndDetails) => _textInputClient!.showToolbar(),
+ onHorizontalDragStart: (dragStartDetails) => _onDragStart(dragStartDetails),
+ onHorizontalDragUpdate: (dragUpdateDetails) => _onDragUpdate(dragUpdateDetails),
+ child: SizedBox(
+ height: double.infinity,
+ width: MediaQuery.of(context).size.width,
+ child: Container(
+ decoration: BoxDecoration(
+ border: Border.all(color: Colors.black),
+ borderRadius: const BorderRadius.all(Radius.circular(4.0)),
+ ),
+ child: BasicTextInputClient(
+ key: textInputClientKey,
+ controller: widget.controller,
+ style: widget.style,
+ focusNode: widget.focusNode,
+ selectionControls: _textSelectionControls,
+ onSelectionChanged: _handleSelectionChanged,
+ showSelectionHandles: _showSelectionHandles,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
\ No newline at end of file
diff --git a/simplistic_editor/lib/basic_text_input_client.dart b/simplistic_editor/lib/basic_text_input_client.dart
new file mode 100644
index 000000000..dbc0cfdd6
--- /dev/null
+++ b/simplistic_editor/lib/basic_text_input_client.dart
@@ -0,0 +1,903 @@
+import 'dart:math' as math;
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
+
+import 'replacements.dart';
+import 'text_editing_delta_history_manager.dart';
+import 'toggle_button_state_manager.dart';
+
+/// Signature for the callback that reports when the user changes the selection
+/// (including the cursor location).
+typedef SelectionChangedCallback = void Function(TextSelection selection, SelectionChangedCause? cause);
+
+/// A basic text input client. An implementation of [DeltaTextInputClient] meant to
+/// send/receive information from the framework to the platform's text input plugin
+/// and vice-versa.
+class BasicTextInputClient extends StatefulWidget {
+ const BasicTextInputClient({
+ Key? key,
+ required this.controller,
+ required this.style,
+ required this.focusNode,
+ this.selectionControls,
+ required this.onSelectionChanged,
+ required this.showSelectionHandles,
+ }) : super(key: key);
+
+ final TextEditingController controller;
+ final TextStyle style;
+ final FocusNode focusNode;
+ final TextSelectionControls? selectionControls;
+ final bool showSelectionHandles;
+ final SelectionChangedCallback onSelectionChanged;
+
+ @override
+ State createState() => BasicTextInputClientState();
+}
+
+class BasicTextInputClientState extends State
+ with TextSelectionDelegate implements DeltaTextInputClient {
+ final GlobalKey _textKey = GlobalKey();
+ late final ToggleButtonsStateManager toggleButtonStateManager;
+ late final TextEditingDeltaHistoryManager textEditingDeltaHistoryManager;
+ final ClipboardStatusNotifier? _clipboardStatus = kIsWeb ? null : ClipboardStatusNotifier();
+
+ @override
+ void initState() {
+ super.initState();
+ widget.focusNode.addListener(_handleFocusChanged);
+ widget.controller.addListener(_didChangeTextEditingValue);
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ toggleButtonStateManager = ToggleButtonsStateManager.of(context);
+ textEditingDeltaHistoryManager = TextEditingDeltaHistoryManager.of(context);
+ }
+
+ @override
+ void dispose() {
+ widget.controller.removeListener(_didChangeTextEditingValue);
+ super.dispose();
+ }
+
+ /// [DeltaTextInputClient] method implementations.
+ @override
+ void connectionClosed() {
+ if (_hasInputConnection) {
+ _textInputConnection!.connectionClosedReceived();
+ _textInputConnection = null;
+ _lastKnownRemoteTextEditingValue = null;
+ widget.focusNode.unfocus();
+ widget.controller.clearComposing();
+ }
+ }
+
+ @override
+ // Will not implement.
+ AutofillScope? get currentAutofillScope => throw UnimplementedError();
+
+ @override
+ TextEditingValue? get currentTextEditingValue => _value;
+
+ @override
+ void insertTextPlaceholder(Size size) {
+ // Will not implement. This method is used for Scribble support.
+ }
+
+ @override
+ void performAction(TextInputAction action) {
+ // Will not implement.
+ }
+
+ @override
+ void performPrivateCommand(String action, Map data) {
+ // Will not implement.
+ }
+
+ @override
+ void removeTextPlaceholder() {
+ // Will not implement. This method is used for Scribble support.
+ }
+
+ @override
+ void showAutocorrectionPromptRect(int start, int end) {
+ // Will not implement.
+ }
+
+ @override
+ bool showToolbar() {
+ // On the web use provided native dom elements to provide clipboard functionality.
+ if (kIsWeb) return false;
+
+ if (_selectionOverlay == null || _selectionOverlay!.toolbarIsVisible) {
+ return false;
+ }
+
+ _selectionOverlay!.showToolbar();
+
+ return true;
+ }
+
+ @override
+ void updateEditingValue(TextEditingValue value) { /* Not using */}
+
+ @override
+ void updateEditingValueWithDeltas(List textEditingDeltas) {
+ TextEditingValue value = _value;
+
+ for (final TextEditingDelta delta in textEditingDeltas) {
+ value = delta.apply(value);
+ }
+
+ _lastKnownRemoteTextEditingValue = value;
+
+ if (value == _value) {
+ // This is possible, for example, when the numeric keyboard is input,
+ // the engine will notify twice for the same value.
+ // Track at https://github.com/flutter/flutter/issues/65811
+ return;
+ }
+
+ final bool selectionChanged = _value.selection.start != value.selection.start || _value.selection.end != value.selection.end;
+ textEditingDeltaHistoryManager.updateTextEditingDeltaHistoryOnInput(textEditingDeltas);
+
+ _value = value;
+
+ if (widget.controller is ReplacementTextEditingController) {
+ for (final TextEditingDelta delta in textEditingDeltas) {
+ (widget.controller as ReplacementTextEditingController).syncReplacementRanges(delta);
+ }
+ }
+
+ if (selectionChanged) {
+ toggleButtonStateManager.updateToggleButtonsOnSelection(value.selection);
+ }
+ }
+
+ @override
+ void updateFloatingCursor(RawFloatingCursorPoint point) {
+ // Will not implement.
+ }
+
+ /// Open/close [DeltaTextInputClient]
+ TextInputConnection? _textInputConnection;
+ bool get _hasInputConnection => _textInputConnection?.attached ?? false;
+
+ TextEditingValue get _value => widget.controller.value;
+ set _value(TextEditingValue value) {
+ widget.controller.value = value;
+ }
+
+ // Keep track of the last known text editing value from the engine so we do not
+ // send an update message if we don't have to.
+ TextEditingValue? _lastKnownRemoteTextEditingValue;
+
+ void _openInputConnection() {
+ // Open an input connection if one does not already exist, as well as set
+ // its style. If one is active then show it.
+ if (!_hasInputConnection) {
+ final TextEditingValue localValue = _value;
+
+ _textInputConnection = TextInput.attach(
+ this,
+ const TextInputConfiguration(
+ enableDeltaModel: true,
+ inputAction: TextInputAction.newline,
+ inputType: TextInputType.multiline,
+ ),
+ );
+ final TextStyle style = widget.style;
+ _textInputConnection!
+ ..setStyle(
+ fontFamily: style.fontFamily,
+ fontSize: style.fontSize,
+ fontWeight: style.fontWeight,
+ textDirection: _textDirection, // make this variable.
+ textAlign: TextAlign.left, // make this variable.
+ )
+ ..setEditingState(localValue)
+ ..show();
+
+ _lastKnownRemoteTextEditingValue = localValue;
+ } else {
+ _textInputConnection!.show();
+ }
+ }
+
+ void _closeInputConnectionIfNeeded() {
+ // Close input connection if one is active.
+ if (_hasInputConnection) {
+ _textInputConnection!.close();
+ _textInputConnection = null;
+ _lastKnownRemoteTextEditingValue = null;
+ }
+ }
+
+ void _openOrCloseInputConnectionIfNeeded() {
+ // Open input connection on gaining focus.
+ // Close input connection on focus loss.
+ if (_hasFocus && widget.focusNode.consumeKeyboardToken()) {
+ _openInputConnection();
+ } else if (!_hasFocus) {
+ _closeInputConnectionIfNeeded();
+ widget.controller.clearComposing();
+ }
+ }
+
+ /// Field focus + keyboard request.
+ bool get _hasFocus => widget.focusNode.hasFocus;
+
+ void requestKeyboard() {
+ if (_hasFocus) {
+ _openInputConnection();
+ } else {
+ widget.focusNode.requestFocus();
+ }
+ }
+
+ void _handleFocusChanged() {
+ // Open or close input connection depending on focus.
+ _openOrCloseInputConnectionIfNeeded();
+ if (_hasFocus) {
+ if (!_value.selection.isValid) {
+ // Place cursor at the end if the selection is invalid when we receive focus.
+ final TextSelection validSelection = TextSelection.collapsed(offset: _value.text.length);
+ _handleSelectionChanged(validSelection, null);
+ toggleButtonStateManager.updateToggleButtonsOnSelection(validSelection);
+ }
+ }
+ }
+
+ /// Misc.
+ TextDirection get _textDirection => Directionality.of(context);
+
+ TextSpan _buildTextSpan() {
+ return widget.controller.buildTextSpan(
+ context: context,
+ style: widget.style,
+ withComposing: true,
+ );
+ }
+
+ void _userUpdateTextEditingValueWithDelta(TextEditingDelta textEditingDelta, SelectionChangedCause cause) {
+ TextEditingValue value = _value;
+
+ value = textEditingDelta.apply(value);
+
+ if (widget.controller is ReplacementTextEditingController) {
+ (widget.controller as ReplacementTextEditingController).syncReplacementRanges(textEditingDelta);
+ }
+
+ if (value != _value) {
+ textEditingDeltaHistoryManager.updateTextEditingDeltaHistoryOnInput([textEditingDelta]);
+ }
+
+ userUpdateTextEditingValue(value, cause);
+ }
+
+ /// Keyboard text editing actions.
+ // The Handling of the default text editing shortcuts with deltas
+ // needs to be in the framework somehow. This should go through some kind of
+ // generic "replace" method like in EditableText.
+ // EditableText converts intents like DeleteCharacterIntent to a generic
+ // ReplaceTextIntent. I wonder if that could be done at a higher level, so
+ // that users could listen to that instead of DeleteCharacterIntent?
+ TextSelection get _selection => _value.selection;
+ late final Map> _actions = >{
+ DeleteCharacterIntent: CallbackAction(
+ onInvoke: (intent) => _delete(),
+ ),
+ ExtendSelectionByCharacterIntent: CallbackAction(
+ onInvoke: (intent) => _extendSelection(intent.forward, intent.collapseSelection),
+ ),
+ SelectAllTextIntent : CallbackAction(
+ onInvoke: (intent) => selectAll(intent.cause),
+ ),
+ CopySelectionTextIntent : CallbackAction(
+ onInvoke: (intent) => copySelection(intent.cause),
+ ),
+ PasteTextIntent : CallbackAction(
+ onInvoke: (intent) => pasteText(intent.cause),
+ ),
+ };
+
+ void _delete() {
+ if (_value.text.isEmpty) return;
+
+ late final TextRange deletedRange;
+ late final TextRange newComposing;
+ final int deletedLength = _value.text.substring(0, _selection.baseOffset).characters.last.length;
+
+ if (_selection.isCollapsed) {
+ if (_selection.baseOffset == 0) return;
+ deletedRange = TextRange(
+ start: _selection.baseOffset - deletedLength,
+ end: _selection.baseOffset,
+ );
+ } else {
+ deletedRange = _selection;
+ }
+
+ final bool isComposing = _selection.isCollapsed && _value.isComposingRangeValid;
+
+ if (isComposing) {
+ newComposing = TextRange.collapsed(deletedRange.start);
+ } else {
+ newComposing = TextRange.empty;
+ }
+
+ _userUpdateTextEditingValueWithDelta(
+ TextEditingDeltaDeletion(
+ oldText: _value.text,
+ selection: TextSelection.collapsed(offset: deletedRange.start),
+ composing: newComposing,
+ deletedRange: deletedRange,
+ ),
+ SelectionChangedCause.keyboard,
+ );
+ }
+
+ void _extendSelection(bool forward, bool collapseSelection) {
+ late final TextSelection selection;
+
+ if (collapseSelection) {
+ if (!_selection.isCollapsed) {
+ final int firstOffset = _selection.isNormalized ? _selection.start : _selection.end;
+ final int lastOffset = _selection.isNormalized ? _selection.end : _selection.start;
+ selection = TextSelection.collapsed(offset: forward ? lastOffset : firstOffset);
+ } else {
+ if (forward && _selection.baseOffset == _value.text.length) return;
+ if (!forward && _selection.baseOffset == 0) return;
+ final int adjustment = forward
+ ? _value.text.substring(_selection.baseOffset).characters.first.length
+ : -_value.text.substring(0, _selection.baseOffset).characters.last.length;
+ selection = TextSelection.collapsed(
+ offset: _selection.baseOffset + adjustment,
+ );
+ }
+ } else {
+ if (forward && _selection.extentOffset == _value.text.length) return;
+ if (!forward && _selection.extentOffset == 0) return;
+ final int adjustment = forward
+ ? _value.text.substring(_selection.baseOffset).characters.first.length
+ : -_value.text.substring(0, _selection.baseOffset).characters.last.length;
+ selection = TextSelection(
+ baseOffset: _selection.baseOffset,
+ extentOffset: _selection.extentOffset + adjustment,
+ );
+ }
+
+ _userUpdateTextEditingValueWithDelta(
+ TextEditingDeltaNonTextUpdate(
+ oldText: _value.text,
+ selection: selection,
+ composing: _value.composing,
+ ),
+ SelectionChangedCause.keyboard,
+ );
+ }
+
+
+ /// For updates to text editing value.
+ void _didChangeTextEditingValue() {
+ _updateRemoteTextEditingValueIfNeeded();
+ _updateOrDisposeOfSelectionOverlayIfNeeded();
+ setState(() {});
+ }
+
+ void _toggleToolbar() {
+ assert(_selectionOverlay != null);
+ if (_selectionOverlay!.toolbarIsVisible) {
+ hideToolbar(false);
+ } else {
+ showToolbar();
+ }
+ }
+
+ // When the framework's text editing value changes we should update the text editing
+ // value contained within the selection overlay or we might observe unexpected behavior.
+ void _updateOrDisposeOfSelectionOverlayIfNeeded() {
+ if (_selectionOverlay != null) {
+ if (_hasFocus) {
+ _selectionOverlay!.update(_value);
+ } else {
+ _selectionOverlay!.dispose();
+ _selectionOverlay = null;
+ }
+ }
+ }
+
+ // Only update the platform's text input plugin's text editing value when it has changed
+ // to avoid sending duplicate update messages to the engine.
+ void _updateRemoteTextEditingValueIfNeeded() {
+ if (_lastKnownRemoteTextEditingValue == _value) return;
+
+ if (_textInputConnection != null) {
+ _textInputConnection!.setEditingState(_value);
+ _lastKnownRemoteTextEditingValue = _value;
+ }
+ }
+
+ /// [TextSelectionDelegate] method implementations.
+ @override
+ void bringIntoView(TextPosition position) {
+ // Not implemented.
+ }
+
+ @override
+ void copySelection(SelectionChangedCause cause) {
+ final TextSelection copyRange = textEditingValue.selection;
+ if (!copyRange.isValid || copyRange.isCollapsed) return;
+ final String text = textEditingValue.text;
+ Clipboard.setData(ClipboardData(text: copyRange.textInside(text)));
+
+ // If copy was done by the text selection toolbar we should hide the toolbar and set the selection
+ // to the end of the copied text.
+ if (cause == SelectionChangedCause.toolbar) {
+ switch (defaultTargetPlatform) {
+ case TargetPlatform.iOS:
+ break;
+ case TargetPlatform.macOS:
+ case TargetPlatform.android:
+ case TargetPlatform.fuchsia:
+ case TargetPlatform.linux:
+ case TargetPlatform.windows:
+ _userUpdateTextEditingValueWithDelta(
+ TextEditingDeltaNonTextUpdate(
+ oldText: textEditingValue.text,
+ selection: TextSelection.collapsed(offset: textEditingValue.selection.end),
+ composing: TextRange.empty,
+ ),
+ cause,
+ );
+ break;
+ }
+ hideToolbar();
+ }
+ _clipboardStatus?.update();
+ }
+
+ @override
+ void cutSelection(SelectionChangedCause cause) {
+ final TextSelection cutRange = textEditingValue.selection;
+ final String text = textEditingValue.text;
+
+ if (cutRange.isCollapsed) return;
+ Clipboard.setData(ClipboardData(text: cutRange.textInside(text)));
+ final int lastSelectionIndex = math.min(cutRange.baseOffset, cutRange.extentOffset);
+ _userUpdateTextEditingValueWithDelta(
+ TextEditingDeltaReplacement(
+ oldText: textEditingValue.text,
+ replacementText: '',
+ replacedRange: cutRange,
+ selection: TextSelection.collapsed(offset: lastSelectionIndex),
+ composing: TextRange.empty,
+ ),
+ cause,
+ );
+ if (cause == SelectionChangedCause.toolbar) hideToolbar();
+ _clipboardStatus?.update();
+ }
+
+ @override
+ void hideToolbar([bool hideHandles = true]) {
+ if (hideHandles) {
+ // Hide the handles and the toolbar.
+ _selectionOverlay?.hide();
+ } else if (_selectionOverlay?.toolbarIsVisible ?? false) {
+ // Hide only the toolbar but not the handles.
+ _selectionOverlay?.hideToolbar();
+ }
+ }
+
+ @override
+ Future pasteText(SelectionChangedCause cause) async {
+ final TextSelection pasteRange = textEditingValue.selection;
+ if (!pasteRange.isValid) return;
+
+ final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
+ if (data == null) return;
+
+ // After the paste, the cursor should be collapsed and located after the
+ // pasted content.
+ final int lastSelectionIndex = math.max(pasteRange.baseOffset, pasteRange.baseOffset + data.text!.length);
+
+ _userUpdateTextEditingValueWithDelta(
+ TextEditingDeltaReplacement(
+ oldText: textEditingValue.text,
+ replacementText: data.text!,
+ replacedRange: pasteRange,
+ selection: TextSelection.collapsed(offset: lastSelectionIndex),
+ composing: TextRange.empty,
+ ),
+ cause,
+ );
+
+ if (cause == SelectionChangedCause.toolbar) hideToolbar();
+ }
+
+ @override
+ void selectAll(SelectionChangedCause cause) {
+ final TextSelection newSelection = _value.selection.copyWith(baseOffset: 0, extentOffset: _value.text.length);
+ _userUpdateTextEditingValueWithDelta(
+ TextEditingDeltaNonTextUpdate(
+ oldText: textEditingValue.text,
+ selection: newSelection,
+ composing: TextRange.empty
+ ),
+ cause,
+ );
+ }
+
+ @override
+ TextEditingValue get textEditingValue => _value;
+
+ @override
+ void userUpdateTextEditingValue(TextEditingValue value, SelectionChangedCause cause) {
+ if (value == _value) return;
+
+ final bool selectionChanged = _value.selection != value.selection;
+
+ if (cause == SelectionChangedCause.drag || cause == SelectionChangedCause.longPress || cause == SelectionChangedCause.tap) {
+ // Here the change is coming from gestures which call on RenderEditable to change the selection.
+ // Create a TextEditingDeltaNonTextUpdate so we can keep track of the delta history. RenderEditable
+ // does not report a delta on selection change.
+ final bool textChanged = _value.text != value.text;
+ if (selectionChanged && !textChanged) {
+ final TextEditingDeltaNonTextUpdate selectionUpdate = TextEditingDeltaNonTextUpdate(
+ oldText: value.text,
+ selection: value.selection,
+ composing: value.composing,
+ );
+ if (widget.controller is ReplacementTextEditingController) {
+ (widget.controller as ReplacementTextEditingController).syncReplacementRanges(selectionUpdate);
+ }
+ textEditingDeltaHistoryManager.updateTextEditingDeltaHistoryOnInput([selectionUpdate]);
+ }
+ }
+
+ final bool selectionRangeChanged = _value.selection.start != value.selection.start
+ || _value.selection.end != value.selection.end;
+
+ _value = value;
+
+ if (selectionChanged) {
+ _handleSelectionChanged(_value.selection, cause);
+
+ if (selectionRangeChanged) {
+ toggleButtonStateManager.updateToggleButtonsOnSelection(_value.selection);
+ }
+ }
+ }
+
+ /// For TextSelection.
+ final LayerLink _startHandleLayerLink = LayerLink();
+ final LayerLink _endHandleLayerLink = LayerLink();
+ final LayerLink _toolbarLayerLink = LayerLink();
+
+ TextSelectionOverlay? _selectionOverlay;
+ RenderEditable get renderEditable => _textKey.currentContext!.findRenderObject()! as RenderEditable;
+
+ void _handleSelectionChanged(TextSelection selection, SelectionChangedCause? cause) {
+ // We return early if the selection is not valid. This can happen when the
+ // text of [EditableText] is updated at the same time as the selection is
+ // changed by a gesture event.
+ if (!widget.controller.isSelectionWithinTextBounds(selection)) return;
+
+ widget.controller.selection = selection;
+
+ // This will show the keyboard for all selection changes on the
+ // EditableText except for those triggered by a keyboard input.
+ // Typically BasicTextInputClient shouldn't take user keyboard input if
+ // it's not focused already.
+ switch (cause) {
+ case null:
+ case SelectionChangedCause.doubleTap:
+ case SelectionChangedCause.drag:
+ case SelectionChangedCause.forcePress:
+ case SelectionChangedCause.longPress:
+ case SelectionChangedCause.scribble:
+ case SelectionChangedCause.tap:
+ case SelectionChangedCause.toolbar:
+ requestKeyboard();
+ break;
+ case SelectionChangedCause.keyboard:
+ if (_hasFocus) {
+ requestKeyboard();
+ }
+ break;
+ }
+ if (widget.selectionControls == null) {
+ _selectionOverlay?.dispose();
+ _selectionOverlay = null;
+ } else {
+ if (_selectionOverlay == null) {
+ _selectionOverlay = TextSelectionOverlay(
+ clipboardStatus: _clipboardStatus,
+ context: context,
+ value: _value,
+ debugRequiredFor: widget,
+ toolbarLayerLink: _toolbarLayerLink,
+ startHandleLayerLink: _startHandleLayerLink,
+ endHandleLayerLink: _endHandleLayerLink,
+ renderObject: renderEditable,
+ selectionControls: widget.selectionControls,
+ selectionDelegate: this,
+ dragStartBehavior: DragStartBehavior.start,
+ onSelectionHandleTapped: () {
+ _toggleToolbar();
+ },
+ );
+ } else {
+ _selectionOverlay!.update(_value);
+ }
+ _selectionOverlay!.handlesVisible = widget.showSelectionHandles;
+ _selectionOverlay!.showHandles();
+ }
+
+ try {
+ widget.onSelectionChanged.call(selection, cause);
+ } catch (exception, stack) {
+ FlutterError.reportError(FlutterErrorDetails(
+ exception: exception,
+ stack: stack,
+ library: 'widgets',
+ context: ErrorDescription('while calling onSelectionChanged for $cause'),
+ ));
+ }
+ }
+
+ static final Map _defaultWebShortcuts = {
+ // Activation
+ const SingleActivator(LogicalKeyboardKey.space): DoNothingAndStopPropagationIntent(),
+
+ // Scrolling
+ const SingleActivator(LogicalKeyboardKey.arrowUp): DoNothingAndStopPropagationIntent(),
+ const SingleActivator(LogicalKeyboardKey.arrowDown): DoNothingAndStopPropagationIntent(),
+ const SingleActivator(LogicalKeyboardKey.arrowLeft): DoNothingAndStopPropagationIntent(),
+ const SingleActivator(LogicalKeyboardKey.arrowRight): DoNothingAndStopPropagationIntent(),
+ };
+
+ @override
+ Widget build(BuildContext context) {
+ return Shortcuts(
+ shortcuts: kIsWeb ? _defaultWebShortcuts : {},
+ child: Actions(
+ actions: _actions,
+ child: Focus(
+ focusNode: widget.focusNode,
+ child: Scrollable(
+ viewportBuilder: (context, position) {
+ return CompositedTransformTarget(
+ link: _toolbarLayerLink,
+ child: _Editable(
+ key: _textKey,
+ startHandleLayerLink: _startHandleLayerLink,
+ endHandleLayerLink: _endHandleLayerLink,
+ inlineSpan: _buildTextSpan(),
+ value: _value, // We pass value.selection to RenderEditable.
+ cursorColor: Colors.blue,
+ backgroundCursorColor: Colors.grey[100],
+ showCursor: ValueNotifier(_hasFocus),
+ forceLine: true, // Whether text field will take full line regardless of width.
+ readOnly: false, // editable text-field.
+ hasFocus: _hasFocus,
+ maxLines: null, // multi-line text-field.
+ minLines: null,
+ expands: false, // expands to height of parent.
+ strutStyle: null,
+ selectionColor: Colors.blue.withOpacity(0.40),
+ textScaleFactor: MediaQuery.textScaleFactorOf(context),
+ textAlign: TextAlign.left,
+ textDirection: _textDirection,
+ locale: Localizations.maybeLocaleOf(context),
+ textHeightBehavior: DefaultTextHeightBehavior.of(context),
+ textWidthBasis: TextWidthBasis.parent,
+ obscuringCharacter: '•',
+ obscureText: false, // This is a non-private text field that does not require obfuscation.
+ offset: position,
+ onCaretChanged: null,
+ rendererIgnoresPointer: true,
+ cursorWidth: 2.0,
+ cursorHeight: null,
+ cursorRadius: const Radius.circular(2.0),
+ cursorOffset: Offset.zero,
+ paintCursorAboveText: false,
+ enableInteractiveSelection: true, // make true to enable selection on mobile.
+ textSelectionDelegate: this,
+ devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
+ promptRectRange: null,
+ promptRectColor: null,
+ clipBehavior: Clip.hardEdge,
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _Editable extends MultiChildRenderObjectWidget {
+ _Editable({
+ Key? key,
+ required this.inlineSpan,
+ required this.value,
+ required this.startHandleLayerLink,
+ required this.endHandleLayerLink,
+ this.cursorColor,
+ this.backgroundCursorColor,
+ required this.showCursor,
+ required this.forceLine,
+ required this.readOnly,
+ this.textHeightBehavior,
+ required this.textWidthBasis,
+ required this.hasFocus,
+ required this.maxLines,
+ this.minLines,
+ required this.expands,
+ this.strutStyle,
+ this.selectionColor,
+ required this.textScaleFactor,
+ required this.textAlign,
+ required this.textDirection,
+ this.locale,
+ required this.obscuringCharacter,
+ required this.obscureText,
+ required this.offset,
+ this.onCaretChanged,
+ this.rendererIgnoresPointer = false,
+ required this.cursorWidth,
+ this.cursorHeight,
+ this.cursorRadius,
+ required this.cursorOffset,
+ required this.paintCursorAboveText,
+ this.enableInteractiveSelection = true,
+ required this.textSelectionDelegate,
+ required this.devicePixelRatio,
+ this.promptRectRange,
+ this.promptRectColor,
+ required this.clipBehavior,
+ }) : super(key: key, children: _extractChildren(inlineSpan));
+
+ // Traverses the InlineSpan tree and depth-first collects the list of
+ // child widgets that are created in WidgetSpans.
+ static List _extractChildren(InlineSpan span) {
+ final List result = [];
+ span.visitChildren((span) {
+ if (span is WidgetSpan) {
+ result.add(span.child);
+ }
+ return true;
+ });
+ return result;
+ }
+
+ final InlineSpan inlineSpan;
+ final TextEditingValue value;
+ final Color? cursorColor;
+ final LayerLink startHandleLayerLink;
+ final LayerLink endHandleLayerLink;
+ final Color? backgroundCursorColor;
+ final ValueNotifier showCursor;
+ final bool forceLine;
+ final bool readOnly;
+ final bool hasFocus;
+ final int? maxLines;
+ final int? minLines;
+ final bool expands;
+ final StrutStyle? strutStyle;
+ final Color? selectionColor;
+ final double textScaleFactor;
+ final TextAlign textAlign;
+ final TextDirection textDirection;
+ final Locale? locale;
+ final String obscuringCharacter;
+ final bool obscureText;
+ final TextHeightBehavior? textHeightBehavior;
+ final TextWidthBasis textWidthBasis;
+ final ViewportOffset offset;
+ final CaretChangedHandler? onCaretChanged;
+ final bool rendererIgnoresPointer;
+ final double cursorWidth;
+ final double? cursorHeight;
+ final Radius? cursorRadius;
+ final Offset cursorOffset;
+ final bool paintCursorAboveText;
+ final bool enableInteractiveSelection;
+ final TextSelectionDelegate textSelectionDelegate;
+ final double devicePixelRatio;
+ final TextRange? promptRectRange;
+ final Color? promptRectColor;
+ final Clip clipBehavior;
+
+ @override
+ RenderEditable createRenderObject(BuildContext context) {
+ return RenderEditable(
+ text: inlineSpan,
+ cursorColor: cursorColor,
+ startHandleLayerLink: startHandleLayerLink,
+ endHandleLayerLink: endHandleLayerLink,
+ backgroundCursorColor: backgroundCursorColor,
+ showCursor: showCursor,
+ forceLine: forceLine,
+ readOnly: readOnly,
+ hasFocus: hasFocus,
+ maxLines: maxLines,
+ minLines: minLines,
+ expands: expands,
+ strutStyle: strutStyle,
+ selectionColor: selectionColor,
+ textScaleFactor: textScaleFactor,
+ textAlign: textAlign,
+ textDirection: textDirection,
+ locale: locale ?? Localizations.maybeLocaleOf(context),
+ selection: value.selection,
+ offset: offset,
+ onCaretChanged: onCaretChanged,
+ ignorePointer: rendererIgnoresPointer,
+ obscuringCharacter: obscuringCharacter,
+ obscureText: obscureText,
+ textHeightBehavior: textHeightBehavior,
+ textWidthBasis: textWidthBasis,
+ cursorWidth: cursorWidth,
+ cursorHeight: cursorHeight,
+ cursorRadius: cursorRadius,
+ cursorOffset: cursorOffset,
+ paintCursorAboveText: paintCursorAboveText,
+ enableInteractiveSelection: enableInteractiveSelection,
+ textSelectionDelegate: textSelectionDelegate,
+ devicePixelRatio: devicePixelRatio,
+ promptRectRange: promptRectRange,
+ promptRectColor: promptRectColor,
+ clipBehavior: clipBehavior,
+ );
+ }
+
+ @override
+ void updateRenderObject(BuildContext context, RenderEditable renderObject) {
+ renderObject
+ ..text = inlineSpan
+ ..cursorColor = cursorColor
+ ..startHandleLayerLink = startHandleLayerLink
+ ..endHandleLayerLink = endHandleLayerLink
+ ..showCursor = showCursor
+ ..forceLine = forceLine
+ ..readOnly = readOnly
+ ..hasFocus = hasFocus
+ ..maxLines = maxLines
+ ..minLines = minLines
+ ..expands = expands
+ ..strutStyle = strutStyle
+ ..selectionColor = selectionColor
+ ..textScaleFactor = textScaleFactor
+ ..textAlign = textAlign
+ ..textDirection = textDirection
+ ..locale = locale ?? Localizations.maybeLocaleOf(context)
+ ..selection = value.selection
+ ..offset = offset
+ ..onCaretChanged = onCaretChanged
+ ..ignorePointer = rendererIgnoresPointer
+ ..textHeightBehavior = textHeightBehavior
+ ..textWidthBasis = textWidthBasis
+ ..obscuringCharacter = obscuringCharacter
+ ..obscureText = obscureText
+ ..cursorWidth = cursorWidth
+ ..cursorHeight = cursorHeight
+ ..cursorRadius = cursorRadius
+ ..cursorOffset = cursorOffset
+ ..enableInteractiveSelection = enableInteractiveSelection
+ ..textSelectionDelegate = textSelectionDelegate
+ ..devicePixelRatio = devicePixelRatio
+ ..paintCursorAboveText = paintCursorAboveText
+ ..promptRectColor = promptRectColor
+ ..clipBehavior = clipBehavior
+ ..setPromptRectRange(promptRectRange);
+ }
+}
\ No newline at end of file
diff --git a/simplistic_editor/lib/main.dart b/simplistic_editor/lib/main.dart
new file mode 100644
index 000000000..fbd0ea9fe
--- /dev/null
+++ b/simplistic_editor/lib/main.dart
@@ -0,0 +1,420 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+import 'basic_text_field.dart';
+import 'replacements.dart';
+import 'text_editing_delta_history_manager.dart';
+import 'toggle_button_state_manager.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ debugShowCheckedModeBanner: false,
+ title: 'Simplistic Editor',
+ theme: ThemeData(
+ primarySwatch: Colors.blue,
+ useMaterial3: true,
+ ),
+ home: const MyHomePage(title: 'Simplistic Editor'),
+ );
+ }
+}
+
+class MyHomePage extends StatefulWidget {
+ const MyHomePage({Key? key, required this.title}) : super(key: key);
+
+ final String title;
+
+ @override
+ State createState() => _MyHomePageState();
+}
+
+class _MyHomePageState extends State {
+ final ReplacementTextEditingController _replacementTextEditingController =
+ ReplacementTextEditingController(
+ text: 'The quick brown fox jumps over the lazy dog.',
+ );
+ final FocusNode _focusNode = FocusNode();
+ final List _isSelected = [false, false, false];
+ final List _textEditingDeltaHistory = [];
+
+ void _updateTextEditingDeltaHistory(List textEditingDeltas) {
+ for (final TextEditingDelta delta in textEditingDeltas) {
+ _textEditingDeltaHistory.add(delta);
+ }
+
+ setState(() {});
+ }
+
+ List _buildTextEditingDeltaHistoryViews(List textEditingDeltas) {
+ List textEditingDeltaViews = [];
+
+ for (final TextEditingDelta delta in textEditingDeltas) {
+ final TextEditingDeltaView deltaView;
+
+ if (delta is TextEditingDeltaInsertion) {
+ deltaView = TextEditingDeltaView(
+ deltaType: delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
+ deltaText: delta.textInserted,
+ deltaRange: TextRange.collapsed(delta.insertionOffset),
+ newSelection: delta.selection,
+ newComposing: delta.composing,
+ );
+ } else if (delta is TextEditingDeltaDeletion) {
+ deltaView = TextEditingDeltaView(
+ deltaType: delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
+ deltaText: delta.textDeleted,
+ deltaRange: delta.deletedRange,
+ newSelection: delta.selection,
+ newComposing: delta.composing,
+ );
+ } else if (delta is TextEditingDeltaReplacement) {
+ deltaView = TextEditingDeltaView(
+ deltaType: delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
+ deltaText: delta.replacementText,
+ deltaRange: delta.replacedRange,
+ newSelection: delta.selection,
+ newComposing: delta.composing,
+ );
+ } else if (delta is TextEditingDeltaNonTextUpdate) {
+ deltaView = TextEditingDeltaView(
+ deltaType: delta.runtimeType.toString().replaceAll('TextEditingDelta', ''),
+ deltaText: '',
+ deltaRange: TextRange.empty,
+ newSelection: delta.selection,
+ newComposing: delta.composing,
+ );
+ } else {
+ deltaView = const TextEditingDeltaView(
+ deltaType: 'Error',
+ deltaText: 'Error',
+ deltaRange: TextRange.empty,
+ newSelection: TextRange.empty,
+ newComposing: TextRange.empty,
+ );
+ }
+
+ textEditingDeltaViews.add(deltaView);
+ }
+
+ return textEditingDeltaViews.reversed.toList();
+ }
+
+ void _updateToggleButtonsStateOnSelectionChanged(TextSelection selection) {
+ // When the selection changes we want to check the replacements at the new
+ // selection. Enable/disable toggle buttons based on the replacements found
+ // at the new selection.
+ final List replacementStyles = _replacementTextEditingController.getReplacementsAtSelection(selection);
+ final List hasChanged = [false, false, false];
+
+ if (replacementStyles.isEmpty) {
+ _isSelected.fillRange(0, _isSelected.length, false);
+ }
+
+ for (final TextStyle style in replacementStyles) {
+ if (style.fontWeight != null && !hasChanged[0]) {
+ _isSelected[0] = true;
+ hasChanged[0] = true;
+ }
+
+ if (style.fontStyle != null && !hasChanged[1]) {
+ _isSelected[1] = true;
+ hasChanged[1] = true;
+ }
+
+ if (style.decoration != null && !hasChanged[2]) {
+ _isSelected[2] = true;
+ hasChanged[2] = true;
+ }
+ }
+
+ for (final TextStyle style in replacementStyles) {
+ if (style.fontWeight == null && !hasChanged[0]) {
+ _isSelected[0] = false;
+ hasChanged[0] = true;
+ }
+
+ if (style.fontStyle == null && !hasChanged[1]) {
+ _isSelected[1] = false;
+ hasChanged[1] = true;
+ }
+
+ if (style.decoration == null && !hasChanged[2]) {
+ _isSelected[2] = false;
+ hasChanged[2] = true;
+ }
+ }
+
+ setState(() {});
+ }
+
+ void _updateToggleButtonsStateOnButtonPressed(int index) {
+ Map attributeMap = const {
+ 0 : TextStyle(fontWeight: FontWeight.bold),
+ 1 : TextStyle(fontStyle: FontStyle.italic),
+ 2 : TextStyle(decoration: TextDecoration.underline),
+ };
+
+ final TextRange replacementRange = TextRange(
+ start: _replacementTextEditingController.selection.start,
+ end: _replacementTextEditingController.selection.end,
+ );
+
+ _isSelected[index] = !_isSelected[index];
+ if (_isSelected[index]) {
+ _replacementTextEditingController.applyReplacement(
+ TextEditingInlineSpanReplacement(
+ replacementRange,
+ (string, range) => TextSpan(text: string, style: attributeMap[index]),
+ true,
+ ),
+ );
+ setState(() {});
+ } else {
+ _replacementTextEditingController.disableExpand(attributeMap[index]!);
+ _replacementTextEditingController.removeReplacementsAtRange(replacementRange, attributeMap[index]);
+ setState(() {});
+ }
+ }
+
+ Widget _buildTextEditingDeltaViewHeading(String text) {
+ return Text(
+ text,
+ style: const TextStyle(
+ fontWeight: FontWeight.w600,
+ decoration: TextDecoration.underline,
+ ),
+ );
+ }
+
+ Widget _buildTextEditingDeltaViewHeader() {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 35.0, vertical: 10.0),
+ child: Row(
+ children: [
+ Expanded(
+ child: Tooltip(
+ message: 'The type of text input that is occurring.'
+ ' Check out the documentation for TextEditingDelta for more information.',
+ child: _buildTextEditingDeltaViewHeading('Delta Type'),
+ ),
+ ),
+ Expanded(
+ child: Tooltip(
+ message: 'The text that is being inserted or deleted',
+ child: _buildTextEditingDeltaViewHeading('Delta Text'),
+ ),
+ ),
+ Expanded(
+ child: Tooltip(
+ message: 'The offset in the text where the text input is occurring.',
+ child: _buildTextEditingDeltaViewHeading('Delta Offset'),
+ ),
+ ),
+ Expanded(
+ child: Tooltip(
+ message: 'The new text selection range after the text input has occurred.',
+ child: _buildTextEditingDeltaViewHeading('New Selection'),
+ ),
+ ),
+ Expanded(
+ child: Tooltip(
+ message: 'The new composing range after the text input has occurred.',
+ child: _buildTextEditingDeltaViewHeading('New Composing'),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ static Route