added project'

This commit is contained in:
2022-10-30 15:25:33 +07:00
commit 4dd5d2f6ff
121 changed files with 14320 additions and 0 deletions

187
lib/utils/callbacks.dart Normal file
View File

@@ -0,0 +1,187 @@
import 'dart:async';
import 'package:html_editor_enhanced/html_editor.dart';
/// Manages all the callback functions the library provides
class Callbacks {
Callbacks({
this.onBeforeCommand,
this.onChangeContent,
this.onChangeCodeview,
this.onChangeSelection,
this.onDialogShown,
this.onEnter,
this.onFocus,
this.onBlur,
this.onBlurCodeview,
this.onImageLinkInsert,
this.onImageUpload,
this.onImageUploadError,
this.onInit,
this.onKeyUp,
this.onKeyDown,
this.onMouseUp,
this.onMouseDown,
this.onNavigationRequestMobile,
this.onPaste,
this.onScroll,
});
/// Called before certain commands are fired and the editor is in rich text view.
/// There is currently no documentation on this parameter, thus it is
/// unclear which commands this will fire before.
///
/// This function will return the current HTML in the editor as an argument.
void Function(String?)? onBeforeCommand;
/// Called whenever the HTML content of the editor is changed and the editor
/// is in rich text view.
///
/// Note: This function also seems to be called if input is detected in the
/// editor field but the content does not change.
/// E.g. repeatedly pressing backspace when the field is empty
/// will also trigger this callback.
///
/// This function will return the current HTML in the editor as an argument.
void Function(String?)? onChangeContent;
/// Called whenever the code of the editor is changed and the editor
/// is in code view.
///
/// Note: This function also seems to be called if input is detected in the
/// editor field but the content does not change.
/// E.g. repeatedly pressing backspace when the field is empty
/// will also trigger this callback.
///
/// This function will return the current code in the codeview as an argument.
void Function(String?)? onChangeCodeview;
/// Called whenever the selection area of the editor is changed.
///
/// It passes all the editor settings at the current selection as an argument.
/// This can be used in custom toolbar item implementations, to update your
/// toolbar item UI when the editor formatting changes.
void Function(EditorSettings)? onChangeSelection;
/// Called whenever a dialog is shown in the editor. The dialogs will be either
/// the link, image, video, or help dialogs.
void Function()? onDialogShown;
/// Called whenever the enter/return key is pressed and the editor
/// is in rich text view. There is currently no way to detect enter/return
/// when the editor is in code view.
///
/// Note: The onChange callback will also be triggered at the same time as
/// this callback, please design your implementation accordingly.
void Function()? onEnter;
/// Called whenever the rich text field gains focus. This will not be called
/// when the code view editor gains focus, instead use [onBlurCodeview] for
/// that.
void Function()? onFocus;
/// Called whenever either the rich text field or the codeview field loses
/// focus. This will also be triggered when switching from the rich text editor
/// to the code view editor.
///
/// Note: Due to the current state of webviews in Flutter, tapping outside
/// the webview or dismissing the keyboard does not trigger this callback.
/// This callback will only be triggered if the user taps on an empty space
/// in the toolbar or switches the view mode of the editor.
void Function()? onBlur;
/// Called whenever the code view either gains or loses focus (the Summernote
/// docs say this will only be called when the code view loses focus but
/// in my testing this is not the case). This will also be triggered when
/// switching between the rich text editor and the code view editor.
///
/// Note: Due to the current state of webviews in Flutter, tapping outside
/// the webview or dismissing the keyboard does not trigger this callback.
/// This callback will only be triggered if the user taps on an empty space
/// in the toolbar or switches the view mode of the editor.
void Function()? onBlurCodeview;
/// Called whenever an image is inserted via a link. The function passes the
/// URL of the image inserted into the editor.
///
/// Note: Setting this function overrides the default summernote image via URL
/// insertion handler! This means you must manually insert the image using
/// [controller.insertNetworkImage] in your callback function, otherwise
/// nothing will be inserted into the editor!
void Function(String?)? onImageLinkInsert;
/// Called whenever an image is inserted via upload. The function passes the
/// [FileUpload] class, containing the filename, size, MIME type, base64 data,
/// and last modified information so you can upload it into your server.
///
/// Note: Setting this function overrides the default summernote upload image
/// insertion handler (base64 handler)! This means you must manually insert
/// the image using [controller.insertNetworkImage] (for uploaded images) or
/// [controller.insertHtml] (for base64 data) in your callback function,
/// otherwise nothing will be inserted into the editor!
void Function(FileUpload)? onImageUpload;
/// Called whenever an image is failed to be inserted via upload. The function
/// passes the [FileUpload] class, containing the filename, size, MIME type,
/// base64 data, and last modified information so you can do error handling.
void Function(FileUpload?, String?, UploadError)? onImageUploadError;
/// Called whenever [InAppWebViewController.onLoadStop] is fired on mobile
/// or when the [IFrameElement.onLoad] stream is fired on web. Note that this
/// method will also be called on refresh on both platforms.
///
/// You can use this method to set certain properties - e.g. set the editor
/// to fullscreen mode - as soon as the editor is ready to accept these commands.
void Function()? onInit;
/// Called whenever a key is released and the editor is in rich text view.
///
/// This function will return the keycode for the released key as an argument.
///
/// Note: The keycode [is broken](https://stackoverflow.com/questions/36753548/keycode-on-android-is-always-229)
/// on Android, you will only ever receive 229, 8 (backspace), or 13 (enter)
/// as a keycode. 8 and 13 only seem to be returned when the editor is empty
/// and those keys are released.
void Function(int?)? onKeyUp;
/// Called whenever a key is downed and the editor is in rich text view.
///
/// This function will return the keycode for the downed key as an argument.
///
/// Note: The keycode [is broken](https://stackoverflow.com/questions/36753548/keycode-on-android-is-always-229)
/// on Android, you will only ever receive 229, 8 (backspace), or 13 (enter)
/// as a keycode. 8 and 13 only seem to be returned when the editor is empty
/// and those keys are downed.
void Function(int?)? onKeyDown;
/// Called whenever the mouse/finger is released and the editor is in rich text view.
void Function()? onMouseUp;
/// Called whenever the mouse/finger is downed and the editor is in rich text view.
void Function()? onMouseDown;
/// Called right before the URL of the webview is changed on mobile. This allows
/// you to prevent URLs from loading, or launch them externally, for example.
///
/// This function passes the URL to be loaded, and you must return a
/// `NavigationActionPolicy` to tell the webview what to do.
FutureOr<NavigationActionPolicy> Function(String)? onNavigationRequestMobile;
/// Called whenever text is pasted into the rich text field. This will not be
/// called when text is pasted into the code view editor.
///
/// Note: This will not be called when programmatically inserting HTML into
/// the editor with [HtmlEditor.insertHtml].
void Function()? onPaste;
/// Called whenever the editor is scrolled and it is in rich text view.
/// Editor scrolled is considered to be the editor box only, not the webview
/// container itself. Thus, this callback will only fire when the content in
/// the editor is longer than the editor height. This function can be called
/// with an explicit scrolling action via the mouse, or also via implied
/// scrolling, e.g. the enter key scrolling the editor to make new text visible.
///
/// Note: This function will be repeatedly called while the editor is scrolled.
/// Make sure to factor that into your implementation.
void Function()? onScroll;
}

