SelectableText field now supports keyboard actions natively (#56)

This commit is contained in:
Nate Brown 2022-01-19 13:29:30 -06:00 committed by GitHub
parent 3e0da2a8f0
commit 2831c84b57
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 67 additions and 731 deletions

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>8.0</string> <string>9.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -42,7 +42,7 @@ PODS:
- MTBBarcodeScanner (5.0.11) - MTBBarcodeScanner (5.0.11)
- package_info (0.0.1): - package_info (0.0.1):
- Flutter - Flutter
- path_provider (0.0.1): - path_provider_ios (0.0.1):
- Flutter - Flutter
- SDWebImage (5.8.0): - SDWebImage (5.8.0):
- SDWebImage/Core (= 5.8.0) - SDWebImage/Core (= 5.8.0)
@ -52,7 +52,7 @@ PODS:
- SDWebImage/Core (~> 5.6) - SDWebImage/Core (~> 5.6)
- SwiftProtobuf (1.9.0) - SwiftProtobuf (1.9.0)
- SwiftyJSON (5.0.1) - SwiftyJSON (5.0.1)
- url_launcher (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
@ -60,9 +60,9 @@ DEPENDENCIES:
- file_picker (from `.symlinks/plugins/file_picker/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- package_info (from `.symlinks/plugins/package_info/ios`) - 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) - SwiftyJSON (~> 5.0)
- url_launcher (from `.symlinks/plugins/url_launcher/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
@ -84,10 +84,10 @@ EXTERNAL SOURCES:
:path: Flutter :path: Flutter
package_info: package_info:
:path: ".symlinks/plugins/package_info/ios" :path: ".symlinks/plugins/package_info/ios"
path_provider: path_provider_ios:
:path: ".symlinks/plugins/path_provider/ios" :path: ".symlinks/plugins/path_provider_ios/ios"
url_launcher: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479 barcode_scan: a5c27959edfafaa0c771905bad0b29d6d39e4479
@ -95,15 +95,15 @@ SPEC CHECKSUMS:
DKPhotoGallery: e880aef16c108333240e1e7327896f2ea380f4f0 DKPhotoGallery: e880aef16c108333240e1e7327896f2ea380f4f0
file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc SDWebImage: 84000f962cbfa70c07f19d2234cbfcf5d779b5dc
SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8 SDWebImageFLPlugin: 6c2295fb1242d44467c6c87dc5db6b0a13228fd8
SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932 SwiftProtobuf: ecbec1be9036d15655f6b3443a1c4ea693c97932
SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e SwiftyJSON: 2f33a42c6fbc52764d96f13368585094bfd8aa5e
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
PODFILE CHECKSUM: 92e176614f91c6517d4254a0edec8b66f076c77e PODFILE CHECKSUM: 92e176614f91c6517d4254a0edec8b66f076c77e

View File

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 46; objectVersion = 50;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -275,7 +275,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 1140; LastSwiftUpdateCheck = 1140;
LastUpgradeCheck = 1020; LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "The Chromium Authors"; ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = { TargetAttributes = {
43AA89532444DA6500EDC39C = { 43AA89532444DA6500EDC39C = {
@ -349,8 +349,8 @@
"${BUILT_PRODUCTS_DIR}/barcode_scan/barcode_scan.framework", "${BUILT_PRODUCTS_DIR}/barcode_scan/barcode_scan.framework",
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
"${BUILT_PRODUCTS_DIR}/package_info/package_info.framework", "${BUILT_PRODUCTS_DIR}/package_info/package_info.framework",
"${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", "${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher/url_launcher.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
); );
name = "[CP] Embed Pods Frameworks"; name = "[CP] Embed Pods Frameworks";
outputPaths = ( outputPaths = (
@ -365,8 +365,8 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/barcode_scan.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.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}/package_info.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
@ -587,7 +587,10 @@
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
@ -622,7 +625,11 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = NebulaNetworkExtension/Info.plist; INFOPLIST_FILE = NebulaNetworkExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; 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; MARKETING_VERSION = 0.0.43;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
@ -659,7 +666,11 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = NebulaNetworkExtension/Info.plist; INFOPLIST_FILE = NebulaNetworkExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; 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; MARKETING_VERSION = 0.0.43;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_LDFLAGS = ""; OTHER_LDFLAGS = "";
@ -693,7 +704,11 @@
GCC_C_LANGUAGE_STANDARD = gnu11; GCC_C_LANGUAGE_STANDARD = gnu11;
INFOPLIST_FILE = NebulaNetworkExtension/Info.plist; INFOPLIST_FILE = NebulaNetworkExtension/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; 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; MARKETING_VERSION = 0.0.43;
MTL_FAST_MATH = YES; MTL_FAST_MATH = YES;
OTHER_LDFLAGS = ""; OTHER_LDFLAGS = "";
@ -830,7 +845,10 @@
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",
@ -863,7 +881,10 @@
); );
INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_FILE = Runner/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 14.0; IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = ( LIBRARY_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
"$(PROJECT_DIR)/Flutter", "$(PROJECT_DIR)/Flutter",

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1300"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -1,677 +0,0 @@
// Copyright 2014 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.
// @dart = 2.8
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
//TODO: please let us delete this file
/// An eyeballed value that moves the cursor slightly left of where it is
/// rendered for text on Android so its positioning more accurately matches the
/// native iOS text cursor positioning.
///
/// This value is in device pixels, not logical pixels as is typically used
/// throughout the codebase.
const int iOSHorizontalOffset = -2;
class _TextSpanEditingController extends TextEditingController {
_TextSpanEditingController({@required TextSpan textSpan})
: assert(textSpan != null),
_textSpan = textSpan,
super(text: textSpan.toPlainText());
final TextSpan _textSpan;
@override
TextSpan buildTextSpan({BuildContext context, TextStyle style, bool withComposing}) {
// This does not care about composing.
return TextSpan(
style: style,
children: <TextSpan>[_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>[
/// 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<String>('data', data, defaultValue: null));
properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('style', style, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
properties.add(DiagnosticsProperty<bool>('showCursor', showCursor, defaultValue: false));
properties.add(IntProperty('minLines', minLines, defaultValue: null));
properties.add(IntProperty('maxLines', maxLines, defaultValue: null));
properties.add(EnumProperty<TextAlign>('textAlign', textAlign, defaultValue: null));
properties.add(EnumProperty<TextDirection>('textDirection', textDirection, defaultValue: null));
properties.add(DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null));
properties.add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0));
properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius, defaultValue: null));
properties.add(DiagnosticsProperty<Color>('cursorColor', cursorColor, defaultValue: null));
properties.add(
FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
properties
.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
}
}
class _SpecialSelectableTextState extends State<SpecialSelectableText>
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<EditableTextState> editableTextKey = GlobalKey<EditableTextState>();
@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;
}
}
}
}

View File

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/SimplePage.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/ConfigItem.dart';
import 'package:mobile_nebula/components/config/ConfigPageItem.dart'; import 'package:mobile_nebula/components/config/ConfigPageItem.dart';
import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart';
@ -79,6 +78,6 @@ class _AboutScreenState extends State<AboutScreen> {
} }
_buildText(String str) { _buildText(String str) {
return Align(alignment: AlignmentDirectional.centerEnd, child: SpecialSelectableText(str)); return Align(alignment: AlignmentDirectional.centerEnd, child: SelectableText(str));
} }
} }

