builder.dart 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import 'dart:convert';
  2. import 'dart:io' as io;
  3. import 'package:flutter/foundation.dart';
  4. import 'package:flutter/gestures.dart';
  5. import 'package:flutter/material.dart';
  6. import 'package:string_validator/string_validator.dart';
  7. import '../model/document/node/leaf.dart';
  8. import '../widget/raw_editor.dart';
  9. import '../widget/selection.dart';
  10. import '../rendering/editor.dart';
  11. /* ----------------------- Selection Gesture Detector ----------------------- */
  12. abstract class EditorTextSelectionGestureDetectorBuilderDelegate {
  13. GlobalKey<EditorState> getEditableTextKey();
  14. bool getForcePressEnabled();
  15. bool getSelectionEnabled();
  16. }
  17. class EditorTextSelectionGestureDetectorBuilder {
  18. EditorTextSelectionGestureDetectorBuilder(this.delegate);
  19. final EditorTextSelectionGestureDetectorBuilderDelegate delegate;
  20. bool shouldShowSelectionToolbar = true;
  21. EditorState? getEditor() {
  22. return delegate.getEditableTextKey().currentState;
  23. }
  24. RenderEditor? getRenderEditor() {
  25. return getEditor()!.getRenderEditor();
  26. }
  27. void onForcePressStart(ForcePressDetails details) {
  28. assert(delegate.getForcePressEnabled());
  29. shouldShowSelectionToolbar = true;
  30. if (delegate.getSelectionEnabled()) {
  31. getRenderEditor()!.selectWordsInRange(
  32. details.globalPosition,
  33. null,
  34. SelectionChangedCause.forcePress,
  35. );
  36. }
  37. }
  38. void onForcePressEnd(ForcePressDetails details) {
  39. assert(delegate.getForcePressEnabled());
  40. getRenderEditor()!.selectWordsInRange(
  41. details.globalPosition,
  42. null,
  43. SelectionChangedCause.forcePress,
  44. );
  45. if (shouldShowSelectionToolbar) {
  46. getEditor()!.showToolbar();
  47. }
  48. }
  49. void onTapDown(TapDownDetails details) {
  50. getRenderEditor()!.handleTapDown(details);
  51. final kind = details.kind;
  52. shouldShowSelectionToolbar = kind == null ||
  53. kind == PointerDeviceKind.touch ||
  54. kind == PointerDeviceKind.stylus;
  55. }
  56. void onTapUp(TapUpDetails details) {
  57. if (delegate.getSelectionEnabled()) {
  58. getRenderEditor()!.selectWordEdge(SelectionChangedCause.tap);
  59. }
  60. }
  61. void onTapCancel() {}
  62. void onLongPressStart(LongPressStartDetails details) {
  63. if (delegate.getSelectionEnabled()) {
  64. getRenderEditor()!.selectPositionAt(
  65. details.globalPosition,
  66. null,
  67. SelectionChangedCause.longPress,
  68. );
  69. }
  70. }
  71. void onLongPressMoveUpdate(LongPressMoveUpdateDetails details) {
  72. if (delegate.getSelectionEnabled()) {
  73. getRenderEditor()!.selectPositionAt(
  74. details.globalPosition,
  75. null,
  76. SelectionChangedCause.longPress,
  77. );
  78. }
  79. }
  80. void onLongPressEnd(LongPressEndDetails details) {
  81. if (shouldShowSelectionToolbar) {
  82. getEditor()!.showToolbar();
  83. }
  84. }
  85. void onDoubleTapDown(TapDownDetails details) {
  86. if (delegate.getSelectionEnabled()) {
  87. getRenderEditor()!.selectWord(SelectionChangedCause.tap);
  88. if (shouldShowSelectionToolbar) {
  89. getEditor()!.showToolbar();
  90. }
  91. }
  92. }
  93. void onDragSelectionStart(DragStartDetails details) {
  94. getRenderEditor()!.selectPositionAt(
  95. details.globalPosition,
  96. null,
  97. SelectionChangedCause.drag,
  98. );
  99. }
  100. void onDragSelectionUpdate(
  101. DragStartDetails startDetails, DragUpdateDetails updateDetails) {
  102. getRenderEditor()!.selectPositionAt(
  103. startDetails.globalPosition,
  104. updateDetails.globalPosition,
  105. SelectionChangedCause.drag,
  106. );
  107. }
  108. void onDragSelectionEnd(DragEndDetails details) {}
  109. Widget build(HitTestBehavior behavior, Widget child) {
  110. return EditorTextSelectionGestureDetector(
  111. onTapUp: onTapUp,
  112. onTapDown: onTapDown,
  113. onTapCancel: onTapCancel,
  114. onForcePressStart:
  115. delegate.getForcePressEnabled() ? onForcePressStart : null,
  116. onForcePressEnd: delegate.getForcePressEnabled() ? onForcePressEnd : null,
  117. onLongPressStart: onLongPressStart,
  118. onLongPressMoveUpdate: onLongPressMoveUpdate,
  119. onLongPressEnd: onLongPressEnd,
  120. onDoubleTapDown: onDoubleTapDown,
  121. onDragSelectionStart: onDragSelectionStart,
  122. onDragSelectionUpdate: onDragSelectionUpdate,
  123. onDragSelectionEnd: onDragSelectionEnd,
  124. behavior: behavior,
  125. child: child,
  126. );
  127. }
  128. }
  129. /* ---------------------------------- Embed --------------------------------- */
  130. class EmbedBuilder {
  131. static const kImageTypeKey = 'image';
  132. static const kVideoTypeKey = 'video';
  133. static Widget defaultBuilder(BuildContext context, Embed node) {
  134. assert(!kIsWeb, 'Please provide EmbedBuilder for Web');
  135. switch (node.value.type) {
  136. case kImageTypeKey:
  137. return _generateImageEmbed(context, node);
  138. default:
  139. throw UnimplementedError(
  140. 'Embeddable type "${node.value.type}" is not supported by default embed '
  141. 'builder of QuillEditor. You must pass your own builder function to '
  142. 'embedBuilder property of QuillEditor or QuillField widgets.');
  143. }
  144. }
  145. // Generator
  146. static Widget _generateImageEmbed(BuildContext context, Embed node) {
  147. final imageUrl = standardizeImageUrl(node.value.data);
  148. return imageUrl.startsWith('http')
  149. ? Image.network(imageUrl)
  150. : isBase64(imageUrl)
  151. ? Image.memory(base64.decode(imageUrl))
  152. : Image.file(io.File(imageUrl));
  153. }
  154. // Helper
  155. static String standardizeImageUrl(String url) {
  156. if (url.contains('base64')) {
  157. return url.split(',')[1];
  158. }
  159. return url;
  160. }
  161. }