View File

@@ -0,0 +1,59 @@
import 'dart:convert';
/// Function that creates an instance of [FileUpload] from a JSON string
FileUpload fileUploadFromJson(String str) =>
FileUpload.fromJson(json.decode(str));
/// The [FileUpload] class stores any known data about a file. This class is used
/// as an argument in some callbacks relating to image and file insertion.
///
/// The class holds last modified information, name, size, type, and the base64
/// of the file.
///
/// Note that all parameters are nullable to prevent any null-exception when
/// getting file data from JavaScript.
class FileUpload {
FileUpload({
this.base64,
this.lastModified,
this.lastModifiedDate,
this.name,
this.size,
this.type,
});
/// The base64 string of the file.
///
/// Note: This includes identifying data (e.g. data:image/jpeg;base64,) at the
/// beginning. To strip this out, use FileUpload().base64.split(",")[1].
String? base64;
/// Last modified information in *milliseconds since epoch* format
DateTime? lastModified;
/// Last modified information in *regular date* format
DateTime? lastModifiedDate;
/// The filename
String? name;
/// The file size in bytes
int? size;
/// The content-type (eg. image/jpeg) of the file
String? type;
/// Creates an instance of [FileUpload] from a JSON string
factory FileUpload.fromJson(Map<String, dynamic> json) => FileUpload(
base64: json['base64'],
lastModified: json['lastModified'] == null
? null
: DateTime.fromMillisecondsSinceEpoch(json['lastModified']),
lastModifiedDate: json['lastModifiedDate'] == null
? null
: DateTime.tryParse(json['lastModifiedDate']),
name: json['name'],
size: json['size'],
type: json['type'],
);
}

429
lib/utils/options.dart Normal file
View File

