Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/devtools_app/lib/src/flutter/table.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ class FlatTable<T> extends StatefulWidget {
@required this.columns,
@required this.data,
@required this.keyFactory,
@required this.onItemSelected,
}) : assert(columns != null),
assert(keyFactory != null),
assert(data != null),
assert(onItemSelected != null),
super(key: key);

final List<ColumnData<T>> columns;
Expand All @@ -31,6 +33,8 @@ class FlatTable<T> extends StatefulWidget {
/// Factory that creates keys for each row in this table.
final Key Function(T data) keyFactory;

final ItemCallback<T> onItemSelected;

@override
_FlatTableState<T> createState() => _FlatTableState<T>();
}
Expand Down Expand Up @@ -80,7 +84,7 @@ class _FlatTableState<T> extends State<FlatTable<T>> {
return _TableRow<T>(
key: widget.keyFactory(node),
node: node,
onPressed: (_) {},
onPressed: widget.onItemSelected,
columns: widget.columns,
columnWidths: columnWidths,
backgroundColor: _TableRow.colorFor(context, index),
Expand Down Expand Up @@ -370,7 +374,7 @@ class _TableRow<T> extends StatefulWidget {

final T node;
final List<ColumnData<T>> columns;
final ItemCallback onPressed;
final ItemCallback<T> onPressed;
final List<double> columnWidths;

/// Which column, if any, should show expansion affordances
Expand Down
130 changes: 126 additions & 4 deletions packages/devtools_app/lib/src/logging/flutter/logging_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:flutter_icons/flutter_icons.dart';

import '../../flutter/controllers.dart';
import '../../flutter/screen.dart';
import '../../flutter/split.dart';
import '../../flutter/table.dart';
import '../../table_data.dart';
import '../../ui/flutter/service_extension_widgets.dart';
Expand Down Expand Up @@ -37,6 +38,7 @@ class LoggingScreenBody extends StatefulWidget {

class _LoggingScreenState extends State<LoggingScreenBody> {
LoggingController controller;
LogData selected;

@override
void didChangeDependencies() {
Expand Down Expand Up @@ -68,13 +70,23 @@ class _LoggingScreenState extends State<LoggingScreenBody> {
],
),
Expanded(
child: LogsTable(
data: controller.data,
child: Split(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we not think expandable table children are practical? showing a separate details view in the existing logging page was more for expediency than by design. I'd hope we can do better in Flutter and actually show the details inline.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is doable, just out of scope for this PR, as discussed offline.

axis: Split.axisFor(context, 1.0),
firstChild: LogsTable(
data: controller.data,
onItemSelected: _select,
),
secondChild: LogDetails(log: selected),
initialFirstFraction: 0.6,
),
),
]);
}

void _select(LogData log) {
setState(() => selected = log);
}

void _clearLogs() {
setState(() {
controller.clear();
Expand All @@ -83,8 +95,9 @@ class _LoggingScreenState extends State<LoggingScreenBody> {
}

class LogsTable extends StatelessWidget {
const LogsTable({Key key, this.data}) : super(key: key);
const LogsTable({Key key, this.data, this.onItemSelected}) : super(key: key);
final List<LogData> data;
final ItemCallback<LogData> onItemSelected;

List<ColumnData<LogData>> get columns => [
_WhenColumn(),
Expand All @@ -98,6 +111,114 @@ class LogsTable extends StatelessWidget {
columns: columns,
data: data,
keyFactory: (LogData data) => ValueKey<LogData>(data),
onItemSelected: onItemSelected,
);
}
}

class LogDetails extends StatefulWidget {
const LogDetails({Key key, @required this.log}) : super(key: key);
final LogData log;

@override
_LogDetailsState createState() => _LogDetailsState();
}

class _LogDetailsState extends State<LogDetails>
with SingleTickerProviderStateMixin {
AnimationController crossFade;

@override
void initState() {
super.initState();
crossFade = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should soon do an audit of all durations used and add some top level duration constants used throughout the app.

)..addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
_oldLog = widget.log;
crossFade.value = 0.0;
});
}
});
// We'll use a linear curve for this animation, so no curve needed.
_computeLogDetails();
}

@override
void dispose() {
crossFade.dispose();
super.dispose();
}

@override
void didUpdateWidget(LogDetails oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.log != oldWidget.log) {
_oldLog = oldWidget.log;
crossFade.forward();
}
_computeLogDetails();
}

Future<void> _computeLogDetails() async {
if (widget.log?.needsComputing ?? false) {
await widget.log.compute();
setState(() {});
}
}

LogData _oldLog;

