Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow embedding Zefyr into custom layouts with ZefyrScaffold #42

Merged
merged 11 commits into from
Nov 15, 2018
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
15 changes: 10 additions & 5 deletions doc/quick_start.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ Add `zefyr` package as a dependency to your `pubspec.yaml`:

```yaml
dependencies:
zefyr: ^0.1.0
zefyr: ^0.3.0
```

And run `flutter packages get` to install. This installs both `zefyr`
and `notus` packages.

### Usage

There are 3 main objects you would normally interact with in your code:
There are 4 main objects you would normally interact with in your code:

* `NotusDocument`, represents a rich text document and provides
high-level methods for manipulating the document's state, like
Expand All @@ -30,6 +30,9 @@ There are 3 main objects you would normally interact with in your code:
* `ZefyrEditor`, a Flutter widget responsible for rendering of rich text
on the screen and reacting to user actions.
* `ZefyrController`, ties the above two objects together.
* `ZefyrScaffold`, allows embedding Zefyr toolbar into any custom layout.

`ZefyrEditor` depends on presence of `ZefyrScaffold` somewhere up the widget tree.

Normally you would need to place `ZefyrEditor` inside of a
`StatefulWidget`. Shown below is a minimal setup required to use the
Expand Down Expand Up @@ -60,9 +63,11 @@ class MyWidgetState extends State<MyWidget> {

@override
Widget build(BuildContext context) {
return ZefyrEditor(
controller: _controller,
focusNode: _focusNode,
return ZefyrScaffold(
child: ZefyrEditor(
controller: _controller,
focusNode: _focusNode,
),
);
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/zefyr/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ example/ios/.symlinks
example/ios/Flutter/Generated.xcconfig
doc/api/
build/

example/feather
15 changes: 15 additions & 0 deletions packages/zefyr/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## 0.3.0

This version introduces new widget `ZefyrScaffold` which allows embedding Zefyr in custom
layouts, like forms with multiple input fields.

It is now required to always wrap `ZefyrEditor` with an instance of this new widget. See examples
and readme for more details.

There is also new `ZefyrField` widget which integrates Zefyr with material design decorations.

* Breaking change: `ZefyrEditor` requires an ancestor `ZefyrScaffold`.
* Upgraded to `url_launcher` version 4.0.0.
* Exposed `ZefyrEditor.physics` property to allow customization of `ScrollPhysics`.
* Added basic `ZefyrField` widget with material design decorations.

## 0.2.0

* Breaking change: `ZefyrImageDelegate.createImageProvider` replaced with
Expand Down
2 changes: 1 addition & 1 deletion packages/zefyr/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
analyzer:
language:
enableSuperMixins: true
# enableSuperMixins: true

# Lint rules and documentation, see http://dart-lang.github.io/linter/lints
linter:
Expand Down
126 changes: 34 additions & 92 deletions packages/zefyr/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,125 +1,67 @@
// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:quill_delta/quill_delta.dart';
import 'package:zefyr/zefyr.dart';
import 'src/form.dart';
import 'src/full_page.dart';

void main() {
runApp(new ZefyrApp());
}

class ZefyrLogo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Ze'),
FlutterLogo(size: 24.0),
Text('yr'),
],
);
}
}

class ZefyrApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Zefyr Editor',
theme: new ThemeData(primarySwatch: Colors.cyan),
home: new MyHomePage(),
theme: ThemeData(primarySwatch: Colors.cyan),
home: HomePage(),
routes: {
"/fullPage": buildFullPage,
"/form": buildFormPage,
},
);
}
}

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => new _MyHomePageState();
}

final doc =
r'[{"insert":"Zefyr"},{"insert":"\n","attributes":{"heading":1}},{"insert":"Soft and gentle rich text editing for Flutter applications.","attributes":{"i":true}},{"insert":"\n"},{"insert":"​","attributes":{"embed":{"type":"image","source":"asset://images/breeze.jpg"}}},{"insert":"\n"},{"insert":"Photo by Hiroyuki Takeda.","attributes":{"i":true}},{"insert":"\nZefyr is currently in "},{"insert":"early preview","attributes":{"b":true}},{"insert":". If you have a feature request or found a bug, please file it at the "},{"insert":"issue tracker","attributes":{"a":"https://github.com/memspace/zefyr/issues"}},{"insert":'
r'".\nDocumentation"},{"insert":"\n","attributes":{"heading":3}},{"insert":"Quick Start","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/quick_start.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Data Format and Document Model","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/data_and_document.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Style Attributes","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/attr'
r'ibutes.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Heuristic Rules","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/heuristics.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"FAQ","attributes":{"a":"https://github.com/memspace/zefyr/blob/master/doc/faq.md"}},{"insert":"\n","attributes":{"block":"ul"}},{"insert":"Clean and modern look"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Zefyr’s rich text editor is built with simplicity and fle'
r'xibility in mind. It provides clean interface for distraction-free editing. Think Medium.com-like experience.\nMarkdown inspired semantics"},{"insert":"\n","attributes":{"heading":2}},{"insert":"Ever needed to have a heading line inside of a quote block, like this:\nI’m a Markdown heading"},{"insert":"\n","attributes":{"block":"quote","heading":3}},{"insert":"And I’m a regular paragraph"},{"insert":"\n","attributes":{"block":"quote"}},{"insert":"Code blocks"},{"insert":"\n","attributes":{"headin'
r'g":2}},{"insert":"Of course:\nimport ‘package:flutter/material.dart’;"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"import ‘package:zefyr/zefyr.dart’;"},{"insert":"\n\n","attributes":{"block":"code"}},{"insert":"void main() {"},{"insert":"\n","attributes":{"block":"code"}},{"insert":" runApp(MyZefyrApp());"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"}"},{"insert":"\n","attributes":{"block":"code"}},{"insert":"\n\n\n"}]';
Widget buildFullPage(BuildContext context) {
return FullPageEditorScreen();
}