@@ -0,0 +1,429 @@
import 'dart:async';
import 'dart:collection';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:html_editor_enhanced/html_editor.dart';
/// Options that modify the editor and its behavior
class HtmlEditorOptions {
const HtmlEditorOptions({
this.autoAdjustHeight = true,
this.androidUseHybridComposition = true,
this.adjustHeightForKeyboard = true,
this.characterLimit,
this.customOptions = '',
this.darkMode,
this.disabled = false,
this.filePath,
this.hint,
this.initialText,
this.inputType = HtmlInputType.text,
this.mobileContextMenu,
this.mobileLongPressDuration,
this.mobileInitialScripts,
this.webInitialScripts,
this.shouldEnsureVisible = false,
this.spellCheck = false,
});
/// The editor will automatically adjust its height when the keyboard is active
/// to prevent the keyboard overlapping the editor.
///
/// The default value is true. It is recommended to leave this as true because
/// it significantly improves the UX.
final bool adjustHeightForKeyboard;
/// ALlows devs to set hybrid composition off in case they would like to
/// prioritize animation smoothness over text input experience.
///
/// The recommended value is `true`.
final bool androidUseHybridComposition;
/// The editor will automatically adjust its height once the page is loaded to
/// ensure there is no vertical scrolling or empty space. It will only perform
/// the adjustment when the summernote editor is the loaded page.
///
/// It will also disable vertical scrolling on the webview, so scrolling on
/// the webview will actually scroll the rest of the page rather than doing
/// nothing because it is trying to scroll the webview container.
///
/// The default value is true. It is recommended to leave this as true because
/// it significantly improves the UX.
final bool autoAdjustHeight;
/// Adds a character limit to the editor.
///
/// NOTE: ONLY WORKS ON iOS AND WEB PLATFORMS!!
final int? characterLimit;
/// Set custom options for the summernote editor by using their syntax.
///
/// Please ensure your syntax is correct (and add a comma at the end of your
/// string!) otherwise the editor may not load.
final String customOptions;
/// Sets the editor to dark mode. `null` - switches with system, `false` -
/// always light, `true` - always dark.
///
/// The default value is null (switches with system).
final bool? darkMode;
/// Disable the editor immediately after startup. You can re-enable the editor
/// by calling [controller.enable()].
final bool disabled;
/// Specify the file path to your custom html editor code.
///
/// Make sure to set the editor's HTML ID to be 'summernote-2'.
///
/// If you plan to use this on Web, you must add comments in your HTML so the
/// package can insert the relevant JS code to communicate between Dart and JS.
/// See the README for more details on this.
final String? filePath;
/// Sets the Html editor's hint (text displayed when there is no text in the
/// editor).
final String? hint;
/// The initial text that is be supplied to the Html editor.
final String? initialText;
/// Changes the display of the virtual keyboard on mobile devices.
///
/// See [HtmlInputType] for the supported modes.
///
/// The default value is [HtmlInputType.text] (the standard virtual keyboard)
final HtmlInputType inputType;
/// Customize the context menu for selected text on mobile
final ContextMenu? mobileContextMenu;
/// Set the duration until a long-press is recognized.
///
/// The default value is 500ms.
final Duration? mobileLongPressDuration;
/// Initial JS to inject into the editor.
final UnmodifiableListView<UserScript>? mobileInitialScripts;
/// Initial JS to add to the editor. These can be called at any time using
/// [controller.evaluateJavascriptWeb]
final UnmodifiableListView<WebScript>? webInitialScripts;
/// Specifies whether the widget should scroll to reveal the HTML editor when
/// it is focused or the text content is changed.
/// See the README examples for the best way to implement this.
///
/// Note: Your editor *must* be in a Scrollable type widget (e.g. ListView,
/// SingleChildScrollView, etc.) for this to work. Otherwise, nothing will
/// happen.
final bool shouldEnsureVisible;
/// Specify whether or not the editor should spellcheck its contents.
///
/// Default value is false.
final bool spellCheck;
}
/// Options that modify the toolbar and its behavior
class HtmlToolbarOptions {
const HtmlToolbarOptions({
this.audioExtensions,
this.customToolbarButtons = const [],
this.customToolbarInsertionIndices = const [],
this.defaultToolbarButtons = const [
StyleButtons(),
FontSettingButtons(fontSizeUnit: false),
FontButtons(clearAll: false),
ColorButtons(),
ListButtons(listStyles: false),
ParagraphButtons(
textDirection: false, lineHeight: false, caseConverter: false),
InsertButtons(
video: false,
audio: false,
table: false,
hr: false,
otherFile: false),
],
this.otherFileExtensions,
this.imageExtensions,
this.initiallyExpanded = false,
this.linkInsertInterceptor,
this.mediaLinkInsertInterceptor,
this.mediaUploadInterceptor,
this.onButtonPressed,
this.onDropdownChanged,
this.onOtherFileLinkInsert,
this.onOtherFileUpload,
this.toolbarType = ToolbarType.nativeScrollable,
this.toolbarPosition = ToolbarPosition.aboveEditor,
this.videoExtensions,
this.dropdownElevation = 8,
this.dropdownIcon,
this.dropdownIconColor,
this.dropdownIconSize = 24,
this.dropdownItemHeight = kMinInteractiveDimension,
this.dropdownFocusColor,
this.dropdownBackgroundColor,
this.dropdownMenuDirection,
this.dropdownMenuMaxHeight,
this.dropdownBoxDecoration,
this.buttonColor,
this.buttonSelectedColor,
this.buttonFillColor,
this.buttonFocusColor,
this.buttonHighlightColor,
this.buttonHoverColor,
this.buttonSplashColor,
this.buttonBorderColor,
this.buttonSelectedBorderColor,
this.buttonBorderRadius,
this.buttonBorderWidth,
this.renderBorder = false,
this.textStyle,
this.separatorWidget =
const VerticalDivider(indent: 2, endIndent: 2, color: Colors.grey),
this.renderSeparatorWidget = true,
this.toolbarItemHeight = 36,
this.gridViewHorizontalSpacing = 5,
this.gridViewVerticalSpacing = 5,
});
/// Allows you to set the allowed extensions when a user inserts an audio file
///
/// By default any audio extension is allowed.
final List<String>? audioExtensions;
/// Allows you to create your own buttons that are added to the end of the
/// default buttons list
final List<Widget> customToolbarButtons;
/// Allows you to set where each custom toolbar button is inserted into the
/// toolbar buttons.
///
/// Notes: 1) This list should have the same length as the [customToolbarButtons]
///
/// 2) If any indices > [defaultToolbarButtons.length] then the plugin will
/// automatically account for this and insert the buttons at the end of the
/// [defaultToolbarButtons]
///
/// 3) If any indices < 0 then the plugin will automatically account for this
/// and insert the buttons at the beginning of the [defaultToolbarButtons]
final List<int> customToolbarInsertionIndices;
/// Sets which options are visible in the toolbar for the editor.
final List<Toolbar> defaultToolbarButtons;
/// Allows you to set the allowed extensions when a user inserts an image
///
/// By default any image extension is allowed.
final List<String>? imageExtensions;
/// Allows you to set whether the toolbar starts out expanded (in gridview)
/// or contracted (in scrollview).
///
/// By default it starts out contracted.
///
/// This option only works when you have set [toolbarType] to
/// [ToolbarType.nativeExpandable].
final bool initiallyExpanded;
/// Allows you to intercept any links being inserted into the editor. The
/// function passes the display text, the URL itself, and whether the
/// URL should open a new tab.
///
/// Return a bool to tell the plugin if it should continue with its own handler
/// or if you want to handle the link by yourself.
/// (true = continue with internal handler, false = do not use internal handler)
///
/// If no interceptor is set, the plugin uses the internal handler.
final FutureOr<bool> Function(String, String, bool)? linkInsertInterceptor;
/// Allows you to intercept any image/video/audio inserted as a link into the editor.
/// The function passes the URL of the media inserted.
///
/// Return a bool to tell the plugin if it should continue with its own handler
/// or if you want to handle the image/video link by yourself.
/// (true = continue with internal handler, false = do not use internal handler)
///
/// If no interceptor is set, the plugin uses the internal handler.
final FutureOr<bool> Function(String, InsertFileType)?
mediaLinkInsertInterceptor;
/// Allows you to intercept any image/video/audio files being inserted into the editor.
/// The function passes the PlatformFile class, which contains all the file data
/// including name, size, type, Uint8List bytes, etc.
///
/// Return a bool to tell the plugin if it should continue with its own handler
/// or if you want to handle the image/video/audio upload by yourself.
/// (true = continue with internal handler, false = do not use internal handler)
///
/// If no interceptor is set, the plugin uses the internal handler.
final FutureOr<bool> Function(PlatformFile, InsertFileType)?
mediaUploadInterceptor;
/// Allows you to intercept any button press. The function passes the ButtonType
/// enum, which tells you which button was pressed, the current selected status of
/// the button, and a function to reverse the status (in case you decide to handle
/// the button press yourself).
///
/// Note: In some cases, the button is never active (e.g. copy/paste buttons)
/// so null will be returned for both the selected status and the function.
///
/// Return a bool to tell the plugin if it should continue with its own handler
/// or if you want to handle the button press by yourself.
/// (true = continue with internal handler, false = do not use internal handler)
///
/// If no interceptor is set, the plugin uses the internal handler.
final FutureOr<bool> Function(ButtonType, bool?, Function?)? onButtonPressed;
/// Allows you to intercept any dropdown changes. The function passes the
/// DropdownType enum, which tells you which dropdown was changed,
/// the changed value to indicate what the dropdown was changed to, and the
/// function to update the changed value (in case you decide to handle the
/// dropdown change yourself). The function is null in some cases because
/// the dropdown does not update its value.
///
/// Return a bool to tell the plugin if it should continue with its own handler
/// or if you want to handle the dropdown change by yourself.
/// (true = continue with internal handler, false = do not use internal handler)
///
/// If no interceptor is set, the plugin uses the internal handler.
final FutureOr<bool> Function(DropdownType, dynamic, void Function(dynamic)?)?
onDropdownChanged;
/// Called when a link is inserted for a file using the "other file" button.
///
/// The package does not have a built in handler for these files, so you should
/// provide this callback when using the button.
///
/// The function passes the URL of the file inserted.
final void Function(String)? onOtherFileLinkInsert;
/// Called when a file is uploaded using the "other file" button.
///
/// The package does not have a built in handler for these files, so if you use
/// the button you should provide this callback.
///
/// The function passes the PlatformFile class, which contains all the file data
/// including name, size, type, Uint8List bytes, etc.
final void Function(PlatformFile)? onOtherFileUpload;
/// Allows you to set the allowed extensions when a user inserts a file other
/// than image/audio/video
///
/// By default any other extension is allowed.
final List<String>? otherFileExtensions;
/// Controls how the toolbar displays. See [ToolbarType] for more details.
///
/// By default the toolbar is rendered as a scrollable one-line list.
final ToolbarType toolbarType;
/// Controls where the toolbar is positioned. See [ToolbarPosition] for more details.
///
/// By default the toolbar is above the editor.
final ToolbarPosition toolbarPosition;
/// Allows you to set the allowed extensions when a user inserts a video.
///
/// By default any video extension is allowed.
final List<String>? videoExtensions;
/// Styling options for the toolbar:
/// Determines whether a border is rendered around all toolbar widgets
///
/// The default value is false. True is recommended for [ToolbarType.nativeGrid].
final bool renderBorder;
/// Sets the text style for all toolbar widgets
final TextStyle? textStyle;
/// Sets the separator widget between toolbar sections. This widget is only
/// used in [ToolbarType.nativeScrollable].
///
/// The default widget is [VerticalDivider(indent: 2, endIndent: 2, color: Colors.grey)]
final Widget separatorWidget;
/// Determines whether the separator widget is rendered
///
/// The default value is true
final bool renderSeparatorWidget;
/// Sets the height of the toolbar items
///
/// Button width is affected by this parameter, however dropdown widths are
/// not affected. The plugin will maintain a square shape for all buttons.
///
/// The default value is 36
final double toolbarItemHeight;
/// Sets the vertical spacing between rows when using [ToolbarType.nativeGrid]
///
/// The default value is 5
final double gridViewVerticalSpacing;
/// Sets the horizontal spacing between items when using [ToolbarType.nativeGrid]
///
/// The default value is 5
final double gridViewHorizontalSpacing;
/// Styling options that only apply to dropdowns:
/// (See the [DropdownButton] class for more information)
final int dropdownElevation;
final Widget? dropdownIcon;
final Color? dropdownIconColor;
final double dropdownIconSize;
final double dropdownItemHeight;
final Color? dropdownFocusColor;
final Color? dropdownBackgroundColor;
/// Set the menu opening direction for the dropdown. Only useful when using
/// [ToolbarPosition.custom] since the toolbar otherwise automatically
/// determines the correct direction.
final DropdownMenuDirection? dropdownMenuDirection;
final double? dropdownMenuMaxHeight;
final BoxDecoration? dropdownBoxDecoration;
/// Styling options that only apply to the buttons:
/// (See the [ToggleButtons] class for more information)
final Color? buttonColor;
final Color? buttonSelectedColor;
final Color? buttonFillColor;
final Color? buttonFocusColor;
final Color? buttonHighlightColor;
final Color? buttonHoverColor;
final Color? buttonSplashColor;
final Color? buttonBorderColor;
final Color? buttonSelectedBorderColor;
final BorderRadius? buttonBorderRadius;
final double? buttonBorderWidth;
}
/// Other options such as the height of the widget and the decoration surrounding it
class OtherOptions {
const OtherOptions({
this.decoration = const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4)),
border:
Border.fromBorderSide(BorderSide(color: Color(0xffececec), width: 1)),
),
this.height = 400,
});
/// The BoxDecoration to use around the Html editor. By default, the widget
/// uses a thin, dark, rounded rectangle border around the widget.
final BoxDecoration decoration;
/// Sets the height of the Html editor widget. This takes the toolbar into
/// account (i.e. this sets the height of the entire widget rather than the
/// editor space)
///
/// The default value is 400.
final double height;
}

