fix merge conflict

pull/56/head
Creator 4 years ago
commit 4fbbf94caa
  1. 15
      CHANGELOG.md
  2. 10
      README.md
  3. 2
      app/android/app/build.gradle
  4. 2
      app/android/build.gradle
  5. 2
      app/android/gradle/wrapper/gradle-wrapper.properties
  6. 57
      app/pubspec.lock
  7. 2
      lib/models/documents/document.dart
  8. 2
      lib/models/documents/history.dart
  9. 2
      lib/models/documents/nodes/block.dart
  10. 2
      lib/models/documents/nodes/leaf.dart
  11. 2
      lib/models/documents/nodes/line.dart
  12. 2
      lib/models/documents/nodes/node.dart
  13. 688
      lib/models/quill_delta.dart
  14. 2
      lib/models/rules/delete.dart
  15. 2
      lib/models/rules/format.dart
  16. 2
      lib/models/rules/insert.dart
  17. 2
      lib/models/rules/rule.dart
  18. 2
      lib/utils/diff_delta.dart
  19. 2
      lib/widgets/controller.dart
  20. 1
      lib/widgets/editor.dart
  21. 0
      lib/widgets/fake_ui.dart
  22. 10
      lib/widgets/raw_editor.dart
  23. 0
      lib/widgets/real_ui.dart
  24. 6
      lib/widgets/toolbar.dart
  25. 51
      pubspec.lock
  26. 8
      pubspec.yaml

@ -103,3 +103,18 @@
## [0.3.3] ## [0.3.3]
* More fix on cursor focus issue when keyboard is on. * More fix on cursor focus issue when keyboard is on.
## [0.3.4]
* Improve link handling for tel, mailto and etc.
## [0.3.5]
* Fix for cursor focus issues when keyboard is on.
## [1.0.0-dev.1]
* Upgrade prerelease SDK & Bump for master.
## [1.0.0-dev.2]
* Improve link handling for tel, mailto and etc.
## [1.0.0]
* Support flutter 2.0.

@ -70,15 +70,7 @@ The `QuillToolbar` class lets you customise which formatting options are availab
## Web ## Web
Default branch `master` is on channel `master`. To use channel `stable`, switch to branch `stable`. For web development, use `flutter config --enable-web` for flutter and use [ReactQuill] for React.
Branch `master` on channel `master` supports web. To run the app on web do the following:
1) Change flutter channel to master using `flutter channel master`, followed by `flutter upgrade`.
2) Enable web using `flutter config --enable-web` and restart the IDE.
3) Upon successful execution of step 1 and 2 you should see `Chrome` as one of the devices which you run `flutter devices`.
4) Run the app.
For web development, [ReactQuill] is recommended to use for compatibility.
--- ---

@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) {
def flutterRoot = localProperties.getProperty('flutter.sdk') def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) { if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
} }
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')

@ -5,7 +5,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.5.0' classpath 'com.android.tools.build:gradle:3.6.3'
} }
} }

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip

@ -98,7 +98,7 @@ packages:
name: file_picker name: file_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.6" version: "3.0.0-nullsafety.2"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -110,42 +110,42 @@ packages:
name: flutter_colorpicker name: flutter_colorpicker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.5" version: "0.4.0-nullsafety.0"
flutter_keyboard_visibility: flutter_keyboard_visibility:
dependency: transitive dependency: transitive
description: description:
name: flutter_keyboard_visibility name: flutter_keyboard_visibility
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.4" version: "5.0.0-nullsafety.3"
flutter_keyboard_visibility_platform_interface: flutter_keyboard_visibility_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: flutter_keyboard_visibility_platform_interface name: flutter_keyboard_visibility_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.0-nullsafety.0"
flutter_keyboard_visibility_web: flutter_keyboard_visibility_web:
dependency: transitive dependency: transitive
description: description:
name: flutter_keyboard_visibility_web name: flutter_keyboard_visibility_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.0-nullsafety.0"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.11" version: "2.0.0"
flutter_quill: flutter_quill:
dependency: "direct main" dependency: "direct main"
description: description:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.3.3" version: "1.0.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -169,28 +169,28 @@ packages:
name: http name: http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.2" version: "0.13.0"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.4" version: "4.0.0"
image_picker: image_picker:
dependency: transitive dependency: transitive
description: description:
name: image_picker name: image_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.7+22" version: "0.7.2"
image_picker_platform_interface: image_picker_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: image_picker_platform_interface name: image_picker_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.1" version: "2.0.0"
js: js:
dependency: transitive dependency: transitive
description: description:
@ -260,7 +260,7 @@ packages:
name: pedantic name: pedantic
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.2" version: "1.11.0"
photo_view: photo_view:
dependency: transitive dependency: transitive
description: description:
@ -281,7 +281,7 @@ packages:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "1.1.0-nullsafety.2"
process: process:
dependency: transitive dependency: transitive
description: description:
@ -289,20 +289,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.1.0" version: "4.1.0"
quill_delta:
dependency: transitive
description:
name: quill_delta
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:
name: quiver name: quiver
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.5" version: "3.0.0"
quiver_hashcode: quiver_hashcode:
dependency: transitive dependency: transitive
description: description:
@ -321,7 +314,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -349,7 +342,7 @@ packages:
name: string_validator name: string_validator
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.4" version: "0.2.0-nullsafety.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -370,7 +363,7 @@ packages:
name: tuple name: tuple
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "2.0.0-nullsafety.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -398,42 +391,42 @@ packages:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.7.10" version: "6.0.2"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+4" version: "2.0.0"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+9" version: "2.0.0"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.9" version: "2.0.1"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.5+1" version: "2.0.0"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+3" version: "2.0.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -463,5 +456,5 @@ packages:
source: hosted source: hosted
version: "0.1.2" version: "0.1.2"
sdks: sdks:
dart: ">=2.12.0-0.0 <3.0.0" dart: ">=2.12.0-259.9.beta <3.0.0"
flutter: ">=1.22.0" flutter: ">=1.24.0-10.2.pre"

