Skip to content

Commit

Permalink
Base Logging V2 behaviour (flutter#7820)
Browse files Browse the repository at this point in the history
Implement the main interaction for the logging experience.
  • Loading branch information
CoderDake authored Jun 13, 2024
1 parent 2600bd2 commit 081057e
Show file tree
Hide file tree
Showing 17 changed files with 767 additions and 115 deletions.
2 changes: 1 addition & 1 deletion packages/devtools_app/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"dart.showTodos": false,
"dart.showTodos": false
}
1 change: 1 addition & 0 deletions packages/devtools_app/lib/devtools_app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export 'src/screens/inspector/inspector_tree_controller.dart';
export 'src/screens/logging/logging_controller.dart';
export 'src/screens/logging/logging_screen.dart';
export 'src/screens/logging/logging_screen_v2/logging_controller_v2.dart';
export 'src/screens/logging/logging_screen_v2/logging_model.dart';
export 'src/screens/logging/logging_screen_v2/logging_screen_v2.dart';
export 'src/screens/memory/framework/memory_controller.dart';
export 'src/screens/memory/framework/memory_screen.dart';
Expand Down
1 change: 1 addition & 0 deletions packages/devtools_app/lib/src/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ class DevToolsAppState extends State<DevToolsApp> with AutoDisposeMixin {
@override
void initState() {
super.initState();
setGlobal(GlobalKey<NavigatorState>, routerDelegate.navigatorKey);

// TODO(https://github.com/flutter/devtools/issues/6018): Once
// https://github.com/flutter/flutter/issues/129692 is fixed, disable the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import '../logging_controller.dart'
FrameInfo,
ImageSizesForFrame;
import '../logging_screen.dart';
import 'logging_model.dart';

final _log = Logger('logging_controller');

Expand Down Expand Up @@ -95,16 +96,8 @@ class LoggingControllerV2 extends DisposableController
/// See also [statusText].
Stream<String> get onLogStatusChanged => _logStatusController.stream;

List<LogDataV2> data = <LogDataV2>[];

final selectedLog = ValueNotifier<LogDataV2?>(null);

void _updateData(List<LogDataV2> logs) {
data = logs;
filteredData.replaceAll(logs);
_updateSelection();
_updateStatus();
}
final loggingModel = LoggingTableModel();

void _updateSelection() {
final selected = selectedLog.value;
Expand All @@ -120,7 +113,7 @@ class LoggingControllerV2 extends DisposableController
serviceConnection.consoleService.objectGroup as ObjectGroup;

String get statusText {
final totalCount = data.length;
final totalCount = loggingModel.logCount;
final showingCount = filteredData.value.length;

String label;
Expand All @@ -140,7 +133,9 @@ class LoggingControllerV2 extends DisposableController
}

void clear() {
_updateData([]);
loggingModel.clear();
_updateSelection();
_updateStatus();
serviceConnection.errorBadgeManager.clearErrors(LoggingScreen.id);
}

Expand Down Expand Up @@ -385,12 +380,7 @@ class LoggingControllerV2 extends DisposableController
}

void log(LogDataV2 log) {
data.add(log);
// TODO(danchevalier): Add filtersearch behavior
// TODO(danchevalier): Add Search behavior
// TODO(danchevalier): Add Selection update behavior
// TODO(danchevalier): Add status update
filteredData.add(log);
loggingModel.add(log);
}

static RemoteDiagnosticsNode? _findFirstSummary(RemoteDiagnosticsNode node) {
Expand Down Expand Up @@ -614,7 +604,10 @@ class LogDataV2 with SearchableDataMixin {
this.isError = false,
this.detailsComputer,
this.node,
});
}) {
// Fetch details immediately on creation.
unawaited(compute());
}

