Skip to content

Commit

Permalink
Merge pull request #42 from memspace/toolbar-overlay
Browse files Browse the repository at this point in the history
Allow embedding Zefyr into custom layouts with ZefyrScaffold
  • Loading branch information
pulyaevskiy authored Nov 15, 2018
2 parents 2b32049 + 60e85bd commit 2ff0df7
Show file tree
Hide file tree
Showing 23 changed files with 566 additions and 207 deletions.
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

0 comments on commit 2ff0df7

Please sign in to comment.