forked from core/mobile_nebula
Ran flutter format -l 120 --suppress-analytics lib (#38)
This commit is contained in:
parent
3a37802f4d
commit
9934f226e3
|
@ -56,42 +56,42 @@ class _CIDRFieldState extends State<CIDRField> {
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
child: Row(children: <Widget>[
|
child: Row(children: <Widget>[
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.fromLTRB(6, 6, 2, 6),
|
padding: EdgeInsets.fromLTRB(6, 6, 2, 6),
|
||||||
child: IPField(
|
child: IPField(
|
||||||
help: widget.ipHelp,
|
help: widget.ipHelp,
|
||||||
ipOnly: true,
|
ipOnly: true,
|
||||||
textPadding: EdgeInsets.all(0),
|
textPadding: EdgeInsets.all(0),
|
||||||
textInputAction: TextInputAction.next,
|
textInputAction: TextInputAction.next,
|
||||||
textAlign: TextAlign.end,
|
textAlign: TextAlign.end,
|
||||||
focusNode: widget.focusNode,
|
focusNode: widget.focusNode,
|
||||||
nextFocusNode: bitsFocus,
|
nextFocusNode: bitsFocus,
|
||||||
onChanged: (val) {
|
|
||||||
cidr.ip = val;
|
|
||||||
widget.onChanged(cidr);
|
|
||||||
},
|
|
||||||
controller: widget.ipController,
|
|
||||||
))),
|
|
||||||
Text("/"),
|
|
||||||
Container(
|
|
||||||
width: Utils.textSize("bits", textStyle).width + 12,
|
|
||||||
padding: EdgeInsets.fromLTRB(2, 6, 6, 6),
|
|
||||||
child: SpecialTextField(
|
|
||||||
keyboardType: TextInputType.number,
|
|
||||||
focusNode: bitsFocus,
|
|
||||||
nextFocusNode: widget.nextFocusNode,
|
|
||||||
controller: widget.bitsController,
|
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
cidr.bits = int.tryParse(val ?? "");
|
cidr.ip = val;
|
||||||
widget.onChanged(cidr);
|
widget.onChanged(cidr);
|
||||||
},
|
},
|
||||||
maxLength: 2,
|
controller: widget.ipController,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
))),
|
||||||
textInputAction: widget.textInputAction ?? TextInputAction.done,
|
Text("/"),
|
||||||
placeholder: 'bits',
|
Container(
|
||||||
))
|
width: Utils.textSize("bits", textStyle).width + 12,
|
||||||
]));
|
padding: EdgeInsets.fromLTRB(2, 6, 6, 6),
|
||||||
|
child: SpecialTextField(
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
focusNode: bitsFocus,
|
||||||
|
nextFocusNode: widget.nextFocusNode,
|
||||||
|
controller: widget.bitsController,
|
||||||
|
onChanged: (val) {
|
||||||
|
cidr.bits = int.tryParse(val ?? "");
|
||||||
|
widget.onChanged(cidr);
|
||||||
|
},
|
||||||
|
maxLength: 2,
|
||||||
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
textInputAction: widget.textInputAction ?? TextInputAction.done,
|
||||||
|
placeholder: 'bits',
|
||||||
|
))
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -8,7 +8,13 @@ import 'package:mobile_nebula/services/utils.dart';
|
||||||
/// SimplePage with a form and built in validation and confirmation to discard changes if any are made
|
/// SimplePage with a form and built in validation and confirmation to discard changes if any are made
|
||||||
class FormPage extends StatefulWidget {
|
class FormPage extends StatefulWidget {
|
||||||
const FormPage(
|
const FormPage(
|
||||||
{Key key, this.title, @required this.child, @required this.onSave, @required this.changed, this.hideSave = false, this.scrollController})
|
{Key key,
|
||||||
|
this.title,
|
||||||
|
@required this.child,
|
||||||
|
@required this.onSave,
|
||||||
|
@required this.changed,
|
||||||
|
this.hideSave = false,
|
||||||
|
this.scrollController})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final String title;
|
final String title;
|
||||||
|
|
|
@ -48,9 +48,7 @@ class IPField extends StatelessWidget {
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
maxLength: ipOnly ? 15 : null,
|
maxLength: ipOnly ? 15 : null,
|
||||||
maxLengthEnforced: ipOnly ? true : false,
|
maxLengthEnforced: ipOnly ? true : false,
|
||||||
inputFormatters: ipOnly
|
inputFormatters: ipOnly ? [IPTextInputFormatter()] : [FilteringTextInputFormatter.allow(RegExp(r'[^\s]+'))],
|
||||||
? [IPTextInputFormatter()]
|
|
||||||
: [FilteringTextInputFormatter.allow(RegExp(r'[^\s]+'))],
|
|
||||||
textInputAction: this.textInputAction,
|
textInputAction: this.textInputAction,
|
||||||
placeholder: help,
|
placeholder: help,
|
||||||
));
|
));
|
||||||
|
@ -68,16 +66,17 @@ class IPTextInputFormatter extends TextInputFormatter {
|
||||||
return whitelistedPattern
|
return whitelistedPattern
|
||||||
.allMatches(substring)
|
.allMatches(substring)
|
||||||
.map<String>((Match match) => match.group(0))
|
.map<String>((Match match) => match.group(0))
|
||||||
.join().replaceAll(RegExp(r','), '.');
|
.join()
|
||||||
|
.replaceAll(RegExp(r','), '.');
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextEditingValue _selectionAwareTextManipulation(
|
TextEditingValue _selectionAwareTextManipulation(
|
||||||
TextEditingValue value,
|
TextEditingValue value,
|
||||||
String substringManipulation(String substring),
|
String substringManipulation(String substring),
|
||||||
) {
|
) {
|
||||||
final int selectionStartIndex = value.selection.start;
|
final int selectionStartIndex = value.selection.start;
|
||||||
final int selectionEndIndex = value.selection.end;
|
final int selectionEndIndex = value.selection.end;
|
||||||
String manipulatedText;
|
String manipulatedText;
|
||||||
|
@ -85,15 +84,9 @@ TextEditingValue _selectionAwareTextManipulation(
|
||||||
if (selectionStartIndex < 0 || selectionEndIndex < 0) {
|
if (selectionStartIndex < 0 || selectionEndIndex < 0) {
|
||||||
manipulatedText = substringManipulation(value.text);
|
manipulatedText = substringManipulation(value.text);
|
||||||
} else {
|
} else {
|
||||||
final String beforeSelection = substringManipulation(
|
final String beforeSelection = substringManipulation(value.text.substring(0, selectionStartIndex));
|
||||||
value.text.substring(0, selectionStartIndex)
|
final String inSelection = substringManipulation(value.text.substring(selectionStartIndex, selectionEndIndex));
|
||||||
);
|
final String afterSelection = substringManipulation(value.text.substring(selectionEndIndex));
|
||||||
final String inSelection = substringManipulation(
|
|
||||||
value.text.substring(selectionStartIndex, selectionEndIndex)
|
|
||||||
);
|
|
||||||
final String afterSelection = substringManipulation(
|
|
||||||
value.text.substring(selectionEndIndex)
|
|
||||||
);
|
|
||||||
manipulatedText = beforeSelection + inSelection + afterSelection;
|
manipulatedText = beforeSelection + inSelection + afterSelection;
|
||||||
if (value.selection.baseOffset > value.selection.extentOffset) {
|
if (value.selection.baseOffset > value.selection.extentOffset) {
|
||||||
manipulatedSelection = value.selection.copyWith(
|
manipulatedSelection = value.selection.copyWith(
|
||||||
|
@ -110,8 +103,6 @@ TextEditingValue _selectionAwareTextManipulation(
|
||||||
return TextEditingValue(
|
return TextEditingValue(
|
||||||
text: manipulatedText,
|
text: manipulatedText,
|
||||||
selection: manipulatedSelection ?? const TextSelection.collapsed(offset: -1),
|
selection: manipulatedSelection ?? const TextSelection.collapsed(offset: -1),
|
||||||
composing: manipulatedText == value.text
|
composing: manipulatedText == value.text ? value.composing : TextRange.empty,
|
||||||
? value.composing
|
|
||||||
: TextRange.empty,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,14 +45,16 @@ class SimplePage extends StatelessWidget {
|
||||||
final VoidCallback onLoading;
|
final VoidCallback onLoading;
|
||||||
final RefreshController refreshController;
|
final RefreshController refreshController;
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget realChild = child;
|
Widget realChild = child;
|
||||||
var addScrollbar = this.scrollbar;
|
var addScrollbar = this.scrollbar;
|
||||||
|
|
||||||
if (scrollable == SimpleScrollable.vertical || scrollable == SimpleScrollable.both) {
|
if (scrollable == SimpleScrollable.vertical || scrollable == SimpleScrollable.both) {
|
||||||
realChild = SingleChildScrollView(scrollDirection: Axis.vertical, child: realChild, controller: refreshController == null ? scrollController : null);
|
realChild = SingleChildScrollView(
|
||||||
|
scrollDirection: Axis.vertical,
|
||||||
|
child: realChild,
|
||||||
|
controller: refreshController == null ? scrollController : null);
|
||||||
addScrollbar = true;
|
addScrollbar = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,15 +69,15 @@ class SimplePage extends StatelessWidget {
|
||||||
footerTriggerDistance: -100,
|
footerTriggerDistance: -100,
|
||||||
maxUnderScrollExtent: 100,
|
maxUnderScrollExtent: 100,
|
||||||
child: SmartRefresher(
|
child: SmartRefresher(
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
onRefresh: onRefresh,
|
onRefresh: onRefresh,
|
||||||
onLoading: onLoading,
|
onLoading: onLoading,
|
||||||
controller: refreshController,
|
controller: refreshController,
|
||||||
child: realChild,
|
child: realChild,
|
||||||
enablePullUp: onLoading != null,
|
enablePullUp: onLoading != null,
|
||||||
enablePullDown: onRefresh != null,
|
enablePullDown: onRefresh != null,
|
||||||
footer: ClassicFooter(loadStyle: LoadStyle.ShowWhenLoading),
|
footer: ClassicFooter(loadStyle: LoadStyle.ShowWhenLoading),
|
||||||
));
|
));
|
||||||
addScrollbar = true;
|
addScrollbar = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
// This is a button that pushes the bare minimum onto you, it doesn't even respect button themes - unless you tell it to
|
// This is a button that pushes the bare minimum onto you, it doesn't even respect button themes - unless you tell it to
|
||||||
class SpecialButton extends StatefulWidget {
|
class SpecialButton extends StatefulWidget {
|
||||||
const SpecialButton({Key key, this.child, this.color, this.onPressed, this.useButtonTheme = false, this.decoration}) : super(key: key);
|
const SpecialButton({Key key, this.child, this.color, this.onPressed, this.useButtonTheme = false, this.decoration})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Color color;
|
final Color color;
|
||||||
|
@ -33,12 +34,12 @@ class _SpecialButtonState extends State<SpecialButton> with SingleTickerProvider
|
||||||
return Material(
|
return Material(
|
||||||
textStyle: textStyle,
|
textStyle: textStyle,
|
||||||
child: Ink(
|
child: Ink(
|
||||||
decoration: widget.decoration,
|
decoration: widget.decoration,
|
||||||
color: widget.color,
|
color: widget.color,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
onTap: widget.onPressed,
|
onTap: widget.onPressed,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildGeneric() {
|
Widget _buildGeneric() {
|
||||||
|
@ -48,22 +49,21 @@ class _SpecialButtonState extends State<SpecialButton> with SingleTickerProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration: widget.decoration,
|
decoration: widget.decoration,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTapDown: _handleTapDown,
|
onTapDown: _handleTapDown,
|
||||||
onTapUp: _handleTapUp,
|
onTapUp: _handleTapUp,
|
||||||
onTapCancel: _handleTapCancel,
|
onTapCancel: _handleTapCancel,
|
||||||
onTap: widget.onPressed,
|
onTap: widget.onPressed,
|
||||||
child: Semantics(
|
child: Semantics(
|
||||||
button: true,
|
button: true,
|
||||||
child: FadeTransition(
|
child: FadeTransition(
|
||||||
opacity: _opacityAnimation,
|
opacity: _opacityAnimation,
|
||||||
child: DefaultTextStyle(style: textStyle, child: Container(child: widget.child, color: widget.color)),
|
child: DefaultTextStyle(style: textStyle, child: Container(child: widget.child, color: widget.color)),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Eyeballed values. Feel free to tweak.
|
// Eyeballed values. Feel free to tweak.
|
||||||
|
|
|
@ -23,15 +23,15 @@ import 'package:flutter/gestures.dart';
|
||||||
const int iOSHorizontalOffset = -2;
|
const int iOSHorizontalOffset = -2;
|
||||||
|
|
||||||
class _TextSpanEditingController extends TextEditingController {
|
class _TextSpanEditingController extends TextEditingController {
|
||||||
_TextSpanEditingController({@required TextSpan textSpan}):
|
_TextSpanEditingController({@required TextSpan textSpan})
|
||||||
assert(textSpan != null),
|
: assert(textSpan != null),
|
||||||
_textSpan = textSpan,
|
_textSpan = textSpan,
|
||||||
super(text: textSpan.toPlainText());
|
super(text: textSpan.toPlainText());
|
||||||
|
|
||||||
final TextSpan _textSpan;
|
final TextSpan _textSpan;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TextSpan buildTextSpan({TextStyle style ,bool withComposing}) {
|
TextSpan buildTextSpan({TextStyle style, bool withComposing}) {
|
||||||
// This does not care about composing.
|
// This does not care about composing.
|
||||||
return TextSpan(
|
return TextSpan(
|
||||||
style: style,
|
style: style,
|
||||||
|
@ -49,7 +49,7 @@ class _TextSpanEditingController extends TextEditingController {
|
||||||
class _SpecialSelectableTextSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder {
|
class _SpecialSelectableTextSelectionGestureDetectorBuilder extends TextSelectionGestureDetectorBuilder {
|
||||||
_SpecialSelectableTextSelectionGestureDetectorBuilder({
|
_SpecialSelectableTextSelectionGestureDetectorBuilder({
|
||||||
@required _SpecialSelectableTextState state,
|
@required _SpecialSelectableTextState state,
|
||||||
}) : _state = state,
|
}) : _state = state,
|
||||||
super(delegate: state);
|
super(delegate: state);
|
||||||
|
|
||||||
final _SpecialSelectableTextState _state;
|
final _SpecialSelectableTextState _state;
|
||||||
|
@ -109,8 +109,7 @@ class _SpecialSelectableTextSelectionGestureDetectorBuilder extends TextSelectio
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (_state.widget.onTap != null)
|
if (_state.widget.onTap != null) _state.widget.onTap();
|
||||||
_state.widget.onTap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -202,40 +201,40 @@ class SpecialSelectableText extends StatefulWidget {
|
||||||
/// must not be null. If specified, the [maxLines] argument must be greater
|
/// must not be null. If specified, the [maxLines] argument must be greater
|
||||||
/// than zero.
|
/// than zero.
|
||||||
const SpecialSelectableText(
|
const SpecialSelectableText(
|
||||||
this.data, {
|
this.data, {
|
||||||
Key key,
|
Key key,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.style,
|
this.style,
|
||||||
this.strutStyle,
|
this.strutStyle,
|
||||||
this.textAlign,
|
this.textAlign,
|
||||||
this.textDirection,
|
this.textDirection,
|
||||||
this.textScaleFactor,
|
this.textScaleFactor,
|
||||||
this.showCursor = false,
|
this.showCursor = false,
|
||||||
this.autofocus = false,
|
this.autofocus = false,
|
||||||
ToolbarOptions toolbarOptions,
|
ToolbarOptions toolbarOptions,
|
||||||
this.minLines,
|
this.minLines,
|
||||||
this.maxLines,
|
this.maxLines,
|
||||||
this.cursorWidth = 2.0,
|
this.cursorWidth = 2.0,
|
||||||
this.cursorRadius,
|
this.cursorRadius,
|
||||||
this.cursorColor,
|
this.cursorColor,
|
||||||
this.dragStartBehavior = DragStartBehavior.start,
|
this.dragStartBehavior = DragStartBehavior.start,
|
||||||
this.enableInteractiveSelection = true,
|
this.enableInteractiveSelection = true,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.scrollPhysics,
|
this.scrollPhysics,
|
||||||
this.textHeightBehavior,
|
this.textHeightBehavior,
|
||||||
this.textWidthBasis,
|
this.textWidthBasis,
|
||||||
}) : assert(showCursor != null),
|
}) : assert(showCursor != null),
|
||||||
assert(autofocus != null),
|
assert(autofocus != null),
|
||||||
assert(dragStartBehavior != null),
|
assert(dragStartBehavior != null),
|
||||||
assert(maxLines == null || maxLines > 0),
|
assert(maxLines == null || maxLines > 0),
|
||||||
assert(minLines == null || minLines > 0),
|
assert(minLines == null || minLines > 0),
|
||||||
assert(
|
assert(
|
||||||
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
|
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
|
||||||
'minLines can\'t be greater than maxLines',
|
'minLines can\'t be greater than maxLines',
|
||||||
),
|
),
|
||||||
assert(
|
assert(
|
||||||
data != null,
|
data != null,
|
||||||
'A non-null String must be provided to a SpecialSelectableText widget.',
|
'A non-null String must be provided to a SpecialSelectableText widget.',
|
||||||
),
|
),
|
||||||
textSpan = null,
|
textSpan = null,
|
||||||
toolbarOptions = toolbarOptions ??
|
toolbarOptions = toolbarOptions ??
|
||||||
|
@ -252,40 +251,40 @@ class SpecialSelectableText extends StatefulWidget {
|
||||||
///
|
///
|
||||||
/// The [autofocus] and [dragStartBehavior] arguments must not be null.
|
/// The [autofocus] and [dragStartBehavior] arguments must not be null.
|
||||||
const SpecialSelectableText.rich(
|
const SpecialSelectableText.rich(
|
||||||
this.textSpan, {
|
this.textSpan, {
|
||||||
Key key,
|
Key key,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.style,
|
this.style,
|
||||||
this.strutStyle,
|
this.strutStyle,
|
||||||
this.textAlign,
|
this.textAlign,
|
||||||
this.textDirection,
|
this.textDirection,
|
||||||
this.textScaleFactor,
|
this.textScaleFactor,
|
||||||
this.showCursor = false,
|
this.showCursor = false,
|
||||||
this.autofocus = false,
|
this.autofocus = false,
|
||||||
ToolbarOptions toolbarOptions,
|
ToolbarOptions toolbarOptions,
|
||||||
this.minLines,
|
this.minLines,
|
||||||
this.maxLines,
|
this.maxLines,
|
||||||
this.cursorWidth = 2.0,
|
this.cursorWidth = 2.0,
|
||||||
this.cursorRadius,
|
this.cursorRadius,
|
||||||
this.cursorColor,
|
this.cursorColor,
|
||||||
this.dragStartBehavior = DragStartBehavior.start,
|
this.dragStartBehavior = DragStartBehavior.start,
|
||||||
this.enableInteractiveSelection = true,
|
this.enableInteractiveSelection = true,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.scrollPhysics,
|
this.scrollPhysics,
|
||||||
this.textHeightBehavior,
|
this.textHeightBehavior,
|
||||||
this.textWidthBasis,
|
this.textWidthBasis,
|
||||||
}) : assert(showCursor != null),
|
}) : assert(showCursor != null),
|
||||||
assert(autofocus != null),
|
assert(autofocus != null),
|
||||||
assert(dragStartBehavior != null),
|
assert(dragStartBehavior != null),
|
||||||
assert(maxLines == null || maxLines > 0),
|
assert(maxLines == null || maxLines > 0),
|
||||||
assert(minLines == null || minLines > 0),
|
assert(minLines == null || minLines > 0),
|
||||||
assert(
|
assert(
|
||||||
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
|
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
|
||||||
'minLines can\'t be greater than maxLines',
|
'minLines can\'t be greater than maxLines',
|
||||||
),
|
),
|
||||||
assert(
|
assert(
|
||||||
textSpan != null,
|
textSpan != null,
|
||||||
'A non-null TextSpan must be provided to a SpecialSelectableText.rich widget.',
|
'A non-null TextSpan must be provided to a SpecialSelectableText.rich widget.',
|
||||||
),
|
),
|
||||||
data = null,
|
data = null,
|
||||||
toolbarOptions = toolbarOptions ??
|
toolbarOptions = toolbarOptions ??
|
||||||
|
@ -434,13 +433,17 @@ class SpecialSelectableText extends StatefulWidget {
|
||||||
properties.add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0));
|
properties.add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0));
|
||||||
properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius, defaultValue: null));
|
properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<Color>('cursorColor', cursorColor, defaultValue: null));
|
properties.add(DiagnosticsProperty<Color>('cursorColor', cursorColor, defaultValue: null));
|
||||||
properties.add(FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
|
properties.add(
|
||||||
|
FlagProperty('selectionEnabled', value: selectionEnabled, defaultValue: true, ifFalse: 'selection disabled'));
|
||||||
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
|
properties.add(DiagnosticsProperty<ScrollPhysics>('scrollPhysics', scrollPhysics, defaultValue: null));
|
||||||
properties.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
|
properties
|
||||||
|
.add(DiagnosticsProperty<TextHeightBehavior>('textHeightBehavior', textHeightBehavior, defaultValue: null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SpecialSelectableTextState extends State<SpecialSelectableText> with AutomaticKeepAliveClientMixin implements TextSelectionGestureDetectorBuilderDelegate {
|
class _SpecialSelectableTextState extends State<SpecialSelectableText>
|
||||||
|
with AutomaticKeepAliveClientMixin
|
||||||
|
implements TextSelectionGestureDetectorBuilderDelegate {
|
||||||
EditableTextState get _editableText => editableTextKey.currentState;
|
EditableTextState get _editableText => editableTextKey.currentState;
|
||||||
|
|
||||||
_TextSpanEditingController _controller;
|
_TextSpanEditingController _controller;
|
||||||
|
@ -468,18 +471,14 @@ class _SpecialSelectableTextState extends State<SpecialSelectableText> with Auto
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_selectionGestureDetectorBuilder = _SpecialSelectableTextSelectionGestureDetectorBuilder(state: this);
|
_selectionGestureDetectorBuilder = _SpecialSelectableTextSelectionGestureDetectorBuilder(state: this);
|
||||||
_controller = _TextSpanEditingController(
|
_controller = _TextSpanEditingController(textSpan: widget.textSpan ?? TextSpan(text: widget.data));
|
||||||
textSpan: widget.textSpan ?? TextSpan(text: widget.data)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(SpecialSelectableText oldWidget) {
|
void didUpdateWidget(SpecialSelectableText oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.data != oldWidget.data || widget.textSpan != oldWidget.textSpan) {
|
if (widget.data != oldWidget.data || widget.textSpan != oldWidget.textSpan) {
|
||||||
_controller = _TextSpanEditingController(
|
_controller = _TextSpanEditingController(textSpan: widget.textSpan ?? TextSpan(text: widget.data));
|
||||||
textSpan: widget.textSpan ?? TextSpan(text: widget.data)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (_effectiveFocusNode.hasFocus && _controller.selection.isCollapsed) {
|
if (_effectiveFocusNode.hasFocus && _controller.selection.isCollapsed) {
|
||||||
_showSelectionHandles = false;
|
_showSelectionHandles = false;
|
||||||
|
@ -525,20 +524,15 @@ class _SpecialSelectableTextState extends State<SpecialSelectableText> with Auto
|
||||||
bool _shouldShowSelectionHandles(SelectionChangedCause cause) {
|
bool _shouldShowSelectionHandles(SelectionChangedCause cause) {
|
||||||
// When the text field is activated by something that doesn't trigger the
|
// When the text field is activated by something that doesn't trigger the
|
||||||
// selection overlay, we shouldn't show the handles either.
|
// selection overlay, we shouldn't show the handles either.
|
||||||
if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar)
|
if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (_controller.selection.isCollapsed)
|
if (_controller.selection.isCollapsed) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (cause == SelectionChangedCause.keyboard)
|
if (cause == SelectionChangedCause.keyboard) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
if (cause == SelectionChangedCause.longPress)
|
if (cause == SelectionChangedCause.longPress) return true;
|
||||||
return true;
|
|
||||||
|
|
||||||
if (_controller.text.isNotEmpty)
|
if (_controller.text.isNotEmpty) return true;
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -555,9 +549,10 @@ class _SpecialSelectableTextState extends State<SpecialSelectableText> with Auto
|
||||||
assert(debugCheckHasMediaQuery(context));
|
assert(debugCheckHasMediaQuery(context));
|
||||||
assert(debugCheckHasDirectionality(context));
|
assert(debugCheckHasDirectionality(context));
|
||||||
assert(
|
assert(
|
||||||
!(widget.style != null && widget.style.inherit == false &&
|
!(widget.style != null &&
|
||||||
(widget.style.fontSize == null || widget.style.textBaseline == null)),
|
widget.style.inherit == false &&
|
||||||
'inherit false style must supply fontSize and textBaseline',
|
(widget.style.fontSize == null || widget.style.textBaseline == null)),
|
||||||
|
'inherit false style must supply fontSize and textBaseline',
|
||||||
);
|
);
|
||||||
|
|
||||||
final ThemeData themeData = Theme.of(context);
|
final ThemeData themeData = Theme.of(context);
|
||||||
|
@ -596,8 +591,7 @@ class _SpecialSelectableTextState extends State<SpecialSelectableText> with Auto
|
||||||
|
|
||||||
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
|
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context);
|
||||||
TextStyle effectiveTextStyle = widget.style;
|
TextStyle effectiveTextStyle = widget.style;
|
||||||
if (widget.style == null || widget.style.inherit)
|
if (widget.style == null || widget.style.inherit) effectiveTextStyle = defaultTextStyle.style.merge(widget.style);
|
||||||
effectiveTextStyle = defaultTextStyle.style.merge(widget.style);
|
|
||||||
if (MediaQuery.boldTextOverride(context))
|
if (MediaQuery.boldTextOverride(context))
|
||||||
effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
|
effectiveTextStyle = effectiveTextStyle.merge(const TextStyle(fontWeight: FontWeight.bold));
|
||||||
final Widget child = RepaintBoundary(
|
final Widget child = RepaintBoundary(
|
||||||
|
@ -648,13 +642,12 @@ class _SpecialSelectableTextState extends State<SpecialSelectableText> with Auto
|
||||||
_effectiveFocusNode.requestFocus();
|
_effectiveFocusNode.requestFocus();
|
||||||
},
|
},
|
||||||
child: _selectionGestureDetectorBuilder.buildGestureDetector(
|
child: _selectionGestureDetectorBuilder.buildGestureDetector(
|
||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
child: RawKeyboardListener(
|
child: RawKeyboardListener(
|
||||||
focusNode: _keyFocusNode,
|
focusNode: _keyFocusNode,
|
||||||
onKey: _onKey,
|
onKey: _onKey,
|
||||||
child: child,
|
child: child,
|
||||||
)
|
)),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -216,7 +216,6 @@ class Site {
|
||||||
});
|
});
|
||||||
|
|
||||||
return hosts;
|
return hosts;
|
||||||
|
|
||||||
} on PlatformException catch (err) {
|
} on PlatformException catch (err) {
|
||||||
//TODO: fix this message
|
//TODO: fix this message
|
||||||
throw err.details ?? err.message ?? err.toString();
|
throw err.details ?? err.message ?? err.toString();
|
||||||
|
@ -239,7 +238,6 @@ class Site {
|
||||||
});
|
});
|
||||||
|
|
||||||
return hosts;
|
return hosts;
|
||||||
|
|
||||||
} on PlatformException catch (err) {
|
} on PlatformException catch (err) {
|
||||||
throw err.details ?? err.message ?? err.toString();
|
throw err.details ?? err.message ?? err.toString();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -251,7 +249,6 @@ class Site {
|
||||||
try {
|
try {
|
||||||
var res = await Future.wait([this.listHostmap(), this.listPendingHostmap()]);
|
var res = await Future.wait([this.listHostmap(), this.listPendingHostmap()]);
|
||||||
return {"active": res[0], "pending": res[1]};
|
return {"active": res[0], "pending": res[1]};
|
||||||
|
|
||||||
} on PlatformException catch (err) {
|
} on PlatformException catch (err) {
|
||||||
throw err.details ?? err.message ?? err.toString();
|
throw err.details ?? err.message ?? err.toString();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -265,14 +262,14 @@ class Site {
|
||||||
|
|
||||||
Future<HostInfo> getHostInfo(String vpnIp, bool pending) async {
|
Future<HostInfo> getHostInfo(String vpnIp, bool pending) async {
|
||||||
try {
|
try {
|
||||||
var ret = await platform.invokeMethod("active.getHostInfo", <String, dynamic>{"id": id, "vpnIp": vpnIp, "pending": pending});
|
var ret = await platform
|
||||||
|
.invokeMethod("active.getHostInfo", <String, dynamic>{"id": id, "vpnIp": vpnIp, "pending": pending});
|
||||||
final h = jsonDecode(ret);
|
final h = jsonDecode(ret);
|
||||||
if (h == null) {
|
if (h == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HostInfo.fromJson(h);
|
return HostInfo.fromJson(h);
|
||||||
|
|
||||||
} on PlatformException catch (err) {
|
} on PlatformException catch (err) {
|
||||||
throw err.details ?? err.message ?? err.toString();
|
throw err.details ?? err.message ?? err.toString();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -282,14 +279,14 @@ class Site {
|
||||||
|
|
||||||
Future<HostInfo> setRemoteForTunnel(String vpnIp, String addr) async {
|
Future<HostInfo> setRemoteForTunnel(String vpnIp, String addr) async {
|
||||||
try {
|
try {
|
||||||
var ret = await platform.invokeMethod("active.setRemoteForTunnel", <String, dynamic>{"id": id, "vpnIp": vpnIp, "addr": addr});
|
var ret = await platform
|
||||||
|
.invokeMethod("active.setRemoteForTunnel", <String, dynamic>{"id": id, "vpnIp": vpnIp, "addr": addr});
|
||||||
final h = jsonDecode(ret);
|
final h = jsonDecode(ret);
|
||||||
if (h == null) {
|
if (h == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return HostInfo.fromJson(h);
|
return HostInfo.fromJson(h);
|
||||||
|
|
||||||
} on PlatformException catch (err) {
|
} on PlatformException catch (err) {
|
||||||
throw err.details ?? err.message ?? err.toString();
|
throw err.details ?? err.message ?? err.toString();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -300,7 +297,6 @@ class Site {
|
||||||
Future<bool> closeTunnel(String vpnIp) async {
|
Future<bool> closeTunnel(String vpnIp) async {
|
||||||
try {
|
try {
|
||||||
return await platform.invokeMethod("active.closeTunnel", <String, dynamic>{"id": id, "vpnIp": vpnIp});
|
return await platform.invokeMethod("active.closeTunnel", <String, dynamic>{"id": id, "vpnIp": vpnIp});
|
||||||
|
|
||||||
} on PlatformException catch (err) {
|
} on PlatformException catch (err) {
|
||||||
throw err.details ?? err.message ?? err.toString();
|
throw err.details ?? err.message ?? err.toString();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -48,18 +48,32 @@ class _AboutScreenState extends State<AboutScreen> {
|
||||||
title: 'About',
|
title: 'About',
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
ConfigSection(children: <Widget>[
|
ConfigSection(children: <Widget>[
|
||||||
ConfigItem(label: Text('App version'), labelWidth: 150, content: _buildText('${packageInfo.version}-${packageInfo.buildNumber} (sha: $gitSha)')),
|
ConfigItem(
|
||||||
ConfigItem(label: Text('Nebula version'), labelWidth: 150, content: _buildText('$nebulaVersion ($goVersion)')),
|
label: Text('App version'),
|
||||||
ConfigItem(label: Text('Flutter version'), labelWidth: 150, content: _buildText(flutterVersion['frameworkVersion'])),
|
labelWidth: 150,
|
||||||
ConfigItem(label: Text('Dart version'), labelWidth: 150, content: _buildText(flutterVersion['dartSdkVersion'])),
|
content: _buildText('${packageInfo.version}-${packageInfo.buildNumber} (sha: $gitSha)')),
|
||||||
|
ConfigItem(
|
||||||
|
label: Text('Nebula version'), labelWidth: 150, content: _buildText('$nebulaVersion ($goVersion)')),
|
||||||
|
ConfigItem(
|
||||||
|
label: Text('Flutter version'), labelWidth: 150, content: _buildText(flutterVersion['frameworkVersion'])),
|
||||||
|
ConfigItem(
|
||||||
|
label: Text('Dart version'), labelWidth: 150, content: _buildText(flutterVersion['dartSdkVersion'])),
|
||||||
]),
|
]),
|
||||||
ConfigSection(children: <Widget>[
|
ConfigSection(children: <Widget>[
|
||||||
//TODO: wire up these other pages
|
//TODO: wire up these other pages
|
||||||
// ConfigPageItem(label: Text('Changelog'), labelWidth: 300, onPressed: () => Utils.launchUrl('https://defined.net/mobile/changelog', context)),
|
// ConfigPageItem(label: Text('Changelog'), labelWidth: 300, onPressed: () => Utils.launchUrl('https://defined.net/mobile/changelog', context)),
|
||||||
ConfigPageItem(label: Text('Privacy policy'), labelWidth: 300, onPressed: () => Utils.launchUrl('https://defined.net/privacy-policy', context)),
|
ConfigPageItem(
|
||||||
|
label: Text('Privacy policy'),
|
||||||
|
labelWidth: 300,
|
||||||
|
onPressed: () => Utils.launchUrl('https://defined.net/privacy-policy', context)),
|
||||||
// ConfigPageItem(label: Text('Licenses'), labelWidth: 300, onPressed: () => Utils.launchUrl('https://defined.net/mobile/license', context)),
|
// ConfigPageItem(label: Text('Licenses'), labelWidth: 300, onPressed: () => Utils.launchUrl('https://defined.net/mobile/license', context)),
|
||||||
]),
|
]),
|
||||||
Padding(padding: EdgeInsets.only(top: 20), child: Text('Copyright © 2020 Defined Networking, Inc', textAlign: TextAlign.center,)),
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 20),
|
||||||
|
child: Text(
|
||||||
|
'Copyright © 2020 Defined Networking, Inc',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
)),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,16 +161,16 @@ class _HostInfoScreenState extends State<HostInfoScreen> {
|
||||||
child: Text('Close Tunnel'),
|
child: Text('Close Tunnel'),
|
||||||
color: CupertinoColors.systemRed.resolveFrom(context),
|
color: CupertinoColors.systemRed.resolveFrom(context),
|
||||||
onPressed: () => Utils.confirmDelete(context, 'Close Tunnel?', () async {
|
onPressed: () => Utils.confirmDelete(context, 'Close Tunnel?', () async {
|
||||||
try {
|
try {
|
||||||
await widget.site.closeTunnel(hostInfo.vpnIp);
|
await widget.site.closeTunnel(hostInfo.vpnIp);
|
||||||
if (widget.onChanged != null) {
|
if (widget.onChanged != null) {
|
||||||
widget.onChanged();
|
widget.onChanged();
|
||||||
}
|
}
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
Utils.popError(context, 'Error while trying to close the tunnel', err);
|
Utils.popError(context, 'Error while trying to close the tunnel', err);
|
||||||
}
|
}
|
||||||
}, deleteLabel: 'Close'))));
|
}, deleteLabel: 'Close'))));
|
||||||
}
|
}
|
||||||
|
|
||||||
_getHostInfo() async {
|
_getHostInfo() async {
|
||||||
|
|
|
@ -87,18 +87,19 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
|
|
||||||
Widget _buildNoSites() {
|
Widget _buildNoSites() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Center(child: Column(
|
child: Center(
|
||||||
children: <Widget>[
|
child: Column(
|
||||||
Padding(
|
children: <Widget>[
|
||||||
padding: const EdgeInsets.fromLTRB(0, 8.0, 0, 8.0),
|
Padding(
|
||||||
child: Text('Welcome to Nebula!', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
padding: const EdgeInsets.fromLTRB(0, 8.0, 0, 8.0),
|
||||||
|
child: Text('Welcome to Nebula!', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20)),
|
||||||
|
),
|
||||||
|
Text('You don\'t have any site configurations installed yet. Hit the plus button above to get started.',
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
Text('You don\'t have any site configurations installed yet. Hit the plus button above to get started.',
|
));
|
||||||
textAlign: TextAlign.center),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSites() {
|
Widget _buildSites() {
|
||||||
|
@ -146,10 +147,7 @@ class _MainScreenState extends State<MainScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The theme here is to remove the hardcoded canvas border reordering forces on us
|
// The theme here is to remove the hardcoded canvas border reordering forces on us
|
||||||
return Theme(
|
return Theme(data: Theme.of(context).copyWith(canvasColor: Colors.transparent), child: child);
|
||||||
data: Theme.of(context).copyWith(canvasColor: Colors.transparent),
|
|
||||||
child: child
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _debugSave() {
|
Widget _debugSave() {
|
||||||
|
@ -173,15 +171,16 @@ mUOcsdFcCZiXrj7ryQIG1+WfqA46w71A/lV4nAc=
|
||||||
-----END NEBULA CERTIFICATE-----''';
|
-----END NEBULA CERTIFICATE-----''';
|
||||||
|
|
||||||
var s = Site(
|
var s = Site(
|
||||||
name: "DEBUG TEST",
|
name: "DEBUG TEST",
|
||||||
id: uuid.v4(),
|
id: uuid.v4(),
|
||||||
staticHostmap: {
|
staticHostmap: {
|
||||||
"10.1.0.1": StaticHost(lighthouse: true, destinations: [IPAndPort(ip: '10.1.1.53', port: 4242), IPAndPort(ip: '1::1', port: 4242)])
|
"10.1.0.1": StaticHost(
|
||||||
},
|
lighthouse: true,
|
||||||
ca: [CertificateInfo.debug(rawCert: ca)],
|
destinations: [IPAndPort(ip: '10.1.1.53', port: 4242), IPAndPort(ip: '1::1', port: 4242)])
|
||||||
certInfo: CertificateInfo.debug(rawCert: cert),
|
},
|
||||||
unsafeRoutes: [UnsafeRoute(route: '10.3.3.3/32', via: '10.1.0.1')]
|
ca: [CertificateInfo.debug(rawCert: ca)],
|
||||||
);
|
certInfo: CertificateInfo.debug(rawCert: cert),
|
||||||
|
unsafeRoutes: [UnsafeRoute(route: '10.3.3.3/32', via: '10.1.0.1')]);
|
||||||
|
|
||||||
s.key = '''-----BEGIN NEBULA X25519 PRIVATE KEY-----
|
s.key = '''-----BEGIN NEBULA X25519 PRIVATE KEY-----
|
||||||
rmXnR1yvDZi1VPVmnNVY8NMsQpEpbbYlq7rul+ByQvg=
|
rmXnR1yvDZi1VPVmnNVY8NMsQpEpbbYlq7rul+ByQvg=
|
||||||
|
@ -239,7 +238,8 @@ rmXnR1yvDZi1VPVmnNVY8NMsQpEpbbYlq7rul+ByQvg=
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasErrors) {
|
if (hasErrors) {
|
||||||
Utils.popError(context, "Site Error(s)", "1 or more sites have errors and need your attention, problem sites have a red border.");
|
Utils.popError(context, "Site Error(s)",
|
||||||
|
"1 or more sites have errors and need your attention, problem sites have a red border.");
|
||||||
}
|
}
|
||||||
|
|
||||||
sites.sort((a, b) {
|
sites.sort((a, b) {
|
||||||
|
|
|
@ -62,7 +62,6 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
||||||
}, onError: (err) {
|
}, onError: (err) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
Utils.popError(context, "Error", err);
|
Utils.popError(context, "Error", err);
|
||||||
|
@ -111,7 +110,8 @@ class _SiteDetailScreenState extends State<SiteDetailScreen> {
|
||||||
List<Widget> items = [];
|
List<Widget> items = [];
|
||||||
site.errors.forEach((error) {
|
site.errors.forEach((error) {
|
||||||
items.add(ConfigItem(
|
items.add(ConfigItem(
|
||||||
labelWidth: 0, content: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: SpecialSelectableText(error))));
|
labelWidth: 0,
|
||||||
|
content: Padding(padding: EdgeInsets.symmetric(vertical: 10), child: SpecialSelectableText(error))));
|
||||||
});
|
});
|
||||||
|
|
||||||
return ConfigSection(
|
return ConfigSection(
|
||||||
|
|
|
@ -54,7 +54,10 @@ class _SiteLogsScreenState extends State<SiteLogsScreen> {
|
||||||
refreshController.loadComplete();
|
refreshController.loadComplete();
|
||||||
},
|
},
|
||||||
refreshController: refreshController,
|
refreshController: refreshController,
|
||||||
child: Container(padding: EdgeInsets.all(5), constraints: logBoxConstraints(context), child: SpecialSelectableText(logs.trim(), style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14))),
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(5),
|
||||||
|
constraints: logBoxConstraints(context),
|
||||||
|
child: SpecialSelectableText(logs.trim(), style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14))),
|
||||||
bottomBar: _buildBottomBar(),
|
bottomBar: _buildBottomBar(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -78,7 +81,10 @@ class _SiteLogsScreenState extends State<SiteLogsScreen> {
|
||||||
padding: padding,
|
padding: padding,
|
||||||
icon: Icon(context.platformIcons.share, size: 30),
|
icon: Icon(context.platformIcons.share, size: 30),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Share.shareFile(title: '${widget.site.name} logs', filePath: widget.site.logFile, filename: '${widget.site.name}.log');
|
Share.shareFile(
|
||||||
|
title: '${widget.site.name} logs',
|
||||||
|
filePath: widget.site.logFile,
|
||||||
|
filename: '${widget.site.name}.log');
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
@ -94,7 +100,8 @@ class _SiteLogsScreenState extends State<SiteLogsScreen> {
|
||||||
padding: padding,
|
padding: padding,
|
||||||
icon: Icon(context.platformIcons.downArrow, size: 30),
|
icon: Icon(context.platformIcons.downArrow, size: 30),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
controller.animateTo(controller.position.maxScrollExtent, duration: const Duration(milliseconds: 500), curve: Curves.linearToEaseOut);
|
controller.animateTo(controller.position.maxScrollExtent,
|
||||||
|
duration: const Duration(milliseconds: 500), curve: Curves.linearToEaseOut);
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
]));
|
]));
|
||||||
|
|
|
@ -72,9 +72,7 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
|
||||||
items.addAll(_buildShare());
|
items.addAll(_buildShare());
|
||||||
items.addAll(_buildLoadCert());
|
items.addAll(_buildLoadCert());
|
||||||
|
|
||||||
return SimplePage(
|
return SimplePage(title: 'Certificate', child: Column(children: items));
|
||||||
title: 'Certificate',
|
|
||||||
child: Column(children: items));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_generateKeys() async {
|
_generateKeys() async {
|
||||||
|
@ -215,8 +213,8 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
|
||||||
if (certs.length > 0) {
|
if (certs.length > 0) {
|
||||||
var tryCertInfo = CertificateInfo.fromJson(certs.first);
|
var tryCertInfo = CertificateInfo.fromJson(certs.first);
|
||||||
if (tryCertInfo.cert.details.isCa) {
|
if (tryCertInfo.cert.details.isCa) {
|
||||||
return Utils.popError(context, 'Error loading certificate content', 'A certificate authority is not appropriate for a client certificate.');
|
return Utils.popError(context, 'Error loading certificate content',
|
||||||
|
'A certificate authority is not appropriate for a client certificate.');
|
||||||
} else if (!tryCertInfo.validity.valid) {
|
} else if (!tryCertInfo.validity.valid) {
|
||||||
return Utils.popError(context, 'Certificate was invalid', tryCertInfo.validity.reason);
|
return Utils.popError(context, 'Certificate was invalid', tryCertInfo.validity.reason);
|
||||||
}
|
}
|
||||||
|
@ -232,10 +230,12 @@ class _AddCertificateScreenState extends State<AddCertificateScreen> {
|
||||||
|
|
||||||
// We have a cert, pop the details screen where they can hit save
|
// We have a cert, pop the details screen where they can hit save
|
||||||
Utils.openPage(context, (context) {
|
Utils.openPage(context, (context) {
|
||||||
return CertificateDetailsScreen(certInfo: tryCertInfo, onSave: () {
|
return CertificateDetailsScreen(
|
||||||
Navigator.pop(context);
|
certInfo: tryCertInfo,
|
||||||
widget.onSave(CertificateResult(certInfo: tryCertInfo, key: privKey));
|
onSave: () {
|
||||||
});
|
Navigator.pop(context);
|
||||||
|
widget.onSave(CertificateResult(certInfo: tryCertInfo, key: privKey));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} on PlatformException catch (err) {
|
} on PlatformException catch (err) {
|
||||||
|
|
|
@ -148,19 +148,21 @@ class _AdvancedScreenState extends State<AdvancedScreen> {
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
ConfigPageItem(
|
ConfigPageItem(
|
||||||
label: Text('Unsafe routes'),
|
label: Text('Unsafe routes'),
|
||||||
labelWidth: 150,
|
labelWidth: 150,
|
||||||
content: Text(Utils.itemCountFormat(settings.unsafeRoutes.length), textAlign: TextAlign.end),
|
content: Text(Utils.itemCountFormat(settings.unsafeRoutes.length), textAlign: TextAlign.end),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Utils.openPage(context, (context) {
|
Utils.openPage(context, (context) {
|
||||||
return UnsafeRoutesScreen(unsafeRoutes: settings.unsafeRoutes, onSave: (routes) {
|
return UnsafeRoutesScreen(
|
||||||
setState(() {
|
unsafeRoutes: settings.unsafeRoutes,
|
||||||
settings.unsafeRoutes = routes;
|
onSave: (routes) {
|
||||||
changed = true;
|
setState(() {
|
||||||
|
settings.unsafeRoutes = routes;
|
||||||
|
changed = true;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
},
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -201,7 +201,6 @@ class _CAListScreenState extends State<CAListScreen> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return Utils.popError(context, 'Failed to load CA file', err.toString());
|
return Utils.popError(context, 'Failed to load CA file', err.toString());
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,8 @@ import 'package:mobile_nebula/services/utils.dart';
|
||||||
|
|
||||||
/// Displays the details of a CertificateInfo object. Respects incomplete objects (missing validity or rawCert)
|
/// Displays the details of a CertificateInfo object. Respects incomplete objects (missing validity or rawCert)
|
||||||
class CertificateDetailsScreen extends StatefulWidget {
|
class CertificateDetailsScreen extends StatefulWidget {
|
||||||
const CertificateDetailsScreen({Key key, this.certInfo, this.onDelete, this.onSave, this.onReplace}) : super(key: key);
|
const CertificateDetailsScreen({Key key, this.certInfo, this.onDelete, this.onSave, this.onReplace})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
final CertificateInfo certInfo;
|
final CertificateInfo certInfo;
|
||||||
|
|
||||||
|
@ -78,8 +79,7 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
|
||||||
return ConfigSection(children: <Widget>[
|
return ConfigSection(children: <Widget>[
|
||||||
ConfigItem(label: Text('Name'), content: SpecialSelectableText(certInfo.cert.details.name)),
|
ConfigItem(label: Text('Name'), content: SpecialSelectableText(certInfo.cert.details.name)),
|
||||||
ConfigItem(
|
ConfigItem(
|
||||||
label: Text('Type'),
|
label: Text('Type'), content: Text(certInfo.cert.details.isCa ? 'CA certificate' : 'Client certificate')),
|
||||||
content: Text(certInfo.cert.details.isCa ? 'CA certificate' : 'Client certificate')),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,18 +106,17 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
|
||||||
Widget _buildFilters() {
|
Widget _buildFilters() {
|
||||||
List<Widget> items = [];
|
List<Widget> items = [];
|
||||||
if (certInfo.cert.details.groups.length > 0) {
|
if (certInfo.cert.details.groups.length > 0) {
|
||||||
items.add(ConfigItem(
|
items.add(
|
||||||
label: Text('Groups'), content: SpecialSelectableText(certInfo.cert.details.groups.join(', '))));
|
ConfigItem(label: Text('Groups'), content: SpecialSelectableText(certInfo.cert.details.groups.join(', '))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (certInfo.cert.details.ips.length > 0) {
|
if (certInfo.cert.details.ips.length > 0) {
|
||||||
items
|
items.add(ConfigItem(label: Text('IPs'), content: SpecialSelectableText(certInfo.cert.details.ips.join(', '))));
|
||||||
.add(ConfigItem(label: Text('IPs'), content: SpecialSelectableText(certInfo.cert.details.ips.join(', '))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (certInfo.cert.details.subnets.length > 0) {
|
if (certInfo.cert.details.subnets.length > 0) {
|
||||||
items.add(ConfigItem(
|
items.add(
|
||||||
label: Text('Subnets'), content: SpecialSelectableText(certInfo.cert.details.subnets.join(', '))));
|
ConfigItem(label: Text('Subnets'), content: SpecialSelectableText(certInfo.cert.details.subnets.join(', '))));
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.length > 0
|
return items.length > 0
|
||||||
|
@ -141,8 +140,8 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
|
||||||
certInfo.rawCert != null
|
certInfo.rawCert != null
|
||||||
? ConfigItem(
|
? ConfigItem(
|
||||||
label: Text('PEM Format'),
|
label: Text('PEM Format'),
|
||||||
content: SpecialSelectableText(certInfo.rawCert,
|
content:
|
||||||
style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
|
SpecialSelectableText(certInfo.rawCert, style: TextStyle(fontFamily: 'RobotoMono', fontSize: 14)),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start)
|
crossAxisAlignment: CrossAxisAlignment.start)
|
||||||
: Container(),
|
: Container(),
|
||||||
],
|
],
|
||||||
|
@ -155,26 +154,26 @@ class _CertificateDetailsScreenState extends State<CertificateDetailsScreen> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10),
|
padding: EdgeInsets.only(top: 50, bottom: 10, left: 10, right: 10),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: PlatformButton(
|
child: PlatformButton(
|
||||||
child: Text('Replace certificate'),
|
child: Text('Replace certificate'),
|
||||||
color: CupertinoColors.systemRed.resolveFrom(context),
|
color: CupertinoColors.systemRed.resolveFrom(context),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Utils.openPage(context, (context) {
|
Utils.openPage(context, (context) {
|
||||||
return AddCertificateScreen(
|
return AddCertificateScreen(onReplace: (result) {
|
||||||
onReplace: (result) {
|
setState(() {
|
||||||
setState(() {
|
changed = true;
|
||||||
changed = true;
|
certResult = result;
|
||||||
certResult = result;
|
certInfo = certResult.certInfo;
|
||||||
certInfo = certResult.certInfo;
|
});
|
||||||
|
// Slam the page back to the top
|
||||||
|
controller.animateTo(0,
|
||||||
|
duration: const Duration(milliseconds: 10), curve: Curves.linearToEaseOut);
|
||||||
});
|
});
|
||||||
// Slam the page back to the top
|
|
||||||
controller.animateTo(0, duration: const Duration(milliseconds: 10), curve: Curves.linearToEaseOut);
|
|
||||||
});
|
});
|
||||||
});
|
})));
|
||||||
})));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDelete() {
|
Widget _buildDelete() {
|
||||||
|
|
|
@ -140,25 +140,23 @@ class _SiteConfigScreenState extends State<SiteConfigScreen> {
|
||||||
Utils.openPage(context, (context) {
|
Utils.openPage(context, (context) {
|
||||||
if (site.certInfo != null) {
|
if (site.certInfo != null) {
|
||||||
return CertificateDetailsScreen(
|
return CertificateDetailsScreen(
|
||||||
certInfo: site.certInfo,
|
certInfo: site.certInfo,
|
||||||
onReplace: (result) {
|
onReplace: (result) {
|
||||||
setState(() {
|
setState(() {
|
||||||
changed = true;
|
changed = true;
|
||||||
site.certInfo = result.certInfo;
|
site.certInfo = result.certInfo;
|
||||||
site.key = result.key;
|
site.key = result.key;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return AddCertificateScreen(
|
return AddCertificateScreen(onSave: (result) {
|
||||||
onSave: (result) {
|
setState(() {
|
||||||
setState(() {
|
changed = true;
|
||||||
changed = true;
|
site.certInfo = result.certInfo;
|
||||||
site.certInfo = result.certInfo;
|
site.key = result.key;
|
||||||
site.key = result.key;
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -83,12 +83,14 @@ class _UnsafeRoutesScreenState extends State<UnsafeRoutesScreen> {
|
||||||
content: Text('Add a new route'),
|
content: Text('Add a new route'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Utils.openPage(context, (context) {
|
Utils.openPage(context, (context) {
|
||||||
return UnsafeRouteScreen(route: UnsafeRoute(), onSave: (route) {
|
return UnsafeRouteScreen(
|
||||||
setState(() {
|
route: UnsafeRoute(),
|
||||||
changed = true;
|
onSave: (route) {
|
||||||
unsafeRoutes[UniqueKey()] = route;
|
setState(() {
|
||||||
});
|
changed = true;
|
||||||
});
|
unsafeRoutes[UniqueKey()] = route;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
|
|
|
@ -44,8 +44,7 @@ class Share {
|
||||||
throw FlutterError('FilePath cannot be null');
|
throw FlutterError('FilePath cannot be null');
|
||||||
}
|
}
|
||||||
|
|
||||||
final bool success =
|
final bool success = await _channel.invokeMethod('shareFile', <String, dynamic>{
|
||||||
await _channel.invokeMethod('shareFile', <String, dynamic>{
|
|
||||||
'title': title,
|
'title': title,
|
||||||
'filePath': filePath,
|
'filePath': filePath,
|
||||||
'filename': filename,
|
'filename': filename,
|
||||||
|
|
|
@ -7,15 +7,19 @@ bool ipValidator(String str, bool enableIPV6) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (ia.type) {
|
switch (ia.type) {
|
||||||
case InternetAddressType.IPv6: {
|
case InternetAddressType.IPv6:
|
||||||
if (enableIPV6) {
|
{
|
||||||
|
if (enableIPV6) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case InternetAddressType.IPv4:
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
case InternetAddressType.IPv4: { return true; }
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
Loading…
Reference in New Issue