From 2831c84b573f3e745eeb98008093c3e51a2ac89b Mon Sep 17 00:00:00 2001 From: Nate Brown Date: Wed, 19 Jan 2022 13:29:30 -0600 Subject: [PATCH] SelectableText field now supports keyboard actions natively (#56) --- ios/Flutter/AppFrameworkInfo.plist | 2 +- ios/Podfile.lock | 22 +- ios/Runner.xcodeproj/project.pbxproj | 45 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/components/SpecialSelectableText.dart | 677 ------------------ lib/screens/AboutScreen.dart | 3 +- lib/screens/HostInfoScreen.dart | 13 +- lib/screens/SiteDetailScreen.dart | 3 +- lib/screens/SiteLogsScreen.dart | 3 +- .../siteConfig/AddCertificateScreen.dart | 3 +- .../siteConfig/CertificateDetailsScreen.dart | 19 +- .../siteConfig/RenderedConfigScreen.dart | 3 +- lib/screens/siteConfig/SiteConfigScreen.dart | 3 +- 13 files changed, 67 insertions(+), 731 deletions(-) delete mode 100644 lib/components/SpecialSelectableText.dart diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f7..f2872cf 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 32a3b2d..9e51132 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -42,7 +42,7 @@ PODS: - MTBBarcodeScanner (5.0.11) - package_info (0.0.1): - Flutter - - path_provider (0.0.1): + - path_provider_ios (0.0.1): - Flutter - SDWebImage (5.8.0): - SDWebImage/Core (= 5.8.0) @@ -52,7 +52,7 @@ PODS: - SDWebImage/Core (~> 5.6) - SwiftProtobuf (1.9.0) - SwiftyJSON (5.0.1) - - url_launcher (0.0.1): + - url_launcher_ios (0.0.1): - Flutter DEPENDENCIES: @@ -60,9 +60,9 @@ DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - package_info (from `.symlinks/plugins/package_info/ios`) - - path_provider (from `.symlinks/plugins/path_provider/ios`) + - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - SwiftyJSON (~> 5.0) - - url_launcher (from `.symlinks/plugins/url_launcher/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) SPEC REPOS: trunk: @@ -84,10 +84,10 @@ EXTERNAL SOURCES: :path: Flutter package_info: :path: ".symlinks/plugins/package_info/ios" - path_provider: - :path: ".symlinks/plugins/path_provider/ios" - url_launcher: - :path: ".symlinks/plugins/url_launcher/ios" + path_provider_ios: + :path: ".symlinks/plugins/path_provider_ios/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479 @@ -95,15 +95,15 @@ SPEC CHECKSUMS: DKPhotoGallery: e880aef16c108333240e1e7327896f2ea380f4f0 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 - Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 - path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c + path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5 SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932 SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e - url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef + url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af PODFILE CHECKSUM: 92e176614f91c6517d4254a0edec8b66f076c77e diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 96ad754..c645a46 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 50; objects = { /* Begin PBXBuildFile section */ @@ -275,7 +275,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1140; - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1300; ORGANIZATIONNAME = "The Chromium Authors"; TargetAttributes = { 43AA89532444DA6500EDC39C = { @@ -349,8 +349,8 @@ "${BUILT_PRODUCTS_DIR}/barcode_scan/barcode_scan.framework", "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", "${BUILT_PRODUCTS_DIR}/package_info/package_info.framework", - "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", - "${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", + "${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework", + "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( @@ -365,8 +365,8 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -587,7 +587,10 @@ ); INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -622,7 +625,11 @@ GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = NebulaNetworkExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 0.0.43; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -659,7 +666,11 @@ GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = NebulaNetworkExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 0.0.43; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -693,7 +704,11 @@ GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = NebulaNetworkExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); MARKETING_VERSION = 0.0.43; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -830,7 +845,10 @@ ); INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", @@ -863,7 +881,10 @@ ); INFOPLIST_FILE = Runner/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index fb2dffc..c87d15a 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ [_textSpan], - ); - } - - @override - set text(String newText) { - // This should never be reached. - throw UnimplementedError(); - } -} - -class _SpecialSelectableTextSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder { - _SpecialSelectableTextSelectionGestureDetectorBuilder({ - @required _SpecialSelectableTextState state, - }) : _state = state, - super(delegate: state); - - final _SpecialSelectableTextState _state; - - @override - void onForcePressStart(ForcePressDetails details) { - super.onForcePressStart(details); - if (delegate.selectionEnabled && shouldShowSelectionToolbar) { - editableText.showToolbar(); - } - } - - @override - void onForcePressEnd(ForcePressDetails details) { - // Not required. - } - - @override - void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) { - if (delegate.selectionEnabled) { - switch (Theme.of(_state.context).platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - renderEditable.selectPositionAt( - from: details.globalPosition, - cause: SelectionChangedCause.longPress, - ); - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - renderEditable.selectWordsInRange( - from: details.globalPosition - details.offsetFromOrigin, - to: details.globalPosition, - cause: SelectionChangedCause.longPress, - ); - break; - } - } - } - - @override - void onSingleTapUp(TapUpDetails details) { - editableText.hideToolbar(); - if (delegate.selectionEnabled) { - switch (Theme.of(_state.context).platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - renderEditable.selectWordEdge(cause: SelectionChangedCause.tap); - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - renderEditable.selectPosition(cause: SelectionChangedCause.tap); - break; - } - } - if (_state.widget.onTap != null) _state.widget.onTap(); - } - - @override - void onSingleLongTapStart(LongPressStartDetails details) { - if (delegate.selectionEnabled) { - switch (Theme.of(_state.context).platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - renderEditable.selectPositionAt( - from: details.globalPosition, - cause: SelectionChangedCause.longPress, - ); - break; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - renderEditable.selectWord(cause: SelectionChangedCause.longPress); - Feedback.forLongPress(_state.context); - break; - } - } - } -} - -/// A run of selectable text with a single style. -/// -/// The [SpecialSelectableText] widget displays a string of text with a single style. -/// The string might break across multiple lines or might all be displayed on -/// the same line depending on the layout constraints. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=ZSU3ZXOs6hc} -/// -/// The [style] argument is optional. When omitted, the text will use the style -/// from the closest enclosing [DefaultTextStyle]. If the given style's -/// [TextStyle.inherit] property is true (the default), the given style will -/// be merged with the closest enclosing [DefaultTextStyle]. This merging -/// behavior is useful, for example, to make the text bold while using the -/// default font family and size. -/// -/// {@tool snippet} -/// -/// ```dart -/// SpecialSelectableText( -/// 'Hello! How are you?', -/// textAlign: TextAlign.center, -/// style: TextStyle(fontWeight: FontWeight.bold), -/// ) -/// ``` -/// {@end-tool} -/// -/// Using the [SpecialSelectableText.rich] constructor, the [SpecialSelectableText] widget can -/// display a paragraph with differently styled [TextSpan]s. The sample -/// that follows displays "Hello beautiful world" with different styles -/// for each word. -/// -/// {@tool snippet} -/// -/// ```dart -/// const SpecialSelectableText.rich( -/// TextSpan( -/// text: 'Hello', // default text style -/// children: [ -/// TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic)), -/// TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)), -/// ], -/// ), -/// ) -/// ``` -/// {@end-tool} -/// -/// ## Interactivity -/// -/// To make [SpecialSelectableText] react to touch events, use callback [onTap] to achieve -/// the desired behavior. -/// -/// See also: -/// -/// * [Text], which is the non selectable version of this widget. -/// * [TextField], which is the editable version of this widget. -class SpecialSelectableText extends StatefulWidget { - /// Creates a selectable text widget. - /// - /// If the [style] argument is null, the text will use the style from the - /// closest enclosing [DefaultTextStyle]. - /// - - /// The [showCursor], [autofocus], [dragStartBehavior], and [data] parameters - /// must not be null. If specified, the [maxLines] argument must be greater - /// than zero. - const SpecialSelectableText( - this.data, { - Key key, - this.focusNode, - this.style, - this.strutStyle, - this.textAlign, - this.textDirection, - this.textScaleFactor, - this.showCursor = false, - this.autofocus = false, - ToolbarOptions toolbarOptions, - this.minLines, - this.maxLines, - this.cursorWidth = 2.0, - this.cursorRadius, - this.cursorColor, - this.dragStartBehavior = DragStartBehavior.start, - this.enableInteractiveSelection = true, - this.onTap, - this.scrollPhysics, - this.textHeightBehavior, - this.textWidthBasis, - }) : assert(showCursor != null), - assert(autofocus != null), - assert(dragStartBehavior != null), - assert(maxLines == null || maxLines > 0), - assert(minLines == null || minLines > 0), - assert( - (maxLines == null) || (minLines == null) || (maxLines >= minLines), - 'minLines can\'t be greater than maxLines', - ), - assert( - data != null, - 'A non-null String must be provided to a SpecialSelectableText widget.', - ), - textSpan = null, - toolbarOptions = toolbarOptions ?? - const ToolbarOptions( - selectAll: true, - copy: true, - ), - super(key: key); - - /// Creates a selectable text widget with a [TextSpan]. - /// - /// The [textSpan] parameter must not be null and only contain [TextSpan] in - /// [textSpan.children]. Other type of [InlineSpan] is not allowed. - /// - /// The [autofocus] and [dragStartBehavior] arguments must not be null. - const SpecialSelectableText.rich( - this.textSpan, { - Key key, - this.focusNode, - this.style, - this.strutStyle, - this.textAlign, - this.textDirection, - this.textScaleFactor, - this.showCursor = false, - this.autofocus = false, - ToolbarOptions toolbarOptions, - this.minLines, - this.maxLines, - this.cursorWidth = 2.0, - this.cursorRadius, - this.cursorColor, - this.dragStartBehavior = DragStartBehavior.start, - this.enableInteractiveSelection = true, - this.onTap, - this.scrollPhysics, - this.textHeightBehavior, - this.textWidthBasis, - }) : assert(showCursor != null), - assert(autofocus != null), - assert(dragStartBehavior != null), - assert(maxLines == null || maxLines > 0), - assert(minLines == null || minLines > 0), - assert( - (maxLines == null) || (minLines == null) || (maxLines >= minLines), - 'minLines can\'t be greater than maxLines', - ), - assert( - textSpan != null, - 'A non-null TextSpan must be provided to a SpecialSelectableText.rich widget.', - ), - data = null, - toolbarOptions = toolbarOptions ?? - const ToolbarOptions( - selectAll: true, - copy: true, - ), - super(key: key); - - /// The text to display. - /// - /// This will be null if a [textSpan] is provided instead. - final String data; - - /// The text to display as a [TextSpan]. - /// - /// This will be null if [data] is provided instead. - final TextSpan textSpan; - - /// Defines the focus for this widget. - /// - /// Text is only selectable when widget is focused. - /// - /// The [focusNode] is a long-lived object that's typically managed by a - /// [StatefulWidget] parent. See [FocusNode] for more information. - /// - /// To give the focus to this widget, provide a [focusNode] and then - /// use the current [FocusScope] to request the focus: - /// - /// ```dart - /// FocusScope.of(context).requestFocus(myFocusNode); - /// ``` - /// - /// This happens automatically when the widget is tapped. - /// - /// To be notified when the widget gains or loses the focus, add a listener - /// to the [focusNode]: - /// - /// ```dart - /// focusNode.addListener(() { print(myFocusNode.hasFocus); }); - /// ``` - /// - /// If null, this widget will create its own [FocusNode]. - final FocusNode focusNode; - - /// The style to use for the text. - /// - /// If null, defaults [DefaultTextStyle] of context. - final TextStyle style; - - /// {@macro flutter.widgets.editableText.strutStyle} - final StrutStyle strutStyle; - - /// {@macro flutter.widgets.editableText.textAlign} - final TextAlign textAlign; - - /// {@macro flutter.widgets.editableText.textDirection} - final TextDirection textDirection; - - /// {@macro flutter.widgets.editableText.textScaleFactor} - final double textScaleFactor; - - /// {@macro flutter.widgets.editableText.autofocus} - final bool autofocus; - - /// {@macro flutter.widgets.editableText.minLines} - final int minLines; - - /// {@macro flutter.widgets.editableText.maxLines} - final int maxLines; - - /// {@macro flutter.widgets.editableText.showCursor} - final bool showCursor; - - /// {@macro flutter.widgets.editableText.cursorWidth} - final double cursorWidth; - - /// {@macro flutter.widgets.editableText.cursorRadius} - final Radius cursorRadius; - - /// The color to use when painting the cursor. - /// - /// Defaults to the theme's `cursorColor` when null. - final Color cursorColor; - - /// {@macro flutter.widgets.editableText.enableInteractiveSelection} - final bool enableInteractiveSelection; - - /// {@macro flutter.widgets.scrollable.dragStartBehavior} - final DragStartBehavior dragStartBehavior; - - /// Configuration of toolbar options. - /// - /// Paste and cut will be disabled regardless. - /// - /// If not set, select all and copy will be enabled by default. - final ToolbarOptions toolbarOptions; - - /// {@macro flutter.rendering.editable.selectionEnabled} - bool get selectionEnabled { - return enableInteractiveSelection; - } - - /// Called when the user taps on this selectable text. - /// - /// The selectable text builds a [GestureDetector] to handle input events like tap, - /// to trigger focus requests, to move the caret, adjust the selection, etc. - /// Handling some of those events by wrapping the selectable text with a competing - /// GestureDetector is problematic. - /// - /// To unconditionally handle taps, without interfering with the selectable text's - /// internal gesture detector, provide this callback. - /// - /// To be notified when the text field gains or loses the focus, provide a - /// [focusNode] and add a listener to that. - /// - /// To listen to arbitrary pointer events without competing with the - /// selectable text's internal gesture detector, use a [Listener]. - final GestureTapCallback onTap; - - /// {@macro flutter.widgets.editableText.scrollPhysics} - final ScrollPhysics scrollPhysics; - - /// {@macro flutter.dart:ui.textHeightBehavior} - final TextHeightBehavior textHeightBehavior; - - /// {@macro flutter.painting.textPainter.textWidthBasis} - final TextWidthBasis textWidthBasis; - - @override - _SpecialSelectableTextState createState() => _SpecialSelectableTextState(); - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties.add(DiagnosticsProperty('data', data, defaultValue: null)); - properties.add(DiagnosticsProperty('focusNode', focusNode, defaultValue: null)); - properties.add(DiagnosticsProperty('style', style, defaultValue: null)); - properties.add(DiagnosticsProperty('autofocus', autofocus, defaultValue: false)); - properties.add(DiagnosticsProperty('showCursor', showCursor, defaultValue: false)); - properties.add(IntProperty('minLines', minLines, defaultValue: null)); - properties.add(IntProperty('maxLines', maxLines, defaultValue: null)); - properties.add(EnumProperty('textAlign', textAlign, defaultValue: null)); - properties.add(EnumProperty('textDirection', textDirection, defaultValue: null)); - properties.add(DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null)); - properties.add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0)); - properties.add(DiagnosticsProperty('cursorRadius', cursorRadius, defaultValue: null)); - properties.add(DiagnosticsProperty('cursorColor', cursorColor, defaultValue: null)); - properties.add( - FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled')); - properties.add(DiagnosticsProperty('scrollPhysics', scrollPhysics, defaultValue: null)); - properties - .add(DiagnosticsProperty('textHeightBehavior', textHeightBehavior, defaultValue: null)); - } -} - -class _SpecialSelectableTextState extends State - with AutomaticKeepAliveClientMixin - implements TextSelectionGestureDetectorBuilderDelegate { - EditableTextState get _editableText => editableTextKey.currentState; - - _TextSpanEditingController _controller; - - FocusNode _keyFocusNode = FocusNode(); - FocusNode _focusNode; - FocusNode get _effectiveFocusNode => widget.focusNode ?? (_focusNode ??= FocusNode()); - - bool _showSelectionHandles = false; - - _SpecialSelectableTextSelectionGestureDetectorBuilder _selectionGestureDetectorBuilder; - - // API for TextSelectionGestureDetectorBuilderDelegate. - @override - bool forcePressEnabled; - - @override - final GlobalKey editableTextKey = GlobalKey(); - - @override - bool get selectionEnabled => widget.selectionEnabled; - // End of API for TextSelectionGestureDetectorBuilderDelegate. - - @override - void initState() { - super.initState(); - _selectionGestureDetectorBuilder = _SpecialSelectableTextSelectionGestureDetectorBuilder(state: this); - _controller = _TextSpanEditingController(textSpan: widget.textSpan ?? TextSpan(text: widget.data)); - } - - @override - void didUpdateWidget(SpecialSelectableText oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.data != oldWidget.data || widget.textSpan != oldWidget.textSpan) { - _controller = _TextSpanEditingController(textSpan: widget.textSpan ?? TextSpan(text: widget.data)); - } - if (_effectiveFocusNode.hasFocus && _controller.selection.isCollapsed) { - _showSelectionHandles = false; - } - } - - @override - void dispose() { - _focusNode?.dispose(); - super.dispose(); - } - - void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) { - final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause); - if (willShowSelectionHandles != _showSelectionHandles) { - setState(() { - _showSelectionHandles = willShowSelectionHandles; - }); - } - - switch (Theme.of(context).platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - if (cause == SelectionChangedCause.longPress) { - _editableText?.bringIntoView(selection.base); - } - return; - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - // Do nothing. - } - } - - /// Toggle the toolbar when a selection handle is tapped. - void _handleSelectionHandleTapped() { - if (_controller.selection.isCollapsed) { - _editableText.toggleToolbar(); - } - } - - bool _shouldShowSelectionHandles(SelectionChangedCause cause) { - // When the text field is activated by something that doesn't trigger the - // selection overlay, we shouldn't show the handles either. - if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) return false; - - if (_controller.selection.isCollapsed) return false; - - if (cause == SelectionChangedCause.keyboard) return false; - - if (cause == SelectionChangedCause.longPress) return true; - - if (_controller.text.isNotEmpty) return true; - - return false; - } - - @override - bool get wantKeepAlive => true; - - @override - Widget build(BuildContext context) { - super.build(context); // See AutomaticKeepAliveClientMixin. - assert(() { - return _controller._textSpan.visitChildren((InlineSpan span) => span.runtimeType == TextSpan); - }(), 'SpecialSelectableText only supports TextSpan; Other type of InlineSpan is not allowed'); - assert(debugCheckHasMediaQuery(context)); - assert(debugCheckHasDirectionality(context)); - assert( - !(widget.style != null && - widget.style.inherit == false && - (widget.style.fontSize == null || widget.style.textBaseline == null)), - 'inherit false style must supply fontSize and textBaseline', - ); - - final ThemeData themeData = Theme.of(context); - final FocusNode focusNode = _effectiveFocusNode; - - TextSelectionControls textSelectionControls; - bool paintCursorAboveText; - bool cursorOpacityAnimates; - Offset cursorOffset; - Color cursorColor = widget.cursorColor; - Radius cursorRadius = widget.cursorRadius; - - switch (themeData.platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - forcePressEnabled = true; - textSelectionControls = cupertinoTextSelectionControls; - paintCursorAboveText = true; - cursorOpacityAnimates = true; - cursorColor ??= CupertinoTheme.of(context).primaryColor; - cursorRadius ??= const Radius.circular(2.0); - cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.of(context).devicePixelRatio, 0); - break; - - case TargetPlatform.android: - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.windows: - forcePressEnabled = false; - textSelectionControls = materialTextSelectionControls; - paintCursorAboveText = false; - cursorOpacityAnimates = false; - cursorColor ??= themeData.cursorColor; - break; - } - - final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); - TextStyle effectiveTextStyle = widget.style; - if (widget.style == null || widget.style.inherit) effectiveTextStyle = defaultTextStyle.style.merge(widget.style); - if (MediaQuery.boldTextOverride(context)) - effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold)); - final Widget child = RepaintBoundary( - child: EditableText( - key: editableTextKey, - style: effectiveTextStyle, - readOnly: true, - textWidthBasis: widget.textWidthBasis ?? defaultTextStyle.textWidthBasis, - textHeightBehavior: widget.textHeightBehavior ?? defaultTextStyle.textHeightBehavior, - showSelectionHandles: _showSelectionHandles, - showCursor: widget.showCursor, - controller: _controller, - focusNode: focusNode, - strutStyle: widget.strutStyle ?? const StrutStyle(), - textAlign: widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start, - textDirection: widget.textDirection, - textScaleFactor: widget.textScaleFactor, - autofocus: widget.autofocus, - forceLine: false, - toolbarOptions: widget.toolbarOptions, - minLines: widget.minLines, - maxLines: widget.maxLines ?? defaultTextStyle.maxLines, - selectionColor: themeData.textSelectionColor, - selectionControls: widget.selectionEnabled ? textSelectionControls : null, - onSelectionChanged: _handleSelectionChanged, - onSelectionHandleTapped: _handleSelectionHandleTapped, - rendererIgnoresPointer: true, - cursorWidth: widget.cursorWidth, - cursorRadius: cursorRadius, - cursorColor: cursorColor, - cursorOpacityAnimates: cursorOpacityAnimates, - cursorOffset: cursorOffset, - paintCursorAboveText: paintCursorAboveText, - backgroundCursorColor: CupertinoColors.inactiveGray, - enableInteractiveSelection: widget.enableInteractiveSelection, - dragStartBehavior: widget.dragStartBehavior, - scrollPhysics: widget.scrollPhysics, - ), - ); - - return Semantics( - onTap: () { - if (!_controller.selection.isValid) - _controller.selection = TextSelection.collapsed(offset: _controller.text.length); - _effectiveFocusNode.requestFocus(); - }, - onLongPress: () { - _effectiveFocusNode.requestFocus(); - }, - child: _selectionGestureDetectorBuilder.buildGestureDetector( - behavior: HitTestBehavior.translucent, - child: RawKeyboardListener( - focusNode: _keyFocusNode, - onKey: _onKey, - child: child, - )), - ); - } - - _onKey(RawKeyEvent event) { - // We don't care about key up events - if (event is RawKeyUpEvent) { - return; - } - - //TODO: tab to next focus node - - // Handle special keyboard events with control key - if (event.data.isControlPressed) { - // Handle select all - if (event.logicalKey == LogicalKeyboardKey.keyA) { - _controller.selection = TextSelection(baseOffset: 0, extentOffset: _controller.text.length); - return; - } - - // Handle copy - if (event.logicalKey == LogicalKeyboardKey.keyC) { - Clipboard.setData(ClipboardData(text: _controller.selection.textInside(_controller.text))); - return; - } - } - } -} diff --git a/lib/screens/AboutScreen.dart b/lib/screens/AboutScreen.dart index 9b18878..aa27a2d 100644 --- a/lib/screens/AboutScreen.dart +++ b/lib/screens/AboutScreen.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; -import 'package:mobile_nebula/components/SpecialSelectableText.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigPageItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; @@ -79,6 +78,6 @@ class _AboutScreenState extends State { } _buildText(String str) { - return Align(alignment: AlignmentDirectional.centerEnd, child: SpecialSelectableText(str)); + return Align(alignment: AlignmentDirectional.centerEnd, child: SelectableText(str)); } } diff --git a/lib/screens/HostInfoScreen.dart b/lib/screens/HostInfoScreen.dart index a139675..df5dfdd 100644 --- a/lib/screens/HostInfoScreen.dart +++ b/lib/screens/HostInfoScreen.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; -import 'package:mobile_nebula/components/SpecialSelectableText.dart'; import 'package:mobile_nebula/components/config/ConfigCheckboxItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigPageItem.dart'; @@ -61,7 +60,7 @@ class _HostInfoScreenState extends State { Widget _buildMain() { return ConfigSection(children: [ - ConfigItem(label: Text('VPN IP'), labelWidth: 150, content: SpecialSelectableText(hostInfo.vpnIp)), + ConfigItem(label: Text('VPN IP'), labelWidth: 150, content: SelectableText(hostInfo.vpnIp)), hostInfo.cert != null ? ConfigPageItem( label: Text('Certificate'), @@ -78,16 +77,16 @@ class _HostInfoScreenState extends State { ConfigItem( label: Text('Lighthouse'), labelWidth: 150, - content: SpecialSelectableText(widget.isLighthouse ? 'Yes' : 'No')), - ConfigItem(label: Text('Local Index'), labelWidth: 150, content: SpecialSelectableText('${hostInfo.localIndex}')), + content: SelectableText(widget.isLighthouse ? 'Yes' : 'No')), + ConfigItem(label: Text('Local Index'), labelWidth: 150, content: SelectableText('${hostInfo.localIndex}')), ConfigItem( - label: Text('Remote Index'), labelWidth: 150, content: SpecialSelectableText('${hostInfo.remoteIndex}')), + label: Text('Remote Index'), labelWidth: 150, content: SelectableText('${hostInfo.remoteIndex}')), ConfigItem( label: Text('Message Counter'), labelWidth: 150, - content: SpecialSelectableText('${hostInfo.messageCounter}')), + content: SelectableText('${hostInfo.messageCounter}')), ConfigItem( - label: Text('Cached Packets'), labelWidth: 150, content: SpecialSelectableText('${hostInfo.cachedPackets}')), + label: Text('Cached Packets'), labelWidth: 150, content: SelectableText('${hostInfo.cachedPackets}')), ]); } diff --git a/lib/screens/SiteDetailScreen.dart b/lib/screens/SiteDetailScreen.dart index c42e03d..2bb1784 100644 --- a/lib/screens/SiteDetailScreen.dart +++ b/lib/screens/SiteDetailScreen.dart @@ -6,7 +6,6 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; -import 'package:mobile_nebula/components/SpecialSelectableText.dart'; import 'package:mobile_nebula/components/config/ConfigPageItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; @@ -111,7 +110,7 @@ class _SiteDetailScreenState extends State { site.errors.forEach((error) { items.add(ConfigItem( labelWidth: 0, - content: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: SpecialSelectableText(error)))); + content: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: SelectableText(error)))); }); return ConfigSection( diff --git a/lib/screens/SiteLogsScreen.dart b/lib/screens/SiteLogsScreen.dart index efbe1a0..9712a78 100644 --- a/lib/screens/SiteLogsScreen.dart +++ b/lib/screens/SiteLogsScreen.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; -import 'package:mobile_nebula/components/SpecialSelectableText.dart'; import 'package:mobile_nebula/models/Site.dart'; import 'package:mobile_nebula/services/settings.dart'; import 'package:mobile_nebula/services/share.dart'; @@ -57,7 +56,7 @@ class _SiteLogsScreenState extends State { child: Container( padding: EdgeInsets.all(5), constraints: logBoxConstraints(context), - child: SpecialSelectableText(logs.trim(), style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14))), + child: SelectableText(logs.trim(), style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14))), bottomBar: _buildBottomBar(), ); } diff --git a/lib/screens/siteConfig/AddCertificateScreen.dart b/lib/screens/siteConfig/AddCertificateScreen.dart index 89171a0..432ac08 100644 --- a/lib/screens/siteConfig/AddCertificateScreen.dart +++ b/lib/screens/siteConfig/AddCertificateScreen.dart @@ -7,7 +7,6 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; -import 'package:mobile_nebula/components/SpecialSelectableText.dart'; import 'package:mobile_nebula/components/config/ConfigButtonItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; @@ -96,7 +95,7 @@ class _AddCertificateScreenState extends State { children: [ ConfigItem( labelWidth: 0, - content: SpecialSelectableText(pubKey, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), + content: SelectableText(pubKey, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), ), ConfigButtonItem( content: Text('Share Public Key'), diff --git a/lib/screens/siteConfig/CertificateDetailsScreen.dart b/lib/screens/siteConfig/CertificateDetailsScreen.dart index 559c38a..9f662e6 100644 --- a/lib/screens/siteConfig/CertificateDetailsScreen.dart +++ b/lib/screens/siteConfig/CertificateDetailsScreen.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/FormPage.dart'; -import 'package:mobile_nebula/components/SpecialSelectableText.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/models/Certificate.dart'; @@ -77,7 +76,7 @@ class _CertificateDetailsScreenState extends State { Widget _buildID() { return ConfigSection(children: [ - ConfigItem(label: Text('Name'), content: SpecialSelectableText(certInfo.cert.details.name)), + ConfigItem(label: Text('Name'), content: SelectableText(certInfo.cert.details.name)), ConfigItem( label: Text('Type'), content: Text(certInfo.cert.details.isCa ? 'CA certificate' : 'Client certificate')), ]); @@ -95,10 +94,10 @@ class _CertificateDetailsScreenState extends State { ConfigItem(label: Text('Valid?'), content: valid), ConfigItem( label: Text('Created'), - content: SpecialSelectableText(certInfo.cert.details.notBefore.toLocal().toString())), + content: SelectableText(certInfo.cert.details.notBefore.toLocal().toString())), ConfigItem( label: Text('Expires'), - content: SpecialSelectableText(certInfo.cert.details.notAfter.toLocal().toString())), + content: SelectableText(certInfo.cert.details.notAfter.toLocal().toString())), ], ); } @@ -107,16 +106,16 @@ class _CertificateDetailsScreenState extends State { List items = []; if (certInfo.cert.details.groups.length > 0) { items.add( - ConfigItem(label: Text('Groups'), content: SpecialSelectableText(certInfo.cert.details.groups.join(', ')))); + ConfigItem(label: Text('Groups'), content: SelectableText(certInfo.cert.details.groups.join(', ')))); } if (certInfo.cert.details.ips.length > 0) { - items.add(ConfigItem(label: Text('IPs'), content: SpecialSelectableText(certInfo.cert.details.ips.join(', ')))); + items.add(ConfigItem(label: Text('IPs'), content: SelectableText(certInfo.cert.details.ips.join(', ')))); } if (certInfo.cert.details.subnets.length > 0) { items.add( - ConfigItem(label: Text('Subnets'), content: SpecialSelectableText(certInfo.cert.details.subnets.join(', ')))); + ConfigItem(label: Text('Subnets'), content: SelectableText(certInfo.cert.details.subnets.join(', ')))); } return items.length > 0 @@ -129,19 +128,19 @@ class _CertificateDetailsScreenState extends State { children: [ ConfigItem( label: Text('Fingerprint'), - content: SpecialSelectableText(certInfo.cert.fingerprint, + content: SelectableText(certInfo.cert.fingerprint, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), crossAxisAlignment: CrossAxisAlignment.start), ConfigItem( label: Text('Public Key'), - content: SpecialSelectableText(certInfo.cert.details.publicKey, + content: SelectableText(certInfo.cert.details.publicKey, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), crossAxisAlignment: CrossAxisAlignment.start), certInfo.rawCert != null ? ConfigItem( label: Text('PEM Format'), content: - SpecialSelectableText(certInfo.rawCert, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), + SelectableText(certInfo.rawCert, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), crossAxisAlignment: CrossAxisAlignment.start) : Container(), ], diff --git a/lib/screens/siteConfig/RenderedConfigScreen.dart b/lib/screens/siteConfig/RenderedConfigScreen.dart index b2fa2f3..0a10a6d 100644 --- a/lib/screens/siteConfig/RenderedConfigScreen.dart +++ b/lib/screens/siteConfig/RenderedConfigScreen.dart @@ -2,7 +2,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:mobile_nebula/components/SimplePage.dart'; -import 'package:mobile_nebula/components/SpecialSelectableText.dart'; import 'package:mobile_nebula/services/share.dart'; class RenderedConfigScreen extends StatelessWidget { @@ -26,7 +25,7 @@ class RenderedConfigScreen extends StatelessWidget { child: Container( padding: EdgeInsets.all(5), constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width), - child: SpecialSelectableText(config, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14))), + child: SelectableText(config, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14))), ); } } diff --git a/lib/screens/siteConfig/SiteConfigScreen.dart b/lib/screens/siteConfig/SiteConfigScreen.dart index af7e03a..9e8a1f7 100644 --- a/lib/screens/siteConfig/SiteConfigScreen.dart +++ b/lib/screens/siteConfig/SiteConfigScreen.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:mobile_nebula/components/FormPage.dart'; import 'package:mobile_nebula/components/PlatformTextFormField.dart'; -import 'package:mobile_nebula/components/SpecialSelectableText.dart'; import 'package:mobile_nebula/components/config/ConfigPageItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart'; @@ -92,7 +91,7 @@ class _SiteConfigScreenState extends State { data = err.toString(); } - return ConfigSection(label: 'DEBUG', children: [ConfigItem(labelWidth: 0, content: SpecialSelectableText(data))]); + return ConfigSection(label: 'DEBUG', children: [ConfigItem(labelWidth: 0, content: SelectableText(data))]); } Widget _main() {