blob: 5a65a12923c54b223414cf23f7a535386b407fb2 [file] [log] [blame]
// Copyright 2018 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.
// This file contain higher level utils, i.e. utils that depend on
// other libraries in this package.
// Utils, that do not have dependencies, should go to primitives/utils.dart.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:vm_service/vm_service.dart';
import '../../devtools.dart' as devtools;
import 'common_widgets.dart';
import 'connected_app.dart';
import 'globals.dart';
import 'theme.dart';
final _log = Logger('lib/src/shared/utils');
/// Attempts to copy a String of `data` to the clipboard.
///
/// Shows a `successMessage` [Notification] on the passed in `context`.
Future<void> copyToClipboard(
String data,
String? successMessage,
) async {
await Clipboard.setData(
ClipboardData(
text: data,
),
);
if (successMessage != null) notificationService.push(successMessage);
}
/// Logging to debug console only in debug runs.
void debugLogger(String message) {
assert(
() {
_log.info(message);
return true;
}(),
);
}
double scaleByFontFactor(double original) {
return (original * ideTheme.fontSizeFactor).roundToDouble();
}
bool isDense() {
return preferences.denseModeEnabled.value || isEmbedded();
}
bool isEmbedded() => ideTheme.embed;
extension VmExtension on VM {
List<IsolateRef> isolatesForDevToolsMode() {
final vmDeveloperModeEnabled = preferences.vmDeveloperModeEnabled.value;
final vmIsolates = isolates ?? <IsolateRef>[];
return [
...vmIsolates,
if (vmDeveloperModeEnabled || vmIsolates.isEmpty)
...systemIsolates ?? <IsolateRef>[],
];
}
String get deviceDisplay {
return [
'$targetCPU',
if (architectureBits != null && architectureBits != -1)
'($architectureBits bit)',
operatingSystem,
].join(' ');
}
}
List<ConnectionDescription> generateDeviceDescription(
VM vm,
ConnectedApp connectedApp, {
bool includeVmServiceConnection = true,
}) {
var version = vm.version!;
// Convert '2.9.0-13.0.dev (dev) (Fri May ... +0200) on "macos_x64"' to
// '2.9.0-13.0.dev'.
if (version.contains(' ')) {
version = version.substring(0, version.indexOf(' '));
}
final flutterVersion = connectedApp.flutterVersionNow;
ConnectionDescription? vmServiceConnection;
if (includeVmServiceConnection && serviceManager.service != null) {
final description = serviceManager.service!.connectedUri.toString();
vmServiceConnection = ConnectionDescription(
title: 'VM Service Connection',
description: description,
actions: [
CopyToClipboardControl(
dataProvider: () => description,
size: defaultIconSize,
),
],
);
}
return [
ConnectionDescription(title: 'CPU / OS', description: vm.deviceDisplay),
ConnectionDescription(
title: 'Connected app type',
description: connectedApp.display,
),
if (vmServiceConnection != null) vmServiceConnection,
ConnectionDescription(title: 'Dart Version', description: version),
if (flutterVersion != null) ...{
ConnectionDescription(
title: 'Flutter Version',
description: '${flutterVersion.version} / ${flutterVersion.channel}',
),
ConnectionDescription(
title: 'Framework / Engine',
description: '${flutterVersion.frameworkRevision} / '
'${flutterVersion.engineRevision}',
),
},
];
}
/// This method should be public, because it is used by g3 specific code.
List<String> issueLinkDetails() {
final issueDescriptionItems = [
'<-- Please describe your problem here. Be sure to include repro steps. -->',
'___', // This will create a separator in the rendered markdown.
'**DevTools version**: ${devtools.version}',
];
final vm = serviceManager.vm;
final connectedApp = serviceManager.connectedApp;
if (vm != null && connectedApp != null) {
final descriptionEntries = generateDeviceDescription(
vm,
connectedApp,
includeVmServiceConnection: false,
);
final deviceDescription = descriptionEntries
.map((entry) => '${entry.title}: ${entry.description}');
issueDescriptionItems.addAll([
'**Connected Device**:',
...deviceDescription,
]);
}
return issueDescriptionItems;
}
typedef ProvidedControllerCallback<T> = void Function(T);
/// Mixin that provides a [controller] from package:provider for a State class.
///
/// [initController] must be called from [State.didChangeDependencies]. If
/// [initController] returns false, return early from [didChangeDependencies] to
/// avoid calling any initialization code that should only be called once for a
/// controller. See [initController] documentation below for more details.
mixin ProvidedControllerMixin<T, V extends StatefulWidget> on State<V> {
T get controller => _controller!;
T? _controller;
final _callWhenReady = <ProvidedControllerCallback>[];
/// Calls the provided [callback] once [_controller] has been initialized.
///
/// The [callback] will be called immediately if [_controller] has already
/// been initialized.
void callWhenControllerReady(ProvidedControllerCallback callback) {
if (_controller != null) {
callback(_controller!);
} else {
_callWhenReady.add(callback);
}
}
/// Initializes [_controller] from package:provider.
///
/// This method should be called in [didChangeDependencies]. Returns whether
/// or not a new controller was provided upon subsequent calls to
/// [initController].
///
/// This method will commonly be used to return early from
/// [didChangeDependencies] when initialization code should not be run again
/// if the provided controller has not changed.
///
/// E.g. `if (!initController()) return;`
bool initController() {
final newController = Provider.of<T>(context);
if (newController == _controller) return false;
final firstInitialization = _controller == null;
_controller = newController;
if (firstInitialization) {
for (final callback in _callWhenReady) {
callback(_controller!);
}
_callWhenReady.clear();
}
return true;
}
}
class ConnectionDescription {
ConnectionDescription({
required this.title,
required this.description,
this.actions = const <Widget>[],
});
final String title;
final String description;
final List<Widget> actions;
}