@ -4,7 +4,7 @@ import 'package:flutter_quill/models/documents/nodes/block.dart';
import 'package:flutter_quill/models/documents/nodes/container.dart'; import 'package:flutter_quill/models/documents/nodes/container.dart';
import 'package:flutter_quill/models/documents/nodes/line.dart'; import 'package:flutter_quill/models/documents/nodes/line.dart';
import 'package:flutter_quill/models/documents/style.dart'; import 'package:flutter_quill/models/documents/style.dart';
import 'package:quill_delta/quill_delta.dart'; import 'package:flutter_quill/models/quill_delta.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import '../rules/rule.dart'; import '../rules/rule.dart';

@ -1,4 +1,4 @@
import 'package:quill_delta/quill_delta.dart'; import 'package:flutter_quill/models/quill_delta.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
import 'document.dart'; import 'document.dart';

@ -1,4 +1,4 @@
import 'package:quill_delta/quill_delta.dart'; import 'package:flutter_quill/models/quill_delta.dart';
import 'container.dart'; import 'container.dart';
import 'line.dart'; import 'line.dart';

@ -1,6 +1,6 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:quill_delta/quill_delta.dart'; import 'package:flutter_quill/models/quill_delta.dart';
import '../style.dart'; import '../style.dart';
import 'embed.dart'; import 'embed.dart';

@ -2,7 +2,7 @@ import 'dart:math' as math;
import 'package:flutter_quill/models/documents/attribute.dart'; import 'package:flutter_quill/models/documents/attribute.dart';
import 'package:flutter_quill/models/documents/nodes/node.dart'; import 'package:flutter_quill/models/documents/nodes/node.dart';
import 'package:quill_delta/quill_delta.dart'; import 'package:flutter_quill/models/quill_delta.dart';
import '../style.dart'; import '../style.dart';
import 'block.dart'; import 'block.dart';

@ -1,7 +1,7 @@
import 'dart:collection'; import 'dart:collection';
import 'package:flutter_quill/models/documents/style.dart'; import 'package:flutter_quill/models/documents/style.dart';
import 'package:quill_delta/quill_delta.dart'; import 'package:flutter_quill/models/quill_delta.dart';
import '../attribute.dart'; import '../attribute.dart';
import 'container.dart'; import 'container.dart';

