diff --git a/.github/ISSUE_TEMPLATE/issue-template.md b/.github/ISSUE_TEMPLATE/issue-template.md
new file mode 100644
index 00000000..b4f552c8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/issue-template.md
@@ -0,0 +1,17 @@
+---
+name: Issue template
+about: Common things to fill
+title: "[Web] or [Mobile] or [Desktop]"
+labels: ''
+assignees: ''
+
+---
+
+My issue is about [Web]
+My issue is about [Mobile]
+My issue is about [Desktop]
+
+I have tried running `example` directory successfully before creating an issue here.
+
+Please note that we are using latest flutter version in stable channel on branch master. If you are using beta or master channel, or you are not using latest flutter version in stable channel, you may experience error.
+
diff --git a/.gitignore b/.gitignore
index 1985397a..e73faed9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -72,3 +72,4 @@ build/
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
+pubspec.lock
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45a82501..669ecd90 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,81 +1,251 @@
-## [0.0.1]
+## [1.9.1]
+* Cursor jumps to the most appropriate offset to display selection.
-* Rich text editor based on Quill Delta.
+## [1.9.0]
+* Support inline image.
-## [0.0.2]
-* Support image upload and launch url in read-only mode.
+## [1.8.3]
+* Updated quill_delta.
-## [0.0.3]
-* Update home page meta data.
+## [1.8.2]
+* Support mobile image alignment.
-## [0.0.4]
-* Update example.
+## [1.8.1]
+* Support mobile custom size image.
-## [0.0.5]
-* Update example.
+## [1.8.0]
+* Support entering link for image/video.
-## [0.0.6]
-* More toolbar functionality.
+## [1.7.3]
+* Bumps photo_view version.
-## [0.0.7]
-* Handle multiple image inserts.
+## [1.7.2]
+* Fix static analysis error.
-## [0.0.8]
-* Fix launching url.
+## [1.7.1]
+* Support Youtube video.
-## [0.0.9]
-* Handle rgba color.
+## [1.7.0]
+* Support video.
-## [0.1.0]
-* Fix insert image.
+## [1.6.4]
+* Bug fix for clear format button.
-## [0.1.1]
-* Fix cursor issue when undo.
+## [1.6.3]
+* Fixed dragging right handle scrolling issue.
-## [0.1.2]
-* Handle more text colors.
+## [1.6.2]
+* Fixed the position of the selection status drag handle.
-## [0.1.3]
-* Handle cursor position change when undo/redo.
+## [1.6.1]
+* Upgrade image_picker and flutter_colorpicker.
-## [0.1.4]
-* Handle url with trailing spaces.
+## [1.6.0]
+* Support Multi Row Toolbar.
-## [0.1.5]
-* Support text alignment.
+## [1.5.0]
+* Remove file_picker dependency.
-## [0.1.6]
-* Fix getExtentEndpointForSelection.
+## [1.4.1]
+* Remove filesystem_picker dependency.
-## [0.1.7]
-* Support checked/unchecked list.
+## [1.4.0]
+* Remove path_provider dependency.
-## [0.1.8]
-* Support font and size attributes.
+## [1.3.4]
+* Add option to paintCursorAboveText.
-## [0.2.0]
-* Add checked/unchecked list button in toolbar.
+## [1.3.3]
+* Upgrade file_picker version.
-## [0.2.1]
+## [1.3.2]
+* Fix copy/paste bug.
+
+## [1.3.1]
+* New logo.
+
+## [1.3.0]
+* Support flutter 2.2.0.
+
+## [1.2.2]
+* Checkbox supports tapping.
+
+## [1.2.1]
+* Indented position not holding while editing.
+
+## [1.2.0]
+* Fix image button cancel causes crash.
+
+## [1.1.8]
+* Fix height of empty line bug.
+
+## [1.1.7]
+* Fix text selection in read-only mode.
+
+## [1.1.6]
+* Remove universal_html dependency.
+
+## [1.1.5]
+* Enable "Select", "Select All" and "Copy" in read-only mode.
+
+## [1.1.4]
+* Fix text selection issue.
+
+## [1.1.3]
+* Update example folder.
+
+## [1.1.2]
+* Add pedantic.
+
+## [1.1.1]
+* Base64 image support.
+
+## [1.1.0]
+* Support null safety.
+
+## [1.0.9]
+* Web support for raw editor and keyboard listener.
+
+## [1.0.8]
+* Support token attribute.
+
+## [1.0.7]
+* Fix crash on web (dart:io).
+
+## [1.0.6]
+* Add desktop support - WINDOWS, MACOS and LINUX.
+
+## [1.0.5]
+* Bug fix: Can not insert newline when Bold is toggled ON.
+
+## [1.0.4]
+* Upgrade photo_view to ^0.11.0.
+
+## [1.0.3]
+* Fix issue that text is not displayed while typing [WEB].
+
+## [1.0.2]
+* Update toolbar in sample home page.
+
+## [1.0.1]
+* Fix static analysis errors.
+
+## [1.0.0]
+* Support flutter 2.0.
+
+## [1.0.0-dev.2]
+* Improve link handling for tel, mailto and etc.
+
+## [1.0.0-dev.1]
+* Upgrade prerelease SDK & Bump for master.
+
+## [0.3.5]
+* Fix for cursor focus issues when keyboard is on.
+
+## [0.3.4]
+* Improve link handling for tel, mailto and etc.
+
+## [0.3.3]
+* More fix on cursor focus issue when keyboard is on.
+
+## [0.3.2]
+* Fix cursor focus issue when keyboard is on.
+
+## [0.3.1]
+* cursor focus when keyboard is on.
+
+## [0.3.0]
+* Line Height calculated based on font size.
+
+## [0.2.12]
+* Support placeholder.
+
+## [0.2.11]
* Fix static analysis error.
-## [0.2.2]
-* Update git repo.
+## [0.2.10]
+* Update TextInputConfiguration autocorrect to true in stable branch.
-## [0.2.3]
-* Support custom styles and image on local device storage without uploading.
+## [0.2.9]
+* Update TextInputConfiguration autocorrect to true.
-## [0.2.4]
-* Support the use of custom icon size in toolbar.
+## [0.2.8]
+* Support display local image besides network image in stable branch.
-## [0.2.5]
-* Toggle text/background color button in toolbar.
+## [0.2.7]
+* Support display local image besides network image.
## [0.2.6]
* Fix cursor after pasting.
-## [0.2.7]
-* Support display local image besides network image.
+## [0.2.5]
+* Toggle text/background color button in toolbar.
-## [0.2.8]
-* Support display local image besides network image in stable branch.
\ No newline at end of file
+## [0.2.4]
+* Support the use of custom icon size in toolbar.
+
+## [0.2.3]
+* Support custom styles and image on local device storage without uploading.
+
+## [0.2.2]
+* Update git repo.
+
+## [0.2.1]
+* Fix static analysis error.
+
+## [0.2.0]
+* Add checked/unchecked list button in toolbar.
+
+## [0.1.8]
+* Support font and size attributes.
+
+## [0.1.7]
+* Support checked/unchecked list.
+
+## [0.1.6]
+* Fix getExtentEndpointForSelection.
+
+## [0.1.5]
+* Support text alignment.
+
+## [0.1.4]
+* Handle url with trailing spaces.
+
+## [0.1.3]
+* Handle cursor position change when undo/redo.
+
+## [0.1.2]
+* Handle more text colors.
+
+## [0.1.1]
+* Fix cursor issue when undo.
+
+## [0.1.0]
+* Fix insert image.
+
+## [0.0.9]
+* Handle rgba color.
+
+## [0.0.8]
+* Fix launching url.
+
+## [0.0.7]
+* Handle multiple image inserts.
+
+## [0.0.6]
+* More toolbar functionality.
+
+## [0.0.5]
+* Update example.
+
+## [0.0.4]
+* Update example.
+
+## [0.0.3]
+* Update home page meta data.
+
+## [0.0.2]
+* Support image upload and launch url in read-only mode.
+
+## [0.0.1]
+* Rich text editor based on Quill Delta.
\ No newline at end of file
diff --git a/README.md b/README.md
index 3b94eaaf..4d5a2756 100644
--- a/README.md
+++ b/README.md
@@ -1,40 +1,142 @@
+
+
+
+A rich text editor for Flutter
-
-
-
-# FlutterQuill
-
-Rich text editor and a [Quill] component for [Flutter].
-
-This library is a WYSIWYG editor built for the modern mobile platform only and web is under development. You can join [Slack Group] for discussion.
-
-https://pub.dev/packages/flutter_quill
+[![MIT License][license-badge]][license-link]
+[![PRs Welcome][prs-badge]][prs-link]
+[![Watch on GitHub][github-watch-badge]][github-watch-link]
+[![Star on GitHub][github-star-badge]][github-star-link]
+[![Watch on GitHub][github-forks-badge]][github-forks-link]
+
+[license-badge]: https://img.shields.io/github/license/singerdmx/flutter-quill.svg?style=for-the-badge
+[license-link]: https://github.com/singerdmx/flutter-quill/blob/master/LICENSE
+[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge
+[prs-link]: https://github.com/singerdmx/flutter-quill/issues
+[github-watch-badge]: https://img.shields.io/github/watchers/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff
+[github-watch-link]: https://github.com/singerdmx/flutter-quill/watchers
+[github-star-badge]: https://img.shields.io/github/stars/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff
+[github-star-link]: https://github.com/singerdmx/flutter-quill/stargazers
+[github-forks-badge]: https://img.shields.io/github/forks/singerdmx/flutter-quill.svg?style=for-the-badge&logo=github&logoColor=ffffff
+[github-forks-link]: https://github.com/singerdmx/flutter-quill/network/members
+
+
+FlutterQuill is a rich text editor and a [Quill] component for [Flutter].
+
+This library is a WYSIWYG editor built for the modern mobile platform, with web compatibility under development. You can join our [Slack Group] for discussion.
+
+Demo App: https://bulletjournal.us/home/index.html
+
+Pub: https://pub.dev/packages/flutter_quill
+
+## Usage
+
+See the `example` directory for a minimal example of how to use FlutterQuill. You typically just need to instantiate a controller:
+
+```
+QuillController _controller = QuillController.basic();
+```
+
+and then embed the toolbar and the editor, within your app. For example:
+
+```dart
+Column(
+ children: [
+ QuillToolbar.basic(controller: _controller),
+ Expanded(
+ child: Container(
+ child: QuillEditor.basic(
+ controller: _controller,
+ readOnly: false, // true for view only mode
+ ),
+ ),
+ )
+ ],
+)
+```
+Check out [Sample Page] for advanced usage.
+
+## Input / Output
This library uses [Quill] as an internal data format.
-Use `_controller.document.toDelta()` to extract it or use `_controller.document.toPlainText()` for plain text.
-
-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) 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.
-
----
-
-
-
-
-
-
-One client and affiliated collaborator of **[FlutterQuill]** is Bullet Journal App: https://bulletjournal.us/home/index.html
-
+
+* Use `_controller.document.toDelta()` to extract the deltas.
+* Use `_controller.document.toPlainText()` to extract plain text.
+
+FlutterQuill provides some JSON serialisation support, so that you can save and open documents. To save a document as JSON, do something like the following:
+
+```
+var json = jsonEncode(_controller.document.toDelta().toJson());
+```
+
+You can then write this to storage.
+
+To open a FlutterQuill editor with an existing JSON representation that you've previously stored, you can do something like this:
+
+```
+var myJSON = jsonDecode(incomingJSONText);
+_controller = QuillController(
+ document: Document.fromJson(myJSON),
+ selection: TextSelection.collapsed(offset: 0));
+```
+
+## Configuration
+
+The `QuillToolbar` class lets you customise which formatting options are available.
+[Sample Page] provides sample code for advanced usage and configuration.
+
+## Web
+
+For web development, use `flutter config --enable-web` for flutter and use [ReactQuill] for React.
+
+It is required to provide `EmbedBuilder`, e.g. [defaultEmbedBuilderWeb](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/universal_ui/universal_ui.dart#L28).
+Also it is required to provide `webImagePickImpl`, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L212).
+
+## Desktop
+
+It is required to provide `filePickImpl` for toolbar image button, e.g. [Sample Page](https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart#L192).
+
+## Custom Size Image for Mobile
+
+Define `mobileWidth`, `mobileHeight`, `mobileMargin`, `mobileAlignment` as follows:
+```
+{
+ "insert": {
+ "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
+ },
+ "attributes":{
+ "style":"mobileWidth: 50; mobileHeight: 50; mobileMargin: 10; mobileAlignment: topLeft"
+ }
+}
+```
+
+## Migrate Zefyr Data
+
+Check out [code](https://github.com/jwehrle/zefyr_quill_convert) and [doc](https://docs.google.com/document/d/1FUSrpbarHnilb7uDN5J5DDahaI0v1RMXBjj4fFSpSuY/edit?usp=sharing).
+
+---
+
+
+
+
+
+
+
+
+
+
+
+
+## Sponsors
+
+
+
+
[Quill]: https://quilljs.com/docs/formats
-[Flutter]: https://github.com/flutter/flutter
-[FlutterQuill]: https://pub.dev/packages/flutter_quill
-[ReactQuill]: https://github.com/zenoamaro/react-quill
+[Flutter]: https://github.com/flutter/flutter
+[FlutterQuill]: https://pub.dev/packages/flutter_quill
+[ReactQuill]: https://github.com/zenoamaro/react-quill
[Slack Group]: https://join.slack.com/t/bulletjournal1024/shared_invite/zt-fys7t9hi-ITVU5PGDen1rNRyCjdcQ2g
+[Sample Page]: https://github.com/singerdmx/flutter-quill/blob/master/example/lib/pages/home_page.dart
diff --git a/analysis_options.yaml b/analysis_options.yaml
new file mode 100644
index 00000000..7749c861
--- /dev/null
+++ b/analysis_options.yaml
@@ -0,0 +1,37 @@
+include: package:pedantic/analysis_options.yaml
+
+analyzer:
+ errors:
+ undefined_prefixed_name: ignore
+ unsafe_html: ignore
+linter:
+ rules:
+ - always_declare_return_types
+ - always_put_required_named_parameters_first
+ - annotate_overrides
+ - avoid_empty_else
+ - avoid_escaping_inner_quotes
+ - avoid_print
+ - avoid_redundant_argument_values
+ - avoid_types_on_closure_parameters
+ - avoid_void_async
+ - cascade_invocations
+ - directives_ordering
+ - lines_longer_than_80_chars
+ - omit_local_variable_types
+ - prefer_const_constructors
+ - prefer_const_constructors_in_immutables
+ - prefer_const_declarations
+ - prefer_final_fields
+ - prefer_final_in_for_each
+ - prefer_final_locals
+ - prefer_initializing_formals
+ - prefer_int_literals
+ - prefer_interpolation_to_compose_strings
+ - prefer_relative_imports
+ - prefer_single_quotes
+ - sort_constructors_first
+ - sort_unnamed_constructors_first
+ - unnecessary_lambdas
+ - unnecessary_parenthesis
+ - unnecessary_string_interpolations
diff --git a/app/ios/Flutter/Debug.xcconfig b/app/ios/Flutter/Debug.xcconfig
deleted file mode 100644
index 592ceee8..00000000
--- a/app/ios/Flutter/Debug.xcconfig
+++ /dev/null
@@ -1 +0,0 @@
-#include "Generated.xcconfig"
diff --git a/app/ios/Flutter/Release.xcconfig b/app/ios/Flutter/Release.xcconfig
deleted file mode 100644
index 592ceee8..00000000
--- a/app/ios/Flutter/Release.xcconfig
+++ /dev/null
@@ -1 +0,0 @@
-#include "Generated.xcconfig"
diff --git a/app/lib/pages/home_page.dart b/app/lib/pages/home_page.dart
deleted file mode 100644
index 46caa40e..00000000
--- a/app/lib/pages/home_page.dart
+++ /dev/null
@@ -1,173 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-import 'dart:io';
-import 'dart:math';
-
-import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter_quill/models/documents/attribute.dart';
-import 'package:flutter_quill/models/documents/document.dart';
-import 'package:flutter_quill/widgets/controller.dart';
-import 'package:flutter_quill/widgets/default_styles.dart';
-import 'package:flutter_quill/widgets/editor.dart';
-import 'package:flutter_quill/widgets/toolbar.dart';
-import 'package:tuple/tuple.dart';
-
-import 'read_only_page.dart';
-
-class HomePage extends StatefulWidget {
- @override
- _HomePageState createState() => _HomePageState();
-}
-
-class _HomePageState extends State {
- QuillController _controller;
- final FocusNode _focusNode = FocusNode();
-
- @override
- void initState() {
- super.initState();
- _loadFromAssets();
- }
-
- Future _loadFromAssets() async {
- try {
- final result = await rootBundle.loadString('assets/sample_data.json');
- final doc = Document.fromJson(jsonDecode(result));
- setState(() {
- _controller = QuillController(
- document: doc, selection: TextSelection.collapsed(offset: 0));
- });
- } catch (error) {
- final doc = Document()..insert(0, 'Empty asset');
- setState(() {
- _controller = QuillController(
- document: doc, selection: TextSelection.collapsed(offset: 0));
- });
- }
- }
-
- @override
- Widget build(BuildContext context) {
- if (_controller == null) {
- return Scaffold(body: Center(child: Text('Loading...')));
- }
-
- return Scaffold(
- appBar: AppBar(
- backgroundColor: Colors.grey.shade800,
- elevation: 0,
- centerTitle: false,
- title: Text(
- 'Flutter Quill',
- ),
- actions: [],
- ),
- drawer: Material(
- color: Colors.grey.shade800,
- child: _buildMenuBar(context),
- ),
- floatingActionButton: FloatingActionButton(
- onPressed: (){
- int option = Random().nextInt(30);
- print(option);
- _controller.formatSelection(SizeAttribute("${option.toDouble()}"));
- },
- ),
- body: RawKeyboardListener(
- focusNode: FocusNode(),
- onKey: (RawKeyEvent event) {
- if (event.data.isControlPressed && event.character == 'b') {
- if (_controller
- .getSelectionStyle()
- .attributes
- .keys
- .contains("bold")) {
- _controller
- .formatSelection(Attribute.clone(Attribute.bold, null));
- } else {
- _controller.formatSelection(Attribute.bold);
- print("not bold");
- }
- }
- },
- child: _buildWelcomeEditor(context),
- ),
- );
- }
-
- Widget _buildWelcomeEditor(BuildContext context) {
- return Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [
- Expanded(
- child: Container(
- color: Colors.white,
- padding: const EdgeInsets.only(left: 16.0, right: 16.0),
- child: QuillEditor(
- controller: _controller,
- scrollController: ScrollController(),
- scrollable: true,
- focusNode: _focusNode,
- autoFocus: false,
- readOnly: false,
- enableInteractiveSelection: true,
- expands: false,
- padding: EdgeInsets.zero,
- customStyles: DefaultStyles(
- h1: DefaultTextBlockStyle(
- TextStyle(
- fontSize: 32.0,
- color: Colors.black,
- height:1.15,
- fontWeight: FontWeight.w300,
- ),
- Tuple2(16.0, 0.0),
- Tuple2(0.0, 0.0),
- null),
- sizeSmall: TextStyle(fontSize: 9.0),
- sizeHuge: TextStyle(fontSize:64.0)
- ),
- ),
- ),
- ),
- Container(
- child: QuillToolbar.basic(
- controller: _controller,
- uploadFileCallback: _fakeUploadImageCallBack),
- )
- ],
- );
- }
-
- Future _fakeUploadImageCallBack(File file) async {
- print(file);
- var completer = new Completer();
- completer.complete(
- 'https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png');
- return completer.future;
- }
-
- Widget _buildMenuBar(BuildContext context) {
- final itemStyle = TextStyle(color: Colors.white);
- return ListView(
- children: [
- ListTile(
- title: Text('Read only demo', style: itemStyle),
- dense: true,
- visualDensity: VisualDensity.compact,
- onTap: _readOnly,
- )
- ],
- );
- }
-
- void _readOnly() {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (BuildContext context) => ReadOnlyPage(),
- ),
- );
- }
-}
diff --git a/app/lib/widgets/field.dart b/app/lib/widgets/field.dart
deleted file mode 100644
index 1358950d..00000000
--- a/app/lib/widgets/field.dart
+++ /dev/null
@@ -1,143 +0,0 @@
-import 'package:flutter/cupertino.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_quill/widgets/controller.dart';
-import 'package:flutter_quill/widgets/delegate.dart';
-import 'package:flutter_quill/widgets/editor.dart';
-
-class QuillField extends StatefulWidget {
- final QuillController controller;
- final FocusNode focusNode;
- final ScrollController scrollController;
- final bool scrollable;
- final EdgeInsetsGeometry padding;
- final bool autofocus;
- final bool showCursor;
- final bool readOnly;
- final bool enableInteractiveSelection;
- final double minHeight;
- final double maxHeight;
- final bool expands;
- final TextCapitalization textCapitalization;
- final Brightness keyboardAppearance;
- final ScrollPhysics scrollPhysics;
- final ValueChanged onLaunchUrl;
- final InputDecoration decoration;
- final Widget toolbar;
- final EmbedBuilder embedBuilder;
- final StyleBuilder styleBuilder;
- QuillField({
- Key key,
- @required this.controller,
- this.focusNode,
- this.scrollController,
- this.scrollable = true,
- this.padding = EdgeInsets.zero,
- this.autofocus = false,
- this.showCursor = true,
- this.readOnly = false,
- this.enableInteractiveSelection = true,
- this.minHeight,
- this.maxHeight,
- this.expands = false,
- this.textCapitalization = TextCapitalization.sentences,
- this.keyboardAppearance = Brightness.light,
- this.scrollPhysics,
- this.onLaunchUrl,
- this.decoration,
- this.toolbar,
- this.embedBuilder, this.styleBuilder,
- }) : super(key: key);
-
- @override
- _QuillFieldState createState() => _QuillFieldState();
-}
-
-class _QuillFieldState extends State {
- bool _focused;
-
- void _editorFocusChanged() {
- setState(() {
- _focused = widget.focusNode.hasFocus;
- });
- }
-
- @override
- void initState() {
- super.initState();
- _focused = widget.focusNode.hasFocus;
- widget.focusNode.addListener(_editorFocusChanged);
- }
-
- @override
- void didUpdateWidget(covariant QuillField oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (widget.focusNode != oldWidget.focusNode) {
- oldWidget.focusNode.removeListener(_editorFocusChanged);
- widget.focusNode.addListener(_editorFocusChanged);
- _focused = widget.focusNode.hasFocus;
- }
- }
-
- @override
- Widget build(BuildContext context) {
- Widget child = QuillEditor(
- controller: widget.controller,
- focusNode: widget.focusNode,
- scrollController: widget.scrollController,
- scrollable: widget.scrollable,
- padding: widget.padding,
- autoFocus: widget.autofocus,
- showCursor: widget.showCursor,
- readOnly: widget.readOnly,
- enableInteractiveSelection: widget.enableInteractiveSelection,
- minHeight: widget.minHeight,
- maxHeight: widget.maxHeight,
- expands: widget.expands,
- textCapitalization: widget.textCapitalization,
- keyboardAppearance: widget.keyboardAppearance,
- scrollPhysics: widget.scrollPhysics,
- onLaunchUrl: widget.onLaunchUrl,
- embedBuilder: widget.embedBuilder,
- styleBuilder:widget.styleBuilder,
- );
-
- if (widget.toolbar != null) {
- child = Column(
- children: [
- child,
- Visibility(
- child: widget.toolbar,
- visible: _focused,
- maintainSize: true,
- maintainAnimation: true,
- maintainState: true,
- ),
- ],
- );
- }
-
- return AnimatedBuilder(
- animation:
- Listenable.merge([widget.focusNode, widget.controller]),
- builder: (BuildContext context, Widget child) {
- return InputDecorator(
- decoration: _getEffectiveDecoration(),
- isFocused: widget.focusNode.hasFocus,
- // TODO: Document should be considered empty of it has single empty line with no styles applied
- isEmpty: widget.controller.document.length == 1,
- child: child,
- );
- },
- child: child,
- );
- }
-
- InputDecoration _getEffectiveDecoration() {
- return (widget.decoration ?? const InputDecoration())
- .applyDefaults(Theme.of(context).inputDecorationTheme)
- .copyWith(
- enabled: !widget.readOnly,
- hintMaxLines: widget.decoration?.hintMaxLines,
- );
- }
-}
diff --git a/app/pubspec.lock b/app/pubspec.lock
deleted file mode 100644
index f4e16a57..00000000
--- a/app/pubspec.lock
+++ /dev/null
@@ -1,355 +0,0 @@
-# Generated by pub
-# See https://dart.dev/tools/pub/glossary#lockfile
-packages:
- async:
- dependency: transitive
- description:
- name: async
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.5.0"
- boolean_selector:
- dependency: transitive
- description:
- name: boolean_selector
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- characters:
- dependency: transitive
- description:
- name: characters
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- charcode:
- dependency: transitive
- description:
- name: charcode
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.0"
- clock:
- dependency: transitive
- description:
- name: clock
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- collection:
- dependency: transitive
- description:
- name: collection
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.15.0"
- convert:
- dependency: transitive
- description:
- name: convert
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.1"
- crypto:
- dependency: transitive
- description:
- name: crypto
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.5"
- csslib:
- dependency: transitive
- description:
- name: csslib
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.16.2"
- cupertino_icons:
- dependency: "direct main"
- description:
- name: cupertino_icons
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.0"
- fake_async:
- dependency: transitive
- description:
- name: fake_async
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.0"
- flutter:
- dependency: "direct main"
- description: flutter
- source: sdk
- version: "0.0.0"
- flutter_colorpicker:
- dependency: transitive
- description:
- name: flutter_colorpicker
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.3.4"
- flutter_plugin_android_lifecycle:
- dependency: transitive
- description:
- name: flutter_plugin_android_lifecycle
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.11"
- flutter_quill:
- dependency: "direct main"
- description:
- path: ".."
- relative: true
- source: path
- version: "0.2.8"
- flutter_test:
- dependency: "direct dev"
- description: flutter
- source: sdk
- version: "0.0.0"
- flutter_web_plugins:
- dependency: transitive
- description: flutter
- source: sdk
- version: "0.0.0"
- html:
- dependency: transitive
- description:
- name: html
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.14.0+4"
- http:
- dependency: transitive
- description:
- name: http
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.12.2"
- http_parser:
- dependency: transitive
- description:
- name: http_parser
- url: "https://pub.dartlang.org"
- source: hosted
- version: "3.1.4"
- image_picker:
- dependency: transitive
- description:
- name: image_picker
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.6.7+17"
- image_picker_platform_interface:
- dependency: transitive
- description:
- name: image_picker_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.1"
- js:
- dependency: transitive
- description:
- name: js
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.6.3"
- matcher:
- dependency: transitive
- description:
- name: matcher
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.12.10"
- meta:
- dependency: transitive
- description:
- name: meta
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.0"
- path:
- dependency: transitive
- description:
- name: path
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.8.0"
- pedantic:
- dependency: transitive
- description:
- name: pedantic
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.9.2"
- photo_view:
- dependency: transitive
- description:
- name: photo_view
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.10.3"
- plugin_platform_interface:
- dependency: transitive
- description:
- name: plugin_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.3"
- 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"
- quiver_hashcode:
- dependency: transitive
- description:
- name: quiver_hashcode
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.0.0"
- sky_engine:
- dependency: transitive
- description: flutter
- source: sdk
- version: "0.0.99"
- source_span:
- dependency: transitive
- description:
- name: source_span
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.8.0"
- stack_trace:
- dependency: transitive
- description:
- name: stack_trace
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.10.0"
- stream_channel:
- dependency: transitive
- description:
- name: stream_channel
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- string_scanner:
- dependency: transitive
- description:
- name: string_scanner
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.1.0"
- term_glyph:
- dependency: transitive
- description:
- name: term_glyph
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.0"
- test_api:
- dependency: transitive
- description:
- name: test_api
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.2.19"
- tuple:
- dependency: transitive
- description:
- name: tuple
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.3"
- typed_data:
- dependency: transitive
- description:
- name: typed_data
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.3.0"
- universal_html:
- dependency: transitive
- description:
- name: universal_html
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.2.4"
- universal_io:
- dependency: transitive
- description:
- name: universal_io
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.2"
- url_launcher:
- dependency: transitive
- description:
- name: url_launcher
- url: "https://pub.dartlang.org"
- source: hosted
- version: "5.7.10"
- url_launcher_linux:
- dependency: transitive
- description:
- name: url_launcher_linux
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.0.1+4"
- url_launcher_macos:
- dependency: transitive
- description:
- name: url_launcher_macos
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.0.1+9"
- url_launcher_platform_interface:
- dependency: transitive
- description:
- name: url_launcher_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "1.0.9"
- url_launcher_web:
- dependency: transitive
- description:
- name: url_launcher_web
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.1.5+1"
- url_launcher_windows:
- dependency: transitive
- description:
- name: url_launcher_windows
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.0.1+3"
- vector_math:
- dependency: transitive
- description:
- name: vector_math
- url: "https://pub.dartlang.org"
- source: hosted
- version: "2.1.0"
- zone_local:
- dependency: transitive
- description:
- name: zone_local
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.1.2"
-sdks:
- dart: ">=2.12.0-0.0 <3.0.0"
- flutter: ">=1.22.0"
diff --git a/app/.gitignore b/example/.gitignore
similarity index 98%
rename from app/.gitignore
rename to example/.gitignore
index 9d532b18..15e3a7c7 100644
--- a/app/.gitignore
+++ b/example/.gitignore
@@ -39,3 +39,4 @@ app.*.symbols
# Obfuscation related
app.*.map.json
+pubspec.lock
\ No newline at end of file
diff --git a/app/.metadata b/example/.metadata
similarity index 100%
rename from app/.metadata
rename to example/.metadata
diff --git a/app/README.md b/example/README.md
similarity index 100%
rename from app/README.md
rename to example/README.md
diff --git a/app/android/.gitignore b/example/android/.gitignore
similarity index 100%
rename from app/android/.gitignore
rename to example/android/.gitignore
diff --git a/app/android/app/build.gradle b/example/android/app/build.gradle
similarity index 90%
rename from app/android/app/build.gradle
rename to example/android/app/build.gradle
index 4d2c8dd1..86d081d8 100644
--- a/app/android/app/build.gradle
+++ b/example/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')
@@ -34,7 +34,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.app"
- minSdkVersion 16
+ minSdkVersion 17
targetSdkVersion 29
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
diff --git a/app/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml
similarity index 100%
rename from app/android/app/src/debug/AndroidManifest.xml
rename to example/android/app/src/debug/AndroidManifest.xml
diff --git a/app/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
similarity index 100%
rename from app/android/app/src/main/AndroidManifest.xml
rename to example/android/app/src/main/AndroidManifest.xml
diff --git a/app/android/app/src/main/java/com/example/app/MainActivity.java b/example/android/app/src/main/java/com/example/app/MainActivity.java
similarity index 100%
rename from app/android/app/src/main/java/com/example/app/MainActivity.java
rename to example/android/app/src/main/java/com/example/app/MainActivity.java
diff --git a/app/android/app/src/main/kotlin/com/example/app/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/app/MainActivity.kt
similarity index 100%
rename from app/android/app/src/main/kotlin/com/example/app/MainActivity.kt
rename to example/android/app/src/main/kotlin/com/example/app/MainActivity.kt
diff --git a/app/android/app/src/main/res/drawable-v21/launch_background.xml b/example/android/app/src/main/res/drawable-v21/launch_background.xml
similarity index 100%
rename from app/android/app/src/main/res/drawable-v21/launch_background.xml
rename to example/android/app/src/main/res/drawable-v21/launch_background.xml
diff --git a/app/android/app/src/main/res/drawable/launch_background.xml b/example/android/app/src/main/res/drawable/launch_background.xml
similarity index 100%
rename from app/android/app/src/main/res/drawable/launch_background.xml
rename to example/android/app/src/main/res/drawable/launch_background.xml
diff --git a/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
diff --git a/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
diff --git a/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
diff --git a/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
rename to example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/app/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml
similarity index 100%
rename from app/android/app/src/main/res/values-night/styles.xml
rename to example/android/app/src/main/res/values-night/styles.xml
diff --git a/app/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml
similarity index 100%
rename from app/android/app/src/main/res/values/styles.xml
rename to example/android/app/src/main/res/values/styles.xml
diff --git a/app/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml
similarity index 100%
rename from app/android/app/src/profile/AndroidManifest.xml
rename to example/android/app/src/profile/AndroidManifest.xml
diff --git a/app/android/build.gradle b/example/android/build.gradle
similarity index 87%
rename from app/android/build.gradle
rename to example/android/build.gradle
index e0d7ae2c..11e3d090 100644
--- a/app/android/build.gradle
+++ b/example/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.properties b/example/android/gradle.properties
similarity index 100%
rename from app/android/gradle.properties
rename to example/android/gradle.properties
diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties
similarity index 93%
rename from app/android/gradle/wrapper/gradle-wrapper.properties
rename to example/android/gradle/wrapper/gradle-wrapper.properties
index 296b146b..c69d51db 100644
--- a/app/android/gradle/wrapper/gradle-wrapper.properties
+++ b/example/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/android/settings.gradle b/example/android/settings.gradle
similarity index 100%
rename from app/android/settings.gradle
rename to example/android/settings.gradle
diff --git a/example/android/settings_aar.gradle b/example/android/settings_aar.gradle
new file mode 100644
index 00000000..e7b4def4
--- /dev/null
+++ b/example/android/settings_aar.gradle
@@ -0,0 +1 @@
+include ':app'
diff --git a/app/assets/sample_data.json b/example/assets/sample_data.json
similarity index 89%
rename from app/assets/sample_data.json
rename to example/assets/sample_data.json
index 979801f4..fdea5163 100644
--- a/app/assets/sample_data.json
+++ b/example/assets/sample_data.json
@@ -1,4 +1,13 @@
[
+ {
+ "insert": {
+ "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
+ },
+ "attributes":{
+ "width":"230",
+ "style":"display: block; margin: auto; mobileWidth: 50; mobileHeight: 50; mobileMargin: 10; mobileAlignment: topLeft"
+ }
+ },
{
"insert":"Flutter Quill"
},
@@ -10,11 +19,12 @@
},
{
"insert": {
- "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
- },
- "attributes":{
- "width":"230",
- "style":"display: block; margin: auto;"
+ "video": "https://www.youtube.com/watch?v=V4hgdKhIqtc&list=PLbhaS_83B97s78HsDTtplRTEhcFsqSqIK&index=1"
+ }
+ },
+ {
+ "insert": {
+ "video": "https://user-images.githubusercontent.com/122956/126238875-22e42501-ad41-4266-b1d6-3f89b5e3b79b.mp4"
}
},
{
@@ -496,6 +506,34 @@
"size": "50"
},
"insert": "font size 50"
+
+ },
+ {
+ "attributes":{
+ "token":"built_in"
+ },
+ "insert":" diff"
+ },
+ {
+ "attributes":{
+ "token":"operator"
+ },
+ "insert":"-match"
+ },
+ {
+ "attributes":{
+ "token":"literal"
+ },
+ "insert":"-patch"
+ },
+ {
+ "insert": {
+ "image": "https://user-images.githubusercontent.com/122956/72955931-ccc07900-3d52-11ea-89b1-d468a6e2aa2b.png"
+ },
+ "attributes":{
+ "width":"230",
+ "style":"display: block; margin: auto;"
+ }
},
{
"insert": "\n"
diff --git a/app/ios/.gitignore b/example/ios/.gitignore
similarity index 100%
rename from app/ios/.gitignore
rename to example/ios/.gitignore
diff --git a/app/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist
similarity index 100%
rename from app/ios/Flutter/AppFrameworkInfo.plist
rename to example/ios/Flutter/AppFrameworkInfo.plist
diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig
new file mode 100644
index 00000000..ec97fc6f
--- /dev/null
+++ b/example/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig
new file mode 100644
index 00000000..c4855bfe
--- /dev/null
+++ b/example/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/app/ios/Podfile b/example/ios/Podfile
similarity index 96%
rename from app/ios/Podfile
rename to example/ios/Podfile
index f7d6a5e6..1e8c3c90 100644
--- a/app/ios/Podfile
+++ b/example/ios/Podfile
@@ -28,6 +28,9 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe
flutter_ios_podfile_setup
target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
end
diff --git a/app/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
similarity index 100%
rename from app/ios/Runner.xcodeproj/project.pbxproj
rename to example/ios/Runner.xcodeproj/project.pbxproj
diff --git a/app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
similarity index 100%
rename from app/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
rename to example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
diff --git a/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 100%
rename from app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
similarity index 100%
rename from app/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
rename to example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
diff --git a/app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
similarity index 100%
rename from app/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
rename to example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
diff --git a/app/ios/Runner.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcworkspace/contents.xcworkspacedata
similarity index 100%
rename from app/ios/Runner.xcworkspace/contents.xcworkspacedata
rename to example/ios/Runner.xcworkspace/contents.xcworkspacedata
diff --git a/app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
similarity index 100%
rename from app/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
rename to example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
diff --git a/app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
similarity index 100%
rename from app/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
rename to example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings
diff --git a/app/ios/Runner/AppDelegate.h b/example/ios/Runner/AppDelegate.h
similarity index 100%
rename from app/ios/Runner/AppDelegate.h
rename to example/ios/Runner/AppDelegate.h
diff --git a/app/ios/Runner/AppDelegate.m b/example/ios/Runner/AppDelegate.m
similarity index 100%
rename from app/ios/Runner/AppDelegate.m
rename to example/ios/Runner/AppDelegate.m
diff --git a/app/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift
similarity index 100%
rename from app/ios/Runner/AppDelegate.swift
rename to example/ios/Runner/AppDelegate.swift
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
diff --git a/app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
rename to example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
diff --git a/app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
similarity index 100%
rename from app/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
rename to example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
diff --git a/app/ios/Runner/Base.lproj/LaunchScreen.storyboard b/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
similarity index 100%
rename from app/ios/Runner/Base.lproj/LaunchScreen.storyboard
rename to example/ios/Runner/Base.lproj/LaunchScreen.storyboard
diff --git a/app/ios/Runner/Base.lproj/Main.storyboard b/example/ios/Runner/Base.lproj/Main.storyboard
similarity index 100%
rename from app/ios/Runner/Base.lproj/Main.storyboard
rename to example/ios/Runner/Base.lproj/Main.storyboard
diff --git a/app/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
similarity index 100%
rename from app/ios/Runner/Info.plist
rename to example/ios/Runner/Info.plist
diff --git a/app/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h
similarity index 100%
rename from app/ios/Runner/Runner-Bridging-Header.h
rename to example/ios/Runner/Runner-Bridging-Header.h
diff --git a/app/ios/Runner/main.m b/example/ios/Runner/main.m
similarity index 100%
rename from app/ios/Runner/main.m
rename to example/ios/Runner/main.m
diff --git a/app/lib/main.dart b/example/lib/main.dart
similarity index 96%
rename from app/lib/main.dart
rename to example/lib/main.dart
index f3ec0666..5b4feb2b 100644
--- a/app/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,4 +1,3 @@
-// import 'package:app/pages/home_page.dart';
import 'package:flutter/material.dart';
import 'pages/home_page.dart';
diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart
new file mode 100644
index 00000000..8abf61d9
--- /dev/null
+++ b/example/lib/pages/home_page.dart
@@ -0,0 +1,301 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:file_picker/file_picker.dart';
+import 'package:filesystem_picker/filesystem_picker.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_quill/flutter_quill.dart' hide Text;
+import 'package:path/path.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:tuple/tuple.dart';
+
+import '../universal_ui/universal_ui.dart';
+import 'read_only_page.dart';
+
+class HomePage extends StatefulWidget {
+ @override
+ _HomePageState createState() => _HomePageState();
+}
+
+class _HomePageState extends State {
+ QuillController? _controller;
+ final FocusNode _focusNode = FocusNode();
+
+ @override
+ void initState() {
+ super.initState();
+ _loadFromAssets();
+ }
+
+ Future _loadFromAssets() async {
+ try {
+ final result = await rootBundle.loadString('assets/sample_data.json');
+ final doc = Document.fromJson(jsonDecode(result));
+ setState(() {
+ _controller = QuillController(
+ document: doc, selection: const TextSelection.collapsed(offset: 0));
+ });
+ } catch (error) {
+ final doc = Document()..insert(0, 'Empty asset');
+ setState(() {
+ _controller = QuillController(
+ document: doc, selection: const TextSelection.collapsed(offset: 0));
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (_controller == null) {
+ return const Scaffold(body: Center(child: Text('Loading...')));
+ }
+
+ return Scaffold(
+ appBar: AppBar(
+ backgroundColor: Colors.grey.shade800,
+ elevation: 0,
+ centerTitle: false,
+ title: const Text(
+ 'Flutter Quill',
+ ),
+ actions: [],
+ ),
+ drawer: Container(
+ constraints:
+ BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.7),
+ color: Colors.grey.shade800,
+ child: _buildMenuBar(context),
+ ),
+ body: RawKeyboardListener(
+ focusNode: FocusNode(),
+ onKey: (event) {
+ if (event.data.isControlPressed && event.character == 'b') {
+ if (_controller!
+ .getSelectionStyle()
+ .attributes
+ .keys
+ .contains('bold')) {
+ _controller!
+ .formatSelection(Attribute.clone(Attribute.bold, null));
+ } else {
+ _controller!.formatSelection(Attribute.bold);
+ }
+ }
+ },
+ child: _buildWelcomeEditor(context),
+ ),
+ );
+ }
+
+ Widget _buildWelcomeEditor(BuildContext context) {
+ var quillEditor = QuillEditor(
+ controller: _controller!,
+ scrollController: ScrollController(),
+ scrollable: true,
+ focusNode: _focusNode,
+ autoFocus: false,
+ readOnly: false,
+ placeholder: 'Add content',
+ expands: false,
+ padding: EdgeInsets.zero,
+ customStyles: DefaultStyles(
+ h1: DefaultTextBlockStyle(
+ const TextStyle(
+ fontSize: 32,
+ color: Colors.black,
+ height: 1.15,
+ fontWeight: FontWeight.w300,
+ ),
+ const Tuple2(16, 0),
+ const Tuple2(0, 0),
+ null),
+ sizeSmall: const TextStyle(fontSize: 9),
+ ));
+ if (kIsWeb) {
+ quillEditor = QuillEditor(
+ controller: _controller!,
+ scrollController: ScrollController(),
+ scrollable: true,
+ focusNode: _focusNode,
+ autoFocus: false,
+ readOnly: false,
+ placeholder: 'Add content',
+ expands: false,
+ padding: EdgeInsets.zero,
+ customStyles: DefaultStyles(
+ h1: DefaultTextBlockStyle(
+ const TextStyle(
+ fontSize: 32,
+ color: Colors.black,
+ height: 1.15,
+ fontWeight: FontWeight.w300,
+ ),
+ const Tuple2(16, 0),
+ const Tuple2(0, 0),
+ null),
+ sizeSmall: const TextStyle(fontSize: 9),
+ ),
+ embedBuilder: defaultEmbedBuilderWeb);
+ }
+ var toolbar = QuillToolbar.basic(
+ controller: _controller!,
+ // provide a callback to enable picking images from device.
+ // if omit, "image" button only allows adding images from url.
+ // same goes for videos.
+ onImagePickCallback: _onImagePickCallback,
+ onVideoPickCallback: _onVideoPickCallback,
+ // uncomment to provide a custom "pick from" dialog.
+ // mediaPickSettingSelector: _selectMediaPickSetting,
+ );
+ if (kIsWeb) {
+ toolbar = QuillToolbar.basic(
+ controller: _controller!,
+ onImagePickCallback: _onImagePickCallback,
+ webImagePickImpl: _webImagePickImpl);
+ }
+ if (_isDesktop()) {
+ toolbar = QuillToolbar.basic(
+ controller: _controller!,
+ onImagePickCallback: _onImagePickCallback,
+ filePickImpl: openFileSystemPickerForDesktop);
+ }
+
+ return SafeArea(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ Expanded(
+ flex: 15,
+ child: Container(
+ color: Colors.white,
+ padding: const EdgeInsets.only(left: 16, right: 16),
+ child: quillEditor,
+ ),
+ ),
+ kIsWeb
+ ? Expanded(
+ child: Container(
+ padding:
+ const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
+ child: toolbar,
+ ))
+ : Container(child: toolbar)
+ ],
+ ),
+ );
+ }
+
+ bool _isDesktop() => !kIsWeb && !Platform.isAndroid && !Platform.isIOS;
+
+ Future openFileSystemPickerForDesktop(BuildContext context) async {
+ return await FilesystemPicker.open(
+ context: context,
+ rootDirectory: await getApplicationDocumentsDirectory(),
+ fsType: FilesystemType.file,
+ fileTileSelectMode: FileTileSelectMode.wholeTile,
+ );
+ }
+
+ // Renders the image picked by imagePicker from local file storage
+ // You can also upload the picked image to any server (eg : AWS s3
+ // or Firebase) and then return the uploaded image URL.
+ Future _onImagePickCallback(File file) async {
+ // Copies the picked file from temporary cache to applications directory
+ final appDocDir = await getApplicationDocumentsDirectory();
+ final copiedFile =
+ await file.copy('${appDocDir.path}/${basename(file.path)}');
+ return copiedFile.path.toString();
+ }
+
+ Future _webImagePickImpl(
+ OnImagePickCallback onImagePickCallback) async {
+ final result = await FilePicker.platform.pickFiles();
+ if (result == null) {
+ return null;
+ }
+
+ // Take first, because we don't allow picking multiple files.
+ final fileName = result.files.first.name;
+ final file = File(fileName);
+
+ return onImagePickCallback(file);
+ }
+
+ // Renders the video picked by imagePicker from local file storage
+ // You can also upload the picked video to any server (eg : AWS s3
+ // or Firebase) and then return the uploaded video URL.
+ Future _onVideoPickCallback(File file) async {
+ // Copies the picked file from temporary cache to applications directory
+ final appDocDir = await getApplicationDocumentsDirectory();
+ final copiedFile =
+ await file.copy('${appDocDir.path}/${basename(file.path)}');
+ return copiedFile.path.toString();
+ }
+
+ Future _selectMediaPickSetting(BuildContext context) =>
+ showDialog(
+ context: context,
+ builder: (ctx) => AlertDialog(
+ contentPadding: EdgeInsets.zero,
+ content: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ TextButton.icon(
+ icon: const Icon(Icons.collections),
+ label: const Text('Gallery'),
+ onPressed: () => Navigator.pop(ctx, MediaPickSetting.Gallery),
+ ),
+ TextButton.icon(
+ icon: const Icon(Icons.link),
+ label: const Text('Link'),
+ onPressed: () => Navigator.pop(ctx, MediaPickSetting.Link),
+ )
+ ],
+ ),
+ ),
+ );
+
+ Widget _buildMenuBar(BuildContext context) {
+ final size = MediaQuery.of(context).size;
+ const itemStyle = TextStyle(
+ color: Colors.white,
+ fontSize: 18,
+ fontWeight: FontWeight.bold,
+ );
+ return Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Divider(
+ thickness: 2,
+ color: Colors.white,
+ indent: size.width * 0.1,
+ endIndent: size.width * 0.1,
+ ),
+ ListTile(
+ title: const Center(child: Text('Read only demo', style: itemStyle)),
+ dense: true,
+ visualDensity: VisualDensity.compact,
+ onTap: _readOnly,
+ ),
+ Divider(
+ thickness: 2,
+ color: Colors.white,
+ indent: size.width * 0.1,
+ endIndent: size.width * 0.1,
+ ),
+ ],
+ );
+ }
+
+ void _readOnly() {
+ Navigator.push(
+ super.context,
+ MaterialPageRoute(
+ builder: (context) => ReadOnlyPage(),
+ ),
+ );
+ }
+}
diff --git a/app/lib/pages/read_only_page.dart b/example/lib/pages/read_only_page.dart
similarity index 65%
rename from app/lib/pages/read_only_page.dart
rename to example/lib/pages/read_only_page.dart
index c72313ad..594d6123 100644
--- a/app/lib/pages/read_only_page.dart
+++ b/example/lib/pages/read_only_page.dart
@@ -1,7 +1,8 @@
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:flutter_quill/widgets/controller.dart';
-import 'package:flutter_quill/widgets/editor.dart';
+import 'package:flutter_quill/flutter_quill.dart' hide Text;
+import '../universal_ui/universal_ui.dart';
import '../widgets/demo_scaffold.dart';
class ReadOnlyPage extends StatefulWidget {
@@ -27,25 +28,37 @@ class _ReadOnlyPageState extends State {
);
}
- Widget _buildContent(BuildContext context, QuillController controller) {
- return Padding(
- padding: const EdgeInsets.all(8.0),
- child: Container(
- decoration: BoxDecoration(
- color: Colors.white,
- border: Border.all(color: Colors.grey.shade200),
- ),
- child: QuillEditor(
+ Widget _buildContent(BuildContext context, QuillController? controller) {
+ var quillEditor = QuillEditor(
+ controller: controller!,
+ scrollController: ScrollController(),
+ scrollable: true,
+ focusNode: _focusNode,
+ autoFocus: true,
+ readOnly: !_edit,
+ expands: false,
+ padding: EdgeInsets.zero,
+ );
+ if (kIsWeb) {
+ quillEditor = QuillEditor(
controller: controller,
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
autoFocus: true,
readOnly: !_edit,
- enableInteractiveSelection: true,
expands: false,
padding: EdgeInsets.zero,
+ embedBuilder: defaultEmbedBuilderWeb);
+ }
+ return Padding(
+ padding: const EdgeInsets.all(8),
+ child: Container(
+ decoration: BoxDecoration(
+ color: Colors.white,
+ border: Border.all(color: Colors.grey.shade200),
),
+ child: quillEditor,
),
);
}
diff --git a/example/lib/universal_ui/fake_ui.dart b/example/lib/universal_ui/fake_ui.dart
new file mode 100644
index 00000000..1711ad5f
--- /dev/null
+++ b/example/lib/universal_ui/fake_ui.dart
@@ -0,0 +1,3 @@
+class PlatformViewRegistry {
+ static void registerViewFactory(String viewId, dynamic cb) {}
+}
diff --git a/example/lib/universal_ui/real_ui.dart b/example/lib/universal_ui/real_ui.dart
new file mode 100644
index 00000000..b701caf4
--- /dev/null
+++ b/example/lib/universal_ui/real_ui.dart
@@ -0,0 +1,7 @@
+import 'dart:ui' as ui;
+
+class PlatformViewRegistry {
+ static void registerViewFactory(String viewId, dynamic cb) {
+ ui.platformViewRegistry.registerViewFactory(viewId, cb);
+ }
+}
diff --git a/example/lib/universal_ui/universal_ui.dart b/example/lib/universal_ui/universal_ui.dart
new file mode 100644
index 00000000..7d04f492
--- /dev/null
+++ b/example/lib/universal_ui/universal_ui.dart
@@ -0,0 +1,58 @@
+library universal_ui;
+
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_quill/flutter_quill.dart';
+import 'package:universal_html/html.dart' as html;
+
+import '../widgets/responsive_widget.dart';
+import 'fake_ui.dart' if (dart.library.html) 'real_ui.dart' as ui_instance;
+
+class PlatformViewRegistryFix {
+ void registerViewFactory(dynamic x, dynamic y) {
+ if (kIsWeb) {
+ ui_instance.PlatformViewRegistry.registerViewFactory(
+ x,
+ y,
+ );
+ }
+ }
+}
+
+class UniversalUI {
+ PlatformViewRegistryFix platformViewRegistry = PlatformViewRegistryFix();
+}
+
+var ui = UniversalUI();
+
+Widget defaultEmbedBuilderWeb(BuildContext context, Embed node, bool readOnly) {
+ switch (node.value.type) {
+ case 'image':
+ final String imageUrl = node.value.data;
+ final size = MediaQuery.of(context).size;
+ UniversalUI().platformViewRegistry.registerViewFactory(
+ imageUrl, (viewId) => html.ImageElement()..src = imageUrl);
+ return Padding(
+ padding: EdgeInsets.only(
+ right: ResponsiveWidget.isMediumScreen(context)
+ ? size.width * 0.5
+ : (ResponsiveWidget.isLargeScreen(context))
+ ? size.width * 0.75
+ : size.width * 0.2,
+ ),
+ child: SizedBox(
+ height: MediaQuery.of(context).size.height * 0.45,
+ child: HtmlElementView(
+ viewType: imageUrl,
+ ),
+ ),
+ );
+
+ default:
+ throw UnimplementedError(
+ 'Embeddable type "${node.value.type}" is not supported by default '
+ 'embed builder of QuillEditor. You must pass your own builder function '
+ 'to embedBuilder property of QuillEditor or QuillField widgets.',
+ );
+ }
+}
diff --git a/app/lib/widgets/demo_scaffold.dart b/example/lib/widgets/demo_scaffold.dart
similarity index 61%
rename from app/lib/widgets/demo_scaffold.dart
rename to example/lib/widgets/demo_scaffold.dart
index 3caa00d5..c67e585c 100644
--- a/app/lib/widgets/demo_scaffold.dart
+++ b/example/lib/widgets/demo_scaffold.dart
@@ -1,39 +1,41 @@
import 'dart:convert';
+import 'dart:io';
+import 'package:filesystem_picker/filesystem_picker.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'package:flutter_quill/models/documents/document.dart';
-import 'package:flutter_quill/widgets/controller.dart';
-import 'package:flutter_quill/widgets/toolbar.dart';
+import 'package:flutter_quill/flutter_quill.dart' hide Text;
+import 'package:path_provider/path_provider.dart';
typedef DemoContentBuilder = Widget Function(
- BuildContext context, QuillController controller);
+ BuildContext context, QuillController? controller);
// Common scaffold for all examples.
class DemoScaffold extends StatefulWidget {
- /// Filename of the document to load into the editor.
- final String documentFilename;
- final DemoContentBuilder builder;
- final List actions;
- final Widget floatingActionButton;
- final bool showToolbar;
-
const DemoScaffold({
- Key key,
- @required this.documentFilename,
- @required this.builder,
+ required this.documentFilename,
+ required this.builder,
this.actions,
this.showToolbar = true,
this.floatingActionButton,
+ Key? key,
}) : super(key: key);
+ /// Filename of the document to load into the editor.
+ final String documentFilename;
+ final DemoContentBuilder builder;
+ final List? actions;
+ final Widget? floatingActionButton;
+ final bool showToolbar;
+
@override
_DemoScaffoldState createState() => _DemoScaffoldState();
}
class _DemoScaffoldState extends State {
final _scaffoldKey = GlobalKey();
- QuillController _controller;
+ QuillController? _controller;
bool _loading = false;
@@ -59,22 +61,37 @@ class _DemoScaffoldState extends State {
final doc = Document.fromJson(jsonDecode(result));
setState(() {
_controller = QuillController(
- document: doc, selection: TextSelection.collapsed(offset: 0));
+ document: doc, selection: const TextSelection.collapsed(offset: 0));
_loading = false;
});
} catch (error) {
final doc = Document()..insert(0, 'Empty asset');
setState(() {
_controller = QuillController(
- document: doc, selection: TextSelection.collapsed(offset: 0));
+ document: doc, selection: const TextSelection.collapsed(offset: 0));
_loading = false;
});
}
}
+ Future openFileSystemPickerForDesktop(BuildContext context) async {
+ return await FilesystemPicker.open(
+ context: context,
+ rootDirectory: await getApplicationDocumentsDirectory(),
+ fsType: FilesystemType.file,
+ fileTileSelectMode: FileTileSelectMode.wholeTile,
+ );
+ }
+
@override
Widget build(BuildContext context) {
final actions = widget.actions ?? [];
+ var toolbar = QuillToolbar.basic(controller: _controller!);
+ if (_isDesktop()) {
+ toolbar = QuillToolbar.basic(
+ controller: _controller!,
+ filePickImpl: openFileSystemPickerForDesktop);
+ }
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
@@ -90,15 +107,15 @@ class _DemoScaffoldState extends State {
),
onPressed: () => Navigator.pop(context),
),
- title: _loading || widget.showToolbar == false
- ? null
- : QuillToolbar.basic(controller: _controller),
+ title: _loading || !widget.showToolbar ? null : toolbar,
actions: actions,
),
floatingActionButton: widget.floatingActionButton,
body: _loading
- ? Center(child: Text('Loading...'))
+ ? const Center(child: Text('Loading...'))
: widget.builder(context, _controller),
);
}
+
+ bool _isDesktop() => !kIsWeb && !Platform.isAndroid && !Platform.isIOS;
}
diff --git a/lib/widgets/responsive_widget.dart b/example/lib/widgets/responsive_widget.dart
similarity index 81%
rename from lib/widgets/responsive_widget.dart
rename to example/lib/widgets/responsive_widget.dart
index a047deab..3829565c 100644
--- a/lib/widgets/responsive_widget.dart
+++ b/example/lib/widgets/responsive_widget.dart
@@ -1,16 +1,16 @@
import 'package:flutter/material.dart';
class ResponsiveWidget extends StatelessWidget {
- final Widget largeScreen;
- final Widget mediumScreen;
- final Widget smallScreen;
+ const ResponsiveWidget({
+ required this.largeScreen,
+ this.mediumScreen,
+ this.smallScreen,
+ Key? key,
+ }) : super(key: key);
- const ResponsiveWidget(
- {Key key,
- @required this.largeScreen,
- this.mediumScreen,
- this.smallScreen})
- : super(key: key);
+ final Widget largeScreen;
+ final Widget? mediumScreen;
+ final Widget? smallScreen;
static bool isSmallScreen(BuildContext context) {
return MediaQuery.of(context).size.width < 800;
diff --git a/example/linux/.gitignore b/example/linux/.gitignore
new file mode 100644
index 00000000..c7ea17fc
--- /dev/null
+++ b/example/linux/.gitignore
@@ -0,0 +1 @@
+flutter/ephemeral
diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt
new file mode 100644
index 00000000..6ec85464
--- /dev/null
+++ b/example/linux/CMakeLists.txt
@@ -0,0 +1,106 @@
+cmake_minimum_required(VERSION 3.10)
+project(runner LANGUAGES CXX)
+
+set(BINARY_NAME "app")
+set(APPLICATION_ID "com.example.app")
+
+cmake_policy(SET CMP0063 NEW)
+
+set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
+
+# Configure build options.
+if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ set(CMAKE_BUILD_TYPE "Debug" CACHE
+ STRING "Flutter build mode" FORCE)
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
+ "Debug" "Profile" "Release")
+endif()
+
+# Compilation settings that should be applied to most targets.
+function(APPLY_STANDARD_SETTINGS TARGET)
+ target_compile_features(${TARGET} PUBLIC cxx_std_14)
+ target_compile_options(${TARGET} PRIVATE -Wall -Werror)
+ target_compile_options(${TARGET} PRIVATE "$<$>:-O3>")
+ target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>")
+endfunction()
+
+set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
+
+# Flutter library and tool build rules.
+add_subdirectory(${FLUTTER_MANAGED_DIR})
+
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+
+add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}")
+
+# Application build
+add_executable(${BINARY_NAME}
+ "main.cc"
+ "my_application.cc"
+ "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
+)
+apply_standard_settings(${BINARY_NAME})
+target_link_libraries(${BINARY_NAME} PRIVATE flutter)
+target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK)
+add_dependencies(${BINARY_NAME} flutter_assemble)
+# Only the install-generated bundle's copy of the executable will launch
+# correctly, since the resources must in the right relative locations. To avoid
+# people trying to run the unbundled copy, put it in a subdirectory instead of
+# the default top-level location.
+set_target_properties(${BINARY_NAME}
+ PROPERTIES
+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run"
+)
+
+# Generated plugin build rules, which manage building the plugins and adding
+# them to the application.
+include(flutter/generated_plugins.cmake)
+
+
+# === Installation ===
+# By default, "installing" just makes a relocatable bundle in the build
+# directory.
+set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle")
+if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
+ set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
+endif()
+
+# Start with a clean build bundle directory every time.
+install(CODE "
+ file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\")
+ " COMPONENT Runtime)
+
+set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
+set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib")
+
+install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
+ COMPONENT Runtime)
+
+install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+
+if(PLUGIN_BUNDLED_LIBRARIES)
+ install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
+ DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
+
+# Fully re-copy the assets directory on each build to avoid having stale files
+# from a previous install.
+set(FLUTTER_ASSET_DIR_NAME "flutter_assets")
+install(CODE "
+ file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\")
+ " COMPONENT Runtime)
+install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
+ DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
+
+# Install the AOT library on non-Debug builds only.
+if(NOT CMAKE_BUILD_TYPE MATCHES "Debug")
+ install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
+ COMPONENT Runtime)
+endif()
diff --git a/example/linux/flutter/CMakeLists.txt b/example/linux/flutter/CMakeLists.txt
new file mode 100644
index 00000000..5cf73dd0
--- /dev/null
+++ b/example/linux/flutter/CMakeLists.txt
@@ -0,0 +1,91 @@
+cmake_minimum_required(VERSION 3.10)
+
+set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
+
+# Configuration provided via flutter tool.
+include(${EPHEMERAL_DIR}/generated_config.cmake)
+
+# TODO: Move the rest of this into files in ephemeral. See
+# https://github.com/flutter/flutter/issues/57146.
+
+# Serves the same purpose as list(TRANSFORM ... PREPEND ...),
+# which isn't available in 3.10.
+function(list_prepend LIST_NAME PREFIX)
+ set(NEW_LIST "")
+ foreach(element ${${LIST_NAME}})
+ list(APPEND NEW_LIST "${PREFIX}${element}")
+ endforeach(element)
+ set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE)
+endfunction()
+
+# === Flutter Library ===
+# System-level dependencies.
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0)
+pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0)
+pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0)
+pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid)
+pkg_check_modules(LZMA REQUIRED IMPORTED_TARGET liblzma)
+
+set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so")
+
+# Published to parent scope for install step.
+set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
+set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
+set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
+set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE)
+
+list(APPEND FLUTTER_LIBRARY_HEADERS
+ "fl_basic_message_channel.h"
+ "fl_binary_codec.h"
+ "fl_binary_messenger.h"
+ "fl_dart_project.h"
+ "fl_engine.h"
+ "fl_json_message_codec.h"
+ "fl_json_method_codec.h"
+ "fl_message_codec.h"
+ "fl_method_call.h"
+ "fl_method_channel.h"
+ "fl_method_codec.h"
+ "fl_method_response.h"
+ "fl_plugin_registrar.h"
+ "fl_plugin_registry.h"
+ "fl_standard_message_codec.h"
+ "fl_standard_method_codec.h"
+ "fl_string_codec.h"
+ "fl_value.h"
+ "fl_view.h"
+ "flutter_linux.h"
+)
+list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/")
+add_library(flutter INTERFACE)
+target_include_directories(flutter INTERFACE
+ "${EPHEMERAL_DIR}"
+)
+target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}")
+target_link_libraries(flutter INTERFACE
+ PkgConfig::GTK
+ PkgConfig::GLIB
+ PkgConfig::GIO
+ PkgConfig::BLKID
+ PkgConfig::LZMA
+)
+add_dependencies(flutter flutter_assemble)
+
+# === Flutter tool backend ===
+# _phony_ is a non-existent file to force this command to run every time,
+# since currently there's no way to get a full input/output list from the
+# flutter tool.
+add_custom_command(
+ OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
+ ${CMAKE_CURRENT_BINARY_DIR}/_phony_
+ COMMAND ${CMAKE_COMMAND} -E env
+ ${FLUTTER_TOOL_ENVIRONMENT}
+ "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh"
+ linux-x64 ${CMAKE_BUILD_TYPE}
+ VERBATIM
+)
+add_custom_target(flutter_assemble DEPENDS
+ "${FLUTTER_LIBRARY}"
+ ${FLUTTER_LIBRARY_HEADERS}
+)
diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc
new file mode 100644
index 00000000..026851fa
--- /dev/null
+++ b/example/linux/flutter/generated_plugin_registrant.cc
@@ -0,0 +1,13 @@
+//
+// Generated file. Do not edit.
+//
+
+#include "generated_plugin_registrant.h"
+
+#include
+
+void fl_register_plugins(FlPluginRegistry* registry) {
+ g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
+ fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
+ url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
+}
diff --git a/example/linux/flutter/generated_plugin_registrant.h b/example/linux/flutter/generated_plugin_registrant.h
new file mode 100644
index 00000000..9bf74789
--- /dev/null
+++ b/example/linux/flutter/generated_plugin_registrant.h
@@ -0,0 +1,13 @@
+//
+// Generated file. Do not edit.
+//
+
+#ifndef GENERATED_PLUGIN_REGISTRANT_
+#define GENERATED_PLUGIN_REGISTRANT_
+
+#include
+
+// Registers Flutter plugins.
+void fl_register_plugins(FlPluginRegistry* registry);
+
+#endif // GENERATED_PLUGIN_REGISTRANT_
diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake
new file mode 100644
index 00000000..1fc8ed34
--- /dev/null
+++ b/example/linux/flutter/generated_plugins.cmake
@@ -0,0 +1,16 @@
+#
+# Generated file, do not edit.
+#
+
+list(APPEND FLUTTER_PLUGIN_LIST
+ url_launcher_linux
+)
+
+set(PLUGIN_BUNDLED_LIBRARIES)
+
+foreach(plugin ${FLUTTER_PLUGIN_LIST})
+ add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin})
+ target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES $)
+ list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
+endforeach(plugin)
diff --git a/example/linux/main.cc b/example/linux/main.cc
new file mode 100644
index 00000000..4340ffc1
--- /dev/null
+++ b/example/linux/main.cc
@@ -0,0 +1,6 @@
+#include "my_application.h"
+
+int main(int argc, char** argv) {
+ g_autoptr(MyApplication) app = my_application_new();
+ return g_application_run(G_APPLICATION(app), argc, argv);
+}
diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc
new file mode 100644
index 00000000..8ef02f26
--- /dev/null
+++ b/example/linux/my_application.cc
@@ -0,0 +1,104 @@
+#include "my_application.h"
+
+#include
+#ifdef GDK_WINDOWING_X11
+#include
+#endif
+
+#include "flutter/generated_plugin_registrant.h"
+
+struct _MyApplication {
+ GtkApplication parent_instance;
+ char** dart_entrypoint_arguments;
+};
+
+G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION)
+
+// Implements GApplication::activate.
+static void my_application_activate(GApplication* application) {
+ MyApplication* self = MY_APPLICATION(application);
+ GtkWindow* window =
+ GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application)));
+
+ // Use a header bar when running in GNOME as this is the common style used
+ // by applications and is the setup most users will be using (e.g. Ubuntu
+ // desktop).
+ // If running on X and not using GNOME then just use a traditional title bar
+ // in case the window manager does more exotic layout, e.g. tiling.
+ // If running on Wayland assume the header bar will work (may need changing
+ // if future cases occur).
+ gboolean use_header_bar = TRUE;
+#ifdef GDK_WINDOWING_X11
+ GdkScreen *screen = gtk_window_get_screen(window);
+ if (GDK_IS_X11_SCREEN(screen)) {
+ const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen);
+ if (g_strcmp0(wm_name, "GNOME Shell") != 0) {
+ use_header_bar = FALSE;
+ }
+ }
+#endif
+ if (use_header_bar) {
+ GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
+ gtk_widget_show(GTK_WIDGET(header_bar));
+ gtk_header_bar_set_title(header_bar, "app");
+ gtk_header_bar_set_show_close_button(header_bar, TRUE);
+ gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
+ }
+ else {
+ gtk_window_set_title(window, "app");
+ }
+
+ gtk_window_set_default_size(window, 1280, 720);
+ gtk_widget_show(GTK_WIDGET(window));
+
+ g_autoptr(FlDartProject) project = fl_dart_project_new();
+ fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments);
+
+ FlView* view = fl_view_new(project);
+ gtk_widget_show(GTK_WIDGET(view));
+ gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
+
+ fl_register_plugins(FL_PLUGIN_REGISTRY(view));
+
+ gtk_widget_grab_focus(GTK_WIDGET(view));
+}
+
+// Implements GApplication::local_command_line.
+static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) {
+ MyApplication* self = MY_APPLICATION(application);
+ // Strip out the first argument as it is the binary name.
+ self->dart_entrypoint_arguments = g_strdupv(*arguments + 1);
+
+ g_autoptr(GError) error = nullptr;
+ if (!g_application_register(application, nullptr, &error)) {
+ g_warning("Failed to register: %s", error->message);
+ *exit_status = 1;
+ return TRUE;
+ }
+
+ g_application_activate(application);
+ *exit_status = 0;
+
+ return TRUE;
+}
+
+// Implements GObject::dispose.
+static void my_application_dispose(GObject *object) {
+ MyApplication* self = MY_APPLICATION(object);
+ g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev);
+ G_OBJECT_CLASS(my_application_parent_class)->dispose(object);
+}
+
+static void my_application_class_init(MyApplicationClass* klass) {
+ G_APPLICATION_CLASS(klass)->activate = my_application_activate;
+ G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line;
+ G_OBJECT_CLASS(klass)->dispose = my_application_dispose;
+}
+
+static void my_application_init(MyApplication* self) {}
+
+MyApplication* my_application_new() {
+ return MY_APPLICATION(g_object_new(my_application_get_type(),
+ "application-id", APPLICATION_ID,
+ nullptr));
+}
diff --git a/example/linux/my_application.h b/example/linux/my_application.h
new file mode 100644
index 00000000..8f20fb55
--- /dev/null
+++ b/example/linux/my_application.h
@@ -0,0 +1,18 @@
+#ifndef FLUTTER_MY_APPLICATION_H_
+#define FLUTTER_MY_APPLICATION_H_
+
+#include
+
+G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION,
+ GtkApplication)
+
+/**
+ * my_application_new:
+ *
+ * Creates a new Flutter-based application.
+ *
+ * Returns: a new #MyApplication.
+ */
+MyApplication* my_application_new();
+
+#endif // FLUTTER_MY_APPLICATION_H_
diff --git a/example/macos/.gitignore b/example/macos/.gitignore
new file mode 100644
index 00000000..e72996ef
--- /dev/null
+++ b/example/macos/.gitignore
@@ -0,0 +1,7 @@
+# Flutter-related
+**/Flutter/ephemeral/
+**/Pods/
+
+# Xcode-related
+**/xcuserdata/
+Podfile.lock
\ No newline at end of file
diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig
new file mode 100644
index 00000000..df4c964c
--- /dev/null
+++ b/example/macos/Flutter/Flutter-Debug.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig
new file mode 100644
index 00000000..e79501e2
--- /dev/null
+++ b/example/macos/Flutter/Flutter-Release.xcconfig
@@ -0,0 +1,2 @@
+#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "ephemeral/Flutter-Generated.xcconfig"
diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift
new file mode 100644
index 00000000..4618f386
--- /dev/null
+++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift
@@ -0,0 +1,14 @@
+//
+// Generated file. Do not edit.
+//
+
+import FlutterMacOS
+import Foundation
+
+import path_provider_macos
+import url_launcher_macos
+
+func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
+ PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
+ UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
+}
diff --git a/example/macos/Podfile b/example/macos/Podfile
new file mode 100644
index 00000000..dade8dfa
--- /dev/null
+++ b/example/macos/Podfile
@@ -0,0 +1,40 @@
+platform :osx, '10.11'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+ 'Debug' => :debug,
+ 'Profile' => :release,
+ 'Release' => :release,
+}
+
+def flutter_root
+ generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
+ unless File.exist?(generated_xcode_build_settings_path)
+ raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
+ end
+
+ File.foreach(generated_xcode_build_settings_path) do |line|
+ matches = line.match(/FLUTTER_ROOT\=(.*)/)
+ return matches[1].strip if matches
+ end
+ raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_macos_podfile_setup
+
+target 'Runner' do
+ use_frameworks!
+ use_modular_headers!
+
+ flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
+end
+
+post_install do |installer|
+ installer.pods_project.targets.each do |target|
+ flutter_additional_macos_build_settings(target)
+ end
+end
diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 00000000..fa640cf1
--- /dev/null
+++ b/example/macos/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,632 @@
+// !$*UTF8*$!
+{
+ archiveVersion = 1;
+ classes = {
+ };
+ objectVersion = 51;
+ objects = {
+
+/* Begin PBXAggregateTarget section */
+ 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = {
+ isa = PBXAggregateTarget;
+ buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */;
+ buildPhases = (
+ 33CC111E2044C6BF0003C045 /* ShellScript */,
+ );
+ dependencies = (
+ );
+ name = "Flutter Assemble";
+ productName = FLX;
+ };
+/* End PBXAggregateTarget section */
+
+/* Begin PBXBuildFile section */
+ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
+ 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
+ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
+ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
+ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
+ 7C7D9700842BA0E01048B7B5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93FCB7C0C7A612A19EB2D2C2 /* Pods_Runner.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+ 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = 33CC10E52044A3C60003C045 /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = 33CC111A2044C6BA0003C045;
+ remoteInfo = FLX;
+ };
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+ 33CC110E2044A8840003C045 /* Bundle Framework */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ );
+ name = "Bundle Framework";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+ 0C1FD22CA47E09B485C5D2F0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; };
+ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; };
+ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; };
+ 33CC10ED2044A3C60003C045 /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; };
+ 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
+ 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; };
+ 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; };
+ 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; };
+ 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; };
+ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; };
+ 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; };
+ 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; };
+ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; };
+ 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; };
+ 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; };
+ 4B8510D07EAD5FFD466A09A7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; };
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; };
+ 93FCB7C0C7A612A19EB2D2C2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; };
+ F44289D50723316BB0B4ADA1 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+ 33CC10EA2044A3C60003C045 /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 7C7D9700842BA0E01048B7B5 /* Pods_Runner.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+ 33BA886A226E78AF003329D5 /* Configs */ = {
+ isa = PBXGroup;
+ children = (
+ 33E5194F232828860026EE4D /* AppInfo.xcconfig */,
+ 9740EEB21CF90195004384FC /* Debug.xcconfig */,
+ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+ 333000ED22D3DE5D00554162 /* Warnings.xcconfig */,
+ );
+ path = Configs;
+ sourceTree = "";
+ };
+ 33CC10E42044A3C60003C045 = {
+ isa = PBXGroup;
+ children = (
+ 33FAB671232836740065AC1E /* Runner */,
+ 33CEB47122A05771004F2AC0 /* Flutter */,
+ 33CC10EE2044A3C60003C045 /* Products */,
+ D73912EC22F37F3D000D13A0 /* Frameworks */,
+ D4C5240DF6823DEC9AB0AD3D /* Pods */,
+ );
+ sourceTree = "";
+ };
+ 33CC10EE2044A3C60003C045 /* Products */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10ED2044A3C60003C045 /* app.app */,
+ );
+ name = Products;
+ sourceTree = "";
+ };
+ 33CC11242044D66E0003C045 /* Resources */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10F22044A3C60003C045 /* Assets.xcassets */,
+ 33CC10F42044A3C60003C045 /* MainMenu.xib */,
+ 33CC10F72044A3C60003C045 /* Info.plist */,
+ );
+ name = Resources;
+ path = ..;
+ sourceTree = "";
+ };
+ 33CEB47122A05771004F2AC0 /* Flutter */ = {
+ isa = PBXGroup;
+ children = (
+ 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */,
+ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */,
+ 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */,
+ 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */,
+ );
+ path = Flutter;
+ sourceTree = "";
+ };
+ 33FAB671232836740065AC1E /* Runner */ = {
+ isa = PBXGroup;
+ children = (
+ 33CC10F02044A3C60003C045 /* AppDelegate.swift */,
+ 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */,
+ 33E51913231747F40026EE4D /* DebugProfile.entitlements */,
+ 33E51914231749380026EE4D /* Release.entitlements */,
+ 33CC11242044D66E0003C045 /* Resources */,
+ 33BA886A226E78AF003329D5 /* Configs */,
+ );
+ path = Runner;
+ sourceTree = "";
+ };
+ D4C5240DF6823DEC9AB0AD3D /* Pods */ = {
+ isa = PBXGroup;
+ children = (
+ 0C1FD22CA47E09B485C5D2F0 /* Pods-Runner.debug.xcconfig */,
+ 4B8510D07EAD5FFD466A09A7 /* Pods-Runner.release.xcconfig */,
+ F44289D50723316BB0B4ADA1 /* Pods-Runner.profile.xcconfig */,
+ );
+ name = Pods;
+ path = Pods;
+ sourceTree = "";
+ };
+ D73912EC22F37F3D000D13A0 /* Frameworks */ = {
+ isa = PBXGroup;
+ children = (
+ 93FCB7C0C7A612A19EB2D2C2 /* Pods_Runner.framework */,
+ );
+ name = Frameworks;
+ sourceTree = "";
+ };
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+ 33CC10EC2044A3C60003C045 /* Runner */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
+ buildPhases = (
+ ECE07D1381B49CCA8A8E0C5F /* [CP] Check Pods Manifest.lock */,
+ 33CC10E92044A3C60003C045 /* Sources */,
+ 33CC10EA2044A3C60003C045 /* Frameworks */,
+ 33CC10EB2044A3C60003C045 /* Resources */,
+ 33CC110E2044A8840003C045 /* Bundle Framework */,
+ 3399D490228B24CF009A79C7 /* ShellScript */,
+ 678C02007C3A278E6325D529 /* [CP] Embed Pods Frameworks */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ 33CC11202044C79F0003C045 /* PBXTargetDependency */,
+ );
+ name = Runner;
+ productName = Runner;
+ productReference = 33CC10ED2044A3C60003C045 /* app.app */;
+ productType = "com.apple.product-type.application";
+ };
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+ 33CC10E52044A3C60003C045 /* Project object */ = {
+ isa = PBXProject;
+ attributes = {
+ LastSwiftUpdateCheck = 0920;
+ LastUpgradeCheck = 0930;
+ ORGANIZATIONNAME = "";
+ TargetAttributes = {
+ 33CC10EC2044A3C60003C045 = {
+ CreatedOnToolsVersion = 9.2;
+ LastSwiftMigration = 1100;
+ ProvisioningStyle = Automatic;
+ SystemCapabilities = {
+ com.apple.Sandbox = {
+ enabled = 1;
+ };
+ };
+ };
+ 33CC111A2044C6BA0003C045 = {
+ CreatedOnToolsVersion = 9.2;
+ ProvisioningStyle = Manual;
+ };
+ };
+ };
+ buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */;
+ compatibilityVersion = "Xcode 9.3";
+ developmentRegion = en;
+ hasScannedForEncodings = 0;
+ knownRegions = (
+ en,
+ Base,
+ );
+ mainGroup = 33CC10E42044A3C60003C045;
+ productRefGroup = 33CC10EE2044A3C60003C045 /* Products */;
+ projectDirPath = "";
+ projectRoot = "";
+ targets = (
+ 33CC10EC2044A3C60003C045 /* Runner */,
+ 33CC111A2044C6BA0003C045 /* Flutter Assemble */,
+ );
+ };
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+ 33CC10EB2044A3C60003C045 /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */,
+ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+ 3399D490228B24CF009A79C7 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ );
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n";
+ };
+ 33CC111E2044C6BF0003C045 /* ShellScript */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ Flutter/ephemeral/FlutterInputs.xcfilelist,
+ );
+ inputPaths = (
+ Flutter/ephemeral/tripwire,
+ );
+ outputFileListPaths = (
+ Flutter/ephemeral/FlutterOutputs.xcfilelist,
+ );
+ outputPaths = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
+ };
+ 678C02007C3A278E6325D529 /* [CP] Embed Pods Frameworks */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
+ );
+ name = "[CP] Embed Pods Frameworks";
+ outputFileListPaths = (
+ "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
+ showEnvVarsInLog = 0;
+ };
+ ECE07D1381B49CCA8A8E0C5F /* [CP] Check Pods Manifest.lock */ = {
+ isa = PBXShellScriptBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ inputFileListPaths = (
+ );
+ inputPaths = (
+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+ "${PODS_ROOT}/Manifest.lock",
+ );
+ name = "[CP] Check Pods Manifest.lock";
+ outputFileListPaths = (
+ );
+ outputPaths = (
+ "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ shellPath = /bin/sh;
+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+ showEnvVarsInLog = 0;
+ };
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+ 33CC10E92044A3C60003C045 /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */,
+ 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */,
+ 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+ 33CC11202044C79F0003C045 /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */;
+ targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */;
+ };
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+ 33CC10F42044A3C60003C045 /* MainMenu.xib */ = {
+ isa = PBXVariantGroup;
+ children = (
+ 33CC10F52044A3C60003C045 /* Base */,
+ );
+ name = MainMenu.xib;
+ path = Runner;
+ sourceTree = "";
+ };
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+ 338D0CE9231458BD00FA5F75 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = macosx;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ };
+ name = Profile;
+ };
+ 338D0CEA231458BD00FA5F75 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Profile;
+ };
+ 338D0CEB231458BD00FA5F75 /* Profile */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Manual;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Profile;
+ };
+ 33CC10F92044A3C60003C045 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = dwarf;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ ENABLE_TESTABILITY = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_DYNAMIC_NO_PIC = NO;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_OPTIMIZATION_LEVEL = 0;
+ GCC_PREPROCESSOR_DEFINITIONS = (
+ "DEBUG=1",
+ "$(inherited)",
+ );
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MTL_ENABLE_DEBUG_INFO = YES;
+ ONLY_ACTIVE_ARCH = YES;
+ SDKROOT = macosx;
+ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ };
+ name = Debug;
+ };
+ 33CC10FA2044A3C60003C045 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+ buildSettings = {
+ ALWAYS_SEARCH_USER_PATHS = NO;
+ CLANG_ANALYZER_NONNULL = YES;
+ CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
+ CLANG_CXX_LIBRARY = "libc++";
+ CLANG_ENABLE_MODULES = YES;
+ CLANG_ENABLE_OBJC_ARC = YES;
+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+ CLANG_WARN_BOOL_CONVERSION = YES;
+ CLANG_WARN_CONSTANT_CONVERSION = YES;
+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+ CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
+ CLANG_WARN_EMPTY_BODY = YES;
+ CLANG_WARN_ENUM_CONVERSION = YES;
+ CLANG_WARN_INFINITE_RECURSION = YES;
+ CLANG_WARN_INT_CONVERSION = YES;
+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
+ CODE_SIGN_IDENTITY = "-";
+ COPY_PHASE_STRIP = NO;
+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+ ENABLE_NS_ASSERTIONS = NO;
+ ENABLE_STRICT_OBJC_MSGSEND = YES;
+ GCC_C_LANGUAGE_STANDARD = gnu11;
+ GCC_NO_COMMON_BLOCKS = YES;
+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+ GCC_WARN_UNUSED_FUNCTION = YES;
+ GCC_WARN_UNUSED_VARIABLE = YES;
+ MACOSX_DEPLOYMENT_TARGET = 10.11;
+ MTL_ENABLE_DEBUG_INFO = NO;
+ SDKROOT = macosx;
+ SWIFT_COMPILATION_MODE = wholemodule;
+ SWIFT_OPTIMIZATION_LEVEL = "-O";
+ };
+ name = Release;
+ };
+ 33CC10FC2044A3C60003C045 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Debug;
+ };
+ 33CC10FD2044A3C60003C045 /* Release */ = {
+ isa = XCBuildConfiguration;
+ baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
+ buildSettings = {
+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CLANG_ENABLE_MODULES = YES;
+ CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
+ CODE_SIGN_STYLE = Automatic;
+ COMBINE_HIDPI_IMAGES = YES;
+ INFOPLIST_FILE = Runner/Info.plist;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/../Frameworks",
+ );
+ PROVISIONING_PROFILE_SPECIFIER = "";
+ SWIFT_VERSION = 5.0;
+ };
+ name = Release;
+ };
+ 33CC111C2044C6BA0003C045 /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Manual;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Debug;
+ };
+ 33CC111D2044C6BA0003C045 /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ };
+ name = Release;
+ };
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+ 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33CC10F92044A3C60003C045 /* Debug */,
+ 33CC10FA2044A3C60003C045 /* Release */,
+ 338D0CE9231458BD00FA5F75 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33CC10FC2044A3C60003C045 /* Debug */,
+ 33CC10FD2044A3C60003C045 /* Release */,
+ 338D0CEA231458BD00FA5F75 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+ 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ 33CC111C2044C6BA0003C045 /* Debug */,
+ 33CC111D2044C6BA0003C045 /* Release */,
+ 338D0CEB231458BD00FA5F75 /* Profile */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
+/* End XCConfigurationList section */
+ };
+ rootObject = 33CC10E52044A3C60003C045 /* Project object */;
+}
diff --git a/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..fc6bf807
--- /dev/null
+++ b/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 00000000..a462e330
--- /dev/null
+++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/example/macos/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 00000000..21a3cc14
--- /dev/null
+++ b/example/macos/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
diff --git a/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 00000000..fc6bf807
--- /dev/null
+++ b/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+
+
+
+
+ IDEDidComputeMac32BitWarning
+
+
+
diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift
new file mode 100644
index 00000000..553a135b
--- /dev/null
+++ b/example/macos/Runner/AppDelegate.swift
@@ -0,0 +1,9 @@
+import Cocoa
+import FlutterMacOS
+
+@NSApplicationMain
+class AppDelegate: FlutterAppDelegate {
+ override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
+ return true
+ }
+}
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 00000000..8d4e7cb8
--- /dev/null
+++ b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,68 @@
+{
+ "images" : [
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "app_icon_16.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "16x16",
+ "idiom" : "mac",
+ "filename" : "app_icon_32.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "app_icon_32.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "32x32",
+ "idiom" : "mac",
+ "filename" : "app_icon_64.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "app_icon_128.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "128x128",
+ "idiom" : "mac",
+ "filename" : "app_icon_256.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "app_icon_256.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "256x256",
+ "idiom" : "mac",
+ "filename" : "app_icon_512.png",
+ "scale" : "2x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "app_icon_512.png",
+ "scale" : "1x"
+ },
+ {
+ "size" : "512x512",
+ "idiom" : "mac",
+ "filename" : "app_icon_1024.png",
+ "scale" : "2x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png
new file mode 100644
index 00000000..3c4935a7
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png
new file mode 100644
index 00000000..ed4cc164
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png
new file mode 100644
index 00000000..483be613
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png
new file mode 100644
index 00000000..bcbf36df
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png
new file mode 100644
index 00000000..9c0a6528
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png
new file mode 100644
index 00000000..e71a7261
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ
diff --git a/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png
new file mode 100644
index 00000000..8a31fe2d
Binary files /dev/null and b/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ
diff --git a/example/macos/Runner/Base.lproj/MainMenu.xib b/example/macos/Runner/Base.lproj/MainMenu.xib
new file mode 100644
index 00000000..030024dc
--- /dev/null
+++ b/example/macos/Runner/Base.lproj/MainMenu.xib
@@ -0,0 +1,339 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/example/macos/Runner/Configs/AppInfo.xcconfig b/example/macos/Runner/Configs/AppInfo.xcconfig
new file mode 100644
index 00000000..38c93ba6
--- /dev/null
+++ b/example/macos/Runner/Configs/AppInfo.xcconfig
@@ -0,0 +1,14 @@
+// Application-level settings for the Runner target.
+//
+// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the
+// future. If not, the values below would default to using the project name when this becomes a
+// 'flutter create' template.
+
+// The application's name. By default this is also the title of the Flutter window.
+PRODUCT_NAME = app
+
+// The application's bundle identifier
+PRODUCT_BUNDLE_IDENTIFIER = com.example.app
+
+// The copyright displayed in application information
+PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved.
diff --git a/example/macos/Runner/Configs/Debug.xcconfig b/example/macos/Runner/Configs/Debug.xcconfig
new file mode 100644
index 00000000..b3988237
--- /dev/null
+++ b/example/macos/Runner/Configs/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Debug.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/example/macos/Runner/Configs/Release.xcconfig b/example/macos/Runner/Configs/Release.xcconfig
new file mode 100644
index 00000000..d93e5dc4
--- /dev/null
+++ b/example/macos/Runner/Configs/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "../../Flutter/Flutter-Release.xcconfig"
+#include "Warnings.xcconfig"
diff --git a/example/macos/Runner/Configs/Warnings.xcconfig b/example/macos/Runner/Configs/Warnings.xcconfig
new file mode 100644
index 00000000..fb4d7d3f
--- /dev/null
+++ b/example/macos/Runner/Configs/Warnings.xcconfig
@@ -0,0 +1,13 @@
+WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings
+GCC_WARN_UNDECLARED_SELECTOR = YES
+CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES
+CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE
+CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
+CLANG_WARN_PRAGMA_PACK = YES
+CLANG_WARN_STRICT_PROTOTYPES = YES
+CLANG_WARN_COMMA = YES
+GCC_WARN_STRICT_SELECTOR_MATCH = YES
+CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES
+CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES
+GCC_WARN_SHADOW = YES
+CLANG_WARN_UNREACHABLE_CODE = YES
diff --git a/example/macos/Runner/DebugProfile.entitlements b/example/macos/Runner/DebugProfile.entitlements
new file mode 100644
index 00000000..51d09670
--- /dev/null
+++ b/example/macos/Runner/DebugProfile.entitlements
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.network.server
+
+
+
diff --git a/example/macos/Runner/Info.plist b/example/macos/Runner/Info.plist
new file mode 100644
index 00000000..3733c1a8
--- /dev/null
+++ b/example/macos/Runner/Info.plist
@@ -0,0 +1,32 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIconFile
+
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ $(PRODUCT_NAME)
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSMinimumSystemVersion
+ $(MACOSX_DEPLOYMENT_TARGET)
+ NSHumanReadableCopyright
+ $(PRODUCT_COPYRIGHT)
+ NSMainNibFile
+ MainMenu
+ NSPrincipalClass
+ NSApplication
+
+
diff --git a/example/macos/Runner/MainFlutterWindow.swift b/example/macos/Runner/MainFlutterWindow.swift
new file mode 100644
index 00000000..4cb95dc9
--- /dev/null
+++ b/example/macos/Runner/MainFlutterWindow.swift
@@ -0,0 +1,15 @@
+import Cocoa
+import FlutterMacOS
+
+class MainFlutterWindow: NSWindow {
+ override func awakeFromNib() {
+ let flutterViewController = FlutterViewController.init()
+ let windowFrame = self.frame
+ self.contentViewController = flutterViewController
+ self.setFrame(windowFrame, display: true)
+
+ RegisterGeneratedPlugins(registry: flutterViewController)
+
+ super.awakeFromNib()
+ }
+}
diff --git a/example/macos/Runner/Release.entitlements b/example/macos/Runner/Release.entitlements
new file mode 100644
index 00000000..04336df3
--- /dev/null
+++ b/example/macos/Runner/Release.entitlements
@@ -0,0 +1,8 @@
+
+
+
+
+ com.apple.security.app-sandbox
+
+
+
diff --git a/example/main.dart b/example/main.dart
deleted file mode 100644
index 3ad674e6..00000000
--- a/example/main.dart
+++ /dev/null
@@ -1,2 +0,0 @@
-import 'dart:async';
-import 'dart:io';
\ No newline at end of file
diff --git a/app/pubspec.yaml b/example/pubspec.yaml
similarity index 94%
rename from app/pubspec.yaml
rename to example/pubspec.yaml
index 3a6814e0..e0ec2585 100644
--- a/app/pubspec.yaml
+++ b/example/pubspec.yaml
@@ -18,16 +18,19 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
- sdk: ">=2.7.0 <3.0.0"
+ sdk: '>=2.12.0 <3.0.0'
dependencies:
flutter:
sdk: flutter
-
+ universal_html: ^2.0.7
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
- cupertino_icons: ^1.0.0
+ cupertino_icons: ^1.0.2
+ path_provider: ^2.0.1
+ filesystem_picker: ^2.0.0-nullsafety.0
+ file_picker: ^3.0.2+2
flutter_quill:
path: ../
diff --git a/app/test/widget_test.dart b/example/test/widget_test.dart
similarity index 86%
rename from app/test/widget_test.dart
rename to example/test/widget_test.dart
index 4088468f..bf233450 100644
--- a/app/test/widget_test.dart
+++ b/example/test/widget_test.dart
@@ -7,13 +7,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
-
-import '../lib/main.dart';
-
-// import 'package:app/main.dart';
+import 'package:app/main.dart';
void main() {
- testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+ testWidgets('Counter increments smoke test', (tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(MyApp());
diff --git a/app/web/favicon.png b/example/web/favicon.png
similarity index 100%
rename from app/web/favicon.png
rename to example/web/favicon.png
diff --git a/app/web/icons/Icon-192.png b/example/web/icons/Icon-192.png
similarity index 100%
rename from app/web/icons/Icon-192.png
rename to example/web/icons/Icon-192.png
diff --git a/app/web/icons/Icon-512.png b/example/web/icons/Icon-512.png
similarity index 100%
rename from app/web/icons/Icon-512.png
rename to example/web/icons/Icon-512.png
diff --git a/app/web/index.html b/example/web/index.html
similarity index 100%
rename from app/web/index.html
rename to example/web/index.html
diff --git a/app/web/manifest.json b/example/web/manifest.json
similarity index 100%
rename from app/web/manifest.json
rename to example/web/manifest.json
diff --git a/app/windows/.gitignore b/example/windows/.gitignore
similarity index 100%
rename from app/windows/.gitignore
rename to example/windows/.gitignore
diff --git a/app/windows/CMakeLists.txt b/example/windows/CMakeLists.txt
similarity index 100%
rename from app/windows/CMakeLists.txt
rename to example/windows/CMakeLists.txt
diff --git a/app/windows/flutter/CMakeLists.txt b/example/windows/flutter/CMakeLists.txt
similarity index 100%
rename from app/windows/flutter/CMakeLists.txt
rename to example/windows/flutter/CMakeLists.txt
diff --git a/app/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc
similarity index 100%
rename from app/windows/flutter/generated_plugin_registrant.cc
rename to example/windows/flutter/generated_plugin_registrant.cc
diff --git a/app/windows/flutter/generated_plugin_registrant.h b/example/windows/flutter/generated_plugin_registrant.h
similarity index 100%
rename from app/windows/flutter/generated_plugin_registrant.h
rename to example/windows/flutter/generated_plugin_registrant.h
diff --git a/app/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake
similarity index 100%
rename from app/windows/flutter/generated_plugins.cmake
rename to example/windows/flutter/generated_plugins.cmake
diff --git a/app/windows/runner/CMakeLists.txt b/example/windows/runner/CMakeLists.txt
similarity index 100%
rename from app/windows/runner/CMakeLists.txt
rename to example/windows/runner/CMakeLists.txt
diff --git a/app/windows/runner/Runner.rc b/example/windows/runner/Runner.rc
similarity index 100%
rename from app/windows/runner/Runner.rc
rename to example/windows/runner/Runner.rc
diff --git a/app/windows/runner/flutter_window.cpp b/example/windows/runner/flutter_window.cpp
similarity index 100%
rename from app/windows/runner/flutter_window.cpp
rename to example/windows/runner/flutter_window.cpp
diff --git a/app/windows/runner/flutter_window.h b/example/windows/runner/flutter_window.h
similarity index 100%
rename from app/windows/runner/flutter_window.h
rename to example/windows/runner/flutter_window.h
diff --git a/app/windows/runner/main.cpp b/example/windows/runner/main.cpp
similarity index 100%
rename from app/windows/runner/main.cpp
rename to example/windows/runner/main.cpp
diff --git a/app/windows/runner/resource.h b/example/windows/runner/resource.h
similarity index 100%
rename from app/windows/runner/resource.h
rename to example/windows/runner/resource.h
diff --git a/app/windows/runner/resources/app_icon.ico b/example/windows/runner/resources/app_icon.ico
similarity index 100%
rename from app/windows/runner/resources/app_icon.ico
rename to example/windows/runner/resources/app_icon.ico
diff --git a/app/windows/runner/run_loop.cpp b/example/windows/runner/run_loop.cpp
similarity index 100%
rename from app/windows/runner/run_loop.cpp
rename to example/windows/runner/run_loop.cpp
diff --git a/app/windows/runner/run_loop.h b/example/windows/runner/run_loop.h
similarity index 100%
rename from app/windows/runner/run_loop.h
rename to example/windows/runner/run_loop.h
diff --git a/app/windows/runner/runner.exe.manifest b/example/windows/runner/runner.exe.manifest
similarity index 100%
rename from app/windows/runner/runner.exe.manifest
rename to example/windows/runner/runner.exe.manifest
diff --git a/app/windows/runner/utils.cpp b/example/windows/runner/utils.cpp
similarity index 100%
rename from app/windows/runner/utils.cpp
rename to example/windows/runner/utils.cpp
diff --git a/app/windows/runner/utils.h b/example/windows/runner/utils.h
similarity index 100%
rename from app/windows/runner/utils.h
rename to example/windows/runner/utils.h
diff --git a/app/windows/runner/win32_window.cpp b/example/windows/runner/win32_window.cpp
similarity index 100%
rename from app/windows/runner/win32_window.cpp
rename to example/windows/runner/win32_window.cpp
diff --git a/app/windows/runner/win32_window.h b/example/windows/runner/win32_window.h
similarity index 100%
rename from app/windows/runner/win32_window.h
rename to example/windows/runner/win32_window.h
diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart
index 5e247d9a..29ba8a6c 100644
--- a/lib/flutter_quill.dart
+++ b/lib/flutter_quill.dart
@@ -1,2 +1,12 @@
library flutter_quill;
export 'package:tuple/tuple.dart';
+
+export 'src/models/documents/attribute.dart';
+export 'src/models/documents/document.dart';
+export 'src/models/documents/nodes/embed.dart';
+export 'src/models/documents/nodes/leaf.dart';
+export 'src/models/quill_delta.dart';
+export 'src/widgets/controller.dart';
+export 'src/widgets/default_styles.dart';
+export 'src/widgets/editor.dart';
+export 'src/widgets/toolbar.dart';
diff --git a/lib/models/documents/attribute.dart b/lib/models/documents/attribute.dart
index cd152e53..e106383e 100644
--- a/lib/models/documents/attribute.dart
+++ b/lib/models/documents/attribute.dart
@@ -1,260 +1,3 @@
-import 'package:quiver_hashcode/hashcode.dart';
-
-enum AttributeScope {
- INLINE, // refer to https://quilljs.com/docs/formats/#inline
- BLOCK, // refer to https://quilljs.com/docs/formats/#block
- EMBEDS, // refer to https://quilljs.com/docs/formats/#embeds
- IGNORE, // attributes that can be ignored
-}
-
-class Attribute {
- final String key;
- final AttributeScope scope;
- final T value;
-
- Attribute(this.key, this.scope, this.value);
-
- static final Map _registry = {
- Attribute.bold.key: Attribute.bold,
- Attribute.italic.key: Attribute.italic,
- Attribute.underline.key: Attribute.underline,
- Attribute.strikeThrough.key: Attribute.strikeThrough,
- Attribute.font.key: Attribute.font,
- Attribute.size.key: Attribute.size,
- Attribute.link.key: Attribute.link,
- Attribute.color.key: Attribute.color,
- Attribute.background.key: Attribute.background,
- Attribute.header.key: Attribute.header,
- Attribute.indent.key: Attribute.indent,
- Attribute.align.key: Attribute.align,
- Attribute.list.key: Attribute.list,
- Attribute.codeBlock.key: Attribute.codeBlock,
- Attribute.blockQuote.key: Attribute.blockQuote,
- Attribute.width.key: Attribute.width,
- Attribute.height.key: Attribute.height,
- Attribute.style.key: Attribute.style,
- };
-
- static final BoldAttribute bold = BoldAttribute();
-
- static final ItalicAttribute italic = ItalicAttribute();
-
- static final UnderlineAttribute underline = UnderlineAttribute();
-
- static final StrikeThroughAttribute strikeThrough = StrikeThroughAttribute();
-
- static final FontAttribute font = FontAttribute(null);
-
- static final SizeAttribute size = SizeAttribute(null);
-
- static final LinkAttribute link = LinkAttribute(null);
-
- static final ColorAttribute color = ColorAttribute(null);
-
- static final BackgroundAttribute background = BackgroundAttribute(null);
-
- static final HeaderAttribute header = HeaderAttribute();
-
- static final IndentAttribute indent = IndentAttribute();
-
- static final AlignAttribute align = AlignAttribute(null);
-
- static final ListAttribute list = ListAttribute(null);
-
- static final CodeBlockAttribute codeBlock = CodeBlockAttribute();
-
- static final BlockQuoteAttribute blockQuote = BlockQuoteAttribute();
-
- static final WidthAttribute width = WidthAttribute(null);
-
- static final HeightAttribute height = HeightAttribute(null);
-
- static final StyleAttribute style = StyleAttribute(null);
-
- static final Set inlineKeys = {
- Attribute.bold.key,
- Attribute.italic.key,
- Attribute.underline.key,
- Attribute.strikeThrough.key,
- Attribute.link.key,
- Attribute.color.key,
- Attribute.background.key
- };
-
- static final Set blockKeys = {
- Attribute.header.key,
- Attribute.indent.key,
- Attribute.align.key,
- Attribute.list.key,
- Attribute.codeBlock.key,
- Attribute.blockQuote.key,
- };
-
- static final Set blockKeysExceptHeader = {
- Attribute.list.key,
- Attribute.indent.key,
- Attribute.align.key,
- Attribute.codeBlock.key,
- Attribute.blockQuote.key,
- };
-
- static Attribute get h1 => HeaderAttribute(level: 1);
-
- static Attribute get h2 => HeaderAttribute(level: 2);
-
- static Attribute get h3 => HeaderAttribute(level: 3);
-
- // "attributes":{"align":"left"}
- static Attribute get leftAlignment => AlignAttribute('left');
-
- // "attributes":{"align":"center"}
- static Attribute get centerAlignment => AlignAttribute('center');
-
- // "attributes":{"align":"right"}
- static Attribute get rightAlignment => AlignAttribute('right');
-
- // "attributes":{"align":"justify"}
- static Attribute get justifyAlignment => AlignAttribute('justify');
-
- // "attributes":{"list":"bullet"}
- static Attribute get ul => ListAttribute('bullet');
-
- // "attributes":{"list":"ordered"}
- static Attribute get ol => ListAttribute('ordered');
-
- // "attributes":{"list":"checked"}
- static Attribute get checked => ListAttribute('checked');
-
- // "attributes":{"list":"unchecked"}
- static Attribute get unchecked => ListAttribute('unchecked');
-
- // "attributes":{"indent":1"}
- static Attribute get indentL1 => IndentAttribute(level: 1);
-
- // "attributes":{"indent":2"}
- static Attribute get indentL2 => IndentAttribute(level: 2);
-
- // "attributes":{"indent":3"}
- static Attribute get indentL3 => IndentAttribute(level: 3);
-
- static Attribute getIndentLevel(int level) {
- if (level == 1) {
- return indentL1;
- }
- if (level == 2) {
- return indentL2;
- }
- return indentL3;
- }
-
- bool get isInline => scope == AttributeScope.INLINE;
-
- bool get isBlockExceptHeader => blockKeysExceptHeader.contains(key);
-
- Map toJson() => {key: value};
-
- static Attribute fromKeyValue(String key, dynamic value) {
- if (!_registry.containsKey(key)) {
- return null;
- // throw ArgumentError.value(key, 'key "$key" not found.');
- }
- Attribute origin = _registry[key];
- Attribute attribute = clone(origin, value);
- return attribute;
- }
-
- static Attribute clone(Attribute origin, dynamic value) {
- return Attribute(origin.key, origin.scope, value);
- }
-
- @override
- bool operator ==(Object other) {
- if (identical(this, other)) return true;
- if (other is! Attribute) return false;
- Attribute typedOther = other;
- return key == typedOther.key &&
- scope == typedOther.scope &&
- value == typedOther.value;
- }
-
- @override
- int get hashCode => hash3(key, scope, value);
-
- @override
- String toString() {
- return 'Attribute{key: $key, scope: $scope, value: $value}';
- }
-}
-
-class BoldAttribute extends Attribute {
- BoldAttribute() : super('bold', AttributeScope.INLINE, true);
-}
-
-class ItalicAttribute extends Attribute {
- ItalicAttribute() : super('italic', AttributeScope.INLINE, true);
-}
-
-class UnderlineAttribute extends Attribute {
- UnderlineAttribute() : super('underline', AttributeScope.INLINE, true);
-}
-
-class StrikeThroughAttribute extends Attribute {
- StrikeThroughAttribute() : super('strike', AttributeScope.INLINE, true);
-}
-
-class FontAttribute extends Attribute {
- FontAttribute(String val) : super('font', AttributeScope.INLINE, val);
-}
-
-class SizeAttribute extends Attribute {
- SizeAttribute(String val) : super('size', AttributeScope.INLINE, val);
-}
-
-class LinkAttribute extends Attribute {
- LinkAttribute(String val) : super('link', AttributeScope.INLINE, val);
-}
-
-class ColorAttribute extends Attribute {
- ColorAttribute(String val) : super('color', AttributeScope.INLINE, val);
-}
-
-class BackgroundAttribute extends Attribute {
- BackgroundAttribute(String val)
- : super('background', AttributeScope.INLINE, val);
-}
-
-class HeaderAttribute extends Attribute {
- HeaderAttribute({int level}) : super('header', AttributeScope.BLOCK, level);
-}
-
-class IndentAttribute extends Attribute {
- IndentAttribute({int level}) : super('indent', AttributeScope.BLOCK, level);
-}
-
-class AlignAttribute extends Attribute {
- AlignAttribute(String val) : super('align', AttributeScope.BLOCK, val);
-}
-
-class ListAttribute extends Attribute {
- ListAttribute(String val) : super('list', AttributeScope.BLOCK, val);
-}
-
-class CodeBlockAttribute extends Attribute {
- CodeBlockAttribute() : super('code-block', AttributeScope.BLOCK, true);
-}
-
-class BlockQuoteAttribute extends Attribute {
- BlockQuoteAttribute() : super('blockquote', AttributeScope.BLOCK, true);
-}
-
-class WidthAttribute extends Attribute {
- WidthAttribute(String val) : super('width', AttributeScope.IGNORE, val);
-}
-
-class HeightAttribute extends Attribute {
- HeightAttribute(String val) : super('height', AttributeScope.IGNORE, val);
-}
-
-class StyleAttribute extends Attribute {
- StyleAttribute(String val) : super('style', AttributeScope.IGNORE, val);
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../src/models/documents/attribute.dart';
diff --git a/lib/models/documents/document.dart b/lib/models/documents/document.dart
index bcfd5152..a187d19d 100644
--- a/lib/models/documents/document.dart
+++ b/lib/models/documents/document.dart
@@ -1,245 +1,3 @@
-import 'dart:async';
-
-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:tuple/tuple.dart';
-
-import '../rules/rule.dart';
-import 'attribute.dart';
-import 'history.dart';
-import 'nodes/embed.dart';
-import 'nodes/node.dart';
-
-/// The rich text document
-class Document {
- /// The root node of the document tree
- final Root _root = Root();
-
- Root get root => _root;
-
- int get length => _root.length;
-
- Delta _delta;
-
- Delta toDelta() => Delta.from(_delta);
-
- final Rules _rules = Rules.getInstance();
-
- final StreamController> _observer =
- StreamController.broadcast();
-
- final History _history = History();
-
- Stream> get changes => _observer.stream;
-
- Document() : _delta = Delta()..insert('\n') {
- _loadDocument(_delta);
- }
-
- Document.fromJson(List data) : _delta = _transform(Delta.fromJson(data)) {
- _loadDocument(_delta);
- }
-
- Delta insert(int index, Object data) {
- assert(index >= 0);
- assert(data is String || data is Embeddable);
- if (data is Embeddable) {
- data = (data as Embeddable).toJson();
- } else if ((data as String).isEmpty) {
- return Delta();
- }
-
- Delta delta = _rules.apply(RuleType.INSERT, this, index, data: data);
- compose(delta, ChangeSource.LOCAL);
- return delta;
- }
-
- Delta delete(int index, int len) {
- assert(index >= 0 && len > 0);
- Delta delta = _rules.apply(RuleType.DELETE, this, index, len: len);
- if (delta.isNotEmpty) {
- compose(delta, ChangeSource.LOCAL);
- }
- return delta;
- }
-
- Delta replace(int index, int len, Object data) {
- assert(index >= 0);
- assert(data is String || data is Embeddable);
-
- bool dataIsNotEmpty = (data is String) ? data.isNotEmpty : true;
-
- assert(dataIsNotEmpty || len > 0);
-
- Delta delta = Delta();
-
- if (dataIsNotEmpty) {
- delta = insert(index + len, data);
- }
-
- if (len > 0) {
- Delta deleteDelta = delete(index, len);
- delta = delta.compose(deleteDelta);
- }
-
- return delta;
- }
-
- Delta format(int index, int len, Attribute attribute) {
- assert(index >= 0 && len >= 0 && attribute != null);
-
- Delta delta = Delta();
-
- Delta formatDelta = _rules.apply(RuleType.FORMAT, this, index,
- len: len, attribute: attribute);
- if (formatDelta.isNotEmpty) {
- compose(formatDelta, ChangeSource.LOCAL);
- delta = delta.compose(formatDelta);
- }
-
- return delta;
- }
-
- Style collectStyle(int index, int len) {
- ChildQuery res = queryChild(index);
- return (res.node as Line).collectStyle(res.offset, len);
- }
-
- ChildQuery queryChild(int offset) {
- ChildQuery res = _root.queryChild(offset, true);
- if (res.node is Line) {
- return res;
- }
- Block block = res.node;
- return block.queryChild(res.offset, true);
- }
-
- compose(Delta delta, ChangeSource changeSource) {
- assert(!_observer.isClosed);
- delta.trim();
- assert(delta.isNotEmpty);
-
- int offset = 0;
- delta = _transform(delta);
- Delta originalDelta = toDelta();
- for (Operation op in delta.toList()) {
- Style style =
- op.attributes != null ? Style.fromJson(op.attributes) : null;
-
- if (op.isInsert) {
- _root.insert(offset, _normalize(op.data), style);
- } else if (op.isDelete) {
- _root.delete(offset, op.length);
- } else if (op.attributes != null) {
- _root.retain(offset, op.length, style);
- }
-
- if (!op.isDelete) {
- offset += op.length;
- }
- }
- try {
- _delta = _delta.compose(delta);
- } catch (e) {
- throw ('_delta compose failed');
- }
-
- if (_delta != _root.toDelta()) {
- throw ('Compose failed');
- }
- final change = Tuple3(originalDelta, delta, changeSource);
- _observer.add(change);
- _history.handleDocChange(change);
- }
-
- Tuple2 undo() {
- return _history.undo(this);
- }
-
- Tuple2 redo() {
- return _history.redo(this);
- }
-
- get hasUndo => _history.hasUndo;
-
- get hasRedo => _history.hasRedo;
-
- static Delta _transform(Delta delta) {
- Delta res = Delta();
- List ops = delta.toList();
- for (int i = 0; i < ops.length; i++) {
- Operation op = ops[i];
- res.push(op);
- _handleImageInsert(i, ops, op, res);
- }
- return res;
- }
-
- static void _handleImageInsert(
- int i, List ops, Operation op, Delta res) {
- bool nextOpIsImage =
- i + 1 < ops.length && ops[i + 1].isInsert && ops[i + 1].data is! String;
- if (nextOpIsImage && !(op.data as String).endsWith('\n')) {
- res.push(Operation.insert('\n', null));
- }
- // Currently embed is equivalent to image and hence `is! String`
- bool opInsertImage = op.isInsert && op.data is! String;
- bool nextOpIsLineBreak = i + 1 < ops.length &&
- ops[i + 1].isInsert &&
- ops[i + 1].data is String &&
- (ops[i + 1].data as String).startsWith('\n');
- if (opInsertImage && (i + 1 == ops.length - 1 || !nextOpIsLineBreak)) {
- // automatically append '\n' for image
- res.push(Operation.insert('\n', null));
- }
- }
-
- Object _normalize(Object data) {
- if (data is String) {
- return data;
- }
-
- if (data is Embeddable) {
- return data;
- }
- return Embeddable.fromJson(data);
- }
-
- close() {
- _observer.close();
- _history.clear();
- }
-
- String toPlainText() => _root.children.map((e) => e.toPlainText()).join('');
-
- _loadDocument(Delta doc) {
- assert((doc.last.data as String).endsWith('\n'));
- int offset = 0;
- for (final op in doc.toList()) {
- if (!op.isInsert) {
- throw ArgumentError.value(doc,
- 'Document Delta can only contain insert operations but ${op.key} found.');
- }
- final style =
- op.attributes != null ? Style.fromJson(op.attributes) : null;
- final data = _normalize(op.data);
- _root.insert(offset, data, style);
- offset += op.length;
- }
- final node = _root.last;
- if (node is Line &&
- node.parent is! Block &&
- node.style.isEmpty &&
- _root.childCount > 1) {
- _root.remove(node);
- }
- }
-}
-
-enum ChangeSource {
- LOCAL,
- REMOTE,
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../src/models/documents/document.dart';
diff --git a/lib/models/documents/history.dart b/lib/models/documents/history.dart
index e0d26948..b07c8e33 100644
--- a/lib/models/documents/history.dart
+++ b/lib/models/documents/history.dart
@@ -1,133 +1,3 @@
-import 'package:quill_delta/quill_delta.dart';
-import 'package:tuple/tuple.dart';
-
-import 'document.dart';
-
-class History {
- final HistoryStack stack = HistoryStack.empty();
-
- get hasUndo => stack.undo.isNotEmpty;
-
- get hasRedo => stack.redo.isNotEmpty;
-
- /// used for disable redo or undo function
- bool ignoreChange;
-
- int lastRecorded;
-
- /// Collaborative editing's conditions should be true
- final bool userOnly;
-
- ///max operation count for undo
- final int maxStack;
-
- ///record delay
- final int interval;
-
- History(
- {this.ignoreChange = false,
- this.interval = 400,
- this.maxStack = 100,
- this.userOnly = false,
- this.lastRecorded = 0});
-
- void handleDocChange(Tuple3 change) {
- if (ignoreChange) return;
- if (!userOnly || change.item3 == ChangeSource.LOCAL) {
- record(change.item2, change.item1);
- } else {
- transform(change.item2);
- }
- }
-
- void clear() {
- stack.clear();
- }
-
- void record(Delta change, Delta before) {
- if (change.isEmpty) return;
- stack.redo.clear();
- Delta undoDelta = change.invert(before);
- final timeStamp = DateTime.now().millisecondsSinceEpoch;
-
- if (lastRecorded + interval > timeStamp && stack.undo.isNotEmpty) {
- final lastDelta = stack.undo.removeLast();
- undoDelta = undoDelta.compose(lastDelta);
- } else {
- lastRecorded = timeStamp;
- }
-
- if (undoDelta.isEmpty) return;
- stack.undo.add(undoDelta);
-
- if (stack.undo.length > maxStack) {
- stack.undo.removeAt(0);
- }
- }
-
- ///
- ///It will override pre local undo delta,replaced by remote change
- ///
- void transform(Delta delta) {
- transformStack(this.stack.undo, delta);
- transformStack(this.stack.redo, delta);
- }
-
- void transformStack(List stack, Delta delta) {
- for (int i = stack.length - 1; i >= 0; i -= 1) {
- final oldDelta = stack[i];
- stack[i] = delta.transform(oldDelta, true);
- delta = oldDelta.transform(delta, false);
- if (stack[i].length == 0) {
- stack.removeAt(i);
- }
- }
- }
-
- Tuple2 _change(Document doc, List source, List dest) {
- if (source.length == 0) {
- return new Tuple2(false, 0);
- }
- Delta delta = source.removeLast();
- // look for insert or delete
- int len = 0;
- List ops = delta.toList();
- for (var i = 0; i < ops.length; i++) {
- if (ops[i].key == Operation.insertKey) {
- len = ops[i].length;
- } else if (ops[i].key == Operation.deleteKey) {
- len = ops[i].length * -1;
- }
- }
- Delta base = Delta.from(doc.toDelta());
- Delta inverseDelta = delta.invert(base);
- dest.add(inverseDelta);
- this.lastRecorded = 0;
- this.ignoreChange = true;
- doc.compose(delta, ChangeSource.LOCAL);
- this.ignoreChange = false;
- return new Tuple2(true, len);
- }
-
- Tuple2 undo(Document doc) {
- return _change(doc, stack.undo, stack.redo);
- }
-
- Tuple2 redo(Document doc) {
- return _change(doc, stack.redo, stack.undo);
- }
-}
-
-class HistoryStack {
- final List undo;
- final List redo;
-
- HistoryStack.empty()
- : undo = [],
- redo = [];
-
- void clear() {
- undo.clear();
- redo.clear();
- }
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../src/models/documents/history.dart';
diff --git a/lib/models/documents/nodes/block.dart b/lib/models/documents/nodes/block.dart
index 710de779..6de6f743 100644
--- a/lib/models/documents/nodes/block.dart
+++ b/lib/models/documents/nodes/block.dart
@@ -1,63 +1,3 @@
-import 'package:quill_delta/quill_delta.dart';
-
-import 'container.dart';
-import 'line.dart';
-import 'node.dart';
-
-class Block extends Container {
- @override
- Line get defaultChild => Line();
-
- @override
- Delta toDelta() {
- return children
- .map((child) => child.toDelta())
- .fold(Delta(), (a, b) => a.concat(b));
- }
-
- @override
- adjust() {
- if (isEmpty) {
- Node sibling = previous;
- unlink();
- if (sibling != null) {
- sibling.adjust();
- }
- return;
- }
-
- Block block = this;
- Node prev = block.previous;
- // merging it with previous block if style is the same
- if (!block.isFirst &&
- block.previous is Block &&
- prev.style == block.style) {
- block.moveChildToNewParent(prev);
- block.unlink();
- block = prev;
- }
- Node next = block.next;
- // merging it with next block if style is the same
- if (!block.isLast && block.next is Block && next.style == block.style) {
- (next as Block).moveChildToNewParent(block);
- next.unlink();
- }
- }
-
- @override
- String toString() {
- final block = style.attributes.toString();
- final buffer = StringBuffer('§ {$block}\n');
- for (var child in children) {
- final tree = child.isLast ? '└' : '├';
- buffer.write(' $tree $child');
- if (!child.isLast) buffer.writeln();
- }
- return buffer.toString();
- }
-
- @override
- Node newInstance() {
- return Block();
- }
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../../src/models/documents/nodes/block.dart';
diff --git a/lib/models/documents/nodes/container.dart b/lib/models/documents/nodes/container.dart
index b4a4aa58..d9a54451 100644
--- a/lib/models/documents/nodes/container.dart
+++ b/lib/models/documents/nodes/container.dart
@@ -1,124 +1,3 @@
-import 'dart:collection';
-
-import '../style.dart';
-import 'node.dart';
-
-/* Container of multiple nodes */
-abstract class Container extends Node {
- final LinkedList _children = LinkedList();
-
- LinkedList get children => _children;
-
- int get childCount => _children.length;
-
- Node get first => _children.first;
-
- Node get last => _children.last;
-
- bool get isEmpty => _children.isEmpty;
-
- bool get isNotEmpty => _children.isNotEmpty;
-
- /// abstract methods begin
-
- T get defaultChild;
-
- /// abstract methods end
-
- add(T node) {
- assert(node.parent == null);
- node.parent = this;
- _children.add(node);
- }
-
- addFirst(T node) {
- assert(node.parent == null);
- node.parent = this;
- _children.addFirst(node);
- }
-
- void remove(T node) {
- assert(node.parent == this);
- node.parent = null;
- _children.remove(node);
- }
-
- void moveChildToNewParent(Container newParent) {
- if (isEmpty) {
- return;
- }
-
- T last = newParent.isEmpty ? null : newParent.last;
- while (isNotEmpty) {
- T child = first;
- child.unlink();
- newParent.add(child);
- }
-
- if (last != null) last.adjust();
- }
-
- ChildQuery queryChild(int offset, bool inclusive) {
- if (offset < 0 || offset > length) {
- return ChildQuery(null, 0);
- }
-
- for (Node node in children) {
- int len = node.length;
- if (offset < len || (inclusive && offset == len && (node.isLast))) {
- return ChildQuery(node, offset);
- }
- offset -= len;
- }
- return ChildQuery(null, 0);
- }
-
- @override
- String toPlainText() => children.map((child) => child.toPlainText()).join();
-
- @override
- int get length => _children.fold(0, (cur, node) => cur + node.length);
-
- @override
- insert(int index, Object data, Style style) {
- assert(index == 0 || (index > 0 && index < length));
-
- if (isNotEmpty) {
- ChildQuery child = queryChild(index, false);
- child.node.insert(child.offset, data, style);
- return;
- }
-
- // empty
- assert(index == 0);
- T node = defaultChild;
- add(node);
- node.insert(index, data, style);
- }
-
- @override
- retain(int index, int length, Style attributes) {
- assert(isNotEmpty);
- ChildQuery child = queryChild(index, false);
- child.node.retain(child.offset, length, attributes);
- }
-
- @override
- delete(int index, int length) {
- assert(isNotEmpty);
- ChildQuery child = queryChild(index, false);
- child.node.delete(child.offset, length);
- }
-
- @override
- String toString() => _children.join('\n');
-}
-
-/// Query of a child in a Container
-class ChildQuery {
- final Node node; // null if not found
-
- final int offset;
-
- ChildQuery(this.node, this.offset);
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../../src/models/documents/nodes/container.dart';
diff --git a/lib/models/documents/nodes/embed.dart b/lib/models/documents/nodes/embed.dart
index a8dff833..01dc357b 100644
--- a/lib/models/documents/nodes/embed.dart
+++ b/lib/models/documents/nodes/embed.dart
@@ -1,28 +1,3 @@
-class Embeddable {
- final String type;
- final dynamic data;
-
- Embeddable(this.type, this.data)
- : assert(type != null),
- assert(data != null);
-
- Map toJson() {
- Map m = {type: data};
- return m;
- }
-
- static Embeddable fromJson(Map json) {
- Map m = Map.from(json);
- assert(m.length == 1, 'Embeddable map has one key');
-
- return BlockEmbed(m.keys.first, m.values.first);
- }
-}
-
-class BlockEmbed extends Embeddable {
- BlockEmbed(String type, String data) : super(type, data);
-
- static final BlockEmbed horizontalRule = BlockEmbed('divider', 'hr');
-
- static BlockEmbed image(String imageUrl) => BlockEmbed('image', imageUrl);
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../../src/models/documents/nodes/embed.dart';
diff --git a/lib/models/documents/nodes/leaf.dart b/lib/models/documents/nodes/leaf.dart
index aefb574d..cc2808f2 100644
--- a/lib/models/documents/nodes/leaf.dart
+++ b/lib/models/documents/nodes/leaf.dart
@@ -1,207 +1,3 @@
-import 'dart:math' as math;
-
-import 'package:quill_delta/quill_delta.dart';
-
-import '../style.dart';
-import 'embed.dart';
-import 'line.dart';
-import 'node.dart';
-
-/* A leaf node in document tree */
-abstract class Leaf extends Node {
- Object _value;
-
- Object get value => _value;
-
- Leaf.val(Object val)
- : assert(val != null),
- _value = val;
-
- factory Leaf([Object data]) {
- assert(data != null);
-
- if (data is Embeddable) {
- return Embed(data);
- }
- String text = data as String;
- assert(text.isNotEmpty);
- return Text(text);
- }
-
- @override
- void applyStyle(Style value) {
- assert(
- value != null && (value.isInline || value.isIgnored || value.isEmpty),
- 'Unable to apply Style to leaf: $value');
- super.applyStyle(value);
- }
-
- @override
- Line get parent => super.parent as Line;
-
- @override
- int get length {
- if (_value is String) {
- return (_value as String).length;
- }
- // return 1 for embedded object
- return 1;
- }
-
- @override
- Delta toDelta() {
- var data = _value is Embeddable ? (_value as Embeddable).toJson() : _value;
- return Delta()..insert(data, style.toJson());
- }
-
- @override
- insert(int index, Object data, Style style) {
- assert(data != null && index >= 0 && index <= length);
- Leaf node = Leaf(data);
- if (index < length) {
- splitAt(index).insertBefore(node);
- } else {
- insertAfter(node);
- }
- node.format(style);
- }
-
- @override
- retain(int index, int len, Style style) {
- if (style == null) {
- return;
- }
-
- int local = math.min(this.length - index, len);
- int remain = len - local;
- Leaf node = _isolate(index, local);
-
- if (remain > 0) {
- assert(node.next != null);
- node.next.retain(0, remain, style);
- }
- node.format(style);
- }
-
- @override
- delete(int index, int len) {
- assert(index < this.length);
-
- int local = math.min(this.length - index, len);
- Leaf target = _isolate(index, local);
- Leaf prev = target.previous;
- Leaf next = target.next;
- target.unlink();
-
- int remain = len - local;
- if (remain > 0) {
- assert(next != null);
- next.delete(0, remain);
- }
-
- if (prev != null) {
- prev.adjust();
- }
- }
-
- @override
- adjust() {
- if (this is Embed) {
- return;
- }
-
- Text node = this as Text;
- // merging it with previous node if style is the same
- Node prev = node.previous;
- if (!node.isFirst && prev is Text && prev.style == node.style) {
- prev._value = prev.value + node.value;
- node.unlink();
- node = prev;
- }
-
- // merging it with next node if style is the same
- Node next = node.next;
- if (!node.isLast && next is Text && next.style == node.style) {
- node._value = node.value + next.value;
- next.unlink();
- }
- }
-
- Leaf cutAt(int index) {
- assert(index >= 0 && index <= length);
- Leaf cut = splitAt(index);
- cut?.unlink();
- return cut;
- }
-
- Leaf splitAt(int index) {
- assert(index >= 0 && index <= length);
- if (index == 0) {
- return this;
- }
- if (index == length) {
- return isLast ? null : next as Leaf;
- }
-
- assert(this is Text);
- String text = _value as String;
- _value = text.substring(0, index);
- Leaf split = Leaf(text.substring(index));
- split.applyStyle(style);
- insertAfter(split);
- return split;
- }
-
- format(Style style) {
- if (style != null && style.isNotEmpty) {
- applyStyle(style);
- }
-
- adjust();
- }
-
- Leaf _isolate(int index, int length) {
- assert(
- index >= 0 && index < this.length && (index + length <= this.length));
- Leaf target = splitAt(index);
- target.splitAt(length);
- return target;
- }
-}
-
-class Text extends Leaf {
- Text([String text = ''])
- : assert(!text.contains('\n')),
- super.val(text);
-
- @override
- String get value => _value as String;
-
- @override
- String toPlainText() {
- return value;
- }
-
- @override
- Node newInstance() {
- return Text();
- }
-}
-
-/// An embedded node such as image or video
-class Embed extends Leaf {
- Embed(Embeddable data) : super.val(data);
-
- @override
- Embeddable get value => super.value as Embeddable;
-
- @override
- String toPlainText() {
- return '\uFFFC';
- }
-
- @override
- Node newInstance() {
- throw UnimplementedError();
- }
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../../src/models/documents/nodes/leaf.dart';
diff --git a/lib/models/documents/nodes/line.dart b/lib/models/documents/nodes/line.dart
index 2727e101..7ca2016e 100644
--- a/lib/models/documents/nodes/line.dart
+++ b/lib/models/documents/nodes/line.dart
@@ -1,318 +1,3 @@
-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 '../style.dart';
-import 'block.dart';
-import 'container.dart';
-import 'embed.dart';
-import 'leaf.dart';
-
-class Line extends Container {
- @override
- Leaf get defaultChild => Text();
-
- @override
- int get length => super.length + 1;
-
- bool get hasEmbed {
- if (childCount != 1) {
- return false;
- }
-
- return children.single is Embed;
- }
-
- Line get nextLine {
- if (!isLast) {
- return next is Block ? (next as Block).first : next;
- }
- if (parent is! Block) {
- return null;
- }
-
- if (parent.isLast) {
- return null;
- }
- return parent.next is Block ? (parent.next as Block).first : parent.next;
- }
-
- @override
- Delta toDelta() {
- final delta = children
- .map((child) => child.toDelta())
- .fold(Delta(), (a, b) => a.concat(b));
- var attributes = style;
- if (parent is Block) {
- Block block = parent;
- attributes = attributes.mergeAll(block.style);
- }
- delta.insert('\n', attributes.toJson());
- return delta;
- }
-
- @override
- String toPlainText() => super.toPlainText() + '\n';
-
- @override
- String toString() {
- final body = children.join(' → ');
- final styleString = style.isNotEmpty ? ' $style' : '';
- return '¶ $body ⏎$styleString';
- }
-
- @override
- insert(int index, Object data, Style style) {
- if (data is Embeddable) {
- _insert(index, data, style);
- return;
- }
-
- String text = data as String;
- int lineBreak = text.indexOf('\n');
- if (lineBreak < 0) {
- _insert(index, text, style);
- return;
- }
-
- String prefix = text.substring(0, lineBreak);
- _insert(index, prefix, style);
- if (prefix.isNotEmpty) {
- index += prefix.length;
- }
-
- Line nextLine = _getNextLine(index);
-
- clearStyle();
-
- if (parent is Block) {
- _unwrap();
- }
-
- _format(style);
-
- // Continue with the remaining
- String remain = text.substring(lineBreak + 1);
- nextLine.insert(0, remain, style);
- }
-
- @override
- retain(int index, int len, Style style) {
- if (style == null) {
- return;
- }
- int thisLen = this.length;
-
- int local = math.min(thisLen - index, len);
-
- if (index + local == thisLen && local == 1) {
- assert(style.values.every((attr) => attr.scope == AttributeScope.BLOCK));
- _format(style);
- } else {
- assert(style.values.every((attr) => attr.scope == AttributeScope.INLINE));
- assert(index + local != thisLen);
- super.retain(index, local, style);
- }
-
- int remain = len - local;
- if (remain > 0) {
- assert(nextLine != null);
- nextLine.retain(0, remain, style);
- }
- }
-
- @override
- delete(int index, int len) {
- int local = math.min(this.length - index, len);
- bool deleted = index + local == this.length;
- if (deleted) {
- clearStyle();
- if (local > 1) {
- super.delete(index, local - 1);
- }
- } else {
- super.delete(index, local);
- }
-
- int remain = len - local;
- if (remain > 0) {
- assert(nextLine != null);
- nextLine.delete(0, remain);
- }
-
- if (deleted && isNotEmpty) {
- assert(nextLine != null);
- nextLine.moveChildToNewParent(this);
- moveChildToNewParent(nextLine);
- }
-
- if (deleted) {
- Node p = parent;
- unlink();
- p.adjust();
- }
- }
-
- void _format(Style newStyle) {
- if (newStyle == null || newStyle.isEmpty) {
- return;
- }
-
- applyStyle(newStyle);
- Attribute blockStyle = newStyle.getBlockExceptHeader();
- if (blockStyle == null) {
- return;
- }
-
- if (parent is Block) {
- Attribute parentStyle = (parent as Block).style.getBlockExceptHeader();
- if (blockStyle.value == null) {
- _unwrap();
- } else if (blockStyle != parentStyle) {
- _unwrap();
- Block block = Block();
- block.applyAttribute(blockStyle);
- _wrap(block);
- block.adjust();
- }
- } else if (blockStyle.value != null) {
- Block block = Block();
- block.applyAttribute(blockStyle);
- _wrap(block);
- block.adjust();
- }
- }
-
- _wrap(Block block) {
- assert(parent != null && parent is! Block);
- insertAfter(block);
- unlink();
- block.add(this);
- }
-
- _unwrap() {
- if (parent is! Block) {
- throw ArgumentError('Invalid parent');
- }
- Block block = parent;
-
- assert(block.children.contains(this));
-
- if (isFirst) {
- unlink();
- block.insertBefore(this);
- } else if (isLast) {
- unlink();
- block.insertAfter(this);
- } else {
- Block before = block.clone();
- block.insertBefore(before);
-
- Line child = block.first;
- while (child != this) {
- child.unlink();
- before.add(child);
- child = block.first as Line;
- }
- unlink();
- block.insertBefore(this);
- }
- block.adjust();
- }
-
- Line _getNextLine(int index) {
- assert(index == 0 || (index > 0 && index < length));
-
- Line line = clone() as Line;
- insertAfter(line);
- if (index == length - 1) {
- return line;
- }
-
- ChildQuery query = queryChild(index, false);
- while (!query.node.isLast) {
- Leaf next = last;
- next.unlink();
- line.addFirst(next);
- }
- Leaf child = query.node;
- Leaf cut = child.splitAt(query.offset);
- cut?.unlink();
- line.addFirst(cut);
- return line;
- }
-
- _insert(int index, Object data, Style style) {
- assert(index == 0 || (index > 0 && index < length));
-
- if (data is String) {
- assert(!data.contains('\n'));
- if (data.isEmpty) {
- return;
- }
- }
-
- if (isNotEmpty) {
- ChildQuery result = queryChild(index, true);
- result.node.insert(result.offset, data, style);
- return;
- }
-
- Leaf child = Leaf(data);
- add(child);
- child.format(style);
- }
-
- @override
- Node newInstance() {
- return Line();
- }
-
- Style collectStyle(int offset, int len) {
- int local = math.min(this.length - offset, len);
- Style res = Style();
- var excluded = {};
-
- void _handle(Style style) {
- if (res.isEmpty) {
- excluded.addAll(style.values);
- } else {
- for (Attribute attr in res.values) {
- if (!style.containsKey(attr.key)) {
- excluded.add(attr);
- }
- }
- }
- Style remain = style.removeAll(excluded);
- res = res.removeAll(excluded);
- res = res.mergeAll(remain);
- }
-
- ChildQuery data = queryChild(offset, true);
- Leaf node = data.node;
- if (node != null) {
- res = res.mergeAll(node.style);
- int pos = node.length - data.offset;
- while (!node.isLast && pos < local) {
- node = node.next as Leaf;
- _handle(node.style);
- pos += node.length;
- }
- }
-
- res = res.mergeAll(style);
- if (parent is Block) {
- Block block = parent;
- res = res.mergeAll(block.style);
- }
-
- int remain = len - local;
- if (remain > 0) {
- _handle(nextLine.collectStyle(0, remain));
- }
-
- return res;
- }
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../../src/models/documents/nodes/line.dart';
diff --git a/lib/models/documents/nodes/node.dart b/lib/models/documents/nodes/node.dart
index 6327a1bf..210c1672 100644
--- a/lib/models/documents/nodes/node.dart
+++ b/lib/models/documents/nodes/node.dart
@@ -1,126 +1,3 @@
-import 'dart:collection';
-
-import 'package:flutter_quill/models/documents/style.dart';
-import 'package:quill_delta/quill_delta.dart';
-
-import '../attribute.dart';
-import 'container.dart';
-import 'line.dart';
-
-/* node in a document tree */
-abstract class Node extends LinkedListEntry {
- Container parent;
- Style _style = Style();
-
- Style get style => _style;
-
- void applyAttribute(Attribute attribute) {
- _style = _style.merge(attribute);
- }
-
- void applyStyle(Style value) {
- if (value == null) {
- throw ArgumentError('null value');
- }
- _style = _style.mergeAll(value);
- }
-
- void clearStyle() {
- _style = Style();
- }
-
- bool get isFirst => list.first == this;
-
- bool get isLast => list.last == this;
-
- int get length;
-
- Node clone() {
- Node node = newInstance();
- node.applyStyle(style);
- return node;
- }
-
- int getOffset() {
- int offset = 0;
-
- if (list == null || isFirst) {
- return offset;
- }
-
- Node cur = this;
- do {
- cur = cur.previous;
- offset += cur.length;
- } while (!cur.isFirst);
- return offset;
- }
-
- int getDocumentOffset() {
- final parentOffset = (parent is! Root) ? parent.getDocumentOffset() : 0;
- return parentOffset + getOffset();
- }
-
- bool containsOffset(int offset) {
- final o = getDocumentOffset();
- return o <= offset && offset < o + length;
- }
-
- @override
- void insertBefore(Node entry) {
- assert(entry.parent == null && parent != null);
- entry.parent = parent;
- super.insertBefore(entry);
- }
-
- @override
- void insertAfter(Node entry) {
- assert(entry.parent == null && parent != null);
- entry.parent = parent;
- super.insertAfter(entry);
- }
-
- @override
- void unlink() {
- assert(parent != null);
- parent = null;
- super.unlink();
- }
-
- adjust() {
- // do nothing
- }
-
- /// abstract methods begin
-
- Node newInstance();
-
- String toPlainText();
-
- Delta toDelta();
-
- insert(int index, Object data, Style style);
-
- retain(int index, int len, Style style);
-
- delete(int index, int len);
-
- /// abstract methods end
-
-}
-
-/* Root node of document tree */
-class Root extends Container> {
- @override
- Container get defaultChild => Line();
-
- @override
- Delta toDelta() => children
- .map((child) => child.toDelta())
- .fold(Delta(), (a, b) => a.concat(b));
-
- @override
- Node newInstance() {
- return Root();
- }
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../../src/models/documents/nodes/node.dart';
diff --git a/lib/models/documents/style.dart b/lib/models/documents/style.dart
index 68825790..6df9412b 100644
--- a/lib/models/documents/style.dart
+++ b/lib/models/documents/style.dart
@@ -1,110 +1,3 @@
-import 'package:collection/collection.dart';
-import 'package:flutter_quill/models/documents/attribute.dart';
-import 'package:quiver_hashcode/hashcode.dart';
-
-/* Collection of style attributes */
-class Style {
- final Map _attributes;
-
- Style.attr(this._attributes);
-
- Style() : _attributes = {};
-
- static Style fromJson(Map attributes) {
- if (attributes == null) {
- return Style();
- }
-
- Map result = attributes.map((String key, dynamic value) {
- Attribute attr = Attribute.fromKeyValue(key, value);
- return MapEntry(key, attr??Attribute(key, AttributeScope.INLINE, value));
- });
- return Style.attr(result);
- }
-
- Map toJson() => _attributes.isEmpty
- ? null
- : _attributes.map((String _, Attribute attribute) =>
- MapEntry(attribute.key, attribute.value));
-
- Iterable get keys => _attributes.keys;
-
- Iterable get values => _attributes.values;
-
- Map get attributes => _attributes;
-
- bool get isEmpty => _attributes.isEmpty;
-
- bool get isNotEmpty => _attributes.isNotEmpty;
-
- bool get isInline => isNotEmpty && values.every((item) => item.isInline);
-
- bool get isIgnored =>
- isNotEmpty && values.every((item) => item.scope == AttributeScope.IGNORE);
-
- Attribute get single => _attributes.values.single;
-
- bool containsKey(String key) => _attributes.containsKey(key);
-
- Attribute getBlockExceptHeader() {
- for (Attribute val in values) {
- if (val.isBlockExceptHeader) {
- return val;
- }
- }
- return null;
- }
-
- Style merge(Attribute attribute) {
- Map merged = Map.from(_attributes);
- if (attribute.value == null) {
- merged.remove(attribute.key);
- } else {
- merged[attribute.key] = attribute;
- }
- return Style.attr(merged);
- }
-
- Style mergeAll(Style other) {
- Style result = Style.attr(_attributes);
- for (Attribute attribute in other.values) {
- result = result.merge(attribute);
- }
- return result;
- }
-
- Style removeAll(Set attributes) {
- Map merged = Map.from(_attributes);
- attributes.map((item) => item.key).forEach(merged.remove);
- return Style.attr(merged);
- }
-
- Style put(Attribute attribute) {
- Map m = Map.from(attributes);
- m[attribute.key] = attribute;
- return Style.attr(m);
- }
-
- @override
- bool operator ==(Object other) {
- if (identical(this, other)) {
- return true;
- }
- if (other is! Style) {
- return false;
- }
- Style typedOther = other;
- final eq = const MapEquality();
- return eq.equals(_attributes, typedOther._attributes);
- }
-
- @override
- int get hashCode {
- final hashes =
- _attributes.entries.map((entry) => hash2(entry.key, entry.value));
- return hashObjects(hashes);
- }
-
- @override
- String toString() => "{${_attributes.values.join(', ')}}";
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../src/models/documents/style.dart';
diff --git a/lib/models/quill_delta.dart b/lib/models/quill_delta.dart
new file mode 100644
index 00000000..796d68ca
--- /dev/null
+++ b/lib/models/quill_delta.dart
@@ -0,0 +1,3 @@
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../src/models/quill_delta.dart';
diff --git a/lib/models/rules/delete.dart b/lib/models/rules/delete.dart
index cac220d1..0430686b 100644
--- a/lib/models/rules/delete.dart
+++ b/lib/models/rules/delete.dart
@@ -1,123 +1,3 @@
-import 'package:flutter_quill/models/documents/attribute.dart';
-import 'package:flutter_quill/models/rules/rule.dart';
-import 'package:quill_delta/quill_delta.dart';
-
-abstract class DeleteRule extends Rule {
- const DeleteRule();
-
- @override
- RuleType get type => RuleType.DELETE;
-
- @override
- validateArgs(int len, Object data, Attribute attribute) {
- assert(len != null);
- assert(data == null);
- assert(attribute == null);
- }
-}
-
-class CatchAllDeleteRule extends DeleteRule {
- const CatchAllDeleteRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- return Delta()
- ..retain(index)
- ..delete(len);
- }
-}
-
-class PreserveLineStyleOnMergeRule extends DeleteRule {
- const PreserveLineStyleOnMergeRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- DeltaIterator itr = DeltaIterator(document);
- itr.skip(index);
- Operation op = itr.next(1);
- if (op.data != '\n') {
- return null;
- }
-
- bool isNotPlain = op.isNotPlain;
- Map attrs = op.attributes;
-
- itr.skip(len - 1);
- Delta delta = Delta()
- ..retain(index)
- ..delete(len);
-
- while (itr.hasNext) {
- op = itr.next();
- String text = op.data is String ? op.data as String : '';
- int lineBreak = text.indexOf('\n');
- if (lineBreak == -1) {
- delta..retain(op.length);
- continue;
- }
-
- Map attributes = op.attributes == null
- ? null
- : op.attributes.map((String key, dynamic value) =>
- MapEntry(key, null));
-
- if (isNotPlain) {
- attributes ??= {};
- attributes.addAll(attrs);
- }
- delta..retain(lineBreak)..retain(1, attributes);
- break;
- }
- return delta;
- }
-}
-
-class EnsureEmbedLineRule extends DeleteRule {
- const EnsureEmbedLineRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- DeltaIterator itr = DeltaIterator(document);
-
- Operation op = itr.skip(index);
- int indexDelta = 0, lengthDelta = 0, remain = len;
- bool embedFound = op != null && op.data is! String;
- bool hasLineBreakBefore =
- !embedFound && (op == null || (op?.data as String).endsWith('\n'));
- if (embedFound) {
- Operation candidate = itr.next(1);
- remain--;
- if (candidate.data == '\n') {
- indexDelta++;
- lengthDelta--;
-
- candidate = itr.next(1);
- remain--;
- if (candidate.data == '\n') {
- lengthDelta++;
- }
- }
- }
-
- op = itr.skip(remain);
- if (op != null &&
- (op?.data is String ? op.data as String : '').endsWith('\n')) {
- Operation candidate = itr.next(1);
- if (candidate.data is! String && !hasLineBreakBefore) {
- embedFound = true;
- lengthDelta--;
- }
- }
-
- if (!embedFound) {
- return null;
- }
-
- return Delta()
- ..retain(index + indexDelta)
- ..delete(len + lengthDelta);
- }
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../src/models/rules/delete.dart';
diff --git a/lib/models/rules/format.dart b/lib/models/rules/format.dart
index db94e571..7d642af3 100644
--- a/lib/models/rules/format.dart
+++ b/lib/models/rules/format.dart
@@ -1,172 +1,3 @@
-import 'package:flutter_quill/models/documents/attribute.dart';
-import 'package:flutter_quill/models/rules/rule.dart';
-import 'package:quill_delta/quill_delta.dart';
-
-abstract class FormatRule extends Rule {
- const FormatRule();
-
- @override
- RuleType get type => RuleType.FORMAT;
-
- @override
- validateArgs(int len, Object data, Attribute attribute) {
- assert(len != null);
- assert(data == null);
- assert(attribute != null);
- }
-}
-
-class ResolveLineFormatRule extends FormatRule {
- const ResolveLineFormatRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (attribute.scope != AttributeScope.BLOCK) {
- return null;
- }
-
- Delta delta = Delta()..retain(index);
- DeltaIterator itr = DeltaIterator(document);
- itr.skip(index);
- Operation op;
- for (int cur = 0; cur < len && itr.hasNext; cur += op.length) {
- op = itr.next(len - cur);
- if (op.data is! String || !(op.data as String).contains('\n')) {
- delta.retain(op.length);
- continue;
- }
- String text = op.data;
- Delta tmp = Delta();
- int offset = 0;
-
- for (int lineBreak = text.indexOf('\n');
- lineBreak >= 0;
- lineBreak = text.indexOf('\n', offset)) {
- tmp..retain(lineBreak - offset)..retain(1, attribute.toJson());
- offset = lineBreak + 1;
- }
- tmp.retain(text.length - offset);
- delta = delta.concat(tmp);
- }
-
- while (itr.hasNext) {
- op = itr.next();
- String text = op.data is String ? op.data as String : '';
- int lineBreak = text.indexOf('\n');
- if (lineBreak < 0) {
- delta..retain(op.length);
- continue;
- }
- delta..retain(lineBreak)..retain(1, attribute.toJson());
- break;
- }
- return delta;
- }
-}
-
-class FormatLinkAtCaretPositionRule extends FormatRule {
- const FormatLinkAtCaretPositionRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (attribute.key != Attribute.link.key || len > 0) {
- return null;
- }
-
- Delta delta = Delta();
- DeltaIterator itr = DeltaIterator(document);
- Operation before = itr.skip(index), after = itr.next();
- int beg = index, retain = 0;
- if (before != null && before.hasAttribute(attribute.key)) {
- beg -= before.length;
- retain = before.length;
- }
- if (after != null && after.hasAttribute(attribute.key)) {
- retain += after.length;
- }
- if (retain == 0) {
- return null;
- }
-
- delta..retain(beg)..retain(retain, attribute.toJson());
- return delta;
- }
-}
-
-class ResolveInlineFormatRule extends FormatRule {
- const ResolveInlineFormatRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (attribute.scope != AttributeScope.INLINE) {
- return null;
- }
-
- Delta delta = Delta()..retain(index);
- DeltaIterator itr = DeltaIterator(document);
- itr.skip(index);
-
- Operation op;
- for (int cur = 0; cur < len && itr.hasNext; cur += op.length) {
- op = itr.next(len - cur);
- String text = op.data is String ? op.data as String : '';
- int lineBreak = text.indexOf('\n');
- if (lineBreak < 0) {
- delta.retain(op.length, attribute.toJson());
- continue;
- }
- int pos = 0;
- while (lineBreak >= 0) {
- delta..retain(lineBreak - pos, attribute.toJson())..retain(1);
- pos = lineBreak + 1;
- lineBreak = text.indexOf('\n', pos);
- }
- if (pos < op.length) {
- delta.retain(op.length - pos, attribute.toJson());
- }
- }
-
- return delta;
- }
-}
-
-class ResolveInlineIgnoreFormatRule extends FormatRule {
- const ResolveInlineIgnoreFormatRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (attribute.scope != AttributeScope.IGNORE) {
- return null;
- }
-
- Delta delta = Delta()..retain(index, attribute.toJson());
- DeltaIterator itr = DeltaIterator(document);
- itr.skip(index);
-
- Operation op;
- for (int cur = 0; cur < len && itr.hasNext; cur += op.length) {
- op = itr.next(len - cur);
- String text = op.data is String ? op.data as String : '';
- int lineBreak = text.indexOf('\n');
- if (lineBreak < 0) {
- delta.retain(op.length, attribute.toJson());
- continue;
- }
- int pos = 0;
- while (lineBreak >= 0) {
- delta..retain(lineBreak - pos, attribute.toJson())..retain(1);
- pos = lineBreak + 1;
- lineBreak = text.indexOf('\n', pos);
- }
- if (pos < op.length) {
- delta.retain(op.length - pos, attribute.toJson());
- }
- }
-
- return delta;
- }
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../src/models/rules/format.dart';
diff --git a/lib/models/rules/insert.dart b/lib/models/rules/insert.dart
index 4715ae87..04e7a4fa 100644
--- a/lib/models/rules/insert.dart
+++ b/lib/models/rules/insert.dart
@@ -1,385 +1,3 @@
-import 'package:flutter_quill/models/documents/attribute.dart';
-import 'package:flutter_quill/models/documents/style.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 {
- const InsertRule();
-
- @override
- RuleType get type => RuleType.INSERT;
-
- @override
- validateArgs(int len, Object data, Attribute attribute) {
- assert(len == null);
- assert(data != null);
- assert(attribute == null);
- }
-}
-
-class PreserveLineStyleOnSplitRule extends InsertRule {
- const PreserveLineStyleOnSplitRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (data is! String || (data as String) != '\n') {
- return null;
- }
-
- DeltaIterator itr = DeltaIterator(document);
- Operation before = itr.skip(index);
- if (before == null ||
- before.data is! String ||
- (before.data as String).endsWith('\n')) {
- return null;
- }
- Operation after = itr.next();
- if (after == null ||
- after.data is! String ||
- (after.data as String).startsWith('\n')) {
- return null;
- }
-
- final text = after.data as String;
-
- Delta delta = Delta()..retain(index);
- if (text.contains('\n')) {
- assert(after.isPlain);
- delta..insert('\n');
- return delta;
- }
- Tuple2 nextNewLine = _getNextNewLine(itr);
- Map attributes = nextNewLine?.item1?.attributes;
-
- return delta..insert('\n', attributes);
- }
-}
-
-class PreserveBlockStyleOnInsertRule extends InsertRule {
- const PreserveBlockStyleOnInsertRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (data is! String || !(data as String).contains('\n')) {
- return null;
- }
-
- DeltaIterator itr = DeltaIterator(document);
- itr.skip(index);
-
- Tuple2 nextNewLine = _getNextNewLine(itr);
- Style lineStyle =
- Style.fromJson(nextNewLine.item1?.attributes ?? {});
-
- Attribute attribute = lineStyle.getBlockExceptHeader();
- if (attribute == null) {
- return null;
- }
-
- var blockStyle = {attribute.key: attribute.value};
-
- Map resetStyle;
-
- if (lineStyle.containsKey(Attribute.header.key)) {
- resetStyle = Attribute.header.toJson();
- }
-
- List lines = (data as String).split('\n');
- Delta delta = Delta()..retain(index);
- for (int i = 0; i < lines.length; i++) {
- String line = lines[i];
- if (line.isNotEmpty) {
- delta.insert(line);
- }
- if (i == 0) {
- delta.insert('\n', lineStyle.toJson());
- } else if (i < lines.length - 1) {
- delta.insert('\n', blockStyle);
- }
- }
-
- if (resetStyle != null) {
- delta.retain(nextNewLine.item2);
- delta
- ..retain((nextNewLine.item1.data as String).indexOf('\n'))
- ..retain(1, resetStyle);
- }
-
- return delta;
- }
-}
-
-class AutoExitBlockRule extends InsertRule {
- const AutoExitBlockRule();
-
- bool _isEmptyLine(Operation before, Operation after) {
- if (before == null) {
- return true;
- }
- return before.data is String &&
- (before.data as String).endsWith('\n') &&
- after.data is String &&
- (after.data as String).startsWith('\n');
- }
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (data is! String || (data as String) != '\n') {
- return null;
- }
-
- DeltaIterator itr = DeltaIterator(document);
- Operation prev = itr.skip(index), cur = itr.next();
- Attribute blockStyle =
- Style.fromJson(cur.attributes).getBlockExceptHeader();
- if (cur.isPlain || blockStyle == null) {
- return null;
- }
- if (!_isEmptyLine(prev, cur)) {
- return null;
- }
-
- if ((cur.value as String).length > 1) {
- return null;
- }
-
- Tuple2 nextNewLine = _getNextNewLine(itr);
- if (nextNewLine.item1 != null &&
- nextNewLine.item1.attributes != null &&
- Style.fromJson(nextNewLine.item1.attributes).getBlockExceptHeader() ==
- blockStyle) {
- return null;
- }
-
- final attributes = cur.attributes ?? {};
- String k = attributes.keys
- .firstWhere((k) => Attribute.blockKeysExceptHeader.contains(k));
- attributes[k] = null;
- // retain(1) should be '\n', set it with no attribute
- return Delta()..retain(index)..retain(1, attributes);
- }
-}
-
-class ResetLineFormatOnNewLineRule extends InsertRule {
- const ResetLineFormatOnNewLineRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (data is! String || (data as String) != '\n') {
- return null;
- }
-
- DeltaIterator itr = DeltaIterator(document);
- itr.skip(index);
- Operation cur = itr.next();
- if (cur.data is! String || !(cur.data as String).startsWith('\n')) {
- return null;
- }
-
- Map resetStyle;
- if (cur.attributes != null &&
- cur.attributes.containsKey(Attribute.header.key)) {
- resetStyle = Attribute.header.toJson();
- }
- return Delta()
- ..retain(index)
- ..insert('\n', cur.attributes)
- ..retain(1, resetStyle)
- ..trim();
- }
-}
-
-class InsertEmbedsRule extends InsertRule {
- const InsertEmbedsRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (data is String) {
- return null;
- }
-
- Delta delta = Delta()..retain(index);
- DeltaIterator itr = DeltaIterator(document);
- Operation prev = itr.skip(index), cur = itr.next();
-
- String textBefore = prev?.data is String ? prev.data as String : '';
- String textAfter = cur.data is String ? cur.data as String : '';
-
- final isNewlineBefore = prev == null || textBefore.endsWith('\n');
- final isNewlineAfter = textAfter.startsWith('\n');
-
- if (isNewlineBefore && isNewlineAfter) {
- return delta..insert(data);
- }
-
- Map lineStyle;
- if (textAfter.contains('\n')) {
- lineStyle = cur.attributes;
- } else {
- while (itr.hasNext) {
- Operation op = itr.next();
- if ((op.data is String ? op.data as String : '').indexOf('\n') >= 0) {
- lineStyle = op.attributes;
- break;
- }
- }
- }
-
- if (!isNewlineBefore) {
- delta..insert('\n', lineStyle);
- }
- delta..insert(data);
- if (!isNewlineAfter) {
- delta..insert('\n');
- }
- return delta;
- }
-}
-
-class ForceNewlineForInsertsAroundEmbedRule extends InsertRule {
- const ForceNewlineForInsertsAroundEmbedRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (data is! String) {
- return null;
- }
-
- String text = data as String;
- DeltaIterator itr = DeltaIterator(document);
- final prev = itr.skip(index);
- final cur = itr.next();
- bool cursorBeforeEmbed = cur.data is! String;
- bool cursorAfterEmbed = prev != null && prev.data is! String;
-
- if (!cursorBeforeEmbed && !cursorAfterEmbed) {
- return null;
- }
- Delta delta = Delta()..retain(index);
- if (cursorBeforeEmbed && !text.endsWith('\n')) {
- return delta..insert(text)..insert('\n');
- }
- if (cursorAfterEmbed && !text.startsWith('\n')) {
- return delta..insert('\n')..insert(text);
- }
- return delta..insert(text);
- }
-}
-
-class AutoFormatLinksRule extends InsertRule {
- const AutoFormatLinksRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (data is! String || (data as String) != ' ') {
- return null;
- }
-
- DeltaIterator itr = DeltaIterator(document);
- Operation prev = itr.skip(index);
- if (prev == null || prev.data is! String) {
- return null;
- }
-
- try {
- String cand = (prev.data as String).split('\n').last.split(' ').last;
- Uri link = Uri.parse(cand);
- if (!['https', 'http'].contains(link.scheme)) {
- return null;
- }
- Map attributes = prev.attributes ?? {};
-
- if (attributes.containsKey(Attribute.link.key)) {
- return null;
- }
-
- attributes.addAll(LinkAttribute(link.toString()).toJson());
- return Delta()
- ..retain(index - cand.length)
- ..retain(cand.length, attributes)
- ..insert(data as String, prev.attributes);
- } on FormatException {
- return null;
- }
- }
-}
-
-class PreserveInlineStylesRule extends InsertRule {
- const PreserveInlineStylesRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- if (data is! String || (data as String).contains('\n')) {
- return null;
- }
-
- DeltaIterator itr = DeltaIterator(document);
- Operation prev = itr.skip(index);
- if (prev == null ||
- prev.data is! String ||
- (prev.data as String).contains('\n')) {
- return null;
- }
-
- Map attributes = prev.attributes;
- String text = data as String;
- if (attributes == null || !attributes.containsKey(Attribute.link.key)) {
- return Delta()
- ..retain(index)
- ..insert(text, attributes);
- }
-
- attributes.remove(Attribute.link.key);
- Delta delta = Delta()
- ..retain(index)
- ..insert(text, attributes.isEmpty ? null : attributes);
- Operation next = itr.next();
- if (next == null) {
- return delta;
- }
- Map nextAttributes =
- next.attributes ?? const {};
- if (!nextAttributes.containsKey(Attribute.link.key)) {
- return delta;
- }
- if (attributes[Attribute.link.key] == nextAttributes[Attribute.link.key]) {
- return Delta()
- ..retain(index)
- ..insert(text, attributes);
- }
- return delta;
- }
-}
-
-class CatchAllInsertRule extends InsertRule {
- const CatchAllInsertRule();
-
- @override
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- return Delta()
- ..retain(index)
- ..insert(data);
- }
-}
-
-Tuple2 _getNextNewLine(DeltaIterator iterator) {
- Operation op;
- for (int skipped = 0; iterator.hasNext; skipped += op.length) {
- op = iterator.next();
- int lineBreak = (op.data is String ? op.data as String : '').indexOf('\n');
- if (lineBreak >= 0) {
- return Tuple2(op, skipped);
- }
- }
- return Tuple2(null, null);
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../src/models/rules/insert.dart';
diff --git a/lib/models/rules/rule.dart b/lib/models/rules/rule.dart
index efd66521..ccea0dda 100644
--- a/lib/models/rules/rule.dart
+++ b/lib/models/rules/rule.dart
@@ -1,76 +1,3 @@
-import 'package:flutter_quill/models/documents/attribute.dart';
-import 'package:flutter_quill/models/documents/document.dart';
-import 'package:quill_delta/quill_delta.dart';
-
-import 'delete.dart';
-import 'format.dart';
-import 'insert.dart';
-
-enum RuleType { INSERT, DELETE, FORMAT }
-
-abstract class Rule {
- const Rule();
-
- Delta apply(Delta document, int index,
- {int len, Object data, Attribute attribute}) {
- assert(document != null);
- assert(index != null);
- validateArgs(len, data, attribute);
- return applyRule(document, index,
- len: len, data: data, attribute: attribute);
- }
-
- validateArgs(int len, Object data, Attribute attribute);
-
- Delta applyRule(Delta document, int index,
- {int len, Object data, Attribute attribute});
-
- RuleType get type;
-}
-
-class Rules {
- final List _rules;
- static final Rules _instance = Rules([
- FormatLinkAtCaretPositionRule(),
- ResolveLineFormatRule(),
- ResolveInlineFormatRule(),
- ResolveInlineIgnoreFormatRule(),
- InsertEmbedsRule(),
- ForceNewlineForInsertsAroundEmbedRule(),
- AutoExitBlockRule(),
- PreserveBlockStyleOnInsertRule(),
- PreserveLineStyleOnSplitRule(),
- ResetLineFormatOnNewLineRule(),
- AutoFormatLinksRule(),
- PreserveInlineStylesRule(),
- CatchAllInsertRule(),
- EnsureEmbedLineRule(),
- PreserveLineStyleOnMergeRule(),
- CatchAllDeleteRule(),
- ]);
-
- Rules(this._rules);
-
- static Rules getInstance() => _instance;
-
- Delta apply(RuleType ruleType, Document document, int index,
- {int len, Object data, Attribute attribute}) {
- final delta = document.toDelta();
- for (var rule in _rules) {
- if (rule.type != ruleType) {
- continue;
- }
- try {
- final result = rule.apply(delta, index,
- len: len, data: data, attribute: attribute);
- if (result != null) {
- print("Rule $rule applied");
- return result..trim();
- }
- } catch (e) {
- throw e;
- }
- }
- // throw ('Apply rules failed');
- }
-}
+/// TODO: Remove this file in the next breaking release, because implementation
+/// files should be located in the src folder, https://bit.ly/3fA23Yz.
+export '../../src/models/rules/rule.dart';
diff --git a/lib/src/models/documents/attribute.dart b/lib/src/models/documents/attribute.dart
new file mode 100644
index 00000000..acd979cb
--- /dev/null
+++ b/lib/src/models/documents/attribute.dart
@@ -0,0 +1,292 @@
+import 'dart:collection';
+
+import 'package:quiver/core.dart';
+
+enum AttributeScope {
+ INLINE, // refer to https://quilljs.com/docs/formats/#inline
+ BLOCK, // refer to https://quilljs.com/docs/formats/#block
+ EMBEDS, // refer to https://quilljs.com/docs/formats/#embeds
+ IGNORE, // attributes that can be ignored
+}
+
+class Attribute {
+ Attribute(this.key, this.scope, this.value);
+
+ final String key;
+ final AttributeScope scope;
+ final T value;
+
+ static final Map _registry = LinkedHashMap.of({
+ Attribute.bold.key: Attribute.bold,
+ Attribute.italic.key: Attribute.italic,
+ Attribute.underline.key: Attribute.underline,
+ Attribute.strikeThrough.key: Attribute.strikeThrough,
+ Attribute.font.key: Attribute.font,
+ Attribute.size.key: Attribute.size,
+ Attribute.link.key: Attribute.link,
+ Attribute.color.key: Attribute.color,
+ Attribute.background.key: Attribute.background,
+ Attribute.placeholder.key: Attribute.placeholder,
+ Attribute.header.key: Attribute.header,
+ Attribute.align.key: Attribute.align,
+ Attribute.list.key: Attribute.list,
+ Attribute.codeBlock.key: Attribute.codeBlock,
+ Attribute.blockQuote.key: Attribute.blockQuote,
+ Attribute.indent.key: Attribute.indent,
+ Attribute.width.key: Attribute.width,
+ Attribute.height.key: Attribute.height,
+ Attribute.style.key: Attribute.style,
+ Attribute.token.key: Attribute.token,
+ });
+
+ static final BoldAttribute bold = BoldAttribute();
+
+ static final ItalicAttribute italic = ItalicAttribute();
+
+ static final UnderlineAttribute underline = UnderlineAttribute();
+
+ static final StrikeThroughAttribute strikeThrough = StrikeThroughAttribute();
+
+ static final FontAttribute font = FontAttribute(null);
+
+ static final SizeAttribute size = SizeAttribute(null);
+
+ static final LinkAttribute link = LinkAttribute(null);
+
+ static final ColorAttribute color = ColorAttribute(null);
+
+ static final BackgroundAttribute background = BackgroundAttribute(null);
+
+ static final PlaceholderAttribute placeholder = PlaceholderAttribute();
+
+ static final HeaderAttribute header = HeaderAttribute();
+
+ static final IndentAttribute indent = IndentAttribute();
+
+ static final AlignAttribute align = AlignAttribute(null);
+
+ static final ListAttribute list = ListAttribute(null);
+
+ static final CodeBlockAttribute codeBlock = CodeBlockAttribute();
+
+ static final BlockQuoteAttribute blockQuote = BlockQuoteAttribute();
+
+ static final WidthAttribute width = WidthAttribute(null);
+
+ static final HeightAttribute height = HeightAttribute(null);
+
+ static final StyleAttribute style = StyleAttribute(null);
+
+ static final TokenAttribute token = TokenAttribute('');
+
+ static final Set inlineKeys = {
+ Attribute.bold.key,
+ Attribute.italic.key,
+ Attribute.underline.key,
+ Attribute.strikeThrough.key,
+ Attribute.link.key,
+ Attribute.color.key,
+ Attribute.background.key,
+ Attribute.placeholder.key,
+ };
+
+ static final Set blockKeys = LinkedHashSet.of({
+ Attribute.header.key,
+ Attribute.align.key,
+ Attribute.list.key,
+ Attribute.codeBlock.key,
+ Attribute.blockQuote.key,
+ Attribute.indent.key,
+ });
+
+ static final Set blockKeysExceptHeader = LinkedHashSet.of({
+ Attribute.list.key,
+ Attribute.align.key,
+ Attribute.codeBlock.key,
+ Attribute.blockQuote.key,
+ Attribute.indent.key,
+ });
+
+ static Attribute get h1 => HeaderAttribute(level: 1);
+
+ static Attribute get h2 => HeaderAttribute(level: 2);
+
+ static Attribute get h3 => HeaderAttribute(level: 3);
+
+ // "attributes":{"align":"left"}
+ static Attribute get leftAlignment => AlignAttribute('left');
+
+ // "attributes":{"align":"center"}
+ static Attribute get centerAlignment => AlignAttribute('center');
+
+ // "attributes":{"align":"right"}
+ static Attribute get rightAlignment => AlignAttribute('right');
+
+ // "attributes":{"align":"justify"}
+ static Attribute get justifyAlignment => AlignAttribute('justify');
+
+ // "attributes":{"list":"bullet"}
+ static Attribute get ul => ListAttribute('bullet');
+
+ // "attributes":{"list":"ordered"}
+ static Attribute get ol => ListAttribute('ordered');
+
+ // "attributes":{"list":"checked"}
+ static Attribute get checked => ListAttribute('checked');
+
+ // "attributes":{"list":"unchecked"}
+ static Attribute get unchecked => ListAttribute('unchecked');
+
+ // "attributes":{"indent":1"}
+ static Attribute get indentL1 => IndentAttribute(level: 1);
+
+ // "attributes":{"indent":2"}
+ static Attribute get indentL2 => IndentAttribute(level: 2);
+
+ // "attributes":{"indent":3"}
+ static Attribute get indentL3 => IndentAttribute(level: 3);
+
+ static Attribute getIndentLevel(int? level) {
+ if (level == 1) {
+ return indentL1;
+ }
+ if (level == 2) {
+ return indentL2;
+ }
+ if (level == 3) {
+ return indentL3;
+ }
+ return IndentAttribute(level: level);
+ }
+
+ bool get isInline => scope == AttributeScope.INLINE;
+
+ bool get isBlockExceptHeader => blockKeysExceptHeader.contains(key);
+
+ Map toJson() => {key: value};
+
+ static Attribute fromKeyValue(String key, dynamic value) {
+ if (!_registry.containsKey(key)) {
+ throw ArgumentError.value(key, 'key "$key" not found.');
+ }
+ final origin = _registry[key]!;
+ final attribute = clone(origin, value);
+ return attribute;
+ }
+
+ static int getRegistryOrder(Attribute attribute) {
+ var order = 0;
+ for (final attr in _registry.values) {
+ if (attr.key == attribute.key) {
+ break;
+ }
+ order++;
+ }
+
+ return order;
+ }
+
+ static Attribute clone(Attribute origin, dynamic value) {
+ return Attribute(origin.key, origin.scope, value);
+ }
+
+ @override
+ bool operator ==(Object other) {
+ if (identical(this, other)) return true;
+ if (other is! Attribute) return false;
+ final typedOther = other;
+ return key == typedOther.key &&
+ scope == typedOther.scope &&
+ value == typedOther.value;
+ }
+
+ @override
+ int get hashCode => hash3(key, scope, value);
+
+ @override
+ String toString() {
+ return 'Attribute{key: $key, scope: $scope, value: $value}';
+ }
+}
+
+class BoldAttribute extends Attribute {
+ BoldAttribute() : super('bold', AttributeScope.INLINE, true);
+}
+
+class ItalicAttribute extends Attribute {
+ ItalicAttribute() : super('italic', AttributeScope.INLINE, true);
+}
+
+class UnderlineAttribute extends Attribute {
+ UnderlineAttribute() : super('underline', AttributeScope.INLINE, true);
+}
+
+class StrikeThroughAttribute extends Attribute {
+ StrikeThroughAttribute() : super('strike', AttributeScope.INLINE, true);
+}
+
+class FontAttribute extends Attribute {
+ FontAttribute(String? val) : super('font', AttributeScope.INLINE, val);
+}
+
+class SizeAttribute extends Attribute {
+ SizeAttribute(String? val) : super('size', AttributeScope.INLINE, val);
+}
+
+class LinkAttribute extends Attribute {
+ LinkAttribute(String? val) : super('link', AttributeScope.INLINE, val);
+}
+
+class ColorAttribute extends Attribute {
+ ColorAttribute(String? val) : super('color', AttributeScope.INLINE, val);
+}
+
+class BackgroundAttribute extends Attribute {
+ BackgroundAttribute(String? val)
+ : super('background', AttributeScope.INLINE, val);
+}
+
+/// This is custom attribute for hint
+class PlaceholderAttribute extends Attribute {
+ PlaceholderAttribute() : super('placeholder', AttributeScope.INLINE, true);
+}
+
+class HeaderAttribute extends Attribute {
+ HeaderAttribute({int? level}) : super('header', AttributeScope.BLOCK, level);
+}
+
+class IndentAttribute extends Attribute {
+ IndentAttribute({int? level}) : super('indent', AttributeScope.BLOCK, level);
+}
+
+class AlignAttribute extends Attribute {
+ AlignAttribute(String? val) : super('align', AttributeScope.BLOCK, val);
+}
+
+class ListAttribute extends Attribute {
+ ListAttribute(String? val) : super('list', AttributeScope.BLOCK, val);
+}
+
+class CodeBlockAttribute extends Attribute {
+ CodeBlockAttribute() : super('code-block', AttributeScope.BLOCK, true);
+}
+
+class BlockQuoteAttribute extends Attribute {
+ BlockQuoteAttribute() : super('blockquote', AttributeScope.BLOCK, true);
+}
+
+class WidthAttribute extends Attribute {
+ WidthAttribute(String? val) : super('width', AttributeScope.IGNORE, val);
+}
+
+class HeightAttribute extends Attribute {
+ HeightAttribute(String? val) : super('height', AttributeScope.IGNORE, val);
+}
+
+class StyleAttribute extends Attribute {
+ StyleAttribute(String? val) : super('style', AttributeScope.IGNORE, val);
+}
+
+class TokenAttribute extends Attribute {
+ TokenAttribute(String val) : super('token', AttributeScope.IGNORE, val);
+}
diff --git a/lib/src/models/documents/document.dart b/lib/src/models/documents/document.dart
new file mode 100644
index 00000000..9e6ce6b7
--- /dev/null
+++ b/lib/src/models/documents/document.dart
@@ -0,0 +1,291 @@
+import 'dart:async';
+
+import 'package:tuple/tuple.dart';
+
+import '../quill_delta.dart';
+import '../rules/rule.dart';
+import 'attribute.dart';
+import 'history.dart';
+import 'nodes/block.dart';
+import 'nodes/container.dart';
+import 'nodes/embed.dart';
+import 'nodes/line.dart';
+import 'nodes/node.dart';
+import 'style.dart';
+
+/// The rich text document
+class Document {
+ Document() : _delta = Delta()..insert('\n') {
+ _loadDocument(_delta);
+ }
+
+ Document.fromJson(List data) : _delta = _transform(Delta.fromJson(data)) {
+ _loadDocument(_delta);
+ }
+
+ Document.fromDelta(Delta delta) : _delta = delta {
+ _loadDocument(delta);
+ }
+
+ /// The root node of the document tree
+ final Root _root = Root();
+
+ Root get root => _root;
+
+ int get length => _root.length;
+
+ Delta _delta;
+
+ Delta toDelta() => Delta.from(_delta);
+
+ final Rules _rules = Rules.getInstance();
+
+ void setCustomRules(List customRules) {
+ _rules.setCustomRules(customRules);
+ }
+
+ final StreamController> _observer =
+ StreamController.broadcast();
+
+ final History _history = History();
+
+ Stream> get changes => _observer.stream;
+
+ Delta insert(int index, Object? data, {int replaceLength = 0}) {
+ assert(index >= 0);
+ assert(data is String || data is Embeddable);
+ if (data is Embeddable) {
+ data = data.toJson();
+ } else if ((data as String).isEmpty) {
+ return Delta();
+ }
+
+ final delta = _rules.apply(RuleType.INSERT, this, index,
+ data: data, len: replaceLength);
+ compose(delta, ChangeSource.LOCAL);
+ return delta;
+ }
+
+ Delta delete(int index, int len) {
+ assert(index >= 0 && len > 0);
+ final delta = _rules.apply(RuleType.DELETE, this, index, len: len);
+ if (delta.isNotEmpty) {
+ compose(delta, ChangeSource.LOCAL);
+ }
+ return delta;
+ }
+
+ Delta replace(int index, int len, Object? data) {
+ assert(index >= 0);
+ assert(data is String || data is Embeddable);
+
+ final dataIsNotEmpty = (data is String) ? data.isNotEmpty : true;
+
+ assert(dataIsNotEmpty || len > 0);
+
+ var delta = Delta();
+
+ // We have to insert before applying delete rules
+ // Otherwise delete would be operating on stale document snapshot.
+ if (dataIsNotEmpty) {
+ delta = insert(index, data, replaceLength: len);
+ }
+
+ if (len > 0) {
+ final deleteDelta = delete(index, len);
+ delta = delta.compose(deleteDelta);
+ }
+
+ return delta;
+ }
+
+ Delta format(int index, int len, Attribute? attribute) {
+ assert(index >= 0 && len >= 0 && attribute != null);
+
+ var delta = Delta();
+
+ final formatDelta = _rules.apply(RuleType.FORMAT, this, index,
+ len: len, attribute: attribute);
+ if (formatDelta.isNotEmpty) {
+ compose(formatDelta, ChangeSource.LOCAL);
+ delta = delta.compose(formatDelta);
+ }
+
+ return delta;
+ }
+
+ /// Only attributes applied to all characters within this range are
+ /// included in the result.
+ Style collectStyle(int index, int len) {
+ final res = queryChild(index);
+ return (res.node as Line).collectStyle(res.offset, len);
+ }
+
+ /// Returns all styles for any character within the specified text range.
+ List