From fe01f66b617d7e0c38a90b76aff81be8b6d57b01 Mon Sep 17 00:00:00 2001 From: Rishi Raj Singh <49035175+rish07@users.noreply.github.com> Date: Thu, 25 Feb 2021 15:45:57 +0530 Subject: [PATCH 01/18] Add base64 support in image import (#41) --- app/android/settings_aar.gradle | 1 + app/pubspec.lock | 11 +++++++++-- lib/widgets/editor.dart | 15 ++++++++++++--- pubspec.lock | 9 ++++++++- pubspec.yaml | 2 ++ 5 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 app/android/settings_aar.gradle diff --git a/app/android/settings_aar.gradle b/app/android/settings_aar.gradle new file mode 100644 index 00000000..e7b4def4 --- /dev/null +++ b/app/android/settings_aar.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/app/pubspec.lock b/app/pubspec.lock index 1b38ea1a..88e56154 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -145,7 +145,7 @@ packages: path: ".." relative: true source: path - version: "0.3.2" + version: "0.3.3" flutter_test: dependency: "direct dev" description: flutter @@ -321,7 +321,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -343,6 +343,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + string_validator: + dependency: transitive + description: + name: string_validator + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" term_glyph: dependency: transitive description: diff --git a/lib/widgets/editor.dart b/lib/widgets/editor.dart index 0c4668d0..fe16ca67 100644 --- a/lib/widgets/editor.dart +++ b/lib/widgets/editor.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io' as io; import 'dart:math' as math; @@ -19,6 +20,7 @@ import 'package:flutter_quill/widgets/image.dart'; import 'package:flutter_quill/widgets/raw_editor.dart'; import 'package:flutter_quill/widgets/responsive_widget.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:url_launcher/url_launcher.dart'; @@ -407,9 +409,16 @@ class _QuillEditorSelectionGestureDetectorBuilder getEditor().context, MaterialPageRoute( builder: (context) => ImageTapWrapper( - imageProvider: imageUrl.startsWith('http') - ? NetworkImage(imageUrl) - : FileImage(io.File(blockEmbed.data))), + imageProvider: imageUrl.startsWith('http') + ? NetworkImage(imageUrl) + : (isBase64(imageUrl)) + ? Image.memory( + base64.decode(imageUrl), + ) + : FileImage( + io.File(blockEmbed.data), + ), + ), ), ); } diff --git a/pubspec.lock b/pubspec.lock index 664e3fc8..8cdc5c8f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -244,7 +244,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0" + version: "1.8.1" stack_trace: dependency: transitive description: @@ -266,6 +266,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.0" + string_validator: + dependency: "direct main" + description: + name: string_validator + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 9c6efdc4..e600b104 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,8 +22,10 @@ dependencies: photo_view: ^0.10.3 universal_html: ^1.2.1 file_picker: ^2.1.6 + string_validator: ^0.1.4 flutter_keyboard_visibility: ^4.0.4 + dev_dependencies: flutter_test: sdk: flutter From ec2b754a87bd9339fd096e3c3d1231d59985d70b Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Thu, 25 Feb 2021 10:26:31 -0800 Subject: [PATCH 02/18] Refactor code --- lib/widgets/editor.dart | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/widgets/editor.dart b/lib/widgets/editor.dart index fe16ca67..14d81146 100644 --- a/lib/widgets/editor.dart +++ b/lib/widgets/editor.dart @@ -411,13 +411,9 @@ class _QuillEditorSelectionGestureDetectorBuilder builder: (context) => ImageTapWrapper( imageProvider: imageUrl.startsWith('http') ? NetworkImage(imageUrl) - : (isBase64(imageUrl)) - ? Image.memory( - base64.decode(imageUrl), - ) - : FileImage( - io.File(blockEmbed.data), - ), + : isBase64(imageUrl) + ? Image.memory(base64.decode(imageUrl)) + : FileImage(io.File(imageUrl)), ), ), ); From 2122f9b7cdf75cb64e50c1c65accd1cf1fe209a6 Mon Sep 17 00:00:00 2001 From: pengw00 Date: Thu, 25 Feb 2021 21:50:13 -0600 Subject: [PATCH 03/18] Upgrade prerelease SDK & Bump master version --- CHANGELOG.md | 5 ++++- pubspec.yaml | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f23d572..bc086b88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -102,4 +102,7 @@ * Fix cursor focus issue when keyboard is on. ## [0.3.3] -* More fix on cursor focus issue when keyboard is on. \ No newline at end of file +* More fix on cursor focus issue when keyboard is on. + +## [1.0.0-dev.1] +* Upgrade prerelease SDK & Bump for master \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index e600b104..133a3358 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,12 +1,12 @@ name: flutter_quill description: One client and affiliated collaborator of Flutter Quill is Bullet Journal App. -version: 0.3.3 +version: 1.0.0-dev.1 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill.git environment: - sdk: ">=2.7.0 <3.0.0" + sdk: ">=2.12.0-29.7.beta <3.0.0" flutter: ">=1.17.0 <2.0.0" dependencies: From 08734a8ec36309a8e4cfb7f046c5ed3654543dfd Mon Sep 17 00:00:00 2001 From: pengw00 Date: Thu, 25 Feb 2021 21:59:01 -0600 Subject: [PATCH 04/18] upgrade NullSafe dependencies --- pubspec.yaml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 133a3358..87e8ab72 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,16 +14,16 @@ dependencies: sdk: flutter quill_delta: ^2.0.0 quiver_hashcode: ^2.0.0 - collection: ^1.14.13 - tuple: ^1.0.3 - url_launcher: ^5.7.10 - flutter_colorpicker: ^0.3.5 - image_picker: ^0.6.7+22 + collection: ^1.15.0 + tuple: ^2.0.0-nullsafety.0 + url_launcher: ^6.0.0 + flutter_colorpicker: ^0.4.0-nullsafety.0 + image_picker: ^0.7.0 photo_view: ^0.10.3 - universal_html: ^1.2.1 - file_picker: ^2.1.6 - string_validator: ^0.1.4 - flutter_keyboard_visibility: ^4.0.4 + universal_html: ^1.2.4 + file_picker: ^3.0.0-nullsafety.2 + string_validator: ^0.2.0-nullsafety.0 + flutter_keyboard_visibility: ^5.0.0-nullsafety.2 dev_dependencies: From 78e480053851f69a6364b0e77edd5e2ee6e1583a Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Thu, 25 Feb 2021 20:14:26 -0800 Subject: [PATCH 05/18] Update README.md --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 73c5079e..848f40c7 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,9 @@ The `QuillToolbar` class lets you customise which formatting options are availab Default branch `master` is on channel `master`. To use channel `stable`, switch to branch `stable`. Branch `master` on channel `master` supports web. To run the app on web do the following: +1 + +

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`. From 9211afc47998a9c337ef953cb0c5f27ed8be0fa3 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Fri, 26 Feb 2021 00:33:59 -0800 Subject: [PATCH 06/18] Remove quill_delta dependency --- lib/models/documents/document.dart | 2 +- lib/models/documents/history.dart | 2 +- lib/models/documents/nodes/block.dart | 2 +- lib/models/documents/nodes/leaf.dart | 2 +- lib/models/documents/nodes/line.dart | 2 +- lib/models/documents/nodes/node.dart | 2 +- lib/models/quill_delta.dart | 688 ++++++++++++++++++++++++++ lib/models/rules/delete.dart | 2 +- lib/models/rules/format.dart | 2 +- lib/models/rules/insert.dart | 2 +- lib/models/rules/rule.dart | 2 +- lib/utils/diff_delta.dart | 2 +- lib/widgets/controller.dart | 2 +- pubspec.lock | 7 - pubspec.yaml | 1 - 15 files changed, 700 insertions(+), 20 deletions(-) create mode 100644 lib/models/quill_delta.dart diff --git a/lib/models/documents/document.dart b/lib/models/documents/document.dart index 1573f9af..73391d7b 100644 --- a/lib/models/documents/document.dart +++ b/lib/models/documents/document.dart @@ -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/line.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 '../rules/rule.dart'; diff --git a/lib/models/documents/history.dart b/lib/models/documents/history.dart index e0d26948..ea71afe9 100644 --- a/lib/models/documents/history.dart +++ b/lib/models/documents/history.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 'document.dart'; diff --git a/lib/models/documents/nodes/block.dart b/lib/models/documents/nodes/block.dart index 710de779..d48dc38d 100644 --- a/lib/models/documents/nodes/block.dart +++ b/lib/models/documents/nodes/block.dart @@ -1,4 +1,4 @@ -import 'package:quill_delta/quill_delta.dart'; +import 'package:flutter_quill/models/quill_delta.dart'; import 'container.dart'; import 'line.dart'; diff --git a/lib/models/documents/nodes/leaf.dart b/lib/models/documents/nodes/leaf.dart index aefb574d..13c7b6f0 100644 --- a/lib/models/documents/nodes/leaf.dart +++ b/lib/models/documents/nodes/leaf.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:quill_delta/quill_delta.dart'; +import 'package:flutter_quill/models/quill_delta.dart'; import '../style.dart'; import 'embed.dart'; diff --git a/lib/models/documents/nodes/line.dart b/lib/models/documents/nodes/line.dart index 2727e101..90b19f03 100644 --- a/lib/models/documents/nodes/line.dart +++ b/lib/models/documents/nodes/line.dart @@ -2,7 +2,7 @@ import 'dart:math' as math; import 'package:flutter_quill/models/documents/attribute.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 'block.dart'; diff --git a/lib/models/documents/nodes/node.dart b/lib/models/documents/nodes/node.dart index 6327a1bf..9c52f9c7 100644 --- a/lib/models/documents/nodes/node.dart +++ b/lib/models/documents/nodes/node.dart @@ -1,7 +1,7 @@ import 'dart:collection'; 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 'container.dart'; diff --git a/lib/models/quill_delta.dart b/lib/models/quill_delta.dart new file mode 100644 index 00000000..40a4998b --- /dev/null +++ b/lib/models/quill_delta.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 _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 get attributes => + _attributes == null ? null : Map.from(_attributes); + final Map _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.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.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 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 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 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 transformAttributes( + Map a, Map b, bool priority) { + if (a == null) return b; + if (b == null) return null; + + if (!priority) return b; + + final result = b.keys.fold>({}, (attributes, key) { + if (!a.containsKey(key)) attributes[key] = b[key]; + return attributes; + }); + + return result.isEmpty ? null : result; + } + + /// Composes two attribute sets. + static Map composeAttributes( + Map a, Map b, + {bool keepNull = false}) { + a ??= const {}; + b ??= const {}; + + final result = Map.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 invertAttributes( + Map attr, Map 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.from(attr.keys.fold(baseInverted, (memo, key) { + if (base[key] != attr[key] && !base.containsKey(key)) { + memo[key] = null; + } + return memo; + })); + return inverted; + } + + final List _operations; + + int _modificationCount = 0; + + Delta._(List operations) + : assert(operations != null), + _operations = operations; + + /// Creates new empty [Delta]. + factory Delta() => Delta._([]); + + /// Creates new [Delta] from [other]. + factory Delta.from(Delta other) => + Delta._(List.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 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(const DefaultEquality()); + return comparator.equals(_operations, typedOther._operations); + } + + @override + int get hashCode => hashObjects(_operations); + + /// Retain [count] of characters from current position. + void retain(int count, [Map 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 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([num length = double.infinity]) { + 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; + } +} diff --git a/lib/models/rules/delete.dart b/lib/models/rules/delete.dart index cac220d1..6cbb28ff 100644 --- a/lib/models/rules/delete.dart +++ b/lib/models/rules/delete.dart @@ -1,6 +1,6 @@ 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:quill_delta/quill_delta.dart'; abstract class DeleteRule extends Rule { const DeleteRule(); diff --git a/lib/models/rules/format.dart b/lib/models/rules/format.dart index 13f7b518..755bc0bb 100644 --- a/lib/models/rules/format.dart +++ b/lib/models/rules/format.dart @@ -1,6 +1,6 @@ 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:quill_delta/quill_delta.dart'; abstract class FormatRule extends Rule { const FormatRule(); diff --git a/lib/models/rules/insert.dart b/lib/models/rules/insert.dart index 4715ae87..1973d981 100644 --- a/lib/models/rules/insert.dart +++ b/lib/models/rules/insert.dart @@ -1,7 +1,7 @@ import 'package:flutter_quill/models/documents/attribute.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:quill_delta/quill_delta.dart'; import 'package:tuple/tuple.dart'; abstract class InsertRule extends Rule { diff --git a/lib/models/rules/rule.dart b/lib/models/rules/rule.dart index d0e3bc14..13c7d12e 100644 --- a/lib/models/rules/rule.dart +++ b/lib/models/rules/rule.dart @@ -1,6 +1,6 @@ import 'package:flutter_quill/models/documents/attribute.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 'format.dart'; diff --git a/lib/utils/diff_delta.dart b/lib/utils/diff_delta.dart index 49a06dab..673dacd2 100644 --- a/lib/utils/diff_delta.dart +++ b/lib/utils/diff_delta.dart @@ -1,6 +1,6 @@ import 'dart:math' as math; -import 'package:quill_delta/quill_delta.dart'; +import 'package:flutter_quill/models/quill_delta.dart'; const Set WHITE_SPACE = { 0x9, diff --git a/lib/widgets/controller.dart b/lib/widgets/controller.dart index 5bd6b2d8..abf2a059 100644 --- a/lib/widgets/controller.dart +++ b/lib/widgets/controller.dart @@ -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/nodes/embed.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:quill_delta/quill_delta.dart'; import 'package:tuple/tuple.dart'; class QuillController extends ChangeNotifier { diff --git a/pubspec.lock b/pubspec.lock index 8cdc5c8f..75d48e83 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -212,13 +212,6 @@ packages: url: "https://pub.dartlang.org" 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" quiver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 87e8ab72..4d1bdf7b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,6 @@ environment: dependencies: flutter: sdk: flutter - quill_delta: ^2.0.0 quiver_hashcode: ^2.0.0 collection: ^1.15.0 tuple: ^2.0.0-nullsafety.0 From a2de79ab49b8b9b3b68a88889ac971d01641d0ef Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Fri, 26 Feb 2021 00:48:56 -0800 Subject: [PATCH 07/18] Fix quill_delta next op --- lib/models/quill_delta.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/quill_delta.dart b/lib/models/quill_delta.dart index 40a4998b..3bbda2eb 100644 --- a/lib/models/quill_delta.dart +++ b/lib/models/quill_delta.dart @@ -639,7 +639,7 @@ class DeltaIterator { /// /// Optional [length] specifies maximum length of operation to return. Note /// that actual length of returned operation may be less than specified value. - Operation next([num length = double.infinity]) { + Operation next([int length = 4294967296]) { assert(length != null); if (_modificationCount != delta._modificationCount) { From c7b0eac42d6b8ec63294a9c2b31737884e1f92b1 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Fri, 26 Feb 2021 00:52:43 -0800 Subject: [PATCH 08/18] Fix for unable to remove the last character (#44) --- lib/widgets/raw_editor.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/widgets/raw_editor.dart b/lib/widgets/raw_editor.dart index 1b84e9b2..2ae65da4 100644 --- a/lib/widgets/raw_editor.dart +++ b/lib/widgets/raw_editor.dart @@ -389,7 +389,7 @@ class RawEditorState extends EditorState ); _textInputConnection.setEditingState(_lastKnownRemoteTextEditingValue); - _sentRemoteValues.add(_lastKnownRemoteTextEditingValue); + // _sentRemoteValues.add(_lastKnownRemoteTextEditingValue); } _textInputConnection.show(); } From bef9c6e75485194d618c6605c8c4fac50ccc8305 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Fri, 26 Feb 2021 09:19:00 -0800 Subject: [PATCH 09/18] Revert the sdk version back to sdk: ">=2.7.0 <3.0.0" --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 4d1bdf7b..61460904 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill.git environment: - sdk: ">=2.12.0-29.7.beta <3.0.0" + sdk: ">=2.7.0 <3.0.0" flutter: ">=1.17.0 <2.0.0" dependencies: From 26652ef5724a2d0fbfc48435cc92911d77d05ea3 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Sun, 28 Feb 2021 02:31:17 -0800 Subject: [PATCH 10/18] Improve link handling for tel, mailto and etc --- lib/widgets/editor.dart | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/widgets/editor.dart b/lib/widgets/editor.dart index 14d81146..5c87b4e6 100644 --- a/lib/widgets/editor.dart +++ b/lib/widgets/editor.dart @@ -31,8 +31,12 @@ import 'cursor.dart'; import 'default_styles.dart'; import 'delegate.dart'; -const urlPattern = - r"^((https?|http)://)?([-A-Z0-9.]+)(/[-A-Z0-9+&@#/%=~_|!:,.;]*)?(\?[A-Z0-9+&@#/%=~_|!:‌​,.;]*)?$"; +const linkPrefixes = [ + 'mailto:', // email + 'tel:', // telephone + 'sms:', // SMS + 'http' +]; abstract class EditorState extends State { TextEditingValue getTextEditingValue(); @@ -317,8 +321,6 @@ class _QuillEditorState extends State class _QuillEditorSelectionGestureDetectorBuilder extends EditorTextSelectionGestureDetectorBuilder { - static final urlRegExp = new RegExp(urlPattern, caseSensitive: false); - final _QuillEditorState _state; _QuillEditorSelectionGestureDetectorBuilder(this._state) : super(_state); @@ -394,9 +396,12 @@ class _QuillEditorSelectionGestureDetectorBuilder launchUrl = _launchUrl; } String link = segment.style.attributes[Attribute.link.key].value; - if (getEditor().widget.readOnly && - link != null && - urlRegExp.firstMatch(link.trim()) != null) { + if (getEditor().widget.readOnly && link != null) { + link = link.trim(); + if (!linkPrefixes + .any((linkPrefix) => link.toLowerCase().startsWith(linkPrefix))) { + link = 'https://$link'; + } launchUrl(link); } return false; @@ -452,9 +457,6 @@ class _QuillEditorSelectionGestureDetectorBuilder } void _launchUrl(String url) async { - if (!url.startsWith('http')) { - url = 'https://$url'; - } await launch(url); } From 9a89953f24ad4b3d9415d1306a2206ff45109279 Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Sun, 28 Feb 2021 02:39:08 -0800 Subject: [PATCH 11/18] Expand link prefixes https://beradrian.wordpress.com/2010/01/15/special-links/ --- lib/widgets/editor.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/widgets/editor.dart b/lib/widgets/editor.dart index 5c87b4e6..b49598bc 100644 --- a/lib/widgets/editor.dart +++ b/lib/widgets/editor.dart @@ -35,6 +35,16 @@ const linkPrefixes = [ 'mailto:', // email 'tel:', // telephone 'sms:', // SMS + 'callto:', + 'wtai:', + 'market:', + 'geopoint:', + 'ymsgr:', + 'msnim:', + 'gtalk:', // Google Talk + 'skype:', + 'sip:', // Lync + 'whatsapp:', 'http' ]; From c816a765c3c6a61c347c6c83524edd425088953f Mon Sep 17 00:00:00 2001 From: Xin Yao Date: Sun, 28 Feb 2021 03:08:23 -0800 Subject: [PATCH 12/18] Upgrade version for Improve link handling for tel, mailto and etc --- CHANGELOG.md | 5 ++++- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc086b88..8f996e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,4 +105,7 @@ * More fix on cursor focus issue when keyboard is on. ## [1.0.0-dev.1] -* Upgrade prerelease SDK & Bump for master \ No newline at end of file +* Upgrade prerelease SDK & Bump for master + +## [1.0.0-dev.2] +* Improve link handling for tel, mailto and etc. \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 61460904..48681f97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: One client and affiliated collaborator of Flutter Quill is Bullet Journal App. -version: 1.0.0-dev.1 +version: 1.0.0-dev.2 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill.git From e8a3d4c222a677b64ad4046f821ab5a74cee685f Mon Sep 17 00:00:00 2001 From: li3317 Date: Mon, 1 Mar 2021 11:39:44 -0500 Subject: [PATCH 13/18] close _keyboardVisibilityController.onChange in dispose --- lib/widgets/raw_editor.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/widgets/raw_editor.dart b/lib/widgets/raw_editor.dart index 2ae65da4..294dd2d8 100644 --- a/lib/widgets/raw_editor.dart +++ b/lib/widgets/raw_editor.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:flutter/cupertino.dart'; @@ -125,6 +126,7 @@ class RawEditorState extends EditorState CursorCont _cursorCont; ScrollController _scrollController; KeyboardVisibilityController _keyboardVisibilityController; + StreamSubscription _keyboardVisibilitySubscription; KeyboardListener _keyboardListener; bool _didAutoFocus = false; bool _keyboardVisible = false; @@ -698,12 +700,13 @@ class RawEditorState extends EditorState ); _keyboardVisibilityController = KeyboardVisibilityController(); - _keyboardVisibilityController.onChange.listen((bool visible) { - _keyboardVisible = visible; - if (visible) { - _onChangeTextEditingValue(); - } - }); + _keyboardVisibilitySubscription = + _keyboardVisibilityController.onChange.listen((bool visible) { + _keyboardVisible = visible; + if (visible) { + _onChangeTextEditingValue(); + } + }); _focusAttachment = widget.focusNode.attach(context, onKey: (node, event) => _keyboardListener.handleRawKeyEvent(event)); @@ -865,6 +868,7 @@ class RawEditorState extends EditorState @override void dispose() { closeConnectionIfNeeded(); + _keyboardVisibilitySubscription.cancel(); assert(!hasConnection); _selectionOverlay?.dispose(); _selectionOverlay = null; From 4dfa6a476f42d5232eac5190c3274b32cac03199 Mon Sep 17 00:00:00 2001 From: li3317 Date: Mon, 1 Mar 2021 11:40:47 -0500 Subject: [PATCH 14/18] format --- lib/widgets/raw_editor.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/widgets/raw_editor.dart b/lib/widgets/raw_editor.dart index 294dd2d8..7d95f4a0 100644 --- a/lib/widgets/raw_editor.dart +++ b/lib/widgets/raw_editor.dart @@ -702,11 +702,11 @@ class RawEditorState extends EditorState _keyboardVisibilityController = KeyboardVisibilityController(); _keyboardVisibilitySubscription = _keyboardVisibilityController.onChange.listen((bool visible) { - _keyboardVisible = visible; - if (visible) { - _onChangeTextEditingValue(); - } - }); + _keyboardVisible = visible; + if (visible) { + _onChangeTextEditingValue(); + } + }); _focusAttachment = widget.focusNode.attach(context, onKey: (node, event) => _keyboardListener.handleRawKeyEvent(event)); From a1d1a5cd2a0567755ea397ff586610ab63fe4c6d Mon Sep 17 00:00:00 2001 From: florianh01 <78513002+florianh01@users.noreply.github.com> Date: Fri, 5 Mar 2021 00:32:05 +0100 Subject: [PATCH 15/18] Adding an article to week 02 of articles of the week (#51) --- lib/widgets/raw_editor.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/widgets/raw_editor.dart b/lib/widgets/raw_editor.dart index 7d95f4a0..6b095a27 100644 --- a/lib/widgets/raw_editor.dart +++ b/lib/widgets/raw_editor.dart @@ -905,6 +905,7 @@ class RawEditorState extends EditorState SchedulerBinding.instance.addPostFrameCallback( (Duration _) => _updateOrDisposeSelectionOverlayIfNeeded()); + if (!mounted) return; setState(() { // Use widget.controller.value in build() // Trigger build and updateChildren @@ -959,6 +960,7 @@ class RawEditorState extends EditorState } _onChangedClipboardStatus() { + if (!mounted) return; setState(() { // Inform the widget that the value of clipboardStatus has changed. // Trigger build and updateChildren From b1155e3ea8a3354d62e60bdc01a9f5e5ffb3039d Mon Sep 17 00:00:00 2001 From: li3317 Date: Fri, 5 Mar 2021 22:21:51 -0500 Subject: [PATCH 16/18] support flutter 2.0 --- CHANGELOG.md | 13 ++++++- README.md | 13 +------ lib/widgets/editor.dart | 2 +- lib/widgets/{FakeUi.dart => fake_ui.dart} | 0 lib/widgets/{RealUi.dart => real_ui.dart} | 0 lib/widgets/toolbar.dart | 4 +- pubspec.lock | 46 +++++++++++------------ pubspec.yaml | 2 +- 8 files changed, 38 insertions(+), 42 deletions(-) rename lib/widgets/{FakeUi.dart => fake_ui.dart} (100%) rename lib/widgets/{RealUi.dart => real_ui.dart} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f996e40..0e5cb7dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -104,8 +104,17 @@ ## [0.3.3] * 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 +* Upgrade prerelease SDK & Bump for master. ## [1.0.0-dev.2] -* Improve link handling for tel, mailto and etc. \ No newline at end of file +* Improve link handling for tel, mailto and etc. + +## [1.0.0] +* Support flutter 2.0. \ No newline at end of file diff --git a/README.md b/README.md index 848f40c7..3cb37a95 100644 --- a/README.md +++ b/README.md @@ -70,18 +70,7 @@ The `QuillToolbar` class lets you customise which formatting options are availab ## Web -Default branch `master` is on channel `master`. To use channel `stable`, switch to branch `stable`. -Branch `master` on channel `master` supports web. To run the app on web do the following: - -1 - -

-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. +For web development, use `flutter config --enable-web` for flutter and use [ReactQuill] for React. --- diff --git a/lib/widgets/editor.dart b/lib/widgets/editor.dart index b49598bc..d58fb7e4 100644 --- a/lib/widgets/editor.dart +++ b/lib/widgets/editor.dart @@ -24,7 +24,7 @@ import 'package:string_validator/string_validator.dart'; import 'package:universal_html/prefer_universal/html.dart' as html; import 'package:url_launcher/url_launcher.dart'; -import 'FakeUi.dart' if (dart.library.html) 'RealUi.dart' as ui; +import 'fake_ui.dart' if (dart.library.html) 'real_ui.dart' as ui; import 'box.dart'; import 'controller.dart'; import 'cursor.dart'; diff --git a/lib/widgets/FakeUi.dart b/lib/widgets/fake_ui.dart similarity index 100% rename from lib/widgets/FakeUi.dart rename to lib/widgets/fake_ui.dart diff --git a/lib/widgets/RealUi.dart b/lib/widgets/real_ui.dart similarity index 100% rename from lib/widgets/RealUi.dart rename to lib/widgets/real_ui.dart diff --git a/lib/widgets/toolbar.dart b/lib/widgets/toolbar.dart index 921cb91c..96c87933 100644 --- a/lib/widgets/toolbar.dart +++ b/lib/widgets/toolbar.dart @@ -144,7 +144,7 @@ class _LinkDialogState extends State<_LinkDialog> { onChanged: _linkChanged, ), actions: [ - FlatButton( + TextButton( onPressed: _link.isNotEmpty ? _applyLink : null, child: Text('Apply'), ), @@ -512,7 +512,6 @@ class ImageButton extends StatefulWidget { class _ImageButtonState extends State { List _paths; - String _directoryPath; String _extension; final _picker = ImagePicker(); FileType _pickingType = FileType.any; @@ -535,7 +534,6 @@ class _ImageButtonState extends State { Future _pickImageWeb() async { try { - _directoryPath = null; _paths = (await FilePicker.platform.pickFiles( type: _pickingType, allowMultiple: false, diff --git a/pubspec.lock b/pubspec.lock index 75d48e83..59f3c93b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -77,7 +77,7 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "3.0.0" flutter: dependency: "direct main" description: flutter @@ -89,35 +89,35 @@ packages: name: flutter_colorpicker url: "https://pub.dartlang.org" source: hosted - version: "0.3.5" + version: "0.4.0-nullsafety.0" flutter_keyboard_visibility: dependency: "direct main" description: name: flutter_keyboard_visibility url: "https://pub.dartlang.org" source: hosted - version: "4.0.4" + version: "5.0.0" flutter_keyboard_visibility_platform_interface: dependency: transitive description: name: flutter_keyboard_visibility_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0" flutter_keyboard_visibility_web: dependency: transitive description: name: flutter_keyboard_visibility_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle url: "https://pub.dartlang.org" source: hosted - version: "1.0.11" + version: "2.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -141,28 +141,28 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.0" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" image_picker: dependency: "direct main" description: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.6.7+22" + version: "0.7.2" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "2.0.1" js: dependency: transitive description: @@ -211,14 +211,14 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.0" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.0" quiver_hashcode: dependency: "direct main" description: @@ -237,7 +237,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.0" stack_trace: dependency: transitive description: @@ -265,7 +265,7 @@ packages: name: string_validator url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.2.0-nullsafety.0" term_glyph: dependency: transitive description: @@ -286,7 +286,7 @@ packages: name: tuple url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.0-nullsafety.0" typed_data: dependency: transitive description: @@ -314,42 +314,42 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.7.10" + version: "6.0.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+4" + version: "2.0.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+9" + version: "2.0.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "2.0.2" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.5+3" + version: "2.0.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+3" + version: "2.0.0" vector_math: dependency: transitive description: @@ -365,5 +365,5 @@ packages: source: hosted version: "0.1.2" sdks: - dart: ">=2.12.0-0.0 <3.0.0" - flutter: ">=1.22.0" + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.24.0-10.2.pre" diff --git a/pubspec.yaml b/pubspec.yaml index 48681f97..68e79da2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: One client and affiliated collaborator of Flutter Quill is Bullet Journal App. -version: 1.0.0-dev.2 +version: 1.0.0 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill.git From e34998ab648f1cbcda595e7b58e62f6d8c18b87e Mon Sep 17 00:00:00 2001 From: li3317 Date: Fri, 5 Mar 2021 22:45:33 -0500 Subject: [PATCH 17/18] upgrade gradle version --- app/android/app/build.gradle | 2 +- app/android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- app/pubspec.lock | 57 ++++++++----------- pubspec.yaml | 2 +- 5 files changed, 29 insertions(+), 36 deletions(-) diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle index 4d2c8dd1..b53f38f0 100644 --- a/app/android/app/build.gradle +++ b/app/android/app/build.gradle @@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) { 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.") + throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") } def flutterVersionCode = localProperties.getProperty('flutter.versionCode') diff --git a/app/android/build.gradle b/app/android/build.gradle index e0d7ae2c..11e3d090 100644 --- a/app/android/build.gradle +++ b/app/android/build.gradle @@ -5,7 +5,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.android.tools.build:gradle:3.6.3' } } diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties index 296b146b..c69d51db 100644 --- a/app/android/gradle/wrapper/gradle-wrapper.properties +++ b/app/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME 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 \ No newline at end of file diff --git a/app/pubspec.lock b/app/pubspec.lock index 88e56154..407518db 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -98,7 +98,7 @@ packages: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "2.1.6" + version: "3.0.0-nullsafety.2" flutter: dependency: "direct main" description: flutter @@ -110,42 +110,42 @@ packages: name: flutter_colorpicker url: "https://pub.dartlang.org" source: hosted - version: "0.3.5" + version: "0.4.0-nullsafety.0" flutter_keyboard_visibility: dependency: transitive description: name: flutter_keyboard_visibility url: "https://pub.dartlang.org" source: hosted - version: "4.0.4" + version: "5.0.0-nullsafety.3" flutter_keyboard_visibility_platform_interface: dependency: transitive description: name: flutter_keyboard_visibility_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0-nullsafety.0" flutter_keyboard_visibility_web: dependency: transitive description: name: flutter_keyboard_visibility_web url: "https://pub.dartlang.org" source: hosted - version: "1.0.1" + version: "2.0.0-nullsafety.0" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle url: "https://pub.dartlang.org" source: hosted - version: "1.0.11" + version: "2.0.0" flutter_quill: dependency: "direct main" description: path: ".." relative: true source: path - version: "0.3.3" + version: "1.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -169,28 +169,28 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.2" + version: "0.13.0" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" image_picker: dependency: transitive description: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.6.7+22" + version: "0.7.2" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "2.0.0" js: dependency: transitive description: @@ -260,7 +260,7 @@ packages: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.9.2" + version: "1.11.0" photo_view: dependency: transitive description: @@ -281,7 +281,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.1.0-nullsafety.2" process: dependency: transitive description: @@ -289,20 +289,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.1.0" - quill_delta: - dependency: transitive - description: - name: quill_delta - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.0" quiver_hashcode: dependency: transitive description: @@ -321,7 +314,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.0" stack_trace: dependency: transitive description: @@ -349,7 +342,7 @@ packages: name: string_validator url: "https://pub.dartlang.org" source: hosted - version: "0.1.4" + version: "0.2.0-nullsafety.0" term_glyph: dependency: transitive description: @@ -370,7 +363,7 @@ packages: name: tuple url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "2.0.0-nullsafety.0" typed_data: dependency: transitive description: @@ -398,42 +391,42 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.7.10" + version: "6.0.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+4" + version: "2.0.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+9" + version: "2.0.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.9" + version: "2.0.1" url_launcher_web: dependency: transitive description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.5+1" + version: "2.0.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+3" + version: "2.0.0" vector_math: dependency: transitive description: @@ -463,5 +456,5 @@ packages: source: hosted version: "0.1.2" sdks: - dart: ">=2.12.0-0.0 <3.0.0" - flutter: ">=1.22.0" + dart: ">=2.12.0-259.9.beta <3.0.0" + flutter: ">=1.24.0-10.2.pre" diff --git a/pubspec.yaml b/pubspec.yaml index 68e79da2..3e87a468 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: image_picker: ^0.7.0 photo_view: ^0.10.3 universal_html: ^1.2.4 - file_picker: ^3.0.0-nullsafety.2 + file_picker: ^3.0.0-nullsafety.4 string_validator: ^0.2.0-nullsafety.0 flutter_keyboard_visibility: ^5.0.0-nullsafety.2 From d2e6c1314657c7a4e497fd581296d24686a2e0f8 Mon Sep 17 00:00:00 2001 From: Jochen Date: Sat, 6 Mar 2021 12:29:40 +0800 Subject: [PATCH 18/18] Update toolbar.dart (#55) fix --- lib/widgets/toolbar.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/widgets/toolbar.dart b/lib/widgets/toolbar.dart index 96c87933..92dccc3a 100644 --- a/lib/widgets/toolbar.dart +++ b/lib/widgets/toolbar.dart @@ -518,6 +518,8 @@ class _ImageButtonState extends State { Future _pickImage(ImageSource source) async { final PickedFile pickedFile = await _picker.getImage(source: source); + if (pickedFile == null) return null; + final File file = File(pickedFile.path); if (file == null || widget.onImagePickCallback == null) return null;