bool showInspector(LogData log) => log != null && log.node != null;
bool showSimple(LogData log) =>
log != null && log.node == null && !log.needsComputing;

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: crossFade,
builder: (context, _) {
return Container(
color: Theme.of(context).cardColor,
child: Stack(children: [
Opacity(
opacity: crossFade.value,
child: _buildContent(context, widget.log),
),
Opacity(
opacity: 1 - crossFade.value,
child: _buildContent(context, _oldLog),
),
]),
);
},
);
}

Widget _buildContent(BuildContext context, LogData log) {
if (log == null) return const SizedBox();
if (log.needsComputing) {
return const Center(child: CircularProgressIndicator());
}
if (showInspector(log)) return _buildInspector(context, log);
if (showSimple(log)) return _buildSimpleLog(context, log);
return const SizedBox();
}

// TODO(https://github.com/flutter/devtools/issues/1370): implement this.
Widget _buildInspector(BuildContext context, LogData log) => const SizedBox();

Widget _buildSimpleLog(BuildContext context, LogData log) {
// TODO(https://github.com/flutter/devtools/issues/1339): Present with monospaced fonts.
return Scrollbar(
child: SingleChildScrollView(
child: Text(
log.prettyPrinted ?? '',
style: Theme.of(context).textTheme.subhead,
),
),
);
}
}
Expand Down Expand Up @@ -127,5 +248,6 @@ class _MessageColumn extends LogMessageColumn {

/// TODO(djshuckerow): Do better than showing raw HTML here.
@override
String getValue(LogData dataObject) => render(dataObject);
String getValue(LogData dataObject) =>
dataObject.summary ?? dataObject.details;
}
27 changes: 20 additions & 7 deletions packages/devtools_app/lib/src/logging/logging_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -276,14 +276,16 @@ class LoggingController {
final FrameInfo frame = FrameInfo.from(e.extensionData.data);

final String frameId = '#${frame.number}';
final String frameInfo =
'<span class="pre">$frameId ${frame.elapsedMs.toStringAsFixed(1).padLeft(4)}ms </span>';
final String frameInfoText =
'$frameId ${frame.elapsedMs.toStringAsFixed(1).padLeft(4)}ms ';
final String frameInfo = '<span class="pre">$frameInfoText</span>';
final String div = _createFrameDivHtml(frame);

_log(LogData(
e.extensionKind.toLowerCase(),
jsonEncode(e.extensionData.data),
e.timestamp,
summary: frameInfoText,
summaryHtml: '$frameInfo$div',
));
} else if (e.extensionKind == NavigationInfo.eventName) {
Expand Down Expand Up @@ -387,7 +389,7 @@ class LoggingController {
final InstanceRef stackTrace = InstanceRef.parse(logRecord['stackTrace']);

final String details = summary;
Future<String> detailsComputer;
Future<String> Function() detailsComputer;

// If the message string was truncated by the VM, or the error object or
// stackTrace objects were non-null, we need to ask the VM for more
Expand All @@ -396,7 +398,7 @@ class LoggingController {
if (messageRef.valueAsStringIsTruncated == true ||
_isNotNull(error) ||
_isNotNull(stackTrace)) {
detailsComputer = Future<String>(() async {
detailsComputer = () async {
// Get the full string value of the message.
String result =
await _retrieveFullStringValue(service, e.isolate, messageRef);
Expand Down Expand Up @@ -436,7 +438,7 @@ class LoggingController {
}

return result;
});
};
}

const int severeIssue = 1000;
Expand Down Expand Up @@ -656,17 +658,28 @@ class LogData {

final RemoteDiagnosticsNode node;
String _details;
Future<String> detailsComputer;
Future<String> Function() detailsComputer;

static const JsonEncoder prettyPrinter = JsonEncoder.withIndent(' ');

String get details => _details;

bool get needsComputing => detailsComputer != null;

Future<void> compute() async {
_details = await detailsComputer;
_details = await detailsComputer();
detailsComputer = null;
}

String get prettyPrinted {
if (needsComputing) return details;
try {
return prettyPrinter.convert(jsonDecode(details));
} catch (_) {
return details;
}
}

@override
String toString() => 'LogData($kind, $timestamp)';
}
Expand Down
1 change: 1 addition & 0 deletions packages/devtools_app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ flutter:
- assets/img/story_of_layout/empty_space.png

# See https://github.com/flutter/flutter/wiki/Desktop-shells#fonts
# TODO(https://github.com/flutter/devtools/issues/1339): Include a monospaced font.
fonts:
- family: Roboto
fonts:
Expand Down
Loading