@ -0,0 +1,688 @@
// Copyright (c) 2018, Anatoly Pulyaevskiy. All rights reserved. Use of this source code
// is governed by a BSD-style license that can be found in the LICENSE file.
/// Implementation of Quill Delta format in Dart.
library quill_delta;
import 'dart:math' as math;
import 'package:collection/collection.dart';
import 'package:quiver_hashcode/hashcode.dart';
const _attributeEquality = DeepCollectionEquality();
const _valueEquality = DeepCollectionEquality();
/// Decoder function to convert raw `data` object into a user-defined data type.
///
/// Useful with embedded content.
typedef DataDecoder = Object Function(Object data);
/// Default data decoder which simply passes through the original value.
Object _passThroughDataDecoder(Object data) => data;
/// Operation performed on a rich-text document.
class Operation {
/// Key of insert operations.
static const String insertKey = 'insert';
/// Key of delete operations.
static const String deleteKey = 'delete';
/// Key of retain operations.
static const String retainKey = 'retain';
/// Key of attributes collection.
static const String attributesKey = 'attributes';
static const List<String> _validKeys = [insertKey, deleteKey, retainKey];
/// Key of this operation, can be "insert", "delete" or "retain".
final String key;
/// Length of this operation.
final int length;
/// Payload of "insert" operation, for other types is set to empty string.
final Object data;
/// Rich-text attributes set by this operation, can be `null`.
Map<String, dynamic> get attributes =>
_attributes == null ? null : Map<String, dynamic>.from(_attributes);
final Map<String, dynamic> _attributes;
Operation._(this.key, this.length, this.data, Map attributes)
: assert(key != null && length != null && data != null),
assert(_validKeys.contains(key), 'Invalid operation key "$key".'),
assert(() {
if (key != Operation.insertKey) return true;
return data is String ? data.length == length : length == 1;
}(), 'Length of insert operation must be equal to the data length.'),
_attributes =
attributes != null ? Map<String, dynamic>.from(attributes) : null;
/// Creates new [Operation] from JSON payload.
///
/// If `dataDecoder` parameter is not null then it is used to additionally
/// decode the operation's data object. Only applied to insert operations.
static Operation fromJson(Map data, {DataDecoder dataDecoder}) {
dataDecoder ??= _passThroughDataDecoder;
final map = Map<String, dynamic>.from(data);
if (map.containsKey(Operation.insertKey)) {
final data = dataDecoder(map[Operation.insertKey]);
final dataLength = data is String ? data.length : 1;
return Operation._(
Operation.insertKey, dataLength, data, map[Operation.attributesKey]);
} else if (map.containsKey(Operation.deleteKey)) {
final int length = map[Operation.deleteKey];
return Operation._(Operation.deleteKey, length, '', null);
} else if (map.containsKey(Operation.retainKey)) {
final int length = map[Operation.retainKey];
return Operation._(
Operation.retainKey, length, '', map[Operation.attributesKey]);
}
throw ArgumentError.value(data, 'Invalid data for Delta operation.');
}
/// Returns JSON-serializable representation of this operation.
Map<String, dynamic> toJson() {
final json = {key: value};
if (_attributes != null) json[Operation.attributesKey] = attributes;
return json;
}
/// Creates operation which deletes [length] of characters.
factory Operation.delete(int length) =>
Operation._(Operation.deleteKey, length, '', null);
/// Creates operation which inserts [text] with optional [attributes].
factory Operation.insert(dynamic data, [Map<String, dynamic> attributes]) =>
Operation._(Operation.insertKey, data is String ? data.length : 1, data,
attributes);
/// Creates operation which retains [length] of characters and optionally
/// applies attributes.
factory Operation.retain(int length, [Map<String, dynamic> attributes]) =>
Operation._(Operation.retainKey, length, '', attributes);
/// Returns value of this operation.
///
/// For insert operations this returns text, for delete and retain - length.
dynamic get value => (key == Operation.insertKey) ? data : length;
/// Returns `true` if this is a delete operation.
bool get isDelete => key == Operation.deleteKey;
/// Returns `true` if this is an insert operation.
bool get isInsert => key == Operation.insertKey;
/// Returns `true` if this is a retain operation.
bool get isRetain => key == Operation.retainKey;
/// Returns `true` if this operation has no attributes, e.g. is plain text.
bool get isPlain => (_attributes == null || _attributes.isEmpty);
/// Returns `true` if this operation sets at least one attribute.
bool get isNotPlain => !isPlain;
/// Returns `true` is this operation is empty.
///
/// An operation is considered empty if its [length] is equal to `0`.
bool get isEmpty => length == 0;
/// Returns `true` is this operation is not empty.
bool get isNotEmpty => length > 0;
@override
bool operator ==(other) {
if (identical(this, other)) return true;
if (other is! Operation) return false;
Operation typedOther = other;
return key == typedOther.key &&
length == typedOther.length &&
_valueEquality.equals(data, typedOther.data) &&
hasSameAttributes(typedOther);
}
/// Returns `true` if this operation has attribute specified by [name].
bool hasAttribute(String name) => isNotPlain && _attributes.containsKey(name);
/// Returns `true` if [other] operation has the same attributes as this one.
bool hasSameAttributes(Operation other) {
return _attributeEquality.equals(_attributes, other._attributes);
}
@override
int get hashCode {
if (_attributes != null && _attributes.isNotEmpty) {
final attrsHash =
hashObjects(_attributes.entries.map((e) => hash2(e.key, e.value)));
return hash3(key, value, attrsHash);
}
return hash2(key, value);
}
@override
String toString() {
final attr = attributes == null ? '' : ' + $attributes';
final text = isInsert
? (data is String
? (data as String).replaceAll('\n', '')
: data.toString())
: '$length';
return '$key$text$attr';
}
}
/// Delta represents a document or a modification of a document as a sequence of
/// insert, delete and retain operations.
///
/// Delta consisting of only "insert" operations is usually referred to as
/// "document delta". When delta includes also "retain" or "delete" operations
/// it is a "change delta".
class Delta {
/// Transforms two attribute sets.
static Map<String, dynamic> transformAttributes(
Map<String, dynamic> a, Map<String, dynamic> b, bool priority) {
if (a == null) return b;
if (b == null) return null;
if (!priority) return b;
final result = b.keys.fold<Map<String, dynamic>>({}, (attributes, key) {
if (!a.containsKey(key)) attributes[key] = b[key];
return attributes;
});
return result.isEmpty ? null : result;
}
/// Composes two attribute sets.
static Map<String, dynamic> composeAttributes(
Map<String, dynamic> a, Map<String, dynamic> b,
{bool keepNull = false}) {
a ??= const {};
b ??= const {};
final result = Map<String, dynamic>.from(a)..addAll(b);
final keys = result.keys.toList(growable: false);
if (!keepNull) {
for (final key in keys) {
if (result[key] == null) result.remove(key);
}
}
return result.isEmpty ? null : result;
}
///get anti-attr result base on base
static Map<String, dynamic> invertAttributes(
Map<String, dynamic> attr, Map<String, dynamic> base) {
attr ??= const {};
base ??= const {};
var baseInverted = base.keys.fold({}, (memo, key) {
if (base[key] != attr[key] && attr.containsKey(key)) {
memo[key] = base[key];
}
return memo;
});
var inverted =
Map<String, dynamic>.from(attr.keys.fold(baseInverted, (memo, key) {
if (base[key] != attr[key] && !base.containsKey(key)) {
memo[key] = null;
}
return memo;
}));
return inverted;
}
final List<Operation> _operations;
int _modificationCount = 0;
Delta._(List<Operation> operations)
: assert(operations != null),
_operations = operations;
/// Creates new empty [Delta].
factory Delta() => Delta._(<Operation>[]);
/// Creates new [Delta] from [other].
factory Delta.from(Delta other) =>
Delta._(List<Operation>.from(other._operations));
/// Creates [Delta] from de-serialized JSON representation.
///
/// If `dataDecoder` parameter is not null then it is used to additionally
/// decode the operation's data object. Only applied to insert operations.
static Delta fromJson(List data, {DataDecoder dataDecoder}) {
return Delta._(data
.map((op) => Operation.fromJson(op, dataDecoder: dataDecoder))
.toList());
}
/// Returns list of operations in this delta.
List<Operation> toList() => List.from(_operations);
/// Returns JSON-serializable version of this delta.
List toJson() => toList();
/// Returns `true` if this delta is empty.
bool get isEmpty => _operations.isEmpty;
/// Returns `true` if this delta is not empty.
bool get isNotEmpty => _operations.isNotEmpty;
/// Returns number of operations in this delta.
int get length => _operations.length;
/// Returns [Operation] at specified [index] in this delta.
Operation operator [](int index) => _operations[index];
/// Returns [Operation] at specified [index] in this delta.
Operation elementAt(int index) => _operations.elementAt(index);
/// Returns the first [Operation] in this delta.
Operation get first => _operations.first;
/// Returns the last [Operation] in this delta.
Operation get last => _operations.last;
@override
bool operator ==(dynamic other) {
if (identical(this, other)) return true;
if (other is! Delta) return false;
Delta typedOther = other;
final comparator =
ListEquality<Operation>(const DefaultEquality<Operation>());
return comparator.equals(_operations, typedOther._operations);
}
@override
int get hashCode => hashObjects(_operations);
/// Retain [count] of characters from current position.
void retain(int count, [Map<String, dynamic> attributes]) {
assert(count >= 0);
if (count == 0) return; // no-op
push(Operation.retain(count, attributes));
}
/// Insert [data] at current position.
void insert(dynamic data, [Map<String, dynamic> attributes]) {
assert(data != null);
if (data is String && data.isEmpty) return; // no-op
push(Operation.insert(data, attributes));
}
/// Delete [count] characters from current position.
void delete(int count) {
assert(count >= 0);
if (count == 0) return;
push(Operation.delete(count));
}
void _mergeWithTail(Operation operation) {
assert(isNotEmpty);
assert(operation != null && last.key == operation.key);
assert(operation.data is String && last.data is String);
final length = operation.length + last.length;
final lastText = last.data as String;
final opText = operation.data as String;
final resultText = lastText + opText;
final index = _operations.length;
_operations.replaceRange(index - 1, index, [
Operation._(operation.key, length, resultText, operation.attributes),
]);
}
/// Pushes new operation into this delta.
///
/// Performs compaction by composing [operation] with current tail operation
/// of this delta, when possible. For instance, if current tail is
/// `insert('abc')` and pushed operation is `insert('123')` then existing
/// tail is replaced with `insert('abc123')` - a compound result of the two
/// operations.
void push(Operation operation) {
if (operation.isEmpty) return;
var index = _operations.length;
final lastOp = _operations.isNotEmpty ? _operations.last : null;
if (lastOp != null) {
if (lastOp.isDelete && operation.isDelete) {
_mergeWithTail(operation);
return;
}
if (lastOp.isDelete && operation.isInsert) {
index -= 1; // Always insert before deleting
final nLastOp = (index > 0) ? _operations.elementAt(index - 1) : null;
if (nLastOp == null) {
_operations.insert(0, operation);
return;
}
}
if (lastOp.isInsert && operation.isInsert) {
if (lastOp.hasSameAttributes(operation) &&
operation.data is String &&
lastOp.data is String) {
_mergeWithTail(operation);
return;
}
}
if (lastOp.isRetain && operation.isRetain) {
if (lastOp.hasSameAttributes(operation)) {
_mergeWithTail(operation);
return;
}
}
}
if (index == _operations.length) {
_operations.add(operation);
} else {
final opAtIndex = _operations.elementAt(index);
_operations.replaceRange(index, index + 1, [operation, opAtIndex]);
}
_modificationCount++;
}
/// Composes next operation from [thisIter] and [otherIter].
///
/// Returns new operation or `null` if operations from [thisIter] and
/// [otherIter] nullify each other. For instance, for the pair `insert('abc')`
/// and `delete(3)` composition result would be empty string.
Operation _composeOperation(DeltaIterator thisIter, DeltaIterator otherIter) {
if (otherIter.isNextInsert) return otherIter.next();
if (thisIter.isNextDelete) return thisIter.next();
final length = math.min(thisIter.peekLength(), otherIter.peekLength());
final thisOp = thisIter.next(length);
final otherOp = otherIter.next(length);
assert(thisOp.length == otherOp.length);
if (otherOp.isRetain) {
final attributes = composeAttributes(
thisOp.attributes,
otherOp.attributes,
keepNull: thisOp.isRetain,
);
if (thisOp.isRetain) {
return Operation.retain(thisOp.length, attributes);
} else if (thisOp.isInsert) {
return Operation.insert(thisOp.data, attributes);
} else {
throw StateError('Unreachable');
}
} else {
// otherOp == delete && thisOp in [retain, insert]
assert(otherOp.isDelete);
if (thisOp.isRetain) return otherOp;
assert(thisOp.isInsert);
// otherOp(delete) + thisOp(insert) => null
}
return null;
}
/// Composes this delta with [other] and returns new [Delta].
///
/// It is not required for this and [other] delta to represent a document
/// delta (consisting only of insert operations).
Delta compose(Delta other) {
final result = Delta();
final thisIter = DeltaIterator(this);
final otherIter = DeltaIterator(other);
while (thisIter.hasNext || otherIter.hasNext) {
final newOp = _composeOperation(thisIter, otherIter);
if (newOp != null) result.push(newOp);
}
return result..trim();
}
/// Transforms next operation from [otherIter] against next operation in
/// [thisIter].
///
/// Returns `null` if both operations nullify each other.
Operation _transformOperation(
DeltaIterator thisIter, DeltaIterator otherIter, bool priority) {
if (thisIter.isNextInsert && (priority || !otherIter.isNextInsert)) {
return Operation.retain(thisIter.next().length);
} else if (otherIter.isNextInsert) {
return otherIter.next();
}
final length = math.min(thisIter.peekLength(), otherIter.peekLength());
final thisOp = thisIter.next(length);
final otherOp = otherIter.next(length);
assert(thisOp.length == otherOp.length);
// At this point only delete and retain operations are possible.
if (thisOp.isDelete) {
// otherOp is either delete or retain, so they nullify each other.
return null;
} else if (otherOp.isDelete) {
return otherOp;
} else {
// Retain otherOp which is either retain or insert.
return Operation.retain(
length,
transformAttributes(thisOp.attributes, otherOp.attributes, priority),
);
}
}
/// Transforms [other] delta against operations in this delta.
Delta transform(Delta other, bool priority) {
final result = Delta();
final thisIter = DeltaIterator(this);
final otherIter = DeltaIterator(other);
while (thisIter.hasNext || otherIter.hasNext) {
final newOp = _transformOperation(thisIter, otherIter, priority);
if (newOp != null) result.push(newOp);
}
return result..trim();
}
/// Removes trailing retain operation with empty attributes, if present.
void trim() {
if (isNotEmpty) {
final last = _operations.last;
if (last.isRetain && last.isPlain) _operations.removeLast();
}
}
/// Concatenates [other] with this delta and returns the result.
Delta concat(Delta other) {
final result = Delta.from(this);
if (other.isNotEmpty) {
// In case first operation of other can be merged with last operation in
// our list.
result.push(other._operations.first);
result._operations.addAll(other._operations.sublist(1));
}
return result;
}
/// Inverts this delta against [base].
///
/// Returns new delta which negates effect of this delta when applied to
/// [base]. This is an equivalent of "undo" operation on deltas.
Delta invert(Delta base) {
final inverted = Delta();
if (base.isEmpty) return inverted;
var baseIndex = 0;
for (final op in _operations) {
if (op.isInsert) {
inverted.delete(op.length);
} else if (op.isRetain && op.isPlain) {
inverted.retain(op.length, null);
baseIndex += op.length;
} else if (op.isDelete || (op.isRetain && op.isNotPlain)) {
final length = op.length;
final sliceDelta = base.slice(baseIndex, baseIndex + length);
sliceDelta.toList().forEach((baseOp) {
if (op.isDelete) {
inverted.push(baseOp);
} else if (op.isRetain && op.isNotPlain) {
var invertAttr = invertAttributes(op.attributes, baseOp.attributes);
inverted.retain(
baseOp.length, invertAttr.isEmpty ? null : invertAttr);
}
});
baseIndex += length;
} else {
throw StateError('Unreachable');
}
}
inverted.trim();
return inverted;
}
/// Returns slice of this delta from [start] index (inclusive) to [end]
/// (exclusive).
Delta slice(int start, [int end]) {
final delta = Delta();
var index = 0;
var opIterator = DeltaIterator(this);
final actualEnd = end ?? double.infinity;
while (index < actualEnd && opIterator.hasNext) {
Operation op;
if (index < start) {
op = opIterator.next(start - index);
} else {
op = opIterator.next(actualEnd - index);
delta.push(op);
}
index += op.length;
}
return delta;
}
/// Transforms [index] against this delta.
///
/// Any "delete" operation before specified [index] shifts it backward, as
/// well as any "insert" operation shifts it forward.
///
/// The [force] argument is used to resolve scenarios when there is an
/// insert operation at the same position as [index]. If [force] is set to
/// `true` (default) then position is forced to shift forward, otherwise
/// position stays at the same index. In other words setting [force] to
/// `false` gives higher priority to the transformed position.
///
/// Useful to adjust caret or selection positions.
int transformPosition(int index, {bool force = true}) {
final iter = DeltaIterator(this);
var offset = 0;
while (iter.hasNext && offset <= index) {
final op = iter.next();
if (op.isDelete) {
index -= math.min(op.length, index - offset);
continue;
} else if (op.isInsert && (offset < index || force)) {
index += op.length;
}
offset += op.length;
}
return index;
}
@override
String toString() => _operations.join('\n');
}
/// Specialized iterator for [Delta]s.
class DeltaIterator {
final Delta delta;
final int _modificationCount;
int _index = 0;
num _offset = 0;
DeltaIterator(this.delta) : _modificationCount = delta._modificationCount;
bool get isNextInsert => nextOperationKey == Operation.insertKey;
bool get isNextDelete => nextOperationKey == Operation.deleteKey;
bool get isNextRetain => nextOperationKey == Operation.retainKey;
String get nextOperationKey {
if (_index < delta.length) {
return delta.elementAt(_index).key;
} else {
return null;
}
}
bool get hasNext => peekLength() < double.infinity;
/// Returns length of next operation without consuming it.
///
/// Returns [double.infinity] if there is no more operations left to iterate.
num peekLength() {
if (_index < delta.length) {
final operation = delta._operations[_index];
return operation.length - _offset;
}
return double.infinity;
}
/// Consumes and returns next operation.
///
/// Optional [length] specifies maximum length of operation to return. Note
/// that actual length of returned operation may be less than specified value.
Operation next([int length = 4294967296]) {
assert(length != null);
if (_modificationCount != delta._modificationCount) {
throw ConcurrentModificationError(delta);
}
if (_index < delta.length) {
final op = delta.elementAt(_index);
final opKey = op.key;
final opAttributes = op.attributes;
final _currentOffset = _offset;
final actualLength = math.min(op.length - _currentOffset, length);
if (actualLength == op.length - _currentOffset) {
_index++;
_offset = 0;
} else {
_offset += actualLength;
}
final opData = op.isInsert && op.data is String
? (op.data as String)
.substring(_currentOffset, _currentOffset + actualLength)
: op.data;
final opIsNotEmpty =
opData is String ? opData.isNotEmpty : true; // embeds are never empty
final opLength = opData is String ? opData.length : 1;
final int opActualLength = opIsNotEmpty ? opLength : actualLength;
return Operation._(opKey, opActualLength, opData, opAttributes);
}
return Operation.retain(length);
}
/// Skips [length] characters in source delta.
///
/// Returns last skipped operation, or `null` if there was nothing to skip.
Operation skip(int length) {
var skipped = 0;
Operation op;
while (skipped < length && hasNext) {
final opLength = peekLength();
final skip = math.min(length - skipped, opLength);
op = next(skip);
skipped += op.length;
}
return op;
}
}

