diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c73ec2a..7dbb538f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - **Breaking change**: the `customButtons` in the `QuillToolbarConfigurations` is now of type `List` - Bug fixes with the new `8.0.0` update - Update `README.md` +- Improve the platform checking ## [8.3.0] - Added a `iconButtonFactor` property to `QuillToolbarBaseButtonOptions` to customise how big the button is compared to its icon size (defaults to `kIconButtonFactor` which is the same as previous releases) diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 538e8f40..379b4f2a 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -414,7 +414,7 @@ class _HomePageState extends State { placeholder: 'Add content', readOnly: _isReadOnly, autoFocus: false, - enableSelectionToolbar: isMobile(), + enableSelectionToolbar: isMobile(supportWeb: false), expands: false, padding: EdgeInsets.zero, onImagePaste: _onImagePaste, @@ -500,7 +500,7 @@ class _HomePageState extends State { // afterButtonPressed: _focusNode.requestFocus, ); } - if (isDesktop()) { + if (isDesktop(supportWeb: false)) { return QuillToolbar( configurations: QuillToolbarConfigurations( customButtons: customButtons, diff --git a/example/lib/pages/read_only_page.dart b/example/lib/pages/read_only_page.dart index 6f38741a..9670c152 100644 --- a/example/lib/pages/read_only_page.dart +++ b/example/lib/pages/read_only_page.dart @@ -23,7 +23,7 @@ class _ReadOnlyPageState extends State { @override Widget build(BuildContext context) { return DemoScaffold( - documentFilename: isDesktop() + documentFilename: isDesktop(supportWeb: false) ? 'assets/sample_data_nomedia.json' : 'sample_data_nomedia.json', builder: _buildContent, diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index baf0e9e3..2cb20ab3 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.6.5 +- Support the new improved platform checking of `flutter_quill` + ## 0.6.4 - Update `QuillImageUtilities` - Add new extension on `QuillController` to access `QuillImageUtilities` instance easier diff --git a/flutter_quill_extensions/lib/presentation/embeds/editor/image/image.dart b/flutter_quill_extensions/lib/presentation/embeds/editor/image/image.dart index 0f40233a..7f3d6e94 100644 --- a/flutter_quill_extensions/lib/presentation/embeds/editor/image/image.dart +++ b/flutter_quill_extensions/lib/presentation/embeds/editor/image/image.dart @@ -41,7 +41,7 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder { final style = node.style.attributes['style']; if (style != null) { - final attrs = base.isMobile() + final attrs = base.isMobile(supportWeb: false) ? base.parseKeyValuePairs(style.value.toString(), { Attribute.mobileWidth, Attribute.mobileHeight, @@ -56,21 +56,21 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder { }); if (attrs.isNotEmpty) { final width = double.tryParse( - (base.isMobile() + (base.isMobile(supportWeb: false) ? attrs[Attribute.mobileWidth] : attrs[Attribute.width.key]) ?? '', ); final height = double.tryParse( - (base.isMobile() + (base.isMobile(supportWeb: false) ? attrs[Attribute.mobileHeight] : attrs[Attribute.height.key]) ?? '', ); - final alignment = base.getAlignment(base.isMobile() + final alignment = base.getAlignment(base.isMobile(supportWeb: false) ? attrs[Attribute.mobileAlignment] : attrs[Attribute.alignment]); - final margin = (base.isMobile() + final margin = (base.isMobile(supportWeb: false) ? double.tryParse(Attribute.mobileMargin) : double.tryParse(Attribute.margin)) ?? 0.0; @@ -106,7 +106,7 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder { } if (!readOnly && - (base.isMobile() || + (base.isMobile(supportWeb: false) || configurations.forceUseMobileOptionMenuForImageClick)) { return GestureDetector( onTap: () { @@ -188,7 +188,8 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder { getImageStyleString(controller), width: w, height: h, - isMobile: base.isMobile(), + isMobile: + base.isMobile(supportWeb: false), ); controller ..skipRequestKeyboard = true @@ -220,7 +221,7 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder { if (!readOnly || isImageBase64(imageUrl)) { // To enforce using it on the desktop and other platforms // and that is up to the developer - if (!base.isMobile() && + if (!base.isMobile(supportWeb: false) && configurations.forceUseMobileOptionMenuForImageClick) { return _menuOptionsForReadonlyImage( context: context, diff --git a/flutter_quill_extensions/lib/presentation/embeds/toolbar/image_button/select_image_source.dart b/flutter_quill_extensions/lib/presentation/embeds/toolbar/image_button/select_image_source.dart index 8753bb87..5d1cf4b2 100644 --- a/flutter_quill_extensions/lib/presentation/embeds/toolbar/image_button/select_image_source.dart +++ b/flutter_quill_extensions/lib/presentation/embeds/toolbar/image_button/select_image_source.dart @@ -28,7 +28,7 @@ class SelectImageSourceDialog extends StatelessWidget { 'Take a photo using your phone camera', ), leading: const Icon(Icons.camera), - enabled: !isDesktop(), + enabled: !isDesktop(supportWeb: false), onTap: () => Navigator.of(context).pop(InsertImageSource.camera), ), ListTile( diff --git a/flutter_quill_extensions/lib/presentation/embeds/toolbar/video_button/select_video_source.dart b/flutter_quill_extensions/lib/presentation/embeds/toolbar/video_button/select_video_source.dart index da2fb8ef..8e2d34d5 100644 --- a/flutter_quill_extensions/lib/presentation/embeds/toolbar/video_button/select_video_source.dart +++ b/flutter_quill_extensions/lib/presentation/embeds/toolbar/video_button/select_video_source.dart @@ -28,7 +28,7 @@ class SelectVideoSourceDialog extends StatelessWidget { 'Record a video using your phone camera', ), leading: const Icon(Icons.camera), - enabled: !isDesktop(), + enabled: !isDesktop(supportWeb: false), onTap: () => Navigator.of(context).pop(InsertVideoSource.camera), ), ListTile( diff --git a/flutter_quill_extensions/lib/presentation/models/config/editor/image/image.dart b/flutter_quill_extensions/lib/presentation/models/config/editor/image/image.dart index f33913e7..3e159d0e 100644 --- a/flutter_quill_extensions/lib/presentation/models/config/editor/image/image.dart +++ b/flutter_quill_extensions/lib/presentation/models/config/editor/image/image.dart @@ -112,7 +112,11 @@ class QuillEditorImageEmbedConfigurations { static ImageEmbedBuilderOnRemovedCallback get defaultOnImageRemovedCallback { return (imageUrl) async { - final mobile = isMobile(); + if (isWeb()) { + return; + } + + final mobile = isMobile(supportWeb: false); // If the platform is not mobile, return void; // Since the mobile OS gives us a copy of the image @@ -134,10 +138,6 @@ class QuillEditorImageEmbedConfigurations { // it without // permission - if (isWeb()) { - return; - } - final dartIoImageFile = File(imageUrl); final isFileExists = await dartIoImageFile.exists(); diff --git a/lib/src/utils/platform.dart b/lib/src/utils/platform.dart index 73a89fcb..020cfae3 100644 --- a/lib/src/utils/platform.dart +++ b/lib/src/utils/platform.dart @@ -4,62 +4,113 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/foundation.dart' show kIsWeb, TargetPlatform, defaultTargetPlatform; -bool isWeb() { - return kIsWeb; +/// If you want to override the [kIsWeb] use [overrideIsWeb] +bool isWeb({bool? overrideIsWeb}) { + return overrideIsWeb ?? kIsWeb; } -bool isMobile([TargetPlatform? targetPlatform]) { - if (isWeb()) return false; - targetPlatform ??= defaultTargetPlatform; - return {TargetPlatform.iOS, TargetPlatform.android}.contains(targetPlatform); +/// [supportWeb] is a parameter that ask you if we should care about web support +/// if the value is true then we will return the result no matter if we are +/// on web or using a native app to run the flutter app +bool isMobile({ + required bool supportWeb, + TargetPlatform? platform, + bool? overrideIsWeb, +}) { + if (isWeb(overrideIsWeb: overrideIsWeb) && !supportWeb) return false; + platform ??= defaultTargetPlatform; + return {TargetPlatform.iOS, TargetPlatform.android}.contains(platform); } -bool isDesktop([TargetPlatform? targetPlatform]) { - if (isWeb()) return false; - targetPlatform ??= defaultTargetPlatform; +/// [supportWeb] is a parameter that ask you if we should care about web support +/// if the value is true then we will return the result no matter if we are +/// on web or using a native app to run the flutter app +bool isDesktop({ + required bool supportWeb, + TargetPlatform? platform, + bool? overrideIsWeb, +}) { + if (isWeb(overrideIsWeb: overrideIsWeb) && !supportWeb) return false; + platform ??= defaultTargetPlatform; return {TargetPlatform.macOS, TargetPlatform.linux, TargetPlatform.windows} - .contains(targetPlatform); + .contains(platform); } -bool isKeyboardOS([TargetPlatform? targetPlatform]) { - targetPlatform ??= defaultTargetPlatform; - return isDesktop(targetPlatform) || targetPlatform == TargetPlatform.fuchsia; +/// [supportWeb] is a parameter that ask you if we should care about web support +/// if the value is true then we will return the result no matter if we are +/// on web or using a native app to run the flutter app +bool isKeyboardOS({ + required bool supportWeb, + TargetPlatform? platform, + bool? overrideIsWeb, +}) { + platform ??= defaultTargetPlatform; + return isDesktop( + platform: platform, + supportWeb: supportWeb, + overrideIsWeb: overrideIsWeb) || + platform == TargetPlatform.fuchsia; } -bool isAppleOS([TargetPlatform? targetPlatform]) { - if (isWeb()) return false; - targetPlatform ??= defaultTargetPlatform; +/// [supportWeb] is a parameter that ask you if we should care about web support +/// if the value is true then we will return the result no matter if we are +/// on web or using a native app to run the flutter app +bool isAppleOS({ + required bool supportWeb, + TargetPlatform? platform, + bool? overrideIsWeb, +}) { + if (isWeb(overrideIsWeb: overrideIsWeb) && !supportWeb) return false; + platform ??= defaultTargetPlatform; return { TargetPlatform.macOS, TargetPlatform.iOS, - }.contains(targetPlatform); + }.contains(platform); } -bool isMacOS([TargetPlatform? targetPlatform]) { - if (isWeb()) return false; - targetPlatform ??= defaultTargetPlatform; - return TargetPlatform.macOS == targetPlatform; +/// [supportWeb] is a parameter that ask you if we should care about web support +/// if the value is true then we will return the result no matter if we are +/// on web or using a native app to run the flutter app +bool isMacOS({ + required bool supportWeb, + TargetPlatform? platform, + bool? overrideIsWeb, +}) { + if (isWeb(overrideIsWeb: overrideIsWeb) && !supportWeb) return false; + platform ??= defaultTargetPlatform; + return TargetPlatform.macOS == platform; } -bool isIOS([TargetPlatform? targetPlatform]) { - if (isWeb()) return false; - targetPlatform ??= defaultTargetPlatform; - return TargetPlatform.iOS == targetPlatform; +/// [supportWeb] is a parameter that ask you if we should care about web support +/// if the value is true then we will return the result no matter if we are +/// on web or using a native app to run the flutter app +bool isIOS({ + required bool supportWeb, + TargetPlatform? platform, + bool? overrideIsWeb, +}) { + if (isWeb(overrideIsWeb: overrideIsWeb) && !supportWeb) return false; + platform ??= defaultTargetPlatform; + return TargetPlatform.iOS == platform; } -bool isAndroid([TargetPlatform? targetPlatform]) { - if (isWeb()) return false; - targetPlatform ??= defaultTargetPlatform; - return TargetPlatform.android == targetPlatform; +/// [supportWeb] is a parameter that ask you if we should care about web support +/// if the value is true then we will return the result no matter if we are +/// on web or using a native app to run the flutter app +bool isAndroid({ + required bool supportWeb, + TargetPlatform? platform, + bool? overrideIsWeb, +}) { + if (isWeb(overrideIsWeb: overrideIsWeb) && !supportWeb) return false; + platform ??= defaultTargetPlatform; + return TargetPlatform.android == platform; } -bool isFlutterTest() { - if (isWeb()) return false; - return Platform.environment.containsKey('FLUTTER_TEST'); -} - -Future isIOSSimulator() async { - if (!isAppleOS()) { +Future isIOSSimulator({ + bool? overrideIsWeb, +}) async { + if (!isAppleOS(supportWeb: false, overrideIsWeb: overrideIsWeb)) { return false; } @@ -73,3 +124,10 @@ Future isIOSSimulator() async { } return false; } + +bool isFlutterTest({ + bool? overrideIsWeb, +}) { + if (isWeb(overrideIsWeb: overrideIsWeb)) return false; + return Platform.environment.containsKey('FLUTTER_TEST'); +} diff --git a/lib/src/widgets/cursor.dart b/lib/src/widgets/cursor.dart index 293b2ec6..929118b1 100644 --- a/lib/src/widgets/cursor.dart +++ b/lib/src/widgets/cursor.dart @@ -291,7 +291,7 @@ class CursorPainter { final caretHeight = editable!.getFullHeightForCaret(position); if (caretHeight != null) { - if (isAppleOS()) { + if (isAppleOS(supportWeb: true)) { // Center the caret vertically along the text. caretRect = Rect.fromLTWH( caretRect.left, diff --git a/lib/src/widgets/default_styles.dart b/lib/src/widgets/default_styles.dart index 6af846eb..5d5949f8 100644 --- a/lib/src/widgets/default_styles.dart +++ b/lib/src/widgets/default_styles.dart @@ -204,7 +204,7 @@ class DefaultStyles { ); const baseSpacing = VerticalSpacing(6, 0); String fontFamily; - if (isAppleOS(themeData.platform)) { + if (isAppleOS(platform: themeData.platform, supportWeb: true)) { fontFamily = 'Menlo'; } else { fontFamily = 'Roboto Mono'; diff --git a/lib/src/widgets/delegate.dart b/lib/src/widgets/delegate.dart index f595beff..5d021606 100644 --- a/lib/src/widgets/delegate.dart +++ b/lib/src/widgets/delegate.dart @@ -331,7 +331,9 @@ class EditorTextSelectionGestureDetectorBuilder { @protected void onDragSelectionEnd(DragEndDetails details) { renderEditor!.handleDragEnd(details); - if (isDesktop() && + // TODO: Should we care if the platform is desktop using native desktop app + // or the flutter app is running using web app?? + if (isDesktop(supportWeb: true) && delegate.selectionEnabled && shouldShowSelectionToolbar) { // added to show selection copy/paste toolbar after drag to select diff --git a/lib/src/widgets/editor/editor.dart b/lib/src/widgets/editor/editor.dart index 9d20a552..dea30332 100644 --- a/lib/src/widgets/editor/editor.dart +++ b/lib/src/widgets/editor/editor.dart @@ -215,7 +215,10 @@ class QuillEditorState extends State Color selectionColor; Radius? cursorRadius; - if (isAppleOS(theme.platform)) { + if (isAppleOS( + platform: theme.platform, + supportWeb: true, + )) { final cupertinoTheme = CupertinoTheme.of(context); textSelectionControls = cupertinoTextSelectionControls; paintCursorAboveText = true; @@ -257,7 +260,10 @@ class QuillEditorState extends State ? (configurations.contextMenuBuilder ?? QuillRawEditor.defaultContextMenuBuilder) : null, - showSelectionHandles: isMobile(theme.platform), + showSelectionHandles: isMobile( + platform: theme.platform, + supportWeb: true, + ), showCursor: configurations.showCursor, cursorStyle: CursorStyle( color: cursorColor, @@ -407,7 +413,10 @@ class _QuillEditorSelectionGestureDetectorBuilder } final platform = Theme.of(_state.context).platform; - if (isAppleOS(platform)) { + if (isAppleOS( + platform: platform, + supportWeb: true, + )) { renderEditor!.selectPositionAt( from: details.globalPosition, cause: SelectionChangedCause.longPress, @@ -480,7 +489,8 @@ class _QuillEditorSelectionGestureDetectorBuilder try { if (delegate.selectionEnabled && !_isPositionSelected(details)) { final platform = Theme.of(_state.context).platform; - if (isAppleOS(platform) || isDesktop()) { + if (isAppleOS(platform: platform, supportWeb: true) || + isDesktop(platform: platform, supportWeb: true)) { // added isDesktop() to enable extend selection in Windows platform switch (details.kind) { case PointerDeviceKind.mouse: @@ -544,7 +554,10 @@ class _QuillEditorSelectionGestureDetectorBuilder if (delegate.selectionEnabled) { final platform = Theme.of(_state.context).platform; - if (isAppleOS(platform)) { + if (isAppleOS( + platform: platform, + supportWeb: true, + )) { renderEditor!.selectPositionAt( from: details.globalPosition, cause: SelectionChangedCause.longPress, diff --git a/lib/src/widgets/raw_editor/raw_editor_state.dart b/lib/src/widgets/raw_editor/raw_editor_state.dart index 9c903ee2..0775686c 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state.dart @@ -185,10 +185,6 @@ class QuillRawEditorState extends EditorState } void _defaultOnTapOutside(PointerDownEvent event) { - if (isWeb()) { - widget.focusNode.unfocus(); - } - /// The focus dropping behavior is only present on desktop platforms /// and mobile browsers. switch (defaultTargetPlatform) { @@ -322,7 +318,7 @@ class QuillRawEditorState extends EditorState // so if we ovveride the platform in material app theme data // it will not depend on it and doesn't change here but I don't think // we need to - final isDesktopMacOS = isMacOS(); + final isDesktopMacOS = isMacOS(supportWeb: true); return TextFieldTapRegion( enabled: widget.enableUnfocusOnTapOutside, @@ -859,7 +855,7 @@ class QuillRawEditorState extends EditorState _floatingCursorResetController = AnimationController(vsync: this); _floatingCursorResetController.addListener(onFloatingCursorResetTick); - if (isKeyboardOS()) { + if (isKeyboardOS(supportWeb: true)) { _keyboardVisible = true; } else if (!isWeb() && isFlutterTest()) { // treat tests like a keyboard OS diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index de0c24bb..0d1243d3 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -90,7 +90,7 @@ class _TextLineState extends State { // Desktop platforms (macOS, Linux, Windows): // only allow Meta (Control) + Click combinations - if (isDesktop()) { + if (isDesktop(supportWeb: false)) { return _metaOrControlPressed; } // Mobile platforms (ios, android): always allow but we install a @@ -434,7 +434,7 @@ class _TextLineState extends State { } if (isLink && canLaunchLinks) { - if (isDesktop() || widget.readOnly) { + if (isDesktop(supportWeb: true) || widget.readOnly) { _linkRecognizers[segment] = TapGestureRecognizer() ..onTap = () => _tapNodeLink(segment); } else { @@ -896,7 +896,7 @@ class RenderEditableTextLine extends RenderEditableBox { void _computeCaretPrototype() { // If the cursor is taller only on iOS and not AppleOS then we should check // only for iOS instead of AppleOS (macOS for example) - if (isIOS()) { + if (isIOS(supportWeb: true)) { _caretPrototype = Rect.fromLTWH(0, 0, cursorWidth, cursorHeight + 2); } else { _caretPrototype = Rect.fromLTWH(0, 2, cursorWidth, cursorHeight - 4.0); diff --git a/pubspec.yaml b/pubspec.yaml index 8cbbdbc7..18f2f9ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,6 +55,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_quill_test: ^0.0.4 + test: ^1.24.3 flutter: uses-material-design: true \ No newline at end of file diff --git a/test/utils/platform_test.dart b/test/utils/platform_test.dart new file mode 100644 index 00000000..aafda16e --- /dev/null +++ b/test/utils/platform_test.dart @@ -0,0 +1,156 @@ +import 'package:flutter/foundation.dart' show TargetPlatform; +import 'package:flutter_quill/src/utils/platform.dart'; +import 'package:test/test.dart'; + +void main() { + group('Test platform checking logic', () { + var platform = TargetPlatform.linux; + test('Check isDesktop()', () { + platform = TargetPlatform.android; + expect( + isDesktop( + platform: platform, + supportWeb: true, + ), + false, + ); + + for (final desktopPlatform in [ + TargetPlatform.macOS, + TargetPlatform.linux, + TargetPlatform.windows + ]) { + expect( + isDesktop( + supportWeb: false, + platform: desktopPlatform, + overrideIsWeb: false, + ), + true, + ); + + expect( + isDesktop( + supportWeb: false, + overrideIsWeb: true, + platform: desktopPlatform, + ), + false, + ); + + expect( + isDesktop( + supportWeb: true, + overrideIsWeb: true, + platform: desktopPlatform, + ), + true, + ); + } + }); + test('Check isMobile()', () { + platform = TargetPlatform.macOS; + expect( + isMobile( + platform: platform, + supportWeb: true, + ), + false, + ); + + for (final mobilePlatform in [ + TargetPlatform.android, + TargetPlatform.iOS, + ]) { + expect( + isMobile( + supportWeb: false, + platform: mobilePlatform, + overrideIsWeb: false, + ), + true, + ); + + expect( + isMobile( + supportWeb: false, + overrideIsWeb: true, + platform: mobilePlatform, + ), + false, + ); + + expect( + isMobile( + supportWeb: true, + overrideIsWeb: true, + platform: mobilePlatform, + ), + true, + ); + } + }); + test( + 'Check supportWeb parameter when using desktop platform on web', + () { + platform = TargetPlatform.macOS; + expect( + isDesktop( + platform: platform, + supportWeb: true, + ), + true, + ); + expect( + isDesktop( + platform: platform, + supportWeb: false, + overrideIsWeb: false, + ), + true, + ); + + expect( + isDesktop( + platform: platform, + supportWeb: false, + overrideIsWeb: true, + ), + false, + ); + }, + ); + + test( + 'Check supportWeb parameter when using mobile platform on web', + () { + platform = TargetPlatform.android; + expect( + isMobile( + platform: platform, + supportWeb: true, + overrideIsWeb: true, + ), + true, + ); + expect( + isMobile( + platform: platform, + supportWeb: false, + overrideIsWeb: false, + ), + true, + ); + + expect( + isMobile( + platform: platform, + supportWeb: false, + overrideIsWeb: true, + ), + false, + ); + }, + ); + }); +} diff --git a/test/widgets/controller_test.dart b/test/widgets/controller_test.dart index 3decfc47..930cdccf 100644 --- a/test/widgets/controller_test.dart +++ b/test/widgets/controller_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_quill/flutter_quill.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:test/test.dart'; void main() { const testDocumentContents = 'data';