final String kind;
final int? timestamp;
Expand All @@ -629,15 +622,21 @@ class LogDataV2 with SearchableDataMixin {

String? get details => _details;

bool get needsComputing => detailsComputer != null;
ValueListenable<bool> get detailsComputed => _detailsComputed;
final _detailsComputed = ValueNotifier<bool>(false);

Future<void> compute() async {
_details = await detailsComputer!();
detailsComputer = null;
if (!detailsComputed.value) {
if (detailsComputer != null) {
_details = await detailsComputer!();
}
detailsComputer = null;
_detailsComputed.value = true;
}
}

String? prettyPrinted() {
if (needsComputing) {
if (!detailsComputed.value) {
return details;
}

Expand All @@ -650,6 +649,10 @@ class LogDataV2 with SearchableDataMixin {
}
}

String asLogDetails() {
return !detailsComputed.value ? '<fetching>' : prettyPrinted() ?? '';
}

@override
bool matchesSearchToken(RegExp regExpSearch) {
return kind.caseInsensitiveContains(regExpSearch) ||
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
// Copyright 2024 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';

import 'package:flutter/foundation.dart';

import '../../../shared/utils.dart';
import 'logging_controller_v2.dart';
import 'logging_table_row.dart';
import 'logging_table_v2.dart';

/// A class for holding state and state changes relevant to [LoggingControllerV2]
/// and [LoggingTableV2].
///
/// The [LoggingTableV2] table uses variable height rows. This model caches the
/// relevant heights and offsets so that the row heights only need to be calculated
/// once per parent width.
class LoggingTableModel extends ChangeNotifier {
LoggingTableModel() {
_worker = InterruptableChunkWorker(
callback: (index) => getFilteredLogHeight(
index,
),
progressCallback: (progress) => _cacheLoadProgress.value = progress,
);
}

final _logs = <LogDataV2>[];
final _filteredLogs = <LogDataV2>[];
final _selectedLogs = <int>{};

final cachedHeights = <int, double>{};
final cachedOffets = <int, double>{};
late final InterruptableChunkWorker _worker;

/// Represents the state of reloading the height caches.
///
/// When null, then the cache is not loading.
/// When double, then the value is represents how much progress has been made.
ValueListenable<double?> get cacheLoadProgress => _cacheLoadProgress;
final _cacheLoadProgress = ValueNotifier<double?>(null);

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

/// Update the width of the table.
///
/// If different from the last width, this will flush all of the calculated heights, and recalculate their heights
/// in the background.
set tableWidth(double width) {
if (width != _tableWidth) {
_tableWidth = width;
cachedHeights.clear();
cachedOffets.clear();
unawaited(_preFetchRowHeights());
}
}

/// Get the filtered log at [index].
LogDataV2 filteredLogAt(int index) => _filteredLogs[index];

double _tableWidth = 0.0;

/// The total number of logs being held by the [LoggingTableModel].
int get logCount => _logs.length;

/// The number of filtered logs.
int get filteredLogCount => _filteredLogs.length;

/// The number of selected logs.
int get selectedLogCount => _selectedLogs.length;

/// Add a log to the list of tracked logs.
void add(LogDataV2 log) {
// TODO(danchevalier): ensure that search and filter lists are updated here.

_logs.add(log);
_filteredLogs.add(log);
getFilteredLogHeight(
_logs.length - 1,
);
notifyListeners();
}

/// Clears all of the logs from the model.
void clear() {
_logs.clear();
_filteredLogs.clear();
notifyListeners();
}

/// Get the offset of a filtered log, at [index], from the top of the list of filtered logs.
double filteredLogOffsetAt(int _) {
throw Exception('Implement this when needed');
}

/// Get the height of a filtered Log at [index].
double getFilteredLogHeight(int index) {
final cachedHeight = cachedHeights[index];
if (cachedHeight != null) return cachedHeight;

return cachedHeights[index] = LoggingTableRow.calculateRowHeight(
_logs[index],
_tableWidth,
);
}

Future<bool> _preFetchRowHeights() async {
final didComplete = await _worker.doWork(_logs.length);
if (didComplete) {
_cacheLoadProgress.value = null;
}
return didComplete;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import 'package:devtools_app_shared/ui.dart';
import 'package:devtools_app_shared/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';

import '../../../service/service_extension_widgets.dart';
Expand All @@ -14,6 +13,7 @@ import '../../../shared/analytics/constants.dart' as gac;
import '../../../shared/screen.dart';
import '../../../shared/utils.dart';
import 'logging_controller_v2.dart';
import 'logging_table_v2.dart';

/// Presents logs from the connected app.
class LoggingScreenV2 extends Screen {
Expand Down Expand Up @@ -82,69 +82,10 @@ class _LoggingScreenBodyV2State extends State<LoggingScreenBodyV2>

@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
Expanded(
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Filter',
),
onSubmitted: (value) {},
),
),
Expanded(
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Search',
),
onSubmitted: (value) {},
),
),
],
),
Expanded(
child: LogsTableV2(filteredLogs),
),
],
);
}
}

class LogsTableV2 extends StatelessWidget {
LogsTableV2(this.filteredLogs, {super.key});

final _verticalController = ScrollController();
final List<LogDataV2> filteredLogs;

@override
Widget build(BuildContext context) {
return Scrollbar(
thumbVisibility: true,
controller: _verticalController,
child: CustomScrollView(
controller: _verticalController,
slivers: <Widget>[
SliverVariedExtentList.builder(
itemCount: filteredLogs.length,
itemBuilder: _buildRow,
itemExtentBuilder: _calculateRowHeight,
),
],
),
return LoggingTableV2(
model: controller.loggingModel,
);
}

Widget? _buildRow(BuildContext context, int index) {
return Text('Row ${filteredLogs[index].summary}');
}

double _calculateRowHeight(int index, SliverLayoutDimensions dimensions) {
return 15.0;
}
}

class LoggingSettingsDialogV2 extends StatelessWidget {
Expand Down
Loading

0 comments on commit 081057e

Please sign in to comment.