@ -1,6 +1,6 @@
import 'package:flutter_quill/models/documents/attribute.dart'; import 'package:flutter_quill/models/documents/attribute.dart';
import 'package:flutter_quill/models/quill_delta.dart';
import 'package:flutter_quill/models/rules/rule.dart'; import 'package:flutter_quill/models/rules/rule.dart';
import 'package:quill_delta/quill_delta.dart';
abstract class DeleteRule extends Rule { abstract class DeleteRule extends Rule {
const DeleteRule(); const DeleteRule();

@ -1,6 +1,6 @@
import 'package:flutter_quill/models/documents/attribute.dart'; import 'package:flutter_quill/models/documents/attribute.dart';
import 'package:flutter_quill/models/quill_delta.dart';
import 'package:flutter_quill/models/rules/rule.dart'; import 'package:flutter_quill/models/rules/rule.dart';
import 'package:quill_delta/quill_delta.dart';
abstract class FormatRule extends Rule { abstract class FormatRule extends Rule {
const FormatRule(); const FormatRule();

@ -1,7 +1,7 @@
import 'package:flutter_quill/models/documents/attribute.dart'; import 'package:flutter_quill/models/documents/attribute.dart';
import 'package:flutter_quill/models/documents/style.dart'; import 'package:flutter_quill/models/documents/style.dart';
import 'package:flutter_quill/models/quill_delta.dart';
import 'package:flutter_quill/models/rules/rule.dart'; import 'package:flutter_quill/models/rules/rule.dart';
import 'package:quill_delta/quill_delta.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
abstract class InsertRule extends Rule { abstract class InsertRule extends Rule {

@ -1,6 +1,6 @@
import 'package:flutter_quill/models/documents/attribute.dart'; import 'package:flutter_quill/models/documents/attribute.dart';
import 'package:flutter_quill/models/documents/document.dart'; import 'package:flutter_quill/models/documents/document.dart';
import 'package:quill_delta/quill_delta.dart'; import 'package:flutter_quill/models/quill_delta.dart';
import 'delete.dart'; import 'delete.dart';
import 'format.dart'; import 'format.dart';

@ -1,6 +1,6 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'package:quill_delta/quill_delta.dart'; import 'package:flutter_quill/models/quill_delta.dart';
const Set<int> WHITE_SPACE = { const Set<int> WHITE_SPACE = {
0x9, 0x9,

@ -5,8 +5,8 @@ import 'package:flutter_quill/models/documents/attribute.dart';
import 'package:flutter_quill/models/documents/document.dart'; import 'package:flutter_quill/models/documents/document.dart';
import 'package:flutter_quill/models/documents/nodes/embed.dart'; import 'package:flutter_quill/models/documents/nodes/embed.dart';
import 'package:flutter_quill/models/documents/style.dart'; import 'package:flutter_quill/models/documents/style.dart';
import 'package:flutter_quill/models/quill_delta.dart';
import 'package:flutter_quill/utils/diff_delta.dart'; import 'package:flutter_quill/utils/diff_delta.dart';
import 'package:quill_delta/quill_delta.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
class QuillController extends ChangeNotifier { class QuillController extends ChangeNotifier {

@ -20,7 +20,6 @@ import 'package:flutter_quill/widgets/image.dart';
import 'package:flutter_quill/widgets/raw_editor.dart'; import 'package:flutter_quill/widgets/raw_editor.dart';
import 'package:flutter_quill/widgets/responsive_widget.dart'; import 'package:flutter_quill/widgets/responsive_widget.dart';
import 'package:flutter_quill/widgets/text_selection.dart'; import 'package:flutter_quill/widgets/text_selection.dart';
import 'package:string_validator/string_validator.dart';
import 'package:universal_html/prefer_universal/html.dart' as html; import 'package:universal_html/prefer_universal/html.dart' as html;
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@ -125,6 +126,7 @@ class RawEditorState extends EditorState
CursorCont _cursorCont; CursorCont _cursorCont;
ScrollController _scrollController; ScrollController _scrollController;
KeyboardVisibilityController _keyboardVisibilityController; KeyboardVisibilityController _keyboardVisibilityController;
StreamSubscription<bool> _keyboardVisibilitySubscription;
KeyboardListener _keyboardListener; KeyboardListener _keyboardListener;
bool _didAutoFocus = false; bool _didAutoFocus = false;
bool _keyboardVisible = false; bool _keyboardVisible = false;
@ -389,7 +391,7 @@ class RawEditorState extends EditorState
); );
_textInputConnection.setEditingState(_lastKnownRemoteTextEditingValue); _textInputConnection.setEditingState(_lastKnownRemoteTextEditingValue);
_sentRemoteValues.add(_lastKnownRemoteTextEditingValue); // _sentRemoteValues.add(_lastKnownRemoteTextEditingValue);
} }
_textInputConnection.show(); _textInputConnection.show();
} }
@ -698,7 +700,8 @@ class RawEditorState extends EditorState
); );
_keyboardVisibilityController = KeyboardVisibilityController(); _keyboardVisibilityController = KeyboardVisibilityController();
_keyboardVisibilityController.onChange.listen((bool visible) { _keyboardVisibilitySubscription =
_keyboardVisibilityController.onChange.listen((bool visible) {
_keyboardVisible = visible; _keyboardVisible = visible;
if (visible) { if (visible) {
_onChangeTextEditingValue(); _onChangeTextEditingValue();
@ -865,6 +868,7 @@ class RawEditorState extends EditorState
@override @override
void dispose() { void dispose() {
closeConnectionIfNeeded(); closeConnectionIfNeeded();
_keyboardVisibilitySubscription.cancel();
assert(!hasConnection); assert(!hasConnection);
_selectionOverlay?.dispose(); _selectionOverlay?.dispose();
_selectionOverlay = null; _selectionOverlay = null;
@ -901,6 +905,7 @@ class RawEditorState extends EditorState
SchedulerBinding.instance.addPostFrameCallback( SchedulerBinding.instance.addPostFrameCallback(
(Duration _) => _updateOrDisposeSelectionOverlayIfNeeded()); (Duration _) => _updateOrDisposeSelectionOverlayIfNeeded());
if (!mounted) return;
setState(() { setState(() {
// Use widget.controller.value in build() // Use widget.controller.value in build()
// Trigger build and updateChildren // Trigger build and updateChildren
@ -955,6 +960,7 @@ class RawEditorState extends EditorState
} }
_onChangedClipboardStatus() { _onChangedClipboardStatus() {
if (!mounted) return;
setState(() { setState(() {
// Inform the widget that the value of clipboardStatus has changed. // Inform the widget that the value of clipboardStatus has changed.
// Trigger build and updateChildren // Trigger build and updateChildren

@ -144,7 +144,7 @@ class _LinkDialogState extends State<_LinkDialog> {
onChanged: _linkChanged, onChanged: _linkChanged,
), ),
actions: [ actions: [
FlatButton( TextButton(
onPressed: _link.isNotEmpty ? _applyLink : null, onPressed: _link.isNotEmpty ? _applyLink : null,
child: Text('Apply'), child: Text('Apply'),
), ),
@ -512,13 +512,14 @@ class ImageButton extends StatefulWidget {
class _ImageButtonState extends State<ImageButton> { class _ImageButtonState extends State<ImageButton> {
List<PlatformFile> _paths; List<PlatformFile> _paths;
String _directoryPath;
String _extension; String _extension;
final _picker = ImagePicker(); final _picker = ImagePicker();
FileType _pickingType = FileType.any; FileType _pickingType = FileType.any;
Future<String> _pickImage(ImageSource source) async { Future<String> _pickImage(ImageSource source) async {
final PickedFile pickedFile = await _picker.getImage(source: source); final PickedFile pickedFile = await _picker.getImage(source: source);
if (pickedFile == null) return null;
final File file = File(pickedFile.path); final File file = File(pickedFile.path);
if (file == null || widget.onImagePickCallback == null) return null; if (file == null || widget.onImagePickCallback == null) return null;
@ -535,7 +536,6 @@ class _ImageButtonState extends State<ImageButton> {
Future<String> _pickImageWeb() async { Future<String> _pickImageWeb() async {
try { try {
_directoryPath = null;
_paths = (await FilePicker.platform.pickFiles( _paths = (await FilePicker.platform.pickFiles(
type: _pickingType, type: _pickingType,
allowMultiple: false, allowMultiple: false,

@ -77,7 +77,7 @@ packages:
name: file_picker name: file_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.6" version: "3.0.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -89,35 +89,35 @@ packages:
name: flutter_colorpicker name: flutter_colorpicker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.3.5" version: "0.4.0-nullsafety.0"
flutter_keyboard_visibility: flutter_keyboard_visibility:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_keyboard_visibility name: flutter_keyboard_visibility
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.4" version: "5.0.0"
flutter_keyboard_visibility_platform_interface: flutter_keyboard_visibility_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: flutter_keyboard_visibility_platform_interface name: flutter_keyboard_visibility_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.0"
flutter_keyboard_visibility_web: flutter_keyboard_visibility_web:
dependency: transitive dependency: transitive
description: description:
name: flutter_keyboard_visibility_web name: flutter_keyboard_visibility_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.0"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.11" version: "2.0.0"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -141,28 +141,28 @@ packages:
name: http name: http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.2" version: "0.13.0"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.4" version: "4.0.0"
image_picker: image_picker:
dependency: "direct main" dependency: "direct main"
description: description:
name: image_picker name: image_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.7+22" version: "0.7.2"
image_picker_platform_interface: image_picker_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: image_picker_platform_interface name: image_picker_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.6" version: "2.0.1"
js: js:
dependency: transitive dependency: transitive
description: description:
@ -211,13 +211,6 @@ packages:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3"
quill_delta:
dependency: "direct main"
description:
name: quill_delta
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0" version: "2.0.0"
quiver: quiver:
dependency: transitive dependency: transitive
@ -225,7 +218,7 @@ packages:
name: quiver name: quiver
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.5" version: "3.0.0"
quiver_hashcode: quiver_hashcode:
dependency: "direct main" dependency: "direct main"
description: description:
@ -244,7 +237,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -272,7 +265,7 @@ packages:
name: string_validator name: string_validator
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.4" version: "0.2.0-nullsafety.0"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -293,7 +286,7 @@ packages:
name: tuple name: tuple
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "2.0.0-nullsafety.0"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -321,42 +314,42 @@ packages:
name: url_launcher name: url_launcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.7.10" version: "6.0.2"
url_launcher_linux: url_launcher_linux:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_linux name: url_launcher_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+4" version: "2.0.0"
url_launcher_macos: url_launcher_macos:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_macos name: url_launcher_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+9" version: "2.0.0"
url_launcher_platform_interface: url_launcher_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_platform_interface name: url_launcher_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.9" version: "2.0.2"
url_launcher_web: url_launcher_web:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.5+3" version: "2.0.0"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_windows name: url_launcher_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1+3" version: "2.0.0"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -372,5 +365,5 @@ packages:
source: hosted source: hosted
version: "0.1.2" version: "0.1.2"
sdks: sdks:
dart: ">=2.12.0-0.0 <3.0.0" dart: ">=2.12.0 <3.0.0"
flutter: ">=1.22.0" flutter: ">=1.24.0-10.2.pre"

@ -20,10 +20,10 @@ dependencies:
flutter_colorpicker: ^0.3.5 flutter_colorpicker: ^0.3.5
image_picker: ^0.6.7+22 image_picker: ^0.6.7+22
photo_view: ^0.10.3 photo_view: ^0.10.3
universal_html: ^1.2.1 universal_html: ^1.2.4
file_picker: ^2.1.6 file_picker: ^3.0.0-nullsafety.4
string_validator: ^0.1.4 string_validator: ^0.2.0-nullsafety.0
flutter_keyboard_visibility: ^4.0.4 flutter_keyboard_visibility: ^5.0.0-nullsafety.2
dev_dependencies: dev_dependencies:

Loading…
Cancel
Save