View File

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/SimplePage.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/ConfigCheckboxItem.dart';
import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart';
import 'package:mobile_nebula/components/config/ConfigPageItem.dart'; import 'package:mobile_nebula/components/config/ConfigPageItem.dart';
@ -61,7 +60,7 @@ class _HostInfoScreenState extends State<HostInfoScreen> {
Widget _buildMain() { Widget _buildMain() {
return ConfigSection(children: [ 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 hostInfo.cert != null
? ConfigPageItem( ? ConfigPageItem(
label: Text('Certificate'), label: Text('Certificate'),
@ -78,16 +77,16 @@ class _HostInfoScreenState extends State<HostInfoScreen> {
ConfigItem( ConfigItem(
label: Text('Lighthouse'), label: Text('Lighthouse'),
labelWidth: 150, labelWidth: 150,
content: SpecialSelectableText(widget.isLighthouse ? 'Yes' : 'No')), content: SelectableText(widget.isLighthouse ? 'Yes' : 'No')),
ConfigItem(label: Text('Local Index'), labelWidth: 150, content: SpecialSelectableText('${hostInfo.localIndex}')), ConfigItem(label: Text('Local Index'), labelWidth: 150, content: SelectableText('${hostInfo.localIndex}')),
ConfigItem( ConfigItem(
label: Text('Remote Index'), labelWidth: 150, content: SpecialSelectableText('${hostInfo.remoteIndex}')), label: Text('Remote Index'), labelWidth: 150, content: SelectableText('${hostInfo.remoteIndex}')),
ConfigItem( ConfigItem(
label: Text('Message Counter'), label: Text('Message Counter'),
labelWidth: 150, labelWidth: 150,
content: SpecialSelectableText('${hostInfo.messageCounter}')), content: SelectableText('${hostInfo.messageCounter}')),
ConfigItem( ConfigItem(
label: Text('Cached Packets'), labelWidth: 150, content: SpecialSelectableText('${hostInfo.cachedPackets}')), label: Text('Cached Packets'), labelWidth: 150, content: SelectableText('${hostInfo.cachedPackets}')),
]); ]);
} }

View File

@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/SimplePage.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/ConfigPageItem.dart';
import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart';
import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart';
@ -111,7 +110,7 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
site.errors.forEach((error) { site.errors.forEach((error) {
items.add(ConfigItem( items.add(ConfigItem(
labelWidth: 0, labelWidth: 0,
content: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: SpecialSelectableText(error)))); content: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: SelectableText(error))));
}); });
return ConfigSection( return ConfigSection(

View File

@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/SimplePage.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/models/Site.dart';
import 'package:mobile_nebula/services/settings.dart'; import 'package:mobile_nebula/services/settings.dart';
import 'package:mobile_nebula/services/share.dart'; import 'package:mobile_nebula/services/share.dart';
@ -57,7 +56,7 @@ class _SiteLogsScreenState extends State<SiteLogsScreen> {
child: Container( child: Container(
padding: EdgeInsets.all(5), padding: EdgeInsets.all(5),
constraints: logBoxConstraints(context), 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(), bottomBar: _buildBottomBar(),
); );
} }

View File

@ -7,7 +7,6 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/SimplePage.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/ConfigButtonItem.dart';
import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart';
import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart';
@ -96,7 +95,7 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
children: [ children: [
ConfigItem( ConfigItem(
labelWidth: 0, labelWidth: 0,
content: SpecialSelectableText(pubKey, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), content: SelectableText(pubKey, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
), ),
ConfigButtonItem( ConfigButtonItem(
content: Text('Share Public Key'), content: Text('Share Public Key'),

View File

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/FormPage.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/ConfigItem.dart';
import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart';
import 'package:mobile_nebula/models/Certificate.dart'; import 'package:mobile_nebula/models/Certificate.dart';
@ -77,7 +76,7 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
Widget _buildID() { Widget _buildID() {
return ConfigSection(children: <Widget>[ return ConfigSection(children: <Widget>[
ConfigItem(label: Text('Name'), content: SpecialSelectableText(certInfo.cert.details.name)), ConfigItem(label: Text('Name'), content: SelectableText(certInfo.cert.details.name)),
ConfigItem( ConfigItem(
label: Text('Type'), content: Text(certInfo.cert.details.isCa ? 'CA certificate' : 'Client certificate')), label: Text('Type'), content: Text(certInfo.cert.details.isCa ? 'CA certificate' : 'Client certificate')),
]); ]);
@ -95,10 +94,10 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
ConfigItem(label: Text('Valid?'), content: valid), ConfigItem(label: Text('Valid?'), content: valid),
ConfigItem( ConfigItem(
label: Text('Created'), label: Text('Created'),
content: SpecialSelectableText(certInfo.cert.details.notBefore.toLocal().toString())), content: SelectableText(certInfo.cert.details.notBefore.toLocal().toString())),
ConfigItem( ConfigItem(
label: Text('Expires'), 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<CertificateDetailsScreen> {
List<Widget> items = []; List<Widget> items = [];
if (certInfo.cert.details.groups.length > 0) { if (certInfo.cert.details.groups.length > 0) {
items.add( 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) { 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) { if (certInfo.cert.details.subnets.length > 0) {
items.add( 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 return items.length > 0
@ -129,19 +128,19 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
children: <Widget>[ children: <Widget>[
ConfigItem( ConfigItem(
label: Text('Fingerprint'), label: Text('Fingerprint'),
content: SpecialSelectableText(certInfo.cert.fingerprint, content: SelectableText(certInfo.cert.fingerprint,
style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
crossAxisAlignment: CrossAxisAlignment.start), crossAxisAlignment: CrossAxisAlignment.start),
ConfigItem( ConfigItem(
label: Text('Public Key'), label: Text('Public Key'),
content: SpecialSelectableText(certInfo.cert.details.publicKey, content: SelectableText(certInfo.cert.details.publicKey,
style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
crossAxisAlignment: CrossAxisAlignment.start), crossAxisAlignment: CrossAxisAlignment.start),
certInfo.rawCert != null certInfo.rawCert != null
? ConfigItem( ? ConfigItem(
label: Text('PEM Format'), label: Text('PEM Format'),
content: content:
SpecialSelectableText(certInfo.rawCert, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)), SelectableText(certInfo.rawCert, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
crossAxisAlignment: CrossAxisAlignment.start) crossAxisAlignment: CrossAxisAlignment.start)
: Container(), : Container(),
], ],

View File

@ -2,7 +2,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart';
import 'package:mobile_nebula/components/SimplePage.dart'; import 'package:mobile_nebula/components/SimplePage.dart';
import 'package:mobile_nebula/components/SpecialSelectableText.dart';
import 'package:mobile_nebula/services/share.dart'; import 'package:mobile_nebula/services/share.dart';
class RenderedConfigScreen extends StatelessWidget { class RenderedConfigScreen extends StatelessWidget {
@ -26,7 +25,7 @@ class RenderedConfigScreen extends StatelessWidget {
child: Container( child: Container(
padding: EdgeInsets.all(5), padding: EdgeInsets.all(5),
constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width), 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))),
); );
} }
} }

View File

@ -6,7 +6,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:mobile_nebula/components/FormPage.dart'; import 'package:mobile_nebula/components/FormPage.dart';
import 'package:mobile_nebula/components/PlatformTextFormField.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/ConfigPageItem.dart';
import 'package:mobile_nebula/components/config/ConfigItem.dart'; import 'package:mobile_nebula/components/config/ConfigItem.dart';
import 'package:mobile_nebula/components/config/ConfigSection.dart'; import 'package:mobile_nebula/components/config/ConfigSection.dart';
@ -92,7 +91,7 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
data = err.toString(); 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() { Widget _main() {