Delta getDelta() {
return Delta.fromJson(json.decode(doc));
Widget buildFormPage(BuildContext context) {
return FormEmbeddedScreen();
}
}

class _MyHomePageState extends State<MyHomePage> {
final ZefyrController _controller =
ZefyrController(NotusDocument.fromDelta(getDelta()));
final FocusNode _focusNode = new FocusNode();
bool _editing = false;

class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = new ZefyrThemeData(
toolbarTheme: ZefyrToolbarTheme.fallback(context).copyWith(
color: Colors.grey.shade800,
toggleColor: Colors.grey.shade900,
iconColor: Colors.white,
disabledIconColor: Colors.grey.shade500,
),
);

final done = _editing
? [new FlatButton(onPressed: _stopEditing, child: Text('DONE'))]
: [new FlatButton(onPressed: _startEditing, child: Text('EDIT'))];
final nav = Navigator.of(context);
return Scaffold(
resizeToAvoidBottomPadding: true,
appBar: AppBar(
elevation: 1.0,
backgroundColor: Colors.grey.shade200,
brightness: Brightness.light,
title: ZefyrLogo(),
actions: done,
),
body: ZefyrTheme(
data: theme,
child: ZefyrEditor(
controller: _controller,
focusNode: _focusNode,
enabled: _editing,
imageDelegate: new CustomImageDelegate(),
),
body: Column(
children: <Widget>[
Expanded(child: Container()),
FlatButton(
onPressed: () => nav.pushNamed('/fullPage'),
child: Text('Full page editor'),
color: Colors.lightBlue,
textColor: Colors.white,
),
FlatButton(
onPressed: () => nav.pushNamed('/form'),
child: Text('Embedded in a form'),
color: Colors.lightBlue,
textColor: Colors.white,
),
Expanded(child: Container()),
],
),
);
}

void _startEditing() {
setState(() {
_editing = true;
});
}

void _stopEditing() {
setState(() {
_editing = false;
});
}
}

/// Custom image delegate used by this example to load image from application
/// assets.
///
/// Default image delegate only supports [FileImage]s.
class CustomImageDelegate extends ZefyrDefaultImageDelegate {
@override
Widget buildImage(BuildContext context, String imageSource) {
// We use custom "asset" scheme to distinguish asset images from other files.
if (imageSource.startsWith('asset://')) {
final asset = new AssetImage(imageSource.replaceFirst('asset://', ''));
return new Image(image: asset);
} else {
return super.buildImage(context, imageSource);
}
}
}
65 changes: 65 additions & 0 deletions packages/zefyr/example/lib/src/form.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
import 'package:zefyr/zefyr.dart';

import 'full_page.dart';

class FormEmbeddedScreen extends StatefulWidget {
@override
_FormEmbeddedScreenState createState() => _FormEmbeddedScreenState();
}

class _FormEmbeddedScreenState extends State<FormEmbeddedScreen> {
final ZefyrController _controller = ZefyrController(NotusDocument());
final FocusNode _focusNode = new FocusNode();

@override
Widget build(BuildContext context) {
final form = ListView(
children: <Widget>[
TextField(decoration: InputDecoration(labelText: 'Name')),
buildEditor(),
TextField(decoration: InputDecoration(labelText: 'Email')),
],
);

return Scaffold(
resizeToAvoidBottomPadding: true,
appBar: AppBar(
elevation: 1.0,
backgroundColor: Colors.grey.shade200,
brightness: Brightness.light,
title: ZefyrLogo(),
),
body: ZefyrScaffold(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: form,
),
),
);
}

Widget buildEditor() {
final theme = new ZefyrThemeData(
toolbarTheme: ZefyrToolbarTheme.fallback(context).copyWith(
color: Colors.grey.shade800,
toggleColor: Colors.grey.shade900,
iconColor: Colors.white,
disabledIconColor: Colors.grey.shade500,
),
);

return ZefyrTheme(
data: theme,
child: ZefyrField(
height: 200.0,
decoration: InputDecoration(labelText: 'Description'),
controller: _controller,
focusNode: _focusNode,
autofocus: false,
imageDelegate: new CustomImageDelegate(),
physics: ClampingScrollPhysics(),
),
);
}
}
Loading