diff --git a/lib/src/utils/platform_helper.dart b/lib/src/utils/platform_helper.dart index 2f1b2b66..1ee63392 100644 --- a/lib/src/utils/platform_helper.dart +++ b/lib/src/utils/platform_helper.dart @@ -5,8 +5,21 @@ bool isMobile([TargetPlatform? targetPlatform]) { return {TargetPlatform.iOS, TargetPlatform.android}.contains(targetPlatform); } -bool get isDesktop => { - TargetPlatform.macOS, - TargetPlatform.linux, - TargetPlatform.windows - }.contains(defaultTargetPlatform); +bool isDesktop([TargetPlatform? targetPlatform]) { + targetPlatform ??= defaultTargetPlatform; + return {TargetPlatform.macOS, TargetPlatform.linux, TargetPlatform.windows} + .contains(targetPlatform); +} + +bool isKeyboardOS([TargetPlatform? targetPlatform]) { + targetPlatform ??= defaultTargetPlatform; + return isDesktop(targetPlatform) || targetPlatform == TargetPlatform.fuchsia; +} + +bool isAppleOS([TargetPlatform? targetPlatform]) { + targetPlatform ??= defaultTargetPlatform; + return { + TargetPlatform.macOS, + TargetPlatform.iOS, + }.contains(targetPlatform); +} diff --git a/lib/src/widgets/cursor.dart b/lib/src/widgets/cursor.dart index 92666d82..12eeafc2 100644 --- a/lib/src/widgets/cursor.dart +++ b/lib/src/widgets/cursor.dart @@ -1,8 +1,8 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; +import '../utils/platform_helper.dart'; import 'box.dart'; /// Style properties of editing cursor. @@ -287,33 +287,26 @@ class CursorPainter { final caretHeight = editable!.getFullHeightForCaret(position); if (caretHeight != null) { - switch (defaultTargetPlatform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - // Override the height to take the full height of the glyph at the - // TextPosition when not on iOS. iOS has special handling that - // creates a taller caret. - caretRect = Rect.fromLTWH( - caretRect.left, - caretRect.top - 2.0, - caretRect.width, - caretHeight, - ); - break; - case TargetPlatform.iOS: - case TargetPlatform.macOS: - // Center the caret vertically along the text. - caretRect = Rect.fromLTWH( - caretRect.left, - caretRect.top + (caretHeight - caretRect.height) / 2, - caretRect.width, - caretRect.height, - ); - break; - default: - throw UnimplementedError(); + if (isKeyboardOS()) { + // Override the height to take the full height of the glyph at the + // TextPosition when not on iOS. iOS has special handling that + // creates a taller caret. + caretRect = Rect.fromLTWH( + caretRect.left, + caretRect.top - 2.0, + caretRect.width, + caretHeight, + ); + } else if (isAppleOS()) { + // Center the caret vertically along the text. + caretRect = Rect.fromLTWH( + caretRect.left, + caretRect.top + (caretHeight - caretRect.height) / 2, + caretRect.width, + caretRect.height, + ); + } else { + throw UnimplementedError(); } } diff --git a/lib/src/widgets/default_styles.dart b/lib/src/widgets/default_styles.dart index 2edc2088..555c2f21 100644 --- a/lib/src/widgets/default_styles.dart +++ b/lib/src/widgets/default_styles.dart @@ -3,6 +3,7 @@ import 'package:tuple/tuple.dart'; import '../../flutter_quill.dart'; import '../../models/documents/style.dart'; +import '../utils/platform_helper.dart'; class QuillStyles extends InheritedWidget { const QuillStyles({ @@ -192,19 +193,12 @@ class DefaultStyles { ); const baseSpacing = Tuple2(6, 0); String fontFamily; - switch (themeData.platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - fontFamily = 'Menlo'; - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.windows: - case TargetPlatform.linux: - fontFamily = 'Roboto Mono'; - break; - default: - throw UnimplementedError(); + if (isAppleOS(themeData.platform)) { + fontFamily = 'Menlo'; + } else if (isKeyboardOS(themeData.platform)) { + fontFamily = 'Roboto Mono'; + } else { + throw UnimplementedError(); } final inlineCodeStyle = TextStyle( diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index f36abdcc..a4ded7d8 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -398,34 +398,26 @@ class _QuillEditorState extends State Color selectionColor; Radius? cursorRadius; - switch (theme.platform) { - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - textSelectionControls = materialTextSelectionControls; - paintCursorAboveText = false; - cursorOpacityAnimates = false; - cursorColor ??= selectionTheme.cursorColor ?? theme.colorScheme.primary; - selectionColor = selectionTheme.selectionColor ?? - theme.colorScheme.primary.withOpacity(0.40); - break; - case TargetPlatform.iOS: - case TargetPlatform.macOS: - final cupertinoTheme = CupertinoTheme.of(context); - textSelectionControls = cupertinoTextSelectionControls; - paintCursorAboveText = true; - cursorOpacityAnimates = true; - cursorColor ??= - selectionTheme.cursorColor ?? cupertinoTheme.primaryColor; - selectionColor = selectionTheme.selectionColor ?? - cupertinoTheme.primaryColor.withOpacity(0.40); - cursorRadius ??= const Radius.circular(2); - cursorOffset = Offset( - iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0); - break; - default: - throw UnimplementedError(); + if (isKeyboardOS(theme.platform)) { + textSelectionControls = materialTextSelectionControls; + paintCursorAboveText = false; + cursorOpacityAnimates = false; + cursorColor ??= selectionTheme.cursorColor ?? theme.colorScheme.primary; + selectionColor = selectionTheme.selectionColor ?? + theme.colorScheme.primary.withOpacity(0.40); + } else if (isAppleOS(theme.platform)) { + final cupertinoTheme = CupertinoTheme.of(context); + textSelectionControls = cupertinoTextSelectionControls; + paintCursorAboveText = true; + cursorOpacityAnimates = true; + cursorColor ??= selectionTheme.cursorColor ?? cupertinoTheme.primaryColor; + selectionColor = selectionTheme.selectionColor ?? + cupertinoTheme.primaryColor.withOpacity(0.40); + cursorRadius ??= const Radius.circular(2); + cursorOffset = Offset( + iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0); + } else { + throw UnimplementedError(); } final child = RawEditor( @@ -524,26 +516,21 @@ class _QuillEditorSelectionGestureDetectorBuilder if (!delegate.selectionEnabled) { return; } - switch (Theme.of(_state.context).platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - renderEditor!.selectPositionAt( - from: details.globalPosition, - cause: SelectionChangedCause.longPress, - ); - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - renderEditor!.selectWordsInRange( - details.globalPosition - details.offsetFromOrigin, - details.globalPosition, - SelectionChangedCause.longPress, - ); - break; - default: - throw 'Invalid platform'; + + final _platform = Theme.of(_state.context).platform; + if (isAppleOS(_platform)) { + renderEditor!.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.longPress, + ); + } else if (isKeyboardOS(_platform)) { + renderEditor!.selectWordsInRange( + details.globalPosition - details.offsetFromOrigin, + details.globalPosition, + SelectionChangedCause.longPress, + ); + } else { + throw UnimplementedError(); } } @@ -599,52 +586,40 @@ class _QuillEditorSelectionGestureDetectorBuilder final positionSelected = _isPositionSelected(details); if (delegate.selectionEnabled && !positionSelected) { - switch (Theme.of(_state.context).platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - switch (details.kind) { - case PointerDeviceKind.mouse: - case PointerDeviceKind.stylus: - case PointerDeviceKind.invertedStylus: - // Precise devices should place the cursor at a precise position. - // If `Shift` key is pressed then - // extend current selection instead. - if (isShiftClick(details.kind)) { - renderEditor! - ..extendSelection(details.globalPosition, - cause: SelectionChangedCause.tap) - ..onSelectionCompleted(); - } else { - renderEditor! - ..selectPosition(cause: SelectionChangedCause.tap) - ..onSelectionCompleted(); - } - - break; - case PointerDeviceKind.touch: - case PointerDeviceKind.unknown: - // On macOS/iOS/iPadOS a touch tap places the cursor at the edge - // of the word. - try { - renderEditor! - ..selectWordEdge(SelectionChangedCause.tap) - ..onSelectionCompleted(); - } finally { - break; - } - } - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - try { + final _platform = Theme.of(_state.context).platform; + if (isAppleOS(_platform)) { + switch (details.kind) { + case PointerDeviceKind.mouse: + case PointerDeviceKind.stylus: + case PointerDeviceKind.invertedStylus: + // Precise devices should place the cursor at a precise position. + // If `Shift` key is pressed then + // extend current selection instead. + if (isShiftClick(details.kind)) { + renderEditor! + ..extendSelection(details.globalPosition, + cause: SelectionChangedCause.tap) + ..onSelectionCompleted(); + } else { + renderEditor! + ..selectPosition(cause: SelectionChangedCause.tap) + ..onSelectionCompleted(); + } + + break; + case PointerDeviceKind.touch: + case PointerDeviceKind.unknown: + // On macOS/iOS/iPadOS a touch tap places the cursor at the edge + // of the word. renderEditor! - ..selectPosition(cause: SelectionChangedCause.tap) + ..selectWordEdge(SelectionChangedCause.tap) ..onSelectionCompleted(); - } finally { break; - } + } + } else if (isKeyboardOS(_platform)) { + renderEditor! + ..selectPosition(cause: SelectionChangedCause.tap) + ..onSelectionCompleted(); } } _state._requestKeyboard(); @@ -661,23 +636,17 @@ class _QuillEditorSelectionGestureDetectorBuilder } if (delegate.selectionEnabled) { - switch (Theme.of(_state.context).platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - renderEditor!.selectPositionAt( - from: details.globalPosition, - cause: SelectionChangedCause.longPress, - ); - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - renderEditor!.selectWord(SelectionChangedCause.longPress); - Feedback.forLongPress(_state.context); - break; - default: - throw 'Invalid platform'; + final _platform = Theme.of(_state.context).platform; + if (isAppleOS(_platform)) { + renderEditor!.selectPositionAt( + from: details.globalPosition, + cause: SelectionChangedCause.longPress, + ); + } else if (isKeyboardOS(_platform)) { + renderEditor!.selectWord(SelectionChangedCause.longPress); + Feedback.forLongPress(_state.context); + } else { + throw UnimplementedError(); } } } diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 6d857d5e..6b413cf2 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'dart:math' as math; import 'package:flutter/cupertino.dart'; @@ -16,6 +17,7 @@ import '../models/documents/attribute.dart'; import '../models/documents/document.dart'; import '../models/documents/nodes/block.dart'; import '../models/documents/nodes/line.dart'; +import '../utils/platform_helper.dart'; import 'controller.dart'; import 'cursor.dart'; import 'default_styles.dart'; @@ -524,10 +526,7 @@ class RawEditorState extends EditorState _floatingCursorResetController = AnimationController(vsync: this); _floatingCursorResetController.addListener(onFloatingCursorResetTick); - if (defaultTargetPlatform == TargetPlatform.windows || - defaultTargetPlatform == TargetPlatform.macOS || - defaultTargetPlatform == TargetPlatform.linux || - defaultTargetPlatform == TargetPlatform.fuchsia) { + if (isKeyboardOS()) { _keyboardVisible = true; } else { _keyboardVisibilityController = KeyboardVisibilityController(); @@ -852,24 +851,16 @@ class RawEditorState extends EditorState bringIntoView(textEditingValue.selection.extent); hideToolbar(false); - switch (defaultTargetPlatform) { - case TargetPlatform.iOS: - break; - case TargetPlatform.macOS: - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - // Collapse the selection and hide the toolbar and handles. - userUpdateTextEditingValue( - TextEditingValue( - text: textEditingValue.text, - selection: TextSelection.collapsed( - offset: textEditingValue.selection.end), - ), - SelectionChangedCause.toolbar, - ); - break; + if (isKeyboardOS() || Platform.isAndroid) { + // Collapse the selection and hide the toolbar and handles. + userUpdateTextEditingValue( + TextEditingValue( + text: textEditingValue.text, + selection: + TextSelection.collapsed(offset: textEditingValue.selection.end), + ), + SelectionChangedCause.toolbar, + ); } } } diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 0e4c05cd..251c3f9f 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -1,7 +1,6 @@ import 'dart:collection'; import 'dart:math' as math; -import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -81,7 +80,7 @@ class _TextLineState extends State { // Desktop platforms (macos, linux, windows): // only allow Meta(Control)+Click combinations - if (isDesktop) { + if (isDesktop()) { return _metaOrControlPressed; } // Mobile platforms (ios, android): always allow but we install a @@ -376,7 +375,7 @@ class _TextLineState extends State { return _linkRecognizers[segment]!; } - if (isDesktop || widget.readOnly) { + if (isDesktop() || widget.readOnly) { _linkRecognizers[segment] = TapGestureRecognizer() ..onTap = () => _tapNodeLink(segment); } else { @@ -854,19 +853,12 @@ class RenderEditableTextLine extends RenderEditableBox { /// of the cursor for iOS is approximate and obtained through an eyeball /// comparison. void _computeCaretPrototype() { - switch (defaultTargetPlatform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - _caretPrototype = Rect.fromLTWH(0, 0, cursorWidth, cursorHeight + 2); - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - _caretPrototype = Rect.fromLTWH(0, 2, cursorWidth, cursorHeight - 4.0); - break; - default: - throw 'Invalid platform'; + if (isAppleOS()) { + _caretPrototype = Rect.fromLTWH(0, 0, cursorWidth, cursorHeight + 2); + } else if (isKeyboardOS()) { + _caretPrototype = Rect.fromLTWH(0, 2, cursorWidth, cursorHeight - 4.0); + } else { + throw UnimplementedError(); } }