123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746 |
- // 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:async';
- import 'dart:io';
-
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:notus/notus.dart';
- import 'package:image_picker/image_picker.dart';
- import 'package:url_launcher/url_launcher.dart';
- import 'package:zefyr/src/extended_assets_picker/ct_asset_picker.dart';
-
- import 'link.dart';
- import 'scope.dart';
- import 'theme.dart';
- import 'toolbar.dart';
-
- const kToolbarButtonIcons = [
- ZefyrToolbarAction.text,
- ZefyrToolbarAction.heading,
- ZefyrToolbarAction.emoji,
- ZefyrToolbarAction.image,
- ZefyrToolbarAction.link,
- ZefyrToolbarAction.undo,
- ZefyrToolbarAction.redo,
- ZefyrToolbarAction.save,
- ZefyrToolbarAction.hideKeyboard,
- ZefyrToolbarAction.showKeyboard,
- ZefyrToolbarAction.close,
- ZefyrToolbarAction.confirm,
- ];
-
- /// A button used in [ZefyrToolbar].
- ///
- /// Create an instance of this widget with [ZefyrButton.icon] or
- /// [ZefyrButton.text] constructors.
- ///
- /// Toolbar buttons are normally created by a [ZefyrToolbarDelegate].
- class ZefyrButton extends StatelessWidget {
- /// Creates a toolbar button with an icon.
- ZefyrButton.icon({
- @required this.action,
- @required IconData icon,
- double iconSize,
- this.onPressed,
- }) : assert(action != null),
- assert(icon != null),
- _icon = icon,
- _iconSize = iconSize,
- _text = null,
- _textStyle = null,
- super();
-
- /// Creates a toolbar button containing text.
- ///
- /// Note that [ZefyrButton] has fixed width and does not expand to accommodate
- /// long texts.
- ZefyrButton.text({
- @required this.action,
- @required String text,
- TextStyle style,
- this.onPressed,
- }) : assert(action != null),
- assert(text != null),
- _icon = null,
- _iconSize = null,
- _text = text,
- _textStyle = style,
- super();
-
- /// Toolbar action associated with this button.
- final ZefyrToolbarAction action;
- final IconData _icon;
- final double _iconSize;
- final String _text;
- final TextStyle _textStyle;
-
- /// Callback to trigger when this button is tapped.
- final VoidCallback onPressed;
-
- bool get isAttributeAction {
- return kZefyrToolbarAttributeActions.keys.contains(action);
- }
-
- @override
- Widget build(BuildContext context) {
- final toolbar = ZefyrToolbar.of(context);
- final editor = toolbar.editor;
- final toolbarTheme = ZefyrTheme.of(context).toolbarTheme;
- final pressedHandler = _getPressedHandler(editor, toolbar);
- if (_icon != null) {
- return RawZefyrButton.icon(
- action: action,
- icon: _icon,
- size: 24,
- iconColor: (pressedHandler == null)
- ? toolbarTheme.disabledIconColor
- : _getColor(editor, toolbarTheme),
- color: _getFillColor(editor, toolbarTheme),
- onPressed: _getPressedHandler(editor, toolbar),
- );
- } else {
- assert(_text != null);
- var style = _textStyle ?? TextStyle();
- style = style.copyWith(
- color: _getColor(editor, toolbarTheme),
- );
- return RawZefyrButton(
- action: action,
- child: Text(_text, style: style),
- color: _getFillColor(editor, toolbarTheme),
- onPressed: _getPressedHandler(editor, toolbar),
- );
- }
- }
-
- Color _getColor(ZefyrScope editor, ToolbarTheme theme) {
- if (kToolbarButtonIcons.contains(action)) {
- return editor.toolbarAction == action
- ? theme.toggleColor
- : theme.surfaceColor;
- } else {
- if (isAttributeAction) {
- final attribute = kZefyrToolbarAttributeActions[action];
- final isToggled = (attribute is NotusAttribute)
- ? editor.selectionStyle.containsSame(attribute)
- : editor.selectionStyle.contains(attribute);
- return isToggled ? theme.toggleColor : theme.iconColor;
- }
- return null;
- }
- }
-
- Color _getFillColor(ZefyrScope editor, ToolbarTheme theme) {
- if (kToolbarButtonIcons.contains(action)) {
- return theme.color;
- } else {
- if (isAttributeAction) {
- final attribute = kZefyrToolbarAttributeActions[action];
- final isToggled = (attribute is NotusAttribute)
- ? editor.selectionStyle.containsSame(attribute)
- : editor.selectionStyle.contains(attribute);
- return isToggled ? Color(0xFFE1F5FF) : theme.iconFillColor;
- }
- return theme.iconFillColor;
- }
- }
-
- VoidCallback _getPressedHandler(
- ZefyrScope editor, ZefyrToolbarState toolbar) {
- if (onPressed != null) {
- return onPressed;
- } else if (isAttributeAction) {
- final attribute = kZefyrToolbarAttributeActions[action];
- if (attribute is NotusAttribute) {
- return () => _toggleAttribute(attribute, editor);
- }
- } else if (action == ZefyrToolbarAction.close) {
- return () => toolbar.closeOverlay();
- } else if (action == ZefyrToolbarAction.hideKeyboard) {
- return () => editor.closeKeyboard();
- } else if (action == ZefyrToolbarAction.showKeyboard) {
- return () => toolbar.closeOverlay();
- } else if (action == ZefyrToolbarAction.redo && editor.controller.document.history.stack.redo.isNotEmpty) {
- return () => editor.redo();
- } else if (action == ZefyrToolbarAction.undo && editor.controller.document.history.stack.undo.isNotEmpty) {
- return () => editor.undo();
- } else if (action == ZefyrToolbarAction.save) {
- return () => editor.onSave();
- }
-
- return null;
- }
-
- void _toggleAttribute(NotusAttribute attribute, ZefyrScope editor) {
- final isToggled = editor.selectionStyle.containsSame(attribute);
- if (isToggled) {
- editor.formatSelection(attribute.unset);
- } else {
- editor.formatSelection(attribute);
- }
- }
- }
-
- /// Raw button widget used by [ZefyrToolbar].
- ///
- /// See also:
- ///
- /// * [ZefyrButton], which wraps this widget and implements most of the
- /// action-specific logic.
- class RawZefyrButton extends StatelessWidget {
- const RawZefyrButton({
- Key key,
- @required this.action,
- @required this.child,
- @required this.color,
- @required this.onPressed,
- }) : super(key: key);
-
- /// Creates a [RawZefyrButton] containing an icon.
- RawZefyrButton.icon({
- @required this.action,
- @required IconData icon,
- double size,
- Color iconColor,
- @required this.color,
- @required this.onPressed,
- }) : child = Icon(icon, size: size, color: iconColor),
- super();
-
- /// Toolbar action associated with this button.
- final ZefyrToolbarAction action;
-
- /// Child widget to show inside this button. Usually an icon.
- final Widget child;
-
- /// Background color of this button.
- final Color color;
-
- /// Callback to trigger when this button is pressed.
- final VoidCallback onPressed;
-
- /// Returns `true` if this button is currently toggled on.
- bool get isToggled => color != null;
-
- @override
- Widget build(BuildContext context) {
- final theme = Theme.of(context);
- final isToolbarButton = kToolbarButtonIcons.contains(action);
- var constraints = theme.buttonTheme.constraints.copyWith(
- minWidth: 64,
- minHeight: 40,
- maxHeight: 40,
- );
- var radius = BorderRadius.all(Radius.circular(0));
- var padding = EdgeInsets.symmetric(horizontal: 1.0, vertical: 5.0);
- if (isToolbarButton) {
- constraints = theme.buttonTheme.constraints.copyWith(
- minWidth: 40,
- maxWidth: 40,
- minHeight: 30,
- maxHeight: 30,
- );
- padding = EdgeInsets.symmetric(horizontal: 0.0, vertical: 10);
- } else {
- if (action == ZefyrToolbarAction.headingLevel1 ||
- action == ZefyrToolbarAction.bold ||
- action == ZefyrToolbarAction.numberList) {
- radius = BorderRadius.horizontal(left: Radius.circular(4));
- }
- if (action == ZefyrToolbarAction.headingLevel6 ||
- action == ZefyrToolbarAction.deleteline ||
- action == ZefyrToolbarAction.bulletList) {
- radius = BorderRadius.horizontal(right: Radius.circular(4));
- }
- if (action == ZefyrToolbarAction.quote ||
- action == ZefyrToolbarAction.horizontalRule ||
- action == ZefyrToolbarAction.code) {
- radius = BorderRadius.all(Radius.circular(4));
- if (action == ZefyrToolbarAction.code ||
- action == ZefyrToolbarAction.quote) {
- padding = padding.copyWith(left: 12.0);
- }
- }
- }
-
- // Widget button = Row(
- // children: <Widget>[
-
- // // if (action == ZefyrToolbarAction.showKeyboard || action == ZefyrToolbarAction.hideKeyboard || action == ZefyrToolbarAction.link)
- // // Container(
- // // width: 1,
- // // color: Color(0xFFE2E2E2),
- // // height: 28,
- // // margin: EdgeInsets.symmetric(horizontal: 10),
- // // ),
- // ],
- // );
- return Padding(
- padding: padding,
- child: ConstrainedBox(
- constraints: constraints,
- child: RawMaterialButton(
- shape: RoundedRectangleBorder(borderRadius: radius),
- elevation: 0.0,
- fillColor: color,
- constraints: constraints,
- onPressed: onPressed,
- child: child,
- ),
- ),
- );
- //return isToolbarButton && action != ZefyrToolbarAction.showKeyboard && action != ZefyrToolbarAction.hideKeyboard ? Expanded(child: button) : button;
- }
- }
-
- /// Controls heading styles.
- ///
- /// When pressed, this button displays overlay toolbar with three
- /// buttons for each heading level.
- class HeadingButton extends StatefulWidget {
- const HeadingButton({Key key}) : super(key: key);
-
- @override
- _HeadingButtonState createState() => _HeadingButtonState();
- }
-
- class _HeadingButtonState extends State<HeadingButton> {
- @override
- Widget build(BuildContext context) {
- final toolbar = ZefyrToolbar.of(context);
- return toolbar.buildButton(
- context,
- ZefyrToolbarAction.heading,
- onPressed: showOverlay,
- );
- }
-
- void showOverlay() {
- final toolbar = ZefyrToolbar.of(context);
- toolbar.showOverlay(buildOverlay, ZefyrToolbarAction.heading);
- }
-
- Widget _buildButtonsView(List<Widget> buttons) {
- return Container(
- height: 52,
- child: ListView(
- scrollDirection: Axis.horizontal,
- children: buttons,
- physics: ClampingScrollPhysics(),
- ),
- );
- }
-
- bool hasColor(NotusStyle style, String color) {
- if (style.contains(NotusAttribute.color)) {
- var _style = style.get(NotusAttribute.color);
- return _style.value.toLowerCase() == color.toLowerCase();
- }
- return false;
- }
-
- Widget buildOverlay(BuildContext context) {
- final theme = ZefyrTheme.of(context).toolbarTheme;
- final toolbar = ZefyrToolbar.of(context);
-
- final headingButtons = <Widget>[
- toolbar.buildButton(context, ZefyrToolbarAction.headingLevel1),
- toolbar.buildButton(context, ZefyrToolbarAction.headingLevel2),
- toolbar.buildButton(context, ZefyrToolbarAction.headingLevel3),
- toolbar.buildButton(context, ZefyrToolbarAction.headingLevel4),
- toolbar.buildButton(context, ZefyrToolbarAction.headingLevel5),
- toolbar.buildButton(context, ZefyrToolbarAction.headingLevel6),
- ];
- final textButtons = <Widget>[
- toolbar.buildButton(context, ZefyrToolbarAction.bold),
- toolbar.buildButton(context, ZefyrToolbarAction.italic),
- toolbar.buildButton(context, ZefyrToolbarAction.underline),
- toolbar.buildButton(context, ZefyrToolbarAction.deleteline),
- toolbar.buildButton(context, ZefyrToolbarAction.quote),
- ];
- final listButtons = <Widget>[
- toolbar.buildButton(context, ZefyrToolbarAction.numberList),
- toolbar.buildButton(context, ZefyrToolbarAction.bulletList),
- ];
- final otherButtons = <Widget>[
- toolbar.buildButton(context, ZefyrToolbarAction.horizontalRule),
- toolbar.buildButton(context, ZefyrToolbarAction.code),
- ];
- final textColors = [
- ZefyrTheme.of(context).defaultLineTheme.textStyle.color, ...List.of(kTextColors),
- ];
-
- return Material(
- color: theme.color,
- child: Container(
- decoration: BoxDecoration(
- border: Border(top: BorderSide(color: theme.dividerColor, width: 1)),
- ),
- child: SingleChildScrollView(
- physics: ClampingScrollPhysics(),
- child: Column(
- children: [
- Container(
- padding: EdgeInsets.symmetric(vertical: 10, horizontal: 15),
- child: Column(
- children: [
- _buildButtonsView(headingButtons),
- _buildButtonsView(textButtons),
- _buildButtonsView(listButtons),
- _buildButtonsView(otherButtons),
- ],
- ),
- ),
- Container(
- decoration: BoxDecoration(
- border: Border(
- top: BorderSide(color: theme.dividerColor, width: 1)),
- ),
- padding: EdgeInsets.symmetric(vertical: 16),
- margin: EdgeInsets.symmetric(horizontal: 16),
- child: Row(
- children: <Widget>[
- Padding(
- padding: EdgeInsets.only(right: 12),
- child: Text(
- '文字色',
- style: Theme.of(context).textTheme.caption.copyWith(
- color: Theme.of(context).colorScheme.onSurface,
- fontSize: 14,
- fontWeight: FontWeight.w500,
- ),
- ),
- ),
- Expanded(
- child: Container(
- height: 20,
- child: ListView(
- scrollDirection: Axis.horizontal,
- children: textColors.map((color) {
- var hex = color.value.toRadixString(16).substring(2, 8);
- var has = false;
- if (!toolbar.editor.selectionStyle.contains(NotusAttribute.color)) {
- if (color == ZefyrTheme.of(context).defaultLineTheme.textStyle.color) {
- has = true;
- }
- } else {
- has = hasColor(toolbar.editor.selectionStyle, '#$hex');
- }
- // var key = kTextColors[index].value;
- return GestureDetector(
- behavior: HitTestBehavior.translucent,
- onTap: () {
- toolbar.editor.formatSelection(NotusAttribute.color.fromString('#$hex'));
- },
- child: Container(
- padding: EdgeInsets.all(1),
- width: 20,
- height: 20,
- decoration: ShapeDecoration(
- shape: CircleBorder(
- side: has ? BorderSide(
- width: 1,
- color: theme.toggleColor,
- ) : BorderSide.none,
- ),
- ),
- margin: EdgeInsets.symmetric(horizontal: 8),
- child: Container(
- // width: 16,
- // height: 16,
- decoration: ShapeDecoration(
- color: color,
- shape: CircleBorder(
- side: color == Color(0xFFFFFFFF) && !has ? BorderSide(
- width: 1,
- color: theme.dividerColor,
- ) : BorderSide.none,
- ),
- ),
- ),
- ),
- );
- }).toList(),
- physics: ClampingScrollPhysics(),
- ),
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- );
- // ZefyrToolbarScaffold(body: Container(
- // height: 200,
- // child: buttons,
- // ));
- }
- }
-
- // class TextColorButton extends StatelessWidget {
-
- // const TextColorButton({Key key, String color, ZefyrThemeData theme}) : super(key: key);
-
- // @override
- // Widget build(BuildContext context) {
- // return GestureDetector(
- // behavior: HitTestBehavior.translucent,
- // onTap: () {
- // // onTap(color);
- // // toolbar.editor.formatSelection(NotusAttribute.color.fromString('#000000'));
- // },
- // child: Container(
- // padding: EdgeInsets.all(1),
- // decoration: ShapeDecoration(
- // shape: CircleBorder(
- // side: BorderSide(
- // width: 1,
- // color: theme,
- // ),
- // ),
- // ),
- // margin: EdgeInsets.symmetric(horizontal: 8),
- // child: Container(
- // width: 16,
- // height: 16,
- // decoration: ShapeDecoration(
- // color: ZefyrTheme.of(context)
- // .defaultLineTheme
- // .textStyle
- // .color,
- // shape: CircleBorder(),
- // ),
- // ),
- // ),
- // );
- // }
- // }
-
- /// Controls image attribute.
- ///
- /// When pressed, this button displays overlay toolbar with three
- /// buttons for each heading level.
- class ImageButton extends StatefulWidget {
- const ImageButton({Key key}) : super(key: key);
-
- @override
- _ImageButtonState createState() => _ImageButtonState();
- }
-
- class _ImageButtonState extends State<ImageButton> {
-
- final provider = ExtendedAssetPickerProvider(
- maxAssets: 9,
- pageSize: 320,
- pathThumbSize: 200,
- selectedAssets: [],
- requestType: RequestType.image,
- sortPathDelegate: CustomSortPathDelegate(),
- routeDuration: const Duration(milliseconds: 300),
- );
-
- @override
- Widget build(BuildContext context) {
- final toolbar = ZefyrToolbar.of(context);
- return toolbar.buildButton(
- context,
- ZefyrToolbarAction.image,
- onPressed: showOverlay,
- );
- }
-
- Future<void> showOverlay() async {
- final toolbar = ZefyrToolbar.of(context);
- if (Platform.isIOS || Platform.isAndroid) {
- var isPermissionGranted = await PhotoManager.requestPermission();
- if (isPermissionGranted) {
- return toolbar.showOverlay(buildOverlay, ZefyrToolbarAction.image);
- } else {
- PhotoManager.openSetting();
- }
- } else {
- print('打开file');
- }
- }
-
-
- Widget buildOverlay(BuildContext context) {
- return ExtendedAssetPicker.buildQuickPicker(
- context,
- provider: provider,
- );
- }
- }
-
- class LinkButton extends StatefulWidget {
- const LinkButton({Key key}) : super(key: key);
-
- @override
- _LinkButtonState createState() => _LinkButtonState();
- }
-
- class _LinkButtonState extends State<LinkButton> {
- final TextEditingController _inputController = TextEditingController();
- Key _inputKey;
- bool _formatError = false;
-
- bool get isEditing => _inputKey != null;
-
- @override
- Widget build(BuildContext context) {
- final toolbar = ZefyrToolbar.of(context);
- final editor = toolbar.editor;
- final enabled =
- hasLink(editor.selectionStyle) || !editor.selection.isCollapsed;
-
- return toolbar.buildButton(
- context,
- ZefyrToolbarAction.link,
- // onPressed: enabled ? showOverlay : null,
- onPressed: showOverlay,
- );
- }
-
- bool hasLink(NotusStyle style) => style.contains(NotusAttribute.link);
-
- String getLink([String defaultValue = '']) {
- final editor = ZefyrToolbar.of(context).editor;
- final attrs = editor.selectionStyle;
- if (hasLink(attrs)) {
- return attrs.value(NotusAttribute.link);
- }
- return defaultValue;
- }
-
- String getText([String defaultValue = '']) {
- final editor = ZefyrToolbar.of(context).editor;
- final plainTextEditingValue = editor.controller.plainTextEditingValue;
- if (!editor.selection.isCollapsed) {
- return plainTextEditingValue.text.substring(editor.selection.start, editor.selection.end);
- }
- return defaultValue;
- }
-
- OverlayEntry _overlayEntry;
-
- Future<void> showOverlay() async {
- final editor = ZefyrToolbar.of(context).editor;
- editor.closeKeyboard(true);
- var _selection = editor.selection;
- var result = await editor.linkDelegate.fillLink(context, ZefyrLinkEntity(
- text: getText(),
- url: getLink(),
- ));
-
- if (result != null) {
- // toolbar.editor.updateSelection(_selection, source: ChangeSource.local);
- // _selection = toolbar.editor.selection;
- editor.controller.replaceText(
- _selection.start, _selection.end - _selection.start, result.text,
- selection: _selection.copyWith(
- baseOffset: _selection.start,
- extentOffset: _selection.start + result.text.length,
- ));
- editor.formatSelection(NotusAttribute.link.fromString(result.url));
- editor.closeKeyboard();
- } else {
- editor.updateSelection(_selection, source: ChangeSource.local);
- }
- }
-
- void closeOverlay() {
- final toolbar = ZefyrToolbar.of(context);
- toolbar.closeOverlay();
- }
-
- void edit() {
- final toolbar = ZefyrToolbar.of(context);
- setState(() {
- _inputKey = UniqueKey();
- _inputController.text = getLink('https://');
- _inputController.addListener(_handleInputChange);
- toolbar.markNeedsRebuild();
- });
- }
-
- void doneEdit() {
- final toolbar = ZefyrToolbar.of(context);
- setState(() {
- var error = false;
- if (_inputController.text.isNotEmpty) {
- try {
- var uri = Uri.parse(_inputController.text);
- if ((uri.isScheme('https') || uri.isScheme('http')) &&
- uri.host.isNotEmpty) {
- toolbar.editor.formatSelection(
- NotusAttribute.link.fromString(_inputController.text));
- } else {
- error = true;
- }
- } on FormatException {
- error = true;
- }
- }
- if (error) {
- _formatError = error;
- toolbar.markNeedsRebuild();
- } else {
- _inputKey = null;
- _inputController.text = '';
- _inputController.removeListener(_handleInputChange);
- toolbar.markNeedsRebuild();
- toolbar.editor.focus();
- }
- });
- }
-
- void cancelEdit() {
- if (mounted) {
- final editor = ZefyrToolbar.of(context).editor;
- setState(() {
- _inputKey = null;
- _inputController.text = '';
- _inputController.removeListener(_handleInputChange);
- editor.focus();
- });
- }
- }
-
- void unlink() {
- final editor = ZefyrToolbar.of(context).editor;
- editor.formatSelection(NotusAttribute.link.unset);
- closeOverlay();
- }
-
- void copyToClipboard() {
- var link = getLink();
- assert(link != null);
- Clipboard.setData(ClipboardData(text: link));
- }
-
- void openInBrowser() async {
- final editor = ZefyrToolbar.of(context).editor;
- var link = getLink();
- assert(link != null);
- if (await canLaunch(link)) {
- editor.closeKeyboard();
- await launch(link, forceWebView: true);
- }
- }
-
- void _handleInputChange() {
- final toolbar = ZefyrToolbar.of(context);
- setState(() {
- _formatError = false;
- toolbar.markNeedsRebuild();
- });
- }
- }
-
|