53
lib/utils/plugins.dart Normal file
View File

@@ -0,0 +1,53 @@
import 'package:flutter/foundation.dart';
/// Abstract class that all the plguin classes extend
abstract class Plugins {
const Plugins();
/// Provides the JS and CSS tags to be inserted inside <head>. Only used for Web
String getHeadString();
/// Provides the toolbar option for the plugin
String getToolbarString();
}
/// Summernote @ Mention plugin - adds a dropdown to select the person to mention whenever
/// the '@' character is typed into the editor. The list of people to mention is
/// drawn from the [getSuggestionsMobile] (on mobile) or [mentionsWeb] (on Web)
/// parameter. You can detect who was mentioned using the [onSelect] callback.
///
/// README available [here](https://github.com/team-loxo/summernote-at-mention)
class SummernoteAtMention extends Plugins {
/// Function used to get the displayed suggestions on mobile
final List<String> Function(String)? getSuggestionsMobile;
/// List of mentions to display on Web. The default behavior is to only return
/// the mentions containing the string entered by the user in the editor
final List<String>? mentionsWeb;
/// Callback to run code when a mention is selected
final void Function(String)? onSelect;
const SummernoteAtMention(
{this.getSuggestionsMobile, this.mentionsWeb, this.onSelect})
: assert(kIsWeb ? mentionsWeb != null : getSuggestionsMobile != null);
@override
String getHeadString() {
return '<script src=\"assets/packages/html_editor_enhanced/assets/plugins/summernote-at-mention/summernote-at-mention.js\"></script>';
}
@override
String getToolbarString() {
return '';
}
String getMentionsWeb() {
var mentionsString = '[';
for (var e in mentionsWeb!) {
mentionsString =
mentionsString + "'$e'" + (e != mentionsWeb!.last ? ', ' : '');
}
return mentionsString + ']';
}
}

View File

@@ -0,0 +1,10 @@
// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// This file shims dart:ui in web-only scenarios, getting rid of the need to
/// suppress analyzer warnings.
// TODO(tneotia): flutter/flutter#55000 Remove this file once web-only dart:ui APIs
// are exposed from a dedicated place.
export 'dart_ui_fake.dart' if (dart.library.html) 'dart_ui_real.dart';

View File

@@ -0,0 +1,21 @@
// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Fake interface for the logic that this package needs from (web-only) dart:ui.
// This is conditionally exported so the analyzer sees these methods as available.
/// Shim for web_ui engine.PlatformViewRegistry
/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L62
// ignore: camel_case_types
class platformViewRegistry {
/// Shim for registerViewFactory
/// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L72
static void registerViewFactory(
String viewTypeId, dynamic Function(int viewId) viewFactory) {}
}
/// Signature of callbacks that have no arguments and return no data.
typedef VoidCallback = void Function();
dynamic get window => null;

View File

@@ -0,0 +1,5 @@
// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
export 'dart:ui';

View File

@@ -0,0 +1,284 @@
///Class that is used by [WebView.shouldOverrideUrlLoading] event.
///It represents the policy to pass back to the decision handler.
class NavigationActionPolicy {
final int _value;
const NavigationActionPolicy._internal(this._value);
int toValue() => _value;
///Cancel the navigation.
static const CANCEL = NavigationActionPolicy._internal(0);
///Allow the navigation to continue.
static const ALLOW = NavigationActionPolicy._internal(1);
@override
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
Map<String, dynamic> toMap() {
return {
'action': _value,
};
}
}
///Class that represents the WebView context menu. It used by [WebView.contextMenu].
///
///**NOTE**: To make it work properly on Android, JavaScript should be enabled!
class ContextMenu {
///Event fired when the context menu for this WebView is being built.
///
///[hitTestResult] represents the hit result for hitting an HTML elements.
final void Function(dynamic hitTestResult)? onCreateContextMenu;
///Event fired when the context menu for this WebView is being hidden.
final void Function()? onHideContextMenu;
///Event fired when a context menu item has been clicked.
///
///[contextMenuItemClicked] represents the [ContextMenuItem] clicked.
final void Function(ContextMenuItem contextMenuItemClicked)?
onContextMenuActionItemClicked;
///Context menu options.
final ContextMenuOptions? options;
///List of the custom [ContextMenuItem].
final List<ContextMenuItem> menuItems;
ContextMenu(
{this.menuItems = const [],
this.onCreateContextMenu,
this.onHideContextMenu,
this.options,
this.onContextMenuActionItemClicked});
Map<String, dynamic> toMap() {
return {
'menuItems': menuItems.map((menuItem) => menuItem.toMap()).toList(),
'options': options?.toMap()
};
}
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represent an item of the [ContextMenu].
class ContextMenuItem {
///Android menu item ID.
int? androidId;
///iOS menu item ID.
String? iosId;
///Menu item title.
String title;
///Menu item action that will be called when an user clicks on it.
Function()? action;
ContextMenuItem(
{this.androidId, this.iosId, required this.title, this.action});
Map<String, dynamic> toMap() {
return {'androidId': androidId, 'iosId': iosId, 'title': title};
}
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents available options used by [ContextMenu].
class ContextMenuOptions {
///Whether all the default system context menu items should be hidden or not. The default value is `false`.
bool hideDefaultSystemContextMenuItems;
ContextMenuOptions({this.hideDefaultSystemContextMenuItems = false});
Map<String, dynamic> toMap() {
return {
'hideDefaultSystemContextMenuItems': hideDefaultSystemContextMenuItems
};
}
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return toMap().toString();
}
}
///Class that represents contains the constants for the times at which to inject script content into a [WebView] used by an [UserScript].
class UserScriptInjectionTime {
final int _value;
const UserScriptInjectionTime._internal(this._value);
static final Set<UserScriptInjectionTime> values = {
UserScriptInjectionTime.AT_DOCUMENT_START,
UserScriptInjectionTime.AT_DOCUMENT_END,
};
static UserScriptInjectionTime? fromValue(int? value) {
if (value != null) {
try {
return UserScriptInjectionTime.values
.firstWhere((element) => element.toValue() == value);
} catch (e) {
return null;
}
}
return null;
}
int toValue() => _value;
@override
String toString() {
switch (_value) {
case 1:
return 'AT_DOCUMENT_END';
case 0:
default:
return 'AT_DOCUMENT_START';
}
}
///**NOTE for iOS**: A constant to inject the script after the creation of the webpages document element, but before loading any other content.
///
///**NOTE for Android**: A constant to try to inject the script as soon as the page starts loading.
static const AT_DOCUMENT_START = UserScriptInjectionTime._internal(0);
///**NOTE for iOS**: A constant to inject the script after the document finishes loading, but before loading any other subresources.
///
///**NOTE for Android**: A constant to inject the script as soon as the page finishes loading.
static const AT_DOCUMENT_END = UserScriptInjectionTime._internal(1);
@override
bool operator ==(value) => value == _value;
@override
int get hashCode => _value.hashCode;
}
///Class that represents a script that the [WebView] injects into the web page.
class UserScript {
///The scripts group name.
String? groupName;
///The scripts source code.
String source;
///The time at which to inject the script into the [WebView].
UserScriptInjectionTime injectionTime;
///A Boolean value that indicates whether to inject the script into the main frame.
///Specify true to inject the script only into the main frame, or false to inject it into all frames.
///The default value is `true`.
///
///**NOTE**: available only on iOS.
bool iosForMainFrameOnly;
///A scope of execution in which to evaluate the script to prevent conflicts between different scripts.
///For more information about content worlds, see [ContentWorld].
late ContentWorld contentWorld;
UserScript(
{this.groupName,
required this.source,
required this.injectionTime,
this.iosForMainFrameOnly = true,
ContentWorld? contentWorld}) {
this.contentWorld = contentWorld ?? ContentWorld.PAGE;
}
Map<String, dynamic> toMap() {
return {
'groupName': groupName,
'source': source,
'injectionTime': injectionTime.toValue(),
'iosForMainFrameOnly': iosForMainFrameOnly,
'contentWorld': contentWorld.toMap()
};
}
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return toMap().toString();
}
}
final _contentWorldNameRegExp = RegExp(r'[\s]');
///Class that represents an object that defines a scope of execution for JavaScript code and which you use to prevent conflicts between different scripts.
///
///**NOTE for iOS**: available on iOS 14.0+. This class represents the native [WKContentWorld](https://developer.apple.com/documentation/webkit/wkcontentworld) class.
///
///**NOTE for Android**: it will create and append an `<iframe>` HTML element with `id` attribute equals to `flutter_inappwebview_[name]`
///to the webpage's content that contains only the inline `<script>` HTML elements in order to define a new scope of execution for JavaScript code.
///Unfortunately, there isn't any other way to do it.
///There are some limitations:
///- for any [ContentWorld], except [ContentWorld.PAGE] (that is the webpage itself), if you need to access to the `window` or `document` global Object,
///you need to use `window.top` and `window.top.document` because the code runs inside an `<iframe>`;
///- also, the execution of the inline `<script>` could be blocked by the `Content-Security-Policy` header.
class ContentWorld {
///The name of a custom content world.
///It cannot contain space characters.
final String name;
///Returns the custom content world with the specified name.
ContentWorld.world({required this.name}) {
// WINDOW-ID- is used internally by the plugin!
assert(!name.startsWith('WINDOW-ID-') &&
!name.contains(_contentWorldNameRegExp));
}
///The default world for clients.
// ignore: non_constant_identifier_names
static final ContentWorld DEFAULT_CLIENT =
ContentWorld.world(name: 'defaultClient');
///The content world for the current webpages content.
///This property contains the content world for scripts that the current webpage executes.
///Be careful when manipulating variables in this content world.
///If you modify a variable with the same name as one the webpage uses, you may unintentionally disrupt the normal operation of that page.
// ignore: non_constant_identifier_names
static final ContentWorld PAGE = ContentWorld.world(name: 'page');
Map<String, dynamic> toMap() {
return {'name': name};
}
Map<String, dynamic> toJson() {
return toMap();
}
@override
String toString() {
return toMap().toString();
}
}

216
lib/utils/toolbar.dart Normal file
View File

@@ -0,0 +1,216 @@
import 'package:flutter/material.dart';
/// Abstract class that all the toolbar classes extend
abstract class Toolbar {
const Toolbar();
}
/// Style group
class StyleButtons extends Toolbar {
final bool style;
const StyleButtons({
this.style = true,
});
}
/// Font setting group
class FontSettingButtons extends Toolbar {
final bool fontName;
final bool fontSize;
final bool fontSizeUnit;
const FontSettingButtons({
this.fontName = true,
this.fontSize = true,
this.fontSizeUnit = true,
});
}
/// Font group
class FontButtons extends Toolbar {
final bool bold;
final bool italic;
final bool underline;
final bool clearAll;
final bool strikethrough;
final bool superscript;
final bool subscript;
const FontButtons({
this.bold = true,
this.italic = true,
this.underline = true,
this.clearAll = true,
this.strikethrough = true,
this.superscript = true,
this.subscript = true,
});
List<Icon> getIcons1() {
var icons = <Icon>[];
if (bold) icons.add(Icon(Icons.format_bold));
if (italic) icons.add(Icon(Icons.format_italic));
if (underline) icons.add(Icon(Icons.format_underline));
if (clearAll) icons.add(Icon(Icons.format_clear));
return icons;
}
List<Icon> getIcons2() {
var icons = <Icon>[];
if (strikethrough) icons.add(Icon(Icons.format_strikethrough));
if (superscript) icons.add(Icon(Icons.superscript));
if (subscript) icons.add(Icon(Icons.subscript));
return icons;
}
}
/// Color bar group
class ColorButtons extends Toolbar {
final bool foregroundColor;
final bool highlightColor;
const ColorButtons({
this.foregroundColor = true,
this.highlightColor = true,
});
List<Icon> getIcons() {
var icons = <Icon>[];
if (foregroundColor) icons.add(Icon(Icons.format_color_text));
if (highlightColor) icons.add(Icon(Icons.format_color_fill));
return icons;
}
}
/// List group
class ListButtons extends Toolbar {
final bool ul;
final bool ol;
final bool listStyles;
const ListButtons({
this.ul = true,
this.ol = true,
this.listStyles = true,
});
List<Icon> getIcons() {
var icons = <Icon>[];
if (ul) icons.add(Icon(Icons.format_list_bulleted));
if (ol) icons.add(Icon(Icons.format_list_numbered));
return icons;
}
}
/// Paragraph group
class ParagraphButtons extends Toolbar {
final bool alignLeft;
final bool alignCenter;
final bool alignRight;
final bool alignJustify;
final bool increaseIndent;
final bool decreaseIndent;
final bool textDirection;
final bool lineHeight;
final bool caseConverter;
const ParagraphButtons({
this.alignLeft = true,
this.alignCenter = true,
this.alignRight = true,
this.alignJustify = true,
this.increaseIndent = true,
this.decreaseIndent = true,
this.textDirection = true,
this.lineHeight = true,
this.caseConverter = true,
});
List<Icon> getIcons1() {
var icons = <Icon>[];
if (alignLeft) icons.add(Icon(Icons.format_align_left));
if (alignCenter) icons.add(Icon(Icons.format_align_center));
if (alignRight) icons.add(Icon(Icons.format_align_right));
if (alignJustify) icons.add(Icon(Icons.format_align_justify));
return icons;
}
List<Icon> getIcons2() {
var icons = <Icon>[];
if (increaseIndent) icons.add(Icon(Icons.format_indent_increase));
if (decreaseIndent) icons.add(Icon(Icons.format_indent_decrease));
return icons;
}
}
/// Insert group
class InsertButtons extends Toolbar {
final bool link;
final bool picture;
final bool audio;
final bool video;
final bool otherFile;
final bool table;
final bool hr;
const InsertButtons({
this.link = true,
this.picture = true,
this.audio = true,
this.video = true,
this.otherFile = false,
this.table = true,
this.hr = true,
});
List<Icon> getIcons() {
var icons = <Icon>[];
if (link) icons.add(Icon(Icons.link));
if (picture) icons.add(Icon(Icons.image_outlined));
if (audio) icons.add(Icon(Icons.audiotrack_outlined));
if (video) icons.add(Icon(Icons.videocam_outlined));
if (otherFile) icons.add(Icon(Icons.attach_file));
if (table) icons.add(Icon(Icons.table_chart_outlined));
if (hr) icons.add(Icon(Icons.horizontal_rule));
return icons;
}
}
/// Miscellaneous group
class OtherButtons extends Toolbar {
final bool fullscreen;
final bool codeview;
final bool undo;
final bool redo;
final bool help;
final bool copy;
final bool paste;
const OtherButtons({
this.fullscreen = true,
this.codeview = true,
this.undo = true,
this.redo = true,
this.help = true,
this.copy = true,
this.paste = true,
});
List<Icon> getIcons1() {
var icons = <Icon>[];
if (fullscreen) icons.add(Icon(Icons.fullscreen));
if (codeview) icons.add(Icon(Icons.code));
if (undo) icons.add(Icon(Icons.undo));
if (redo) icons.add(Icon(Icons.redo));
if (help) icons.add(Icon(Icons.help_outline));
return icons;
}
List<Icon> getIcons2() {
var icons = <Icon>[];
if (copy) icons.add(Icon(Icons.copy));
if (paste) icons.add(Icon(Icons.paste));
return icons;
}
}

1201
lib/utils/utils.dart Normal file

File diff suppressed because it is too large Load Diff