新增网络可视化请求工具

This commit is contained in:
2026-07-22 09:24:48 +08:00
parent 4ff93edaab
commit d6e80df5bf
230 changed files with 92710 additions and 51 deletions
@@ -0,0 +1,3 @@
## 0.0.1
* First version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,3 @@
# flutter_ume_kit_channel_monitor
Used to monitor channel transmissions in Flutter.
@@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
@@ -0,0 +1,3 @@
library flutter_ume_kit_channel_monitor;
export 'src/channel_plugin.dart';
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'package:flutter_ume_kit_channel_monitor/src/ui/channel_pages.dart';
import 'dart:convert';
import 'core/channel_binding.dart';
import 'icon.dart' as icon;
class ChannelPlugin extends Pluggable {
ChannelPlugin() {
ChannelBinding.ensureInitialized();
}
@override
Widget buildWidget(BuildContext? context) {
return const ChannelPages();
}
@override
String get displayName => 'Channel Monitor';
@override
ImageProvider<Object> get iconImageProvider =>
MemoryImage(base64Decode(icon.iconData));
@override
String get name => 'Channel Monitor';
@override
void onTrigger() {}
}
@@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/ume_binary_messenger.dart';
class ChannelBinding extends WidgetsFlutterBinding {
static WidgetsBinding? ensureInitialized() {
if (WidgetsBinding.instance == null) {
// make sure init this before WidgetsFlutterBinding ensureInitialized called
ChannelBinding();
}
return WidgetsBinding.instance;
}
@override
@protected
// 替换 BinaryMessenger
BinaryMessenger createBinaryMessenger() {
return UmeBinaryMessenger.binaryMessenger;
}
}
@@ -0,0 +1,40 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ui' as ui;
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_info_model.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_store.dart';
class _ChannelController {
final StandardMethodCodec codec = const StandardMethodCodec();
void trackChannelEvent(String channel, DateTime sendTime, bool send,
{ByteData? data,
MessageHandler? handler,
ui.PlatformMessageResponseCallback? callback}) {
MethodCall call = const MethodCall('unknown');
try {
call = codec.decodeMethodCall(data);
} catch (e) {
debugPrint('decode data failed, caused by: $e');
debugPrint('data: ${data.toString()}');
}
final ChannelInfoModel model = ChannelInfoModel(
type: ChannelType.method,
channelName: channel,
direction: send
? TransDirection.flutterToNative
: TransDirection.nativeToFlutter,
methodName: call.method,
timestamp: sendTime,
duration: DateTime.now().difference(sendTime),
sendDataSize: send ? (data?.elementSizeInBytes ?? 0) : 0,
sendData: send ? call.arguments : null,
receiveData: send ? null : call.arguments,
receiveDataSize: send ? 0 : (data?.elementSizeInBytes ?? 0),
);
channelStore.saveChannelInfo(model);
}
}
_ChannelController channelController = _ChannelController();
@@ -0,0 +1,45 @@
class ChannelInfoModel {
// channel 传输方向
final TransDirection direction;
// channel 名称
final String channelName;
// 方法名称
final String methodName;
// channel 类型(Method / Event / Basic
final ChannelType type;
// 发送时间
final DateTime timestamp;
// 发送耗时
final Duration duration;
// 输入数据大小(可能为空)
final int? sendDataSize;
// 返回数据大小(可能为空)
final int? receiveDataSize;
// 是否为系统channel
bool get isSystemChannel => methodName.contains('flutter/');
// 发送数据内容(可能为空)
final dynamic sendData;
// 接收的数据内容(可能为空)
final dynamic receiveData;
ChannelInfoModel({
required this.channelName,
required this.direction,
required this.methodName,
required this.timestamp,
required this.duration,
required this.sendDataSize,
required this.type,
this.receiveDataSize,
this.sendData,
this.receiveData,
});
@override
String toString() {
return 'ChannelInfoModel{direction: $direction, channelName: $channelName, methodName: $methodName, type: $type, timestamp: $timestamp, duration: $duration, sendDataSize: $sendDataSize, receiveDataSize: $receiveDataSize, sendData: $sendData, receiveData: $receiveData}';
}
}
enum TransDirection { flutterToNative, nativeToFlutter }
enum ChannelType { event, method, basic }
@@ -0,0 +1,39 @@
import 'dart:async';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_info_model.dart';
import 'package:rxdart/rxdart.dart';
class _ChannelStore {
final BehaviorSubject<List<String>> _orderedChannelNamePublisher =
BehaviorSubject();
final Map<String, List<ChannelInfoModel>> _orderedChannelEvents = {};
Stream<List<String>> get channelNamePublisher =>
_orderedChannelNamePublisher.stream;
void saveChannelInfo(ChannelInfoModel model) {
if (_orderedChannelEvents[model.channelName] == null) {
_orderedChannelEvents[model.channelName] = [];
}
_orderedChannelEvents[model.channelName]!.add(model);
refresh();
}
void getChannelByName(String name, Sink sink) {
if (name == '') {
return;
}
sink.add(_orderedChannelEvents[name]);
}
void clearChannelRecords() {
refresh();
}
void refresh() {
_orderedChannelNamePublisher.add(_orderedChannelEvents.keys.toList());
}
}
_ChannelStore channelStore = _ChannelStore();
@@ -0,0 +1,88 @@
import 'dart:async';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_controller.dart';
// 在 _DefaultBinaryMessenger 的基础上增加数据监控
class UmeBinaryMessenger extends BinaryMessenger {
static UmeBinaryMessenger binaryMessenger = UmeBinaryMessenger._();
UmeBinaryMessenger._();
@override
Future<void> handlePlatformMessage(String channel, ByteData? data,
ui.PlatformMessageResponseCallback? callback) async {
DateTime now = DateTime.now();
ui.channelBuffers.push(channel, data, (ByteData? data) {
if (callback != null) {
callback(data);
}
channelController.trackChannelEvent(channel, now, false,
data: data, callback: callback);
// print(
// '\n handlePlatformMessage: channel: $channel \n data:${data.toString()} \n');
});
}
@override
Future<ByteData?>? send(String channel, ByteData? message) {
DateTime now = DateTime.now();
final Completer<ByteData?> completer = Completer<ByteData?>();
// ui.PlatformDispatcher.instance is accessed directly instead of using
// ServicesBinding.instance.platformDispatcher because this method might be
// invoked before any binding is initialized. This issue was reported in
// #27541. It is not ideal to statically access
// ui.PlatformDispatcher.instance because the PlatformDispatcher may be
// dependency injected elsewhere with a different instance. However, static
// access at this location seems to be the least bad option.
// TODO(ianh): Use ServicesBinding.instance once we have better diagnostics
// on that getter.
ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message,
(ByteData? reply) {
try {
completer.complete(reply);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context:
ErrorDescription('during a platform message response callback'),
));
}
});
channelController.trackChannelEvent(channel, now, true, data: message);
// print('\n send \n channel: $channel \n message: ${message.toString()} \n}');
return completer.future;
}
@override
void setMessageHandler(String channel, MessageHandler? handler) {
DateTime now = DateTime.now();
if (handler == null) {
ui.channelBuffers.clearListener(channel);
} else {
ui.channelBuffers.setListener(channel,
(ByteData? data, ui.PlatformMessageResponseCallback callback) async {
ByteData? response;
try {
response = await handler(data);
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
library: 'services library',
context: ErrorDescription('during a platform message callback'),
));
} finally {
callback(response);
}
});
}
channelController.trackChannelEvent(channel, now, false, handler: handler);
// print(
// '\n setMessageHandler \n channel: $channel \n handler: ${handler.toString()} \n');
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,216 @@
import 'package:flutter/material.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_info_model.dart';
import 'package:flutter_ume_kit_channel_monitor/src/core/channel_store.dart';
import 'package:flutter_ume_kit_channel_monitor/src/ui/template_ui.dart';
import 'package:rxdart/rxdart.dart';
class ChannelPages extends StatefulWidget {
const ChannelPages({Key? key}) : super(key: key);
@override
State<ChannelPages> createState() => _ChannelPagesState();
}
class _ChannelPagesState extends State<ChannelPages> {
int currentIndex = 0;
String currentChannel = '';
ChannelInfoModel? currentModel;
@override
Widget build(BuildContext context) {
return IndexedStack(
index: currentIndex,
children: [
buildOrderedChannels(context),
buildSingleChannelPage(),
buildChannelInfoPage(),
],
);
}
ChannelInfoPage buildChannelInfoPage() {
return ChannelInfoPage(
channelInfoModel: currentModel,
onBackPressed: () {
currentIndex = 1;
currentModel = null;
setState(() {});
},
);
}
SingleChannelPage buildSingleChannelPage() {
return SingleChannelPage(
title: currentChannel,
onBackPressed: () {
currentIndex = 0;
currentChannel = '';
setState(() {});
},
onTap: (model) {
currentIndex = 2;
currentModel = model;
setState(() {});
},
);
}
Widget buildOrderedChannels(BuildContext context) {
return TemplatePageWidget(
title: 'Ordered Channels',
body: StreamBuilder<List<String>>(
stream: channelStore.channelNamePublisher,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final List<String> channels = snapshot.data as List<String>;
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: channels.length,
itemBuilder: (context, index) {
return TemplateItemWidget(
title: channels[index],
onTap: () {
currentChannel = channels[index];
currentIndex = 1;
setState(() {});
});
});
}),
);
}
}
class SingleChannelPage extends StatefulWidget {
final String title;
final VoidCallback onBackPressed;
final OnChannelModelSelected onTap;
const SingleChannelPage({
Key? key,
required this.title,
required this.onBackPressed,
required this.onTap,
}) : super(key: key);
@override
State<SingleChannelPage> createState() => _SingleChannelPageState();
}
class _SingleChannelPageState extends State<SingleChannelPage> {
final BehaviorSubject<List<ChannelInfoModel>> _publisher = BehaviorSubject();
@override
void didUpdateWidget(SingleChannelPage oldWidget) {
super.didUpdateWidget(oldWidget);
channelStore.getChannelByName(widget.title, _publisher.sink);
}
@override
Widget build(BuildContext context) {
return TemplatePageWidget(
onBackPressed: widget.onBackPressed,
title: widget.title,
body: StreamBuilder<List<ChannelInfoModel>>(
stream: _publisher,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final List<ChannelInfoModel> channels =
snapshot.data as List<ChannelInfoModel>;
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: channels.length,
itemBuilder: (context, index) {
return TemplateItemWidget(
title: channels[index].methodName,
onTap: () => widget.onTap(channels[index]),
);
});
}),
);
}
}
class ChannelInfoPage extends StatelessWidget {
final ChannelInfoModel? channelInfoModel;
final VoidCallback onBackPressed;
const ChannelInfoPage(
{Key? key, this.channelInfoModel, required this.onBackPressed})
: super(key: key);
@override
Widget build(BuildContext context) {
if (channelInfoModel == null) {
return Container();
}
ChannelInfoModel model = channelInfoModel!;
bool isFlutterToNative = model.direction == TransDirection.flutterToNative;
return TemplatePageWidget(
title: 'Method : ${model.methodName}',
body: SingleChildScrollView(
child: Table(
border: TableBorder.all(color: Colors.black),
children: [
buildCell('Channel Name', model.channelName),
buildCell('Channel Type',
'${model.type.toString().substring(12)} channel'),
buildCell(
'Is System Channel', model.isSystemChannel ? 'yes' : 'no'),
buildCell(
'Trans Direction', model.direction.toString().substring(15)),
buildCell('Time Cost \n(millisecond)',
model.timestamp.millisecond.toString()),
if (isFlutterToNative)
buildCell('Send Data Size', model.sendDataSize.toString()),
if (isFlutterToNative)
...buildDataTable('Send Data', model.sendData),
if (!isFlutterToNative)
buildCell('Receive Data Size', model.receiveDataSize.toString()),
if (!isFlutterToNative)
...buildDataTable('Receive Data', model.receiveData),
],
),
),
onBackPressed: onBackPressed,
);
}
List<TableRow> buildDataTable(String title, dynamic data) {
if (data is Map) {
return (data.keys
.map((keyword) => TableRow(children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(keyword.toString())),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(data[keyword].toString()),
),
]))
.toList())
..insert(0, buildCell(title, ''));
}
return [buildCell(title, data.toString())];
}
TableRow buildCell(String type, String desc) {
return TableRow(children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(alignment: Alignment.centerLeft, child: Text(type)),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(desc.toString()),
),
]);
}
}
typedef OnChannelModelSelected = void Function(ChannelInfoModel model);
@@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
class TemplatePageWidget extends StatelessWidget {
final String title;
final Widget body;
final VoidCallback? onBackPressed;
const TemplatePageWidget({
Key? key,
required this.title,
required this.body,
this.onBackPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: MediaQuery.of(context).padding.top),
if (onBackPressed != null)
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onBackPressed,
child: const Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: Icon(
Icons.navigate_before_rounded,
size: 42,
),
),
),
Expanded(
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildTitle(),
const SizedBox(height: 20),
buildBody(),
],
),
))
],
),
);
}
Expanded buildBody() {
return Expanded(
child: DecoratedBox(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.95),
borderRadius: BorderRadius.circular(12)),
child: body),
);
}
Text buildTitle() {
return Text(title,
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold));
}
}
class TemplateItemWidget extends StatelessWidget {
final String title;
final VoidCallback onTap;
const TemplateItemWidget({Key? key, required this.title, required this.onTap})
: super(key: key);
@override
Widget build(BuildContext context) {
return Container(
height: 54,
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.grey.shade400))),
margin: const EdgeInsets.symmetric(horizontal: 8),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => onTap(),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: Text(title, overflow: TextOverflow.clip)),
const Icon(
Icons.navigate_next_rounded,
color: Colors.grey,
),
],
),
),
);
}
}
@@ -0,0 +1,21 @@
name: flutter_ume_kit_channel_monitor
description: channel monitor kit for flutter_ume
version: 0.0.1
homepage: https://github.com/bytedance/flutter_ume
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=1.17.0"
dependencies:
flutter:
sdk: flutter
flutter_ume: ^1.0.1
rxdart: ^0.27.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ">=1.0.0 <3.0.0"
flutter:
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: unknown
project_type: package
@@ -0,0 +1,43 @@
# Changelog
## 1.1.0
* 增加 `consolePrint` 方法,用于适配其他日志库。
* Add `consolePrint` method to adapt other log libraries.
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 更新版本号
* Update version
## 0.2.1
* null-safety 正式版本
* Null-Safety formal version.
## 0.2.0-dev.0
* 适配 null-safety
* Adapted Null-Safety.
## 0.1.0
* 发布开源版本。
* Release opensource version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,57 @@
# flutter_ume_kit_console
[flutter_ume](https://pub.dev/packages/flutter_ume) 是由字节跳动 Flutter Infra 团队出品的应用内调试工具平台。
flutter_ume_kit_console 是 flutter_ume 的日志查看插件包。接入方式请见 [flutter_ume](https://pub.dev/packages/flutter_ume)。
此插件无法直接监听 `print``developer.log`,需要使用 `debugPrint` 方法打印日志,或者结合 [logging](https://pub.dev/packages/logging)、[logger](https://pub.dev/packages/logger) 等日志库使用。
如果使用其他日志库,可以调用 `consolePrint` 将日志输出到应用内控制台。
```dart
// logging
Logger.root.onRecord.listen((record) {
consolePrint(record.message);
});
```
```dart
// logger
class UmeConsoleOutput extends LogOutput {
@override
void output(OutputEvent event) {
for (var line in event.lines) {
consolePrint(line);
}
}
}
```
----
[flutter_ume](https://pub.dev/packages/flutter_ume) is an in-app debug kits platform produced for Flutter apps by ByteDance Flutter Infra team.
flutter_ume_kit_console is the Console kits package of flutter_ume. Please visit [flutter_ume](https://pub.dev/packages/flutter_ume) for details.
This plugin cannot listen to `print` or `developer.log` directly. You need to use the `debugPrint` method to print logs, or use it with another log library such as [logging](https://pub.dev/packages/logging) or [logger](https://pub.dev/packages/logger).
If you use another log library, you can call `consolePrint` to print the log to the in-app console.
```dart
// logging
Logger.root.onRecord.listen((record) {
consolePrint(record.message);
});
```
```dart
// logger
class UmeConsoleOutput extends LogOutput {
@override
void output(OutputEvent event) {
for (var line in event.lines) {
consolePrint(line);
}
}
}
```
@@ -0,0 +1,170 @@
SF:lib/console/show_date_time_style.dart
DA:1,3
DA:3,2
DA:5,2
DA:7,2
DA:9,2
DA:11,2
DA:18,2
DA:20,2
DA:22,2
DA:24,2
DA:26,2
LF:11
LH:11
end_of_record
SF:lib/console/console_manager.dart
DA:10,6
DA:14,4
DA:16,4
DA:20,2
DA:22,2
DA:24,2
DA:25,2
DA:26,2
DA:27,2
DA:28,4
DA:30,0
DA:34,8
DA:35,6
DA:36,4
DA:38,2
DA:45,2
DA:47,2
DA:48,2
DA:49,6
DA:51,2
DA:56,2
DA:57,4
DA:58,2
DA:61,2
LF:24
LH:23
end_of_record
SF:lib/console/console_panel.dart
DA:16,1
DA:17,1
DA:20,1
DA:21,1
DA:23,1
DA:26,1
DA:28,2
DA:30,1
DA:33,0
DA:36,1
DA:39,0
DA:40,0
DA:42,0
DA:43,0
DA:55,1
DA:57,2
DA:58,1
DA:59,1
DA:60,1
DA:61,1
DA:62,1
DA:65,1
DA:67,1
DA:68,1
DA:69,3
DA:70,0
DA:71,0
DA:73,1
DA:74,2
DA:75,2
DA:77,2
DA:79,2
DA:80,3
DA:81,5
DA:82,1
DA:83,1
DA:84,0
DA:85,0
DA:86,0
DA:87,0
DA:89,3
DA:92,2
DA:93,2
DA:94,4
DA:99,1
DA:100,1
DA:101,4
DA:102,4
DA:103,3
DA:104,1
DA:106,3
DA:110,1
DA:112,1
DA:113,1
DA:115,10
DA:117,1
DA:119,9
DA:120,2
DA:122,1
DA:124,9
DA:126,1
DA:135,1
DA:137,1
DA:138,1
DA:140,2
DA:141,1
DA:142,1
DA:143,2
DA:144,1
DA:145,1
DA:148,1
DA:149,2
DA:150,1
DA:151,1
DA:152,1
DA:158,1
DA:160,8
DA:161,1
DA:172,1
DA:173,1
DA:177,1
DA:178,1
DA:179,1
DA:180,1
DA:181,2
DA:183,0
DA:185,2
DA:186,1
DA:188,1
DA:191,1
DA:195,1
DA:196,1
DA:197,1
DA:200,1
DA:204,1
DA:210,1
DA:211,1
DA:213,1
DA:217,1
DA:218,1
DA:220,1
DA:224,2
DA:225,1
DA:227,1
DA:231,1
DA:232,1
DA:234,1
DA:238,1
DA:243,1
DA:244,6
DA:245,2
DA:246,2
DA:247,2
DA:250,1
DA:251,2
DA:252,2
DA:253,1
DA:254,1
DA:257,1
DA:260,1
DA:261,2
DA:264,8
DA:265,3
LF:123
LH:111
end_of_record
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#88ca03" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">91%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">91%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,66 @@
import 'dart:async';
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:tuple/tuple.dart';
const int maxLine = 1000;
class ConsoleManager {
static final Queue<Tuple2<DateTime, String>> _logData = Queue();
// ignore: close_sinks
static StreamController? _logStreamController;
static Queue<Tuple2<DateTime, String>> get logData => _logData;
static StreamController? get streamController => _getLogStreamController();
static DebugPrintCallback? _originalDebugPrint;
static StreamController? _getLogStreamController() {
if (_logStreamController == null) {
_logStreamController = StreamController.broadcast();
var transformer =
StreamTransformer<dynamic, Tuple2<DateTime, String>>.fromHandlers(
handleData: (str, sink) {
final now = DateTime.now();
if (str is String) {
sink.add(Tuple2(now, str));
} else {
sink.add(Tuple2(now, str.toString()));
}
});
_logStreamController!.stream.transform(transformer).listen((value) {
if (_logData.length < maxLine) {
_logData.addFirst(value);
} else {
_logData.removeLast();
}
});
}
return _logStreamController;
}
static redirectDebugPrint() {
if (_originalDebugPrint != null) return;
_originalDebugPrint = debugPrint;
debugPrint = (String? message, {int? wrapWidth}) {
ConsoleManager.streamController!.sink.add(message);
if (_originalDebugPrint != null) {
_originalDebugPrint!(message, wrapWidth: wrapWidth);
}
};
}
static clearLog() {
logData.clear();
_logStreamController!.add('UME CONSOLE == ClearLog');
}
@visibleForTesting
static clearRedirect() {
debugPrint = _originalDebugPrint!;
_originalDebugPrint = null;
}
}
@@ -0,0 +1,264 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:share/share.dart';
import 'package:tuple/tuple.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'package:flutter_ume_kit_console/console/console_manager.dart';
import 'package:flutter_ume_kit_console/console/icon.dart' as icon;
import 'package:flutter_ume/util/floating_widget.dart';
import 'package:flutter_ume/util/store_mixin.dart';
import 'package:flutter_ume_kit_console/console/show_date_time_style.dart';
class Console extends StatefulWidget implements PluggableWithStream {
Console({Key? key}) {
ConsoleManager.redirectDebugPrint();
}
@override
ConsoleState createState() => ConsoleState();
@override
Widget buildWidget(BuildContext? context) => this;
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'Console';
@override
String get displayName => 'Console';
@override
void onTrigger() {}
@override
Stream get stream => ConsoleManager.streamController!.stream;
@override
StreamFilter get streamFilter => (e) => true;
}
class ConsoleState extends State<Console>
with WidgetsBindingObserver, StoreMixin {
List<Tuple2<DateTime, String>> _logList = <Tuple2<DateTime, String>>[];
StreamSubscription? _subscription;
ScrollController? _controller;
ShowDateTimeStyle? _showDateTimeStyle;
bool _showFilter = false;
RegExp? _filterExp;
@override
void dispose() {
_subscription?.cancel();
_subscription = null;
_controller = null;
_showDateTimeStyle = ShowDateTimeStyle.datetime;
_showFilter = false;
super.dispose();
}
@override
void initState() {
super.initState();
_showDateTimeStyle = ShowDateTimeStyle.none;
fetchWithKey('console_panel_datetime_style').then((value) async {
if (value != null && value is int) {
_showDateTimeStyle = styleById(value);
} else {
_showDateTimeStyle = ShowDateTimeStyle.datetime;
await storeWithKey(
'console_panel_datetime_style', idByStyle(_showDateTimeStyle!));
}
setState(() {});
});
_controller = ScrollController();
_logList = ConsoleManager.logData.toList();
_subscription = ConsoleManager.streamController!.stream.listen((onData) {
if (mounted) {
if (_filterExp != null) {
_logList = ConsoleManager.logData.where((e) {
return _filterExp!.hasMatch(e.item1.toString()) ||
_filterExp!.hasMatch(e.item2);
}).toList();
} else {
_logList = ConsoleManager.logData.toList();
}
setState(() {});
_controller!.jumpTo(
_controller!.position.maxScrollExtent + 22); // 22 is a magic number
}
});
}
void _refreshConsole() {
if (_filterExp != null) {
_logList = ConsoleManager.logData.where((e) {
return _filterExp!.hasMatch(e.item1.toString()) ||
_filterExp!.hasMatch(e.item2);
}).toList();
} else {
_logList = ConsoleManager.logData.toList();
}
}
String _dateTimeString(int logIndex) {
String result = '';
switch (_showDateTimeStyle) {
case ShowDateTimeStyle.datetime:
result =
'${_logList[_logList.length - logIndex - 1].item1.toString().padRight(26, '0')}';
break;
case ShowDateTimeStyle.time:
result =
'${_logList[_logList.length - logIndex - 1].item1.toString().padRight(26, '0')}'
.substring(11);
break;
case ShowDateTimeStyle.timestamp:
result =
'${_logList[_logList.length - logIndex - 1].item1.millisecondsSinceEpoch}';
break;
case ShowDateTimeStyle.none:
result = '';
break;
default:
break;
}
return result;
}
@override
Widget build(BuildContext context) {
return FloatingWidget(
contentWidget: Container(
color: Colors.black,
child: Stack(children: [
ListView.builder(
controller: _controller,
itemCount: _logList.length,
itemBuilder: (BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.only(
left: 8, right: 8, top: 3, bottom: 3),
child: RichText(
text: TextSpan(children: [
TextSpan(
text: _dateTimeString(index),
style: TextStyle(
color: Colors.white60,
fontFamily: 'Courier',
fontSize: 16,
fontWeight: FontWeight.w400,
)),
TextSpan(
text:
'${_logList[_logList.length - index - 1].item2}',
style: TextStyle(
color: Colors.white,
fontFamily: 'Courier',
fontSize: 16,
fontWeight: FontWeight.w400,
)),
]),
),
);
},
),
if (_showFilter)
Positioned(
left: 0,
right: 0,
top: 0,
child: Container(
child: TextField(
onChanged: (value) {
if (value.isNotEmpty) {
_filterExp = RegExp(value);
} else {
_filterExp = null;
}
setState(() {});
_refreshConsole();
},
style: TextStyle(
fontSize: 16,
),
decoration: InputDecoration(
fillColor: Colors.white,
filled: true,
hintText: 'RegExp',
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(50),
),
),
contentPadding: EdgeInsets.only(
top: 0,
bottom: 0,
),
prefixIcon: Icon(Icons.search),
),
),
),
),
])),
toolbarActions: [
Tuple3(
'Style',
Icon(
Icons.access_time,
size: 20,
),
_triggerShowDate),
Tuple3(
'Clear',
Icon(
Icons.do_not_disturb,
size: 20,
),
() => ConsoleManager.clearLog()),
Tuple3(
'Filter',
Icon(
Icons.search,
size: 20,
),
_triggerFilter),
Tuple3(
'Share',
Icon(
Icons.share,
size: 20,
),
_share),
],
);
}
void _triggerShowDate() async {
_showDateTimeStyle = styleById((idByStyle(_showDateTimeStyle!) + 1) % 4);
await storeWithKey(
'console_panel_datetime_style', idByStyle(_showDateTimeStyle!));
setState(() {});
}
void _triggerFilter() {
setState(() {
_showFilter = !_showFilter;
if (!_showFilter) {
_filterExp = null;
}
});
_refreshConsole();
}
Future<void> _share() async {
if (_logList.isEmpty) {
return;
}
final l = _logList.map((e) => '${e.item1.toString()} ${e.item2}').toList();
return Share.share("${l.join('\n')}");
}
}
@@ -0,0 +1,6 @@
import 'console_manager.dart';
/// Print the message to the console.
void consolePrint(String message) {
ConsoleManager.streamController!.add(message);
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
enum ShowDateTimeStyle { datetime, time, timestamp, none }
ShowDateTimeStyle styleById(int id) {
switch (id) {
case 0:
return ShowDateTimeStyle.datetime;
case 1:
return ShowDateTimeStyle.time;
case 2:
return ShowDateTimeStyle.timestamp;
case 3:
return ShowDateTimeStyle.none;
default:
return ShowDateTimeStyle.datetime;
}
}
int idByStyle(ShowDateTimeStyle style) {
switch (style) {
case ShowDateTimeStyle.datetime:
return 0;
case ShowDateTimeStyle.time:
return 1;
case ShowDateTimeStyle.timestamp:
return 2;
case ShowDateTimeStyle.none:
return 3;
}
}
@@ -0,0 +1,4 @@
library flutter_ume_kit_console;
export 'console/console_panel.dart';
export 'console/console_print.dart';
@@ -0,0 +1,27 @@
name: flutter_ume_kit_console
description: Show debugPrint kit for flutter_ume.
version: 1.1.0
homepage: https://github.com/bytedance/flutter_ume
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
tuple: ^2.0.0
share: ^2.0.4
shared_preferences: ^2.0.6
flutter_ume: ">=1.0.0 <2.0.0"
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.12
flutter_coverage_badge:
git:
url: https://github.com/smileShirely/flutter_coverage_badge.git
ref: 59b7580f406bb712e9d9049c8c99212946e34f65
flutter:
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: unknown
project_type: package
@@ -0,0 +1,37 @@
# Changelog
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 更新版本号
* Update version
## 0.2.1
* null-safety 正式版本
* Null-Safety formal version.
## 0.2.0-dev.0
* 适配 null-safety
* Adapted Null-Safety.
## 0.1.0
* 发布开源版本。
* Release opensource version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,11 @@
# flutter_ume_kit_device
[flutter_ume](https://pub.dev/packages/flutter_ume) 是由字节跳动 Flutter Infra 团队出品的应用内调试工具平台。
flutter_ume_kit_device 是 flutter_ume 的设备信息插件包。接入方式请见 [flutter_ume](https://pub.dev/packages/flutter_ume)。
----
[flutter_ume](https://pub.dev/packages/flutter_ume) is an in-app debug kits platform produced for Flutter apps by ByteDance Flutter Infra team.
flutter_ume_kit_device is the Device Info kits package of flutter_ume. Please visit [flutter_ume](https://pub.dev/packages/flutter_ume) for details.
@@ -0,0 +1,160 @@
SF:lib/components/device_info/device_info_panel.dart
DA:11,1
DA:13,1
DA:14,1
DA:16,1
DA:19,1
DA:21,2
DA:23,1
DA:26,0
DA:29,1
DA:36,1
DA:38,1
DA:39,1
DA:42,1
DA:43,1
DA:44,1
DA:45,3
DA:46,2
DA:47,1
DA:48,3
DA:49,2
DA:50,1
DA:52,1
DA:53,2
DA:54,2
DA:56,2
DA:57,2
DA:60,1
DA:61,1
DA:62,2
DA:63,2
DA:64,2
DA:65,2
DA:66,2
DA:67,2
DA:68,2
DA:69,1
DA:70,1
DA:71,1
DA:72,1
DA:73,1
DA:74,1
DA:75,1
DA:76,1
DA:77,1
DA:78,1
DA:79,1
DA:80,1
DA:81,1
DA:82,1
DA:83,1
DA:84,1
DA:85,1
DA:86,1
DA:87,1
DA:91,1
DA:92,1
DA:93,1
DA:94,1
DA:95,1
DA:96,1
DA:97,1
DA:98,1
DA:99,1
DA:100,2
DA:101,2
DA:102,2
DA:103,2
DA:104,2
DA:108,1
DA:110,1
DA:113,1
DA:114,1
DA:115,1
DA:117,1
DA:120,1
DA:121,1
DA:123,1
DA:131,1
DA:132,1
DA:133,4
DA:134,1
DA:135,1
DA:136,2
LF:83
LH:82
end_of_record
SF:lib/components/cpu_info/cpu_info_page.dart
DA:9,1
DA:10,1
DA:16,1
DA:17,1
DA:19,1
DA:22,1
DA:25,0
DA:28,1
DA:31,1
DA:33,2
DA:39,1
DA:41,3
DA:42,1
DA:44,1
DA:45,1
DA:48,1
DA:50,1
DA:52,1
DA:53,2
DA:54,5
DA:55,5
DA:57,2
DA:58,2
DA:62,1
DA:64,1
DA:65,4
DA:68,1
DA:70,1
DA:71,2
DA:72,3
DA:73,3
DA:74,3
DA:75,3
DA:76,3
DA:77,3
DA:78,3
DA:79,3
DA:80,3
DA:81,3
DA:82,1
DA:84,2
DA:85,1
DA:86,1
DA:88,2
DA:89,1
DA:90,1
DA:92,2
DA:93,1
DA:94,1
DA:96,2
DA:97,1
DA:98,1
DA:100,2
DA:101,1
DA:104,1
DA:105,1
DA:106,3
DA:108,2
DA:109,2
DA:110,1
DA:111,2
DA:112,1
DA:113,1
DA:114,5
DA:115,5
DA:116,5
DA:119,3
DA:120,2
DA:121,1
LF:69
LH:68
end_of_record
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#4ecb0e" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">98%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">98%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,123 @@
import 'package:flutter/material.dart';
import 'package:system_info/system_info.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'icon.dart' as icon;
import 'package:platform/platform.dart';
class CpuInfoPage extends StatefulWidget implements Pluggable {
CpuInfoPage({Key? key, this.child, this.platform = const LocalPlatform()})
: super(key: key);
final Platform platform;
final Widget? child;
@override
_CpuInfoPageState createState() => _CpuInfoPageState();
@override
Widget buildWidget(BuildContext? context) => this;
@override
String get name => 'CPUInfo';
@override
String get displayName => 'CPUInfo';
@override
void onTrigger() {}
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
}
class _CpuInfoPageState extends State<CpuInfoPage> {
var _deviceInfo = <Map<String, String>>[];
@override
Widget build(BuildContext context) {
if (!widget.platform.isAndroid)
return Container(
color: Colors.white,
child: Center(
child: Text('Only available on Android device'),
),
);
return Container(
color: Colors.white,
child: SafeArea(
bottom: false,
child: ListView.separated(
itemBuilder: (ctx, index) => ListTile(
title: Text(_deviceInfo[index].keys.first),
trailing: Text(_deviceInfo[index].values.first),
),
separatorBuilder: (ctx, index) => Divider(),
itemCount: _deviceInfo.length)),
);
}
@override
void initState() {
super.initState();
if (widget.platform.isAndroid) _setupData();
}
_setupData() {
const int MEGABYTE = 1024 * 1024;
final deviceInfo = <Map<String, String>>[];
deviceInfo.addAll([
{'Kernel architecture': '${SysInfo.kernelArchitecture}'},
{'Kernel bitness': '${SysInfo.kernelBitness}'},
{'Kernel name': '${SysInfo.kernelName}'},
{'Kernel version': '${SysInfo.kernelVersion}'},
{'Operating system name': '${SysInfo.operatingSystemName}'},
{'Operating system ': '${SysInfo.operatingSystemVersion}'},
{'User directory': '${SysInfo.userDirectory}'},
{'User id': '${SysInfo.userId}'},
{'User name': '${SysInfo.userName}'},
{'User space bitness': '${SysInfo.userSpaceBitness}'},
{
'Total physical memory':
'${SysInfo.getTotalPhysicalMemory() ~/ MEGABYTE} MB'
},
{
'Free physical memory':
'${SysInfo.getFreePhysicalMemory() ~/ MEGABYTE} MB'
},
{
'Total virtual memory':
'${SysInfo.getTotalVirtualMemory() ~/ MEGABYTE} MB'
},
{
'Free virtual memory':
'${SysInfo.getFreeVirtualMemory() ~/ MEGABYTE} MB'
},
{
'Virtual memory size':
'${SysInfo.getVirtualMemorySize() ~/ MEGABYTE} MB'
},
]);
final processors = SysInfo.processors;
deviceInfo.add(
{'Number of processors': '${processors.length}'},
);
for (var processor in processors) {
deviceInfo.addAll([
{
'[${processors.indexOf(processor)}] Architecture':
'${processor.architecture}'
},
{'[${processors.indexOf(processor)}] Name': '${processor.name}'},
{'[${processors.indexOf(processor)}] Socket': '${processor.socket}'},
{'[${processors.indexOf(processor)}] Vendor': '${processor.vendor}'},
]);
}
Future.delayed(Duration(seconds: 1), () {
setState(() {
_deviceInfo = deviceInfo;
});
});
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,148 @@
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:platform/platform.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'icon.dart' as icon;
class DeviceInfoPanel extends StatefulWidget implements Pluggable {
final Platform platform;
const DeviceInfoPanel({this.platform = const LocalPlatform()});
@override
_DeviceInfoPanelState createState() => _DeviceInfoPanelState();
@override
Widget buildWidget(BuildContext? context) => this;
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'DeviceInfo';
@override
String get displayName => 'DeviceInfo';
@override
void onTrigger() {}
}
class _DeviceInfoPanelState extends State<DeviceInfoPanel> {
String _content = '';
@override
void initState() {
super.initState();
_getDeviceInfo();
}
void _getDeviceInfo() async {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
Map dataMap = Map();
if (widget.platform.isAndroid) {
AndroidDeviceInfo androidDeviceInfo = await deviceInfo.androidInfo;
dataMap = _readAndroidBuildData(androidDeviceInfo);
} else if (widget.platform.isIOS) {
IosDeviceInfo iosDeviceInfo = await deviceInfo.iosInfo;
dataMap = _readIosDeviceInfo(iosDeviceInfo);
}
StringBuffer buffer = StringBuffer();
dataMap.forEach((k, v) {
buffer.write('$k: $v\n');
});
_content = buffer.toString();
setState(() {});
}
Map<String, dynamic> _readAndroidBuildData(AndroidDeviceInfo build) {
return <String, dynamic>{
'version.securityPatch': build.version.securityPatch,
'version.sdkInt': build.version.sdkInt,
'version.release': build.version.release,
'version.previewSdkInt': build.version.previewSdkInt,
'version.incremental': build.version.incremental,
'version.codename': build.version.codename,
'version.baseOS': build.version.baseOS,
'board': build.board,
'bootloader': build.bootloader,
'brand': build.brand,
'device': build.device,
'display': build.display,
'fingerprint': build.fingerprint,
'hardware': build.hardware,
'host': build.host,
'id': build.id,
'manufacturer': build.manufacturer,
'model': build.model,
'product': build.product,
'supported32BitAbis': build.supported32BitAbis,
'supported64BitAbis': build.supported64BitAbis,
'supportedAbis': build.supportedAbis,
'tags': build.tags,
'type': build.type,
'isPhysicalDevice': build.isPhysicalDevice,
'androidId': build.id
};
}
Map<String, dynamic> _readIosDeviceInfo(IosDeviceInfo data) {
return <String, dynamic>{
'name': data.name,
'systemName': data.systemName,
'systemVersion': data.systemVersion,
'model': data.model,
'localizedModel': data.localizedModel,
'identifierForVendor': data.identifierForVendor,
'isPhysicalDevice': data.isPhysicalDevice,
'utsname.sysname': data.utsname.sysname,
'utsname.nodename': data.utsname.nodename,
'utsname.release': data.utsname.release,
'utsname.version': data.utsname.version,
'utsname.machine': data.utsname.machine,
};
}
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(left: 12, right: 12, top: 32, bottom: 32),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0),
color: Colors.black.withOpacity(0.85),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
padding: const EdgeInsets.only(bottom: 8),
child: Text(
'Device Info',
textScaleFactor: 1.15,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Colors.red),
)),
Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height - 150),
child: SingleChildScrollView(
physics: BouncingScrollPhysics(),
child: Text(_content,
style: const TextStyle(
fontSize: 15,
color: Colors.white,
fontWeight: FontWeight.w500,
),
strutStyle:
const StrutStyle(forceStrutHeight: true, height: 2)),
),
),
],
),
);
}
}
@@ -0,0 +1,6 @@
import 'dart:convert';
const iconData =
r'iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAANBElEQVR4Xu2dfYxcVRnG33Nndrd8tqFfhAJqNSSasmTvne1iJLpWQ/wANRj5o4qKgKTFhChRY4zYTRtDiUBjQQxpMFL/0DQpRoISASWSWrfZe7ct0ABagy2mEaoCC5TuzsxrBpco2N2Ze84zM+fcefbfnve55/ye++vdnW5njPCLBEhgTgKGbEiABOYmQEF4d5DAPAQoCG8PEqAgvAdIwI4AnyB23DjVIwQoSI8UzWPaEaAgdtw41SMEKEiPFM1j2hGgIHbcONUjBChIjxTNY9oRoCB23DjVIwQoSI8UzWPaEaAgdtw41SMECi1IkiR91Wp1WalUOt8Yc54xpt4jvfbcMVU1UtV/lkqlx/v7+w/u2rVrCgGhsIIkSbJERH4gIh8TkYUIWMwIhsBBVf1ylmW/ExF12XUhBZmVY6+IrHCBw9mgCTTE2Coi30rT9FXbkxROkJGRkdNnZmYeMsYM20LhXGEIVFX18izL7rU9UeEESZLkehHZYguEc4Uj8Fi1Wv3ovn37/mZzskIJMjg4eEq5XL7FGHOtDQzOFJLAERH5Ypqmv7E5XaEEGR4ePqdWq91tjPmwDQzOFJbA+jRN77Q5XaEEGRoaek8URb8VkeU2MDhTWAI3pmm60eZ0hRKkUqmsUtVHRGSxDQzOFJOAqm7IsmzM5nQUxIYaZ4IiQEFm6+ITJKj7tmObpSAUpGM3W4gXoiAUJMT7tmN7piBugjxijNlijHmhVqud1LHWeCEbAhpF0WmquiPPMAVxEERVb86y7Jt5gHNt9wisXr16ca1WO5pnBxTEQRBjzNjExMSGPMC5tnsERkZGller1cavjZRa3QUFoSCt3ivBr6MgDhXavMzLJ4gD8C6MUhAH6BTEAV4goxTEoSgK4gAvkFEK4lAUBXGAF8goBXEoioI4wAtklII4FEVBHOAFMkpBHIqiIA7wAhmlIA5FURAHeIGMUhCHoiiIA7xARimIQ1EUxAFeIKMUxKEoCuIAL5BRCuJQFAVxgBfIKAVxKIqCOMALZJSCOBRFQRzgBTJKQRyKoiAO8AIZpSAORVEQB3iBjFIQh6IoiAO8QEYpiENRFMQBXiCjFMShKAriAC+QUQriUBQFcYAXyCgFcSiKgjjAC2SUgjgURUEc4AUySkEciqIgDvACGaUgDkVREAd4gYxSEIeiKIgDvEBGgxUkjuMLRWSNMaaiqmd0g3cURaeo6gUi0pfj+n9V1WdyrEcvPRhF0Z5SqXT/+Pj4s+jwouUFJ8jw8PCZqnqHql5WtDI6fJ5XRGTDypUrb9uxY0etw9cO5nJBCdL42OW+vr4DInJuMIT93+idaZqu93+b3dlhUIIkSbJNRK7qDqriXlVVP5RlWePTevn1FgLBCJIkycki0vi2gF9gAqr6yyzLPgmOLURcSIJcJCKPFoK6f4c4lKbp2/zbVvd3FIwgcRxfY4y5q/vIirmDY8eOnXbgwIGXi3k6+1PNfsLU373/AJ04jq8zxtxuf1ROzkegXq8vm5ycfJ6U3kxgaGhoaRRFRyhIj98ZFOTEN0BI32LxCdJGiSkIBWnj7RV+NAWhIOHfxW08AQUJXJAkSdaJyA/z3COvvH2VaKmcZ6QQa/teOioDz+f+NaulaZrm+jzwQsBqcohC/wzyxNh9Uj11US/0+KYzLtm1U1bsvC3XufkECfwJYvMyLwVp3REKQkFav1sCX8knCK5AfouFY+lNEgXBVUFBcCy9SaIguCooCI6lN0kUBFcFBcGx9CaJguCqoCA4lt4kURBcFRQEx9KbJAqCq4KC4Fh6k0RBcFVQEBxLb5IoCK4KCoJj6U0SBcFVQUFwLL1JoiC4KigIjqU3SRQEVwUFwbH0JomC4KqgIDiW3iRREFwVFATH0pskCoKrgoLgWHqTREFwVVAQHEtvkigIrgoKgmPpTRIFwVVBQXAsvUmiILgqKAiOpTdJFARXBQXBsfQmiYLgqqAgOJbeJFEQXBUUBMfSmyQKgquCguBYepNEQXBVUBAcS2+SKAiuCgqCY+lNEgXBVUFBcCy9SaIguCooCI6lN0kUBFcFBcGx9CaJguCqoCA4lt4kURBcFRQEx9KbJAqCq4KC4Fh6k0RBcFVQEBxLb5IoCK4KCoJj6U0SBcFVQUFwLL1JoiC4KigIjqU3SRQEVwUFwbH0JomC4KqgIDiW3iRREFwVFATH0pskCoKrgoLgWHqTREFwVVAQHEtvkigIrgoKgmPpTRIFwVVBQXAsvUmiILgqKAiOpTdJFARXBQXBsfQmiYLgqqAgOJbeJFEQXBUUBMfSmyQKgquCguBYepNEQXBVUBAcS2+SKAiuCgqCY+lNEgXBVUFBcCy9SaIguCooCI6lN0kUBFcFBcGx9CaJguCqoCA4lt4kURBcFRQEx9KbJAqCq4KC4Fh6k0RBcFVQEBxLb5IoCK4KCoJj6U0SBcFVQUFwLL1JoiC4KigIjqU3SRQEVwUFwbH0JomC4KqgIDiW3iRREFwVFATH0pskCoKrgoLgWHqTREFwVVAQHEtvkigIrgoKgmPpTRIFwVVBQXAsvUmiILgqKAiOpTdJFARXBQXBsfQmiYLgqqAgOJbeJFEQXBUUBMfSmyQKgquCguBYepNEQXBVUBAcS2+SKAiuCgqCY+lNEgXBVUFBcCy9SaIguCooCI6lN0kUBFcFBUGwPPaymOOvNU3ShWeImKjpOtcFnRQkSZIl9XrdzLdnVZ3Zu3fvC67n6sY8BXGgXt6+WUoP/VzMoadaSzFG6kMfkNqlX5La6Kdbm7FY1W5B4jheY4z5ioh8REROanGLh0Vkx8zMzOb9+/c/1+JM15dREIsKzOE/Sf/YFWIOPmYx/Z+R2iVXyswNt1vPzzfYTkGSJLlRRMYcNv5cvV5fOzk5+bBDRsdGKUhu1CoDV7/XSY43Llm9/Hqprvte7h00G2iXIEmSrBeRO5pdv4U/f1FEkjRND7awtqtLKEhO/OXtN0n57o05p+ZefnzbH0XfeT4srxHUDkFGR0cXTE1NHRGRRYjNqur2LMs+j8hqZwYFyUl34AtDYg49nXNq7uXVz35dqldvgOW1S5BKpXKFqt6D3Gh/f//Ju3fvPobMRGdRkDxEX52SBR8/M89E07X1kYtl+qZ7m67Ls6AdT5AkSb4vIjfk2UeztcaYkYmJiT3N1nXzzylIDvrmxX/IwKfOzTHRfGk9HpXpW+5vvjDHinYIEsfx1tlXrnLsZP6lqvr+LMsehQW2IYiC5IQ68ImzxUz9K+fU3Mvb8WpWOwSpVCpfVdVbYQcXkXK5fM74+PizyEx0FgXJSbRv87VSeuCnOafmXj6z8WdSu+hSWF67fgaJ4/jdxpgDqI2q6h+yLHsfKq9dORQkJ1nzl8dl4KqRnFMnXt74R8PpW38FyfrfkHY8QRr5SZLcJSLXIDasqpdlWYb94QuxsbdkUBALqKVf3yN9N6+zmPzviJ79Lpne/AvRs97hlHOi4XYJIiJRkiQPisgal00bY8YmJiawL925bGieWQpiCTba86CUf7xJoicncifULl77+j8Q6qKluWdbGWijIK9ffvYVra+JyLy/g3WCvf5ZRDalafqTVs7hwxoK4thC9FQmjW+7pIVfVpSFi6W+6kLRpSscrzr/eLsFmZVkoYh8UETOUtVmosyo6pOTk5O/b+vB2xBOQdoAtduRnRCk22fs1PUpSKdId/A6FAQHm4LgWHqTREFwVVAQHEtvkigIrgoKgmPpTRIFwVVBQXAsvUmiILgqKAiOpTdJFARXBQXBsfQmiYLgqqAgOJbeJFEQXBUUBMfSmyQKgquCguBYepNEQXBVDA4OLuvr62v8X/yW39BMVTdkWWb1zi/NfmdnzpPFcXydMSbX++Q8MXafVE+FvMcAjngHkmwEqdVq59VqtaMd2F5QlyiXy6dHUfRMnk0HI0ieQ3GtVMng/wg0nhotPznemKYgvJNIYB4CFIS3BwlQEN4DJGBHgE8QO26c6hECFKRHiuYxrQl8J03TTTbTLi/zfs4Ys93mopwhgQ4TWJem6Y9srukiyKAxZp/NRTlDAh0k8Fq9Xr/E9uMdrAWZfcuZxsc49XXwsLwUCeQiYIx5eHp6eq3thwS5CCKVSuUbqro51465mAQ6R+AlVb0yy7Kdtpd0EqRx0TiONxljvm27Ac6RQJsINN6w+btpmm51yXcWpHHxoaGhC6Io2iIioy6b4SwJOBI4LiKHVbXxs/G2LMsecMzL/U58816v8QmrIvIZY8xy1431wryqRsaYBaraeEd1yF9WvcDtRGc0xtRE5KiqPp2maYbiwFJQJJlTSAIUpJC18lAoAhQERZI5hSRAQQpZKw+FIkBBUCSZU0gCFKSQtfJQKAIUBEWSOYUkQEEKWSsPhSJAQVAkmVNIAhSkkLXyUCgCFARFkjmFJEBBClkrD4UiQEFQJJlTSAIUpJC18lAoAv8GH84Vqjytw1cAAAAASUVORK5CYII=';
final iconBytes = base64Decode(iconData);
@@ -0,0 +1,4 @@
library flutter_ume_kit_device;
export 'components/cpu_info/cpu_info_page.dart';
export 'components/device_info/device_info_panel.dart';
@@ -0,0 +1,29 @@
name: flutter_ume_kit_device
description: Device info kit for flutter_ume.
version: 1.0.0
homepage: https://github.com/bytedance/flutter_ume
publish_to: none
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
flutter_ume:
path: ../../../flutter_ume
system_info: ^1.0.1
device_info_plus: ^12.1.0
platform: ^3.0.0
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.12
flutter_coverage_badge:
git:
url: https://github.com/smileShirely/flutter_coverage_badge.git
ref: 59b7580f406bb712e9d9049c8c99212946e34f65
flutter:
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: d79295af24c3ed621c33713ecda14ad196fd9c31
channel: stable
project_type: package
@@ -0,0 +1,23 @@
# Changelog
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 更新版本号
* Update version
## 0.2.0-dev.0
* Initial release.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,3 @@
# flutter_ume_kit_dio
Dio kit for flutter_ume.
@@ -0,0 +1,229 @@
SF:lib/src/models/http_interceptor.dart
DA:10,0
DA:20,0
DA:22,0
DA:23,0
DA:26,0
DA:31,0
DA:32,0
DA:33,0
DA:36,0
DA:39,0
DA:40,0
DA:41,0
DA:42,0
LF:13
LH:0
end_of_record
SF:lib/src/pluggable.dart
DA:18,2
DA:19,4
DA:24,1
DA:25,1
DA:27,1
DA:29,2
DA:31,1
DA:34,0
DA:37,1
DA:40,1
LF:10
LH:9
end_of_record
SF:lib/src/constants/extensions.dart
DA:10,0
DA:11,0
DA:13,0
DA:14,0
DA:16,0
DA:17,0
DA:19,0
DA:20,0
LF:8
LH:0
end_of_record
SF:lib/src/containers/http_container.dart
DA:14,0
DA:18,2
DA:23,1
DA:24,7
DA:27,0
DA:29,0
DA:30,0
DA:31,0
DA:34,0
DA:35,0
DA:38,0
DA:39,0
DA:42,1
DA:43,1
DA:44,1
DA:47,0
DA:48,0
DA:49,0
DA:50,0
DA:53,0
DA:55,0
DA:56,0
LF:22
LH:6
end_of_record
SF:lib/src/instances.dart
DA:11,0
DA:13,3
LF:2
LH:1
end_of_record
SF:lib/src/widgets/pluggable_state.dart
DA:16,1
DA:20,1
DA:23,1
DA:24,1
DA:26,2
DA:33,1
DA:35,1
DA:37,3
DA:40,1
DA:42,1
DA:43,2
DA:44,1
DA:45,1
DA:50,0
DA:51,0
DA:52,0
DA:53,0
DA:54,0
DA:58,1
DA:59,1
DA:60,2
DA:61,1
DA:68,1
DA:78,1
DA:80,2
DA:81,1
DA:82,1
DA:83,0
DA:84,0
DA:85,0
DA:86,0
DA:87,0
DA:88,0
DA:89,0
DA:90,0
DA:92,0
DA:93,0
DA:99,0
DA:100,0
DA:101,0
DA:117,1
DA:119,1
DA:121,1
DA:122,3
DA:123,1
DA:125,1
DA:126,1
DA:128,4
DA:130,1
DA:134,2
DA:136,1
DA:137,1
DA:138,1
DA:140,1
DA:141,1
DA:143,1
DA:145,3
DA:147,1
DA:148,1
DA:150,1
DA:156,1
DA:157,1
DA:158,2
DA:159,1
DA:172,0
DA:175,0
DA:179,0
DA:180,0
DA:186,0
DA:188,0
DA:189,0
DA:192,0
DA:193,0
DA:196,0
DA:198,0
DA:201,0
DA:204,0
DA:207,0
DA:210,0
DA:213,0
DA:214,0
DA:217,0
DA:220,0
DA:223,0
DA:230,0
DA:233,0
DA:236,0
DA:237,0
DA:238,0
DA:240,0
DA:244,0
DA:245,0
DA:246,0
DA:248,0
DA:251,0
DA:252,0
DA:253,0
DA:254,0
DA:262,0
DA:263,0
DA:264,0
DA:265,0
DA:267,0
DA:272,0
DA:273,0
DA:274,0
DA:276,0
DA:277,0
DA:282,0
DA:283,0
DA:287,0
DA:289,0
DA:294,0
DA:295,0
DA:296,0
DA:297,0
DA:301,0
DA:303,0
DA:305,0
DA:306,0
DA:307,0
DA:308,0
DA:309,0
DA:311,0
DA:320,0
DA:322,0
DA:324,0
DA:326,0
DA:328,0
DA:330,0
DA:331,0
DA:333,0
DA:334,0
DA:343,0
DA:348,0
DA:354,0
DA:355,0
DA:356,0
DA:357,0
DA:358,0
DA:361,0
DA:366,0
DA:369,0
DA:370,0
DA:372,0
DA:374,0
DA:382,0
DA:386,0
DA:387,0
DA:388,0
LF:150
LH:46
end_of_record
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#e05d44" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">30%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">30%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,4 @@
library flutter_ume_kit_dio;
export 'src/models/http_interceptor.dart';
export 'src/pluggable.dart';
@@ -0,0 +1,6 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 2021/8/6 13:25
///
const String DIO_EXTRA_START_TIME = 'ume_start_time';
const String DIO_EXTRA_END_TIME = 'ume_end_time';
@@ -0,0 +1,21 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 2021/8/6 13:58
///
import 'package:dio/dio.dart' show Response;
import 'constants.dart';
extension ResponseExtension on Response<dynamic> {
int get startTimeMilliseconds =>
requestOptions.extra[DIO_EXTRA_START_TIME] as int;
int get endTimeMilliseconds =>
requestOptions.extra[DIO_EXTRA_END_TIME] as int;
DateTime get startTime =>
DateTime.fromMillisecondsSinceEpoch(startTimeMilliseconds);
DateTime get endTime =>
DateTime.fromMillisecondsSinceEpoch(endTimeMilliseconds);
}
@@ -0,0 +1,58 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 4/13/21 2:49 PM
///
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
import 'package:dio/dio.dart' show Response;
/// Implements a [ChangeNotifier] to notify listeners when new responses
/// were recorded. Use [page] to support paging.
class HttpContainer extends ChangeNotifier {
/// Store all responses.
List<Response<dynamic>> get requests => _requests;
final List<Response<dynamic>> _requests = <Response<dynamic>>[];
/// Paging fields.
int get page => _page;
int _page = 1;
final int _perPage = 10;
/// Return requests according to the paging.
List<Response<dynamic>> get pagedRequests {
return _requests.sublist(0, math.min(page * _perPage, _requests.length));
}
bool get _hasNextPage => _page * _perPage < _requests.length;
void addRequest(Response<dynamic> response) {
_requests.insert(0, response);
notifyListeners();
}
void loadNextPage() {
if (!_hasNextPage) {
return;
}
_page++;
notifyListeners();
}
void resetPaging() {
_page = 1;
notifyListeners();
}
void clearRequests() {
_requests.clear();
_page = 1;
notifyListeners();
}
@override
void dispose() {
_requests.clear();
super.dispose();
}
}
@@ -0,0 +1,14 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 4/13/21 2:54 PM
///
import 'containers/http_container.dart';
/// The inner singleton instance to keep containers.
///
/// Currently we only have a http container here.
class InspectorInstance {
const InspectorInstance._();
static final HttpContainer httpContainer = HttpContainer();
}
@@ -0,0 +1,44 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 4/13/21 1:58 PM
///
import 'package:dio/dio.dart';
import '../constants/constants.dart';
import '../instances.dart';
int get _timestamp => DateTime.now().millisecondsSinceEpoch;
/// Implement a [Interceptor] to handle dio methods.
///
/// Main idea about this interceptor:
/// - Use [RequestOptions.extra] to store our timestamps.
/// - Add [DIO_EXTRA_START_TIME] when a request was requested.
/// - Add [DIO_EXTRA_END_TIME] when a response is respond or thrown an error.
/// - Deliver the [Response] to the container.
class UMEDioInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
options.extra[DIO_EXTRA_START_TIME] = _timestamp;
handler.next(options);
}
@override
void onResponse(
Response<dynamic> response,
ResponseInterceptorHandler handler,
) {
response.requestOptions.extra[DIO_EXTRA_END_TIME] = _timestamp;
InspectorInstance.httpContainer.addRequest(response);
handler.next(response);
}
@override
void onError(DioException err, ErrorInterceptorHandler handler) {
// Create an empty response with the [RequestOptions] for delivery.
// err.response ??= Response<dynamic>(requestOptions: err.requestOptions);
err.response!.requestOptions.extra[DIO_EXTRA_END_TIME] = _timestamp;
InspectorInstance.httpContainer.addRequest(err.response!);
handler.next(err);
}
}
@@ -0,0 +1,40 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 2021/8/6 11:24
///
import 'package:flutter/material.dart';
import 'package:dio/dio.dart' show Dio;
import 'package:flutter_ume/core/pluggable.dart';
import 'models/http_interceptor.dart';
import 'widgets/icon.dart' as icon;
import 'widgets/pluggable_state.dart';
// TODO(Alex): Implement [PluggableStream] for dot features.
/// Implement a [Pluggable] to integrate with UME.
class DioInspector extends StatefulWidget implements Pluggable {
DioInspector({Key? key, required this.dio}) : super(key: key) {
dio.interceptors.add(UMEDioInterceptor());
}
final Dio dio;
@override
DioPluggableState createState() => DioPluggableState();
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'DioInspector';
@override
String get displayName => 'DioInspector';
@override
void onTrigger() {}
@override
Widget buildWidget(BuildContext? context) => this;
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,418 @@
///
/// [Author] Alex (https://github.com/AlexV525)
/// [Date] 2021/8/6 11:25
///
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import '../constants/extensions.dart';
import '../instances.dart';
import '../pluggable.dart';
const JsonEncoder _encoder = JsonEncoder.withIndent(' ');
ButtonStyle _buttonStyle(
BuildContext context, {
EdgeInsetsGeometry? padding,
}) {
return TextButton.styleFrom(
padding: padding ?? const EdgeInsets.symmetric(horizontal: 5, vertical: 3),
minimumSize: Size.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(999999),
),
backgroundColor: Theme.of(context).primaryColor,
// primary: Colors.white,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
);
}
class DioPluggableState extends State<DioInspector> {
@override
void initState() {
super.initState();
// Bind listener to refresh requests.
InspectorInstance.httpContainer.addListener(_listener);
}
@override
void dispose() {
InspectorInstance.httpContainer
..removeListener(_listener) // First, remove refresh listener.
..resetPaging(); // Then reset the paging field.
super.dispose();
}
/// Using [setState] won't cause too much performance regression,
/// since we've implemented the list with `findChildIndexCallback`.
void _listener() {
Future.microtask(() {
if (mounted &&
!context.debugDoingBuild &&
context.owner?.debugBuilding != true) {
setState(() {});
}
});
}
Widget _clearAllButton(BuildContext context) {
return TextButton(
onPressed: InspectorInstance.httpContainer.clearRequests,
style: _buttonStyle(
context,
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 3,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const <Widget>[
Text('Clear'),
Icon(Icons.cleaning_services, size: 14),
],
),
);
}
Widget _itemList(BuildContext context) {
final List<Response<dynamic>> requests =
InspectorInstance.httpContainer.pagedRequests;
final int length = requests.length;
if (length > 0) {
return CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate(
(_, int index) {
final Response<dynamic> r = requests[index];
if (index == length - 2) {
InspectorInstance.httpContainer.loadNextPage();
}
return _ResponseCard(
key: ValueKey<int>(r.startTimeMilliseconds),
response: r,
);
},
childCount: length,
),
),
],
);
}
return const Center(
child: Text(
'Come back later...\n🧐',
style: TextStyle(fontSize: 28),
textAlign: TextAlign.center,
),
);
}
@override
Widget build(BuildContext context) {
return Material(
color: Colors.black26,
child: DefaultTextStyle.merge(
style: Theme.of(context).textTheme.bodyLarge,
child: Align(
alignment: Alignment.bottomCenter,
child: Container(
constraints: BoxConstraints.tightFor(
width: double.maxFinite,
height: MediaQuery.of(context).size.height / 1.25,
),
decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(
top: Radius.circular(20),
),
color: Theme.of(context).cardColor,
),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(12.0),
child: Row(
children: <Widget>[
const Spacer(),
Text(
'Dio Requests',
style: Theme.of(context).textTheme.bodySmall,
),
Expanded(
child: Align(
alignment: AlignmentDirectional.centerEnd,
child: _clearAllButton(context),
),
),
],
),
),
Expanded(
child: ColoredBox(
color: Theme.of(context).canvasColor,
child: _itemList(context),
),
),
],
),
),
),
),
);
}
}
class _ResponseCard extends StatefulWidget {
const _ResponseCard({
required Key? key,
required this.response,
}) : super(key: key);
final Response<dynamic> response;
@override
_ResponseCardState createState() => _ResponseCardState();
}
class _ResponseCardState extends State<_ResponseCard> {
final ValueNotifier<bool> _isExpanded = ValueNotifier<bool>(false);
@override
void dispose() {
_isExpanded.dispose();
super.dispose();
}
void _switchExpand() {
_isExpanded.value = !_isExpanded.value;
}
Response<dynamic> get _response => widget.response;
RequestOptions get _request => _response.requestOptions;
/// The start time for the [_request].
DateTime get _startTime => _response.startTime;
/// The end time for the [_response].
DateTime get _endTime => _response.endTime;
/// The duration between the request and the response.
Duration get _duration => _endTime.difference(_startTime);
/// Status code for the [_response].
int get _statusCode => _response.statusCode ?? 0;
/// Colors matching status.
Color get _statusColor {
if (_statusCode >= 200 && _statusCode < 300) {
return Colors.lightGreen;
}
if (_statusCode >= 300 && _statusCode < 400) {
return Colors.orangeAccent;
}
if (_statusCode >= 400 && _statusCode < 500) {
return Colors.purple;
}
if (_statusCode >= 500 && _statusCode < 600) {
return Colors.red;
}
return Colors.blueAccent;
}
/// The method that the [_request] used.
String get _method => _request.method;
/// The [Uri] that the [_request] requested.
Uri get _requestUri => _request.uri;
String? get _requestHeadersBuilder {
final Map<String, List<String>> map = _request.headers.map(
(key, value) => MapEntry(
key,
value is Iterable ? value.map((v) => v.toString()).toList() : [value],
),
);
final Headers headers = Headers.fromMap(map);
if (headers.isEmpty) {
return null;
}
return '$headers';
}
/// Data for the [_request].
String? get _requestDataBuilder {
if (_request.data is Map) {
return _encoder.convert(_request.data);
}
return _request.data?.toString();
}
/// Data for the [_response].
String? get _responseDataBuilder {
final data = _response.data;
if (data == null) {
return null;
}
if (_response.data is Map) {
return _encoder.convert(_response.data);
}
return _response.data.toString();
}
String? get _responseHeadersBuilder {
if (_response.headers.isEmpty) {
return null;
}
return '${_response.headers}';
}
Widget _detailButton(BuildContext context) {
return TextButton(
onPressed: _switchExpand,
style: _buttonStyle(context),
child: const Text(
'Detail🔍',
style: TextStyle(fontSize: 12, height: 1.2),
),
);
}
Widget _infoContent(BuildContext context) {
return Row(
children: <Widget>[
Text(_startTime.hms()),
const SizedBox(width: 6),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 5,
vertical: 1,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3),
color: _statusColor,
),
child: Text(
_statusCode.toString(),
style: const TextStyle(color: Colors.white, fontSize: 12),
),
),
const SizedBox(width: 6),
Text(
_method,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const SizedBox(width: 6),
Text('${_duration.inMilliseconds}ms'),
const Spacer(),
_detailButton(context),
],
);
}
Widget _detailedContent(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: _isExpanded,
builder: (_, bool value, __) {
if (!value) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_TagText(
tag: 'Request headers',
content: _requestHeadersBuilder,
),
_TagText(
tag: 'Request data',
content: _requestDataBuilder,
),
_TagText(
tag: 'Response body',
content: _responseDataBuilder,
),
_TagText(
tag: 'Response headers',
content: _responseHeadersBuilder,
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.all(8.0),
shadowColor: Theme.of(context).canvasColor,
elevation: 5,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
_infoContent(context),
const SizedBox(height: 10),
_TagText(
tag: 'Uri',
content: '$_requestUri',
shouldStartFromNewLine: false,
),
_detailedContent(context),
],
),
),
);
}
}
class _TagText extends StatelessWidget {
const _TagText({
Key? key,
required this.tag,
this.content,
this.shouldStartFromNewLine = true,
}) : super(key: key);
final String tag;
final String? content;
final bool shouldStartFromNewLine;
TextSpan get span {
return TextSpan(
children: <TextSpan>[
TextSpan(
text: '$tag: ',
style: const TextStyle(fontWeight: FontWeight.bold),
),
if (shouldStartFromNewLine) TextSpan(text: '\n'),
TextSpan(text: content!),
],
);
}
@override
Widget build(BuildContext context) {
if (content == null) {
return const SizedBox.shrink();
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: SelectableText.rich(span),
);
}
}
extension _DateTimeExtension on DateTime {
String hms([String separator = ':']) => '$hour$separator'
'${'$minute'.padLeft(2, '0')}$separator'
'${'$second'.padLeft(2, '0')}';
}
@@ -0,0 +1,26 @@
name: flutter_ume_kit_dio
description: Dio kit for flutter_ume.
version: 1.0.0
homepage: https://github.com/bytedance/flutter_ume
publish_to: none
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
flutter_ume:
path: ../../../flutter_ume
dio: ^5.4.3+1
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.12
flutter_coverage_badge:
git:
url: https://github.com/smileShirely/flutter_coverage_badge.git
ref: 59b7580f406bb712e9d9049c8c99212946e34f65
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: unknown
project_type: package
@@ -0,0 +1,37 @@
# Changelog
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 修复分页错误 #28
* Fix DioInspector paging issue #28
## 0.2.1
* null-safety 正式版本
* Null-Safety formal version.
## 0.2.0-dev.0
* 适配 null-safety
* Adapted Null-Safety.
## 0.1.0
* 发布开源版本。
* Release opensource version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,11 @@
# flutter_ume_kit_perf
[flutter_ume](https://pub.dev/packages/flutter_ume) 是由字节跳动 Flutter Infra 团队出品的应用内调试工具平台。
flutter_ume_kit_perf 是 flutter_ume 的性能插件包。接入方式请见 [flutter_ume](https://pub.dev/packages/flutter_ume)。
----
[flutter_ume](https://pub.dev/packages/flutter_ume) is an in-app debug kits platform produced for Flutter apps by ByteDance Flutter Infra team.
flutter_ume_kit_perf is the Performance kits package of flutter_ume. Please visit [flutter_ume](https://pub.dev/packages/flutter_ume) for details.
@@ -0,0 +1,279 @@
SF:lib/components/memory_info/memory_service.dart
DA:11,1
DA:12,1
DA:13,1
DA:14,1
DA:15,1
DA:16,1
DA:17,1
DA:18,1
DA:20,1
DA:21,1
DA:23,4
DA:26,1
DA:33,1
DA:45,1
DA:46,3
DA:47,2
DA:48,2
DA:49,2
DA:51,0
DA:52,0
DA:53,0
DA:55,0
DA:59,0
DA:61,0
DA:63,0
DA:64,0
DA:67,0
DA:69,0
DA:71,0
DA:72,0
DA:73,0
DA:74,0
DA:75,0
DA:76,0
DA:77,0
DA:79,0
DA:81,0
DA:82,0
DA:83,0
DA:84,0
DA:87,0
DA:88,0
DA:89,0
DA:92,0
DA:94,0
DA:97,0
DA:98,0
DA:99,0
DA:100,0
DA:101,0
DA:102,0
DA:105,0
DA:106,0
DA:107,0
DA:108,0
DA:109,0
DA:110,0
DA:113,0
DA:114,0
DA:115,0
DA:116,0
DA:117,0
DA:118,0
DA:121,1
DA:123,0
DA:124,0
DA:125,0
DA:127,2
DA:131,1
DA:133,1
DA:134,1
DA:135,0
DA:136,0
DA:138,0
DA:139,0
DA:143,2
DA:144,2
DA:146,1
DA:150,0
DA:154,0
DA:155,0
DA:156,0
DA:157,0
DA:159,0
LF:84
LH:26
end_of_record
SF:lib/components/performance/performance.dart
DA:8,1
DA:10,1
DA:13,1
DA:14,1
DA:15,3
DA:18,1
DA:21,1
DA:23,2
DA:25,1
DA:28,0
DA:31,1
LF:11
LH:10
end_of_record
SF:lib/components/memory_info/memory_info_page.dart
DA:9,2
DA:11,1
DA:13,1
DA:14,1
DA:15,1
DA:19,1
DA:22,1
DA:24,2
DA:26,1
DA:29,0
DA:32,1
DA:41,0
DA:45,2
DA:47,1
DA:48,1
DA:58,1
DA:60,1
DA:61,2
DA:62,0
DA:66,1
DA:67,1
DA:68,2
DA:69,2
DA:72,0
DA:73,0
DA:74,0
DA:75,0
DA:76,0
DA:77,0
DA:78,0
DA:82,1
DA:83,2
DA:84,1
DA:87,1
DA:90,1
DA:93,3
DA:94,1
DA:97,1
DA:100,1
DA:103,3
DA:104,1
DA:106,2
DA:107,1
DA:110,1
DA:112,1
DA:113,1
DA:114,1
DA:115,1
DA:116,1
DA:119,2
DA:123,1
DA:125,1
DA:127,1
DA:129,1
DA:130,1
DA:132,1
DA:133,1
DA:134,1
DA:135,1
DA:136,3
DA:137,1
DA:140,2
DA:141,1
DA:142,1
DA:143,2
DA:144,1
DA:147,2
DA:148,1
DA:151,1
DA:152,2
DA:153,0
DA:154,1
DA:155,2
DA:156,1
DA:159,2
DA:160,1
DA:162,1
DA:166,2
DA:170,1
DA:171,1
DA:172,1
DA:173,0
DA:174,0
DA:175,0
DA:177,0
DA:178,0
DA:179,0
DA:180,0
DA:181,0
DA:182,0
DA:184,0
DA:185,0
DA:186,0
DA:187,0
DA:188,0
DA:189,0
DA:190,0
DA:191,0
DA:192,0
DA:193,0
DA:198,3
DA:210,1
DA:217,1
DA:225,1
DA:226,1
DA:232,1
DA:234,1
DA:235,3
DA:238,1
DA:240,1
DA:241,1
DA:242,2
DA:243,2
DA:244,2
DA:246,2
DA:247,6
DA:251,1
DA:252,2
DA:253,3
DA:255,2
DA:256,2
DA:257,1
DA:264,1
DA:266,1
DA:272,1
DA:274,1
DA:276,1
DA:277,0
DA:278,0
DA:279,0
DA:280,1
DA:282,1
DA:283,3
DA:284,1
DA:285,1
DA:289,1
DA:295,0
DA:296,0
DA:297,0
DA:298,0
DA:304,0
DA:305,0
DA:312,0
DA:314,0
DA:316,0
DA:317,0
DA:318,0
DA:319,0
DA:321,0
DA:322,0
DA:323,0
DA:324,0
DA:326,0
DA:327,0
DA:331,0
DA:333,0
DA:334,0
DA:335,0
DA:336,0
DA:337,0
DA:338,0
DA:339,0
DA:340,0
DA:342,0
DA:343,0
DA:346,0
DA:350,0
DA:351,0
DA:352,0
DA:355,0
DA:359,0
DA:360,0
LF:172
LH:103
end_of_record
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#df6e3a" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">52%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">52%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

File diff suppressed because one or more lines are too long
@@ -0,0 +1,362 @@
import 'package:flutter/material.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'memory_service.dart';
import 'icon.dart' as icon;
class MemoryInfoPage extends StatelessWidget implements Pluggable {
const MemoryInfoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: Colors.white),
home: _MemoryWidget(),
);
}
@override
Widget buildWidget(BuildContext? context) => this;
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'MemoryInfo';
@override
String get displayName => 'MemoryInfo';
@override
void onTrigger() {}
}
class _DetailModel {
final int? count;
final String? classId;
final String? className;
_DetailModel(this.count, this.classId, this.className);
}
class _MemoryWidget extends StatefulWidget {
_MemoryWidget({Key? key}) : super(key: key);
@override
_MemoryWidgetState createState() => _MemoryWidgetState();
}
class _MemoryWidgetState extends State<_MemoryWidget> {
MemoryService _memoryservice = MemoryService();
int _sortColumnIndex = 0;
bool? _checked = true;
@override
void initState() {
super.initState();
_memoryservice.getInfos(() {
setState(() {});
});
}
void _hidePrivateClass(bool? check) {
_checked = check;
_memoryservice.hidePrivateClasses(check!);
setState(() {});
}
void _enterDetailPage(_DetailModel detail) {
Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
return Scaffold(
body: _MemoryDetail(detail: detail, service: _memoryservice),
appBar: PreferredSize(
child: AppBar(elevation: 0.0, title: Text(detail.className!)),
preferredSize: Size.fromHeight(44)));
}));
}
Widget _header() {
return Column(mainAxisSize: MainAxisSize.min, children: [
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10),
child: Text("VM Info: ",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
textAlign: TextAlign.left)),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 15, right: 5),
child: Text(_memoryservice.vmInfo)),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 15, bottom: 10),
child: Text("Memory Info: ",
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
textAlign: TextAlign.left)),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(left: 15, right: 5),
child: Text(_memoryservice.memoryUseage)),
Padding(
padding: const EdgeInsets.only(left: 12.0),
child: Row(children: [
SizedBox(
height: 24,
width: 24,
child: Checkbox(
materialTapTargetSize: MaterialTapTargetSize.padded,
value: _checked,
onChanged: _hidePrivateClass)),
Padding(
padding: EdgeInsets.only(left: 5),
child: Text("Hide private class"))
]),
),
Padding(padding: EdgeInsets.only(top: 10))
]);
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: SafeArea(
bottom: false,
child: Scaffold(
body: NestedScrollView(
headerSliverBuilder:
(BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
SliverAppBar(
bottom: PreferredSize(
child: _PerRow(customColor: Color(0xFFF4F4F4), widgets: [
_DropButton(
title: "Size",
index: 0,
stateChanged: (index, descending) => _memoryservice
.sort((d) => d.accumulatedSize, descending,
() {
setState(() {
_sortColumnIndex = index;
});
}),
showArrow: _sortColumnIndex == 0),
_DropButton(
title: "Count",
index: 1,
stateChanged: (index, descending) =>
_memoryservice.sort(
(d) => d.instancesAccumulated, descending,
() {
setState(() {
_sortColumnIndex = index;
});
}),
showArrow: _sortColumnIndex == 1),
_DropButton(title: "ClassName")
]),
preferredSize: Size.fromHeight(44)),
expandedHeight: 310.0,
floating: true,
pinned: true,
flexibleSpace: FlexibleSpaceBar(background: _header()),
)
];
},
body: Scrollbar(
child: ListView.builder(
physics: BouncingScrollPhysics(),
itemBuilder: (_, index) {
var stats = _memoryservice.infoList[index];
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
_DetailModel detail = _DetailModel(
stats.instancesAccumulated,
stats.classRef!.id,
stats.classRef!.name);
_enterDetailPage(detail);
},
child: _PerRow(
darkColor: index % 2 == 0,
widgets: [
Text(
"${_memoryservice.byteToString(stats.accumulatedSize!)}",
style: TextStyle(color: Colors.black87)),
Text("${stats.instancesAccumulated}",
style: TextStyle(color: Colors.black87)),
Text("${stats.classRef!.name}",
style: TextStyle(color: Colors.black87)),
],
),
);
},
itemCount: _memoryservice.infoList.length),
),
),
),
),
);
}
}
typedef _DropState = void Function(int, bool);
class _DropButton extends StatefulWidget {
_DropButton(
{Key? key,
this.showArrow = false,
this.descending = true,
required this.title,
this.index = 0,
this.stateChanged})
: super(key: key);
final bool showArrow;
final bool descending;
final int index;
final String title;
final _DropState? stateChanged;
@override
__DropButtonState createState() => __DropButtonState();
}
class __DropButtonState extends State<_DropButton> {
bool _descending = false;
@override
void initState() {
super.initState();
_descending = widget.descending;
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
if (widget.showArrow) {
_descending = !_descending;
}
if (widget.stateChanged != null) {
widget.stateChanged!(widget.index, _descending);
}
});
},
child: Container(
child: Row(children: [
Text(widget.title,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
widget.showArrow
? Icon(_descending ? Icons.arrow_drop_down : Icons.arrow_drop_up)
: Container()
], mainAxisSize: MainAxisSize.min)),
);
}
}
class _PerRow extends StatelessWidget {
const _PerRow(
{Key? key, this.widgets, this.customColor, this.darkColor = false})
: super(key: key);
final List<Widget>? widgets;
final bool darkColor;
final Color? customColor;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.only(left: 15, right: 15),
color: this.customColor ??
(this.darkColor
? Colors.grey.withOpacity(0.2)
: Colors.grey.withOpacity(0.03)),
child: Row(
children: this
.widgets!
.map((e) => Expanded(
child: Align(
child: Padding(
padding: const EdgeInsets.only(top: 10, bottom: 10),
child: e),
alignment: Alignment.centerLeft)))
.toList()),
);
}
}
class _MemoryDetail extends StatefulWidget {
_MemoryDetail({Key? key, required this.detail, required this.service})
: assert(service != null),
assert(detail != null),
super(key: key);
final _DetailModel detail;
final MemoryService service;
@override
__MemoryDetailState createState() => __MemoryDetailState();
}
class __MemoryDetailState extends State<_MemoryDetail> {
String _textInfoO = "";
String _textInfoT = "";
@override
void initState() {
super.initState();
widget.service.getClassDetailInfo(widget.detail.classId!, (info) {
StringBuffer buffer = StringBuffer();
info?.propeties?.forEach((element) {
buffer.writeln(element.propertyStr);
});
_textInfoO = buffer.toString();
StringBuffer bf = StringBuffer();
info?.functions?.forEach((element) {
bf.writeln(element);
});
_textInfoT = bf.toString();
setState(() {});
});
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 15, left: 15, right: 15),
child: _textInfoO.isEmpty && _textInfoT.isEmpty
? Center(
child: Text('The Object is Sentinel',
style: TextStyle(fontSize: 20)))
: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(bottom: 10),
child: Text("Property: ",
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.w500),
textAlign: TextAlign.left)),
Text(_textInfoO,
textAlign: TextAlign.left, style: TextStyle(fontSize: 16)),
Container(
alignment: Alignment.centerLeft,
padding: const EdgeInsets.only(bottom: 10),
child: Text("Function: ",
style: const TextStyle(
fontSize: 20, fontWeight: FontWeight.w500),
textAlign: TextAlign.left)),
Text(_textInfoT,
textAlign: TextAlign.left, style: TextStyle(fontSize: 16)),
],
)),
);
}
}
@@ -0,0 +1,159 @@
import 'package:vm_service/vm_service.dart';
import 'package:flutter_ume/flutter_ume.dart';
class Property {
final bool? isConst;
final bool? isStatic;
final bool? isFinal;
final String? type;
final String? name;
String get propertyStr {
StringBuffer val = StringBuffer();
if (this.isStatic!) {
val.write("static");
val.write(' ');
} else if (this.isConst!) {
val.write("const");
val.write(' ');
} else {
val.write('final');
val.write(' ');
}
return "${val.toString()} ${this.type} ${this.name}";
}
Property(this.isConst, this.isStatic, this.isFinal, this.type, this.name);
}
class ClsModel {
final List<Property>? propeties;
final List<String>? functions;
ClsModel({this.propeties, this.functions});
}
class MemoryService with VMServiceWrapper {
List<ClassHeapStats> infoList = [];
List<ClassHeapStats> allClasses = [];
String vmInfo = "";
String memoryUseage = "";
void getInfos(Function completion) async {
List results = await Future.wait([
serviceWrapper.getClassHeapStats(),
serviceWrapper.getMemoryUsage(),
serviceWrapper.getVM()
]);
_heapInfoList(results[0]);
_memoryUsed(results[1]);
_vmToInfo(results[2]);
completion();
}
void getInstanceIds(
String classId, int limit, Function(List<String?>) completion) async {
InstanceSet instanceSet = await serviceWrapper.getInstances(classId, limit);
List<String?> instanceIds =
instanceSet.instances!.map((e) => e.id).toList();
completion(instanceIds);
}
void getClassDetailInfo(
String classId, Function(ClsModel?) completion) async {
Class cls = await serviceWrapper.getObject(classId) as Class;
ClsModel? _clsModel;
if (cls.fields != null && cls.fields!.isNotEmpty) {
List<Property> properties = [];
List<String> functions = [];
cls.fields?.forEach((fieldRef) {
Property _property = Property(fieldRef.isConst, fieldRef.isStatic,
fieldRef.isFinal, fieldRef.declaredType!.name, fieldRef.name);
properties.add(_property);
});
for (var fucRef in cls.functions!) {
String? code;
Obj func = await serviceWrapper.getObject(fucRef.id!);
if (func is Func) {
code = func.code!.name;
if (func.code!.name!.contains("[Stub]")) {
continue;
}
code = code!.replaceAll('[Unoptimized] ', '');
code = code.replaceAll('[Optimized] ', '');
functions.add(code);
}
}
_clsModel = ClsModel(propeties: properties, functions: functions);
}
completion(_clsModel);
}
void _heapInfoList(List<ClassHeapStats> list) {
allClasses = list;
allClasses.sort((a, b) => b.accumulatedSize!.compareTo(a.accumulatedSize!));
infoList = allClasses
.where((element) => !element.classRef!.name!.startsWith("_"))
.toList();
}
void _vmToInfo(VM vm) {
StringBuffer buffer = StringBuffer();
buffer.writeln("Pid: ${vm.pid}");
buffer.writeln("CPU: ${vm.hostCPU}");
buffer.writeln("Version: ${vm.version}");
vmInfo = buffer.toString();
}
void _memoryUsed(MemoryUsage usage) {
StringBuffer buffer = StringBuffer();
buffer.writeln("ExternalUsage: ${byteToString(usage.externalUsage!)}");
buffer.writeln("HeapCapacity: ${byteToString(usage.heapCapacity!)}");
buffer.writeln("HeapUsage: ${byteToString(usage.heapUsage!)}");
memoryUseage = buffer.toString();
}
void hidePrivateClasses(bool hide) {
if (hide) {
infoList = allClasses
.where((element) => !element.classRef!.name!.startsWith("_"))
.toList();
} else {
infoList = allClasses;
}
}
void sort<T>(Comparable<T>? Function(ClassHeapStats d) getField,
bool descending, void Function() completion) {
s(List list) {
list.sort((a, b) {
final aValue = getField(a);
final bValue = getField(b);
return descending
? Comparable.compare(bValue!, aValue!)
: Comparable.compare(aValue!, bValue!);
});
}
s(infoList);
s(allClasses);
completion();
}
String byteToString(int size) {
const int m = 1024 * 1024;
const int k = 1024;
String resultSize = "";
if (size / m >= 1) {
resultSize = "${(size / m).toStringAsFixed(1)} M";
} else if (size / k >= 1) {
resultSize = "${(size / k).toStringAsFixed(1)} K";
} else {
resultSize = "$size B";
}
return resultSize;
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'icon.dart' as icon;
class Performance extends StatelessWidget implements Pluggable {
@override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.topCenter,
margin: const EdgeInsets.only(top: 20),
child: SizedBox(
child: PerformanceOverlay.allEnabled(),
width: MediaQuery.of(context).size.width));
}
@override
Widget buildWidget(BuildContext? context) => this;
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'PerfOverlay';
@override
String get displayName => 'PerfOverlay';
@override
void onTrigger() {}
}
@@ -0,0 +1,4 @@
library flutter_ume_kit_perf;
export 'components/performance/performance.dart';
export 'components/memory_info/memory_info_page.dart';
@@ -0,0 +1,25 @@
name: flutter_ume_kit_perf
description: Performance kits for flutter_ume.
version: 1.0.0
homepage: https://github.com/bytedance/flutter_ume
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
vm_service: 14.2.1
flutter_ume: ">=1.0.0 <2.0.0"
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.12
flutter_coverage_badge:
git:
url: https://github.com/smileShirely/flutter_coverage_badge.git
ref: 59b7580f406bb712e9d9049c8c99212946e34f65
flutter:
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: unknown
project_type: package
@@ -0,0 +1,39 @@
# Changelog
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 更新版本号
* Update version
## 0.2.1
* null-safety 正式版本
* 代码优化,修复 typo #11
* Null-Safety formal version.
* Optimization and fix typo #11
## 0.2.0-dev.0
* 适配 null-safety
* Adapted Null-Safety.
## 0.1.0
* 发布开源版本。
* Release opensource version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,11 @@
# flutter_ume_kit_show_code
[flutter_ume](https://pub.dev/packages/flutter_ume) 是由字节跳动 Flutter Infra 团队出品的应用内调试工具平台。
flutter_ume_kit_show_code 是 flutter_ume 的代码查看插件包。接入方式请见 [flutter_ume](https://pub.dev/packages/flutter_ume)。
----
[flutter_ume](https://pub.dev/packages/flutter_ume) is an in-app debug kits platform produced for Flutter apps by ByteDance Flutter Infra team.
flutter_ume_kit_show_code is the Show code kits package of flutter_ume. Please visit [flutter_ume](https://pub.dev/packages/flutter_ume) for details.
@@ -0,0 +1,322 @@
SF:lib/show_code/code_display_service.dart
DA:5,1
DA:7,3
DA:8,3
DA:9,4
DA:10,1
DA:17,2
DA:18,6
DA:20,3
DA:21,2
DA:22,1
DA:29,2
DA:30,6
DA:32,1
DA:34,3
DA:35,2
DA:36,3
DA:42,1
DA:43,3
DA:44,1
DA:45,1
LF:20
LH:20
end_of_record
SF:lib/show_code/page_info_helper.dart
DA:8,1
DA:9,1
DA:15,3
DA:17,0
DA:19,4
DA:21,1
DA:22,1
DA:23,2
DA:24,3
DA:25,1
DA:26,2
DA:27,2
DA:31,0
DA:35,0
DA:36,0
DA:39,1
DA:40,1
DA:41,1
DA:43,3
DA:47,2
DA:48,1
DA:51,1
DA:52,2
DA:53,3
DA:57,1
DA:58,4
DA:59,2
DA:60,1
DA:62,1
DA:63,1
DA:64,3
DA:65,1
DA:66,4
DA:68,1
DA:69,1
DA:70,1
DA:74,1
DA:76,5
DA:78,1
DA:79,1
DA:80,2
DA:83,1
DA:84,1
DA:85,3
DA:87,2
DA:90,0
DA:94,0
DA:95,0
DA:98,0
DA:100,0
DA:105,0
DA:106,0
DA:107,0
DA:108,0
DA:109,0
DA:110,0
DA:112,0
DA:113,0
DA:114,0
LF:59
LH:41
end_of_record
SF:lib/show_code/show_code.dart
DA:11,2
DA:13,1
DA:14,1
DA:16,1
DA:19,1
DA:21,2
DA:23,1
DA:26,0
DA:29,1
DA:43,1
DA:45,2
DA:46,1
DA:47,4
DA:48,3
DA:49,0
DA:50,0
DA:52,1
DA:53,1
DA:54,3
DA:55,1
DA:58,1
DA:59,1
DA:60,1
DA:65,4
DA:66,0
DA:67,1
DA:68,1
DA:69,1
DA:71,1
DA:73,1
DA:74,1
DA:75,1
DA:76,1
DA:77,1
DA:78,2
DA:81,2
DA:82,1
DA:83,1
DA:90,1
DA:91,1
DA:92,1
DA:93,1
DA:95,1
DA:96,2
DA:97,1
DA:98,2
DA:99,1
DA:102,1
DA:104,1
DA:105,1
DA:106,1
DA:107,0
DA:110,0
DA:112,1
DA:113,0
DA:115,0
DA:116,0
DA:117,0
DA:118,0
DA:119,0
DA:120,0
DA:121,0
DA:124,0
DA:125,0
DA:126,0
DA:128,0
DA:130,0
DA:131,0
DA:132,0
DA:133,0
DA:135,0
DA:139,0
DA:144,0
DA:145,0
DA:147,1
DA:148,1
DA:149,1
DA:152,1
DA:153,0
DA:154,1
DA:156,1
DA:157,1
DA:164,0
DA:165,0
DA:168,0
DA:169,0
DA:170,0
DA:172,0
DA:173,0
DA:174,0
DA:175,0
DA:176,0
DA:177,0
DA:179,0
DA:181,0
DA:182,0
DA:183,0
DA:184,0
DA:185,0
DA:195,1
DA:197,1
DA:199,1
DA:200,0
DA:201,1
DA:203,1
DA:205,1
DA:206,1
DA:207,1
DA:208,1
DA:209,1
DA:210,1
DA:212,1
DA:219,0
DA:220,0
DA:223,0
LF:115
LH:67
end_of_record
SF:lib/show_code/syntax_highlighter.dart
DA:9,1
DA:19,1
DA:20,1
DA:31,0
DA:32,0
DA:59,1
DA:60,2
DA:61,1
DA:134,1
DA:136,1
DA:137,3
DA:139,1
DA:141,1
DA:144,1
DA:145,0
DA:147,0
DA:149,0
DA:150,0
DA:152,0
DA:155,3
DA:157,0
DA:159,3
DA:162,0
DA:166,1
DA:167,2
DA:169,2
DA:171,0
DA:174,0
DA:175,0
DA:176,0
DA:181,0
DA:182,0
DA:186,0
DA:187,0
DA:190,0
DA:193,0
DA:194,0
DA:202,0
DA:203,0
DA:204,0
DA:209,0
DA:210,0
DA:211,0
DA:216,0
DA:217,0
DA:218,0
DA:223,0
DA:224,0
DA:225,0
DA:230,0
DA:231,0
DA:232,0
DA:237,0
DA:238,0
DA:239,0
DA:244,0
DA:245,0
DA:246,0
DA:251,0
DA:252,0
DA:253,0
DA:258,0
DA:259,0
DA:260,0
DA:265,0
DA:266,0
DA:267,0
DA:272,0
DA:275,0
DA:276,0
DA:278,0
DA:280,0
DA:282,0
DA:284,0
DA:285,0
DA:286,0
DA:290,0
DA:291,0
DA:296,0
DA:300,0
DA:303,1
DA:307,1
DA:308,4
DA:309,0
DA:310,0
DA:311,0
DA:312,0
DA:313,0
DA:318,0
DA:319,0
DA:320,0
DA:321,0
DA:327,2
DA:338,0
DA:343,0
DA:344,0
DA:347,0
DA:348,0
DA:349,0
DA:350,0
DA:351,0
DA:352,0
DA:353,0
DA:354,0
DA:355,0
DA:356,0
DA:357,0
DA:358,0
DA:359,0
DA:360,0
DA:361,0
DA:363,0
LF:112
LH:21
end_of_record
@@ -0,0 +1,20 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="94" height="20">
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="a">
<rect width="94" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#a)">
<path fill="#555" d="M0 0h59v20H0z"/>
<path fill="#d0b712" d="M59 0h35v20H59z"/>
<path fill="url(#b)" d="M0 0h94v20H0z"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
<text x="305" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">coverage</text>
<text x="305" y="140" transform="scale(.1)" textLength="490">coverage</text>
<text x="755" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">65%</text>
<text x="755" y="140" transform="scale(.1)" textLength="250">65%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,3 @@
library flutter_ume_kit_perf;
export 'show_code/show_code.dart';
@@ -0,0 +1,48 @@
import 'package:vm_service/vm_service.dart';
import 'package:flutter_ume/flutter_ume.dart';
class CodeDisplayService with VMServiceWrapper {
Future<String?> getIdWithClassName(String className) async {
final classList = await serviceWrapper.getClassList();
final classes = classList.classes;
if (classes == null) return null;
for (final cls in classes) {
if (cls.name == className) return cls.id;
}
return null;
}
Future<String?> getScriptIdWithFileName(String fileName) async {
ScriptList scriptList = await serviceWrapper.getScripts();
final scripts = scriptList.scripts!;
for (final script in scripts) {
if (script.uri!.contains(fileName)) return script.id;
}
return null;
}
Future<Map<String?, String?>> getScriptIdsWithKeyword(String keyword) async {
ScriptList scriptList = await serviceWrapper.getScripts();
var result = <String?, String?>{};
scriptList.scripts!.forEach((script) {
if (script.uri!.contains(keyword)) {
result[script.id] = script.uri;
}
});
return result;
}
Future<String?> getSourceCodeWithScriptId(String scriptId) async {
Obj script = await serviceWrapper.getObject(scriptId);
if (script is Script) {
return script.source;
}
return null;
}
}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,121 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'code_display_service.dart';
class PageInfoHelper {
PageInfoHelper() {
_selectionInit();
}
final InspectorSelection selection =
WidgetInspectorService.instance.selection;
RenderObject? get renderObject => selection.current;
Element? get element => selection.currentElement;
String? get filePath => _jsonInfo!['creationLocation']['file'];
String packagePathConvertFromFilePath(String filePath) {
final parts = filePath.split(r'/lib/');
final fileForwardPart = parts.sublist(1).join('/lib/');
final packageName = parts.first.split('/').last;
final keyword = "package:$packageName/$fileForwardPart";
CodeDisplayService().getScriptIdsWithKeyword(keyword);
debugPrint(keyword);
return keyword;
}
int? get line => _jsonInfo!['creationLocation']['line'];
dynamic _ignorePointer;
String get message {
return '''${element!.toStringShort()}\nsize: ${renderObject!.paintBounds.size}\nfilePath: $filePath\nline: $line''';
}
Map? get _jsonInfo {
if (renderObject == null) return null;
final widgetId = WidgetInspectorService.instance
// ignore: invalid_use_of_protected_member
.toId(renderObject!.toDiagnosticsNode(), '');
if (widgetId == null) return null;
String infoStr =
WidgetInspectorService.instance.getSelectedSummaryWidget(widgetId, '');
return json.decode(infoStr);
}
double _area(RenderObject object) {
final Size size = object.paintBounds.size;
return size == null ? double.maxFinite : size.width * size.height;
}
// Init selection of current page
void _selectionInit() {
_ignorePointer = rootKey.currentContext!.findRenderObject();
final RenderObject userRender = _ignorePointer.child;
List<RenderObject> objectList = [];
void findAllRenderObject(RenderObject object) {
final List<DiagnosticsNode> children = object.debugDescribeChildren();
for (int i = 0; i < children.length; i++) {
DiagnosticsNode c = children[i];
if (c.style == DiagnosticsTreeStyle.offstage || c.value is! RenderBox)
continue;
RenderObject child = c.value as RenderObject;
objectList.add(child);
findAllRenderObject(child);
}
}
findAllRenderObject(userRender);
objectList
.sort((RenderObject a, RenderObject b) => _area(a).compareTo(_area(b)));
Set<RenderObject> objectSet = Set<RenderObject>();
objectSet.addAll(objectList);
objectList = objectSet.toList();
selection.candidates = objectList;
}
Future<String?> getCode() async {
CodeDisplayService codeDisplayService = CodeDisplayService();
String targetFileName = filePath!.split('/').last;
String? scriptId =
await codeDisplayService.getScriptIdWithFileName(targetFileName);
if (scriptId == null) return null;
String? sourceCode =
await codeDisplayService.getSourceCodeWithScriptId(scriptId);
return sourceCode;
}
Future<String?> getCodeByFileName(String fileName) async {
CodeDisplayService codeDisplayService = CodeDisplayService();
String? sourceCode;
String? scriptId =
await codeDisplayService.getScriptIdWithFileName(fileName);
if (scriptId != null) {
sourceCode = await codeDisplayService.getSourceCodeWithScriptId(scriptId);
}
return sourceCode;
}
Future<Map<String?, String>> getCodeListByKeyword(String keyword) async {
CodeDisplayService codeDisplayService = CodeDisplayService();
Map<String?, String> result = <String?, String>{};
final scriptIds = await codeDisplayService.getScriptIdsWithKeyword(keyword);
if (scriptIds.isNotEmpty) {
for (final entry in scriptIds.entries) {
final code =
await codeDisplayService.getSourceCodeWithScriptId(entry.key!);
if (code != null && code.isNotEmpty) {
result[entry.value] = code;
}
}
}
return result;
}
}
@@ -0,0 +1,224 @@
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:flutter_ume/flutter_ume.dart';
import 'package:share_plus/share_plus.dart';
import 'page_info_helper.dart';
import 'syntax_highlighter.dart';
import 'icon.dart' as icon;
class ShowCode extends StatefulWidget implements Pluggable {
const ShowCode({Key? key}) : super(key: key);
@override
ShowCodeState createState() => ShowCodeState();
@override
Widget buildWidget(BuildContext? context) => this;
@override
ImageProvider<Object> get iconImageProvider => MemoryImage(icon.iconBytes);
@override
String get name => 'ShowCode';
@override
String get displayName => 'ShowCode';
@override
void onTrigger() {}
}
class ShowCodeState extends State<ShowCode> with WidgetsBindingObserver {
late PageInfoHelper pageInfoHelper;
String? code;
String? filePath;
Map<String?, String>? _codeList;
late bool showCodeList;
late bool isSearching;
TextEditingController? textEditingController;
@override
void initState() {
pageInfoHelper = PageInfoHelper();
filePath =
pageInfoHelper.packagePathConvertFromFilePath(pageInfoHelper.filePath!);
pageInfoHelper.getCode().then((c) {
code = c;
setState(() {});
});
showCodeList = false;
isSearching = false;
textEditingController = TextEditingController(text: filePath);
super.initState();
}
Widget _codeView() {
String codeContent = code ?? '';
if (_codeList != null && _codeList!.isNotEmpty && codeContent.isEmpty) {
codeContent = '已找到匹配项,请点击菜单选择';
}
double _textScaleFactor = 1.0;
final SyntaxHighlighterStyle style =
Theme.of(context).brightness == Brightness.dark
? SyntaxHighlighterStyle.darkThemeStyle()
: SyntaxHighlighterStyle.lightThemeStyle();
return Scrollbar(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SelectableText.rich(
TextSpan(
style: TextStyle(fontFamily: 'monospace', fontSize: 12.0)
.apply(fontSizeFactor: _textScaleFactor),
children: <TextSpan>[
DartSyntaxHighlighter(style).format(codeContent)
],
),
style: DefaultTextStyle.of(context)
.style
.apply(fontSizeFactor: _textScaleFactor),
),
),
),
);
}
Widget _infoView() {
return Container(
padding: EdgeInsets.all(8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("页面代码", style: TextStyle(fontSize: 20, height: 1.5)),
Text.rich(
TextSpan(children: [
TextSpan(
text: "当前路径(点击以编辑,支持部分匹配):",
),
], style: TextStyle(height: 2)),
),
Row(
children: <Widget>[
if (isSearching)
SizedBox(
width: 22,
height: 22,
child: CircularProgressIndicator(),
),
if (showCodeList &&
_codeList != null &&
_codeList!.isNotEmpty)
PopupMenuButton<String>(
padding: EdgeInsets.zero,
icon: Icon(Icons.arrow_drop_down),
onSelected: (String codepath) {
debugPrint(codepath);
setState(() {
code = _codeList![codepath];
filePath = codepath;
textEditingController!.text = filePath!;
});
},
itemBuilder: (BuildContext context) => _codeList!
.map((codepath, codeid) {
return MapEntry(
codepath,
PopupMenuItem<String>(
value: codepath,
child: Column(
children: <Widget>[
ListTile(
title: Text(
codepath!,
style: TextStyle(
color: Colors.teal, fontSize: 14),
),
),
Divider(),
],
)),
);
})
.values
.toList(),
),
Expanded(
child: TextField(
decoration: InputDecoration(
hintText: "请输入路径",
border: InputBorder.none,
suffixIcon: IconButton(
onPressed: () => textEditingController!.clear(),
icon: Icon(Icons.clear),
)),
controller: textEditingController,
style: TextStyle(color: Colors.teal, fontSize: 14),
maxLines: 5,
minLines: 1,
// decoration: null,
autocorrect: false,
enableSuggestions: false,
textInputAction: TextInputAction.done,
onSubmitted: (value) {
if (value.length < 2) {
return;
}
setState(() {
isSearching = true;
filePath = value;
});
pageInfoHelper
.getCodeListByKeyword(value)
.then((codeList) {
if (codeList != null && codeList.isNotEmpty) {
showCodeList = true;
_codeList = codeList;
} else {
showCodeList = false;
}
isSearching = false;
code = null;
filePath = null;
setState(() {});
debugPrint(codeList.length.toString());
});
},
),
),
],
)
]));
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endTop,
floatingActionButton: FloatingActionButton(
onPressed: () => _share(),
child: Icon(Icons.share),
),
body: Container(
color: Colors.white,
child: SafeArea(
child: Column(
children: <Widget>[
_infoView(),
Divider(),
Expanded(
flex: 1,
child: _codeView(),
)
],
))),
);
}
Future<ShareResult> _share() async {
return Share.share(code!);
}
}
@@ -0,0 +1,365 @@
// Copyright 2016 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 'package:flutter/material.dart';
import 'package:string_scanner/string_scanner.dart';
class SyntaxHighlighterStyle {
SyntaxHighlighterStyle(
{this.baseStyle,
this.numberStyle,
this.commentStyle,
this.keywordStyle,
this.stringStyle,
this.punctuationStyle,
this.classStyle,
this.constantStyle});
static SyntaxHighlighterStyle lightThemeStyle() {
return SyntaxHighlighterStyle(
baseStyle: const TextStyle(color: const Color(0xFF000000)),
numberStyle: const TextStyle(color: const Color(0xFF1565C0)),
commentStyle: const TextStyle(color: const Color(0xFF9E9E9E)),
keywordStyle: const TextStyle(color: const Color(0xFF9C27B0)),
stringStyle: const TextStyle(color: const Color(0xFF43A047)),
punctuationStyle: const TextStyle(color: const Color(0xFF000000)),
classStyle: const TextStyle(color: const Color(0xFF512DA8)),
constantStyle: const TextStyle(color: const Color(0xFF795548)));
}
static SyntaxHighlighterStyle darkThemeStyle() {
return SyntaxHighlighterStyle(
baseStyle: const TextStyle(color: const Color(0xFFFFFFFF)),
numberStyle: const TextStyle(color: const Color(0xFF1565C0)),
commentStyle: const TextStyle(color: const Color(0xFF9E9E9E)),
keywordStyle: const TextStyle(color: const Color(0xFF80CBC4)),
stringStyle: const TextStyle(color: const Color(0xFF009688)),
punctuationStyle: const TextStyle(color: const Color(0xFFFFFFFF)),
classStyle: const TextStyle(color: const Color(0xFF009688)),
constantStyle: const TextStyle(color: const Color(0xFF795548)));
}
final TextStyle? baseStyle;
final TextStyle? numberStyle;
final TextStyle? commentStyle;
final TextStyle? keywordStyle;
final TextStyle? stringStyle;
final TextStyle? punctuationStyle;
final TextStyle? classStyle;
final TextStyle? constantStyle;
}
abstract class SyntaxHighlighter {
// ignore: one_member_abstracts
TextSpan format(String src);
}
class DartSyntaxHighlighter extends SyntaxHighlighter {
DartSyntaxHighlighter([this._style]) {
_spans = <_HighlightSpan>[];
_style ??= SyntaxHighlighterStyle.darkThemeStyle();
}
SyntaxHighlighterStyle? _style;
static const List<String> _keywords = const <String>[
'abstract',
'as',
'assert',
'async',
'await',
'break',
'case',
'catch',
'class',
'const',
'continue',
'default',
'deferred',
'do',
'dynamic',
'else',
'enum',
'export',
'external',
'extends',
'factory',
'false',
'final',
'finally',
'for',
'get',
'if',
'implements',
'import',
'in',
'is',
'library',
'new',
'null',
'operator',
'part',
'rethrow',
'return',
'set',
'static',
'super',
'switch',
'sync',
'this',
'throw',
'true',
'try',
'typedef',
'var',
'void',
'while',
'with',
'yield'
];
static const List<String> _builtInTypes = const <String>[
'int',
'double',
'num',
'bool'
];
late String _src;
late StringScanner _scanner;
late List<_HighlightSpan> _spans;
@override
TextSpan format(String src) {
_src = src;
_scanner = StringScanner(_src);
if (_generateSpans()) {
// Successfully parsed the code
final List<TextSpan> formattedText = <TextSpan>[];
int currentPosition = 0;
for (_HighlightSpan span in _spans) {
if (currentPosition != span.start)
formattedText
.add(TextSpan(text: _src.substring(currentPosition, span.start)));
formattedText.add(TextSpan(
style: span.textStyle(_style), text: span.textForSpan(_src)));
currentPosition = span.end;
}
if (currentPosition != _src.length)
formattedText
.add(TextSpan(text: _src.substring(currentPosition, _src.length)));
return TextSpan(style: _style!.baseStyle, children: formattedText);
} else {
// Parsing failed, return with only basic formatting
return TextSpan(style: _style!.baseStyle, text: src);
}
}
bool _generateSpans() {
int lastLoopPosition = _scanner.position;
while (!_scanner.isDone) {
// Skip White space
_scanner.scan(RegExp(r'\s+'));
// Block comments
if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) {
_spans.add(_HighlightSpan(_HighlightType.comment,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Line comments
if (_scanner.scan('//')) {
final int startComment = _scanner.lastMatch!.start;
bool eof = false;
int endComment;
if (_scanner.scan(RegExp(r'.*\n'))) {
endComment = _scanner.lastMatch!.end - 1;
} else {
eof = true;
endComment = _src.length;
}
_spans.add(
_HighlightSpan(_HighlightType.comment, startComment, endComment));
if (eof) break;
continue;
}
// Raw r"String"
if (_scanner.scan(RegExp(r'r".*"'))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Raw r'String'
if (_scanner.scan(RegExp(r"r'.*'"))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Multiline """String"""
if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Multiline '''String'''
if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// "String"
if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// 'String'
if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) {
_spans.add(_HighlightSpan(_HighlightType.string,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Double
if (_scanner.scan(RegExp(r'\d+\.\d+'))) {
_spans.add(_HighlightSpan(_HighlightType.number,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Integer
if (_scanner.scan(RegExp(r'\d+'))) {
_spans.add(_HighlightSpan(_HighlightType.number,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Punctuation
if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) {
_spans.add(_HighlightSpan(_HighlightType.punctuation,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Meta data
if (_scanner.scan(RegExp(r'@\w+'))) {
_spans.add(_HighlightSpan(_HighlightType.keyword,
_scanner.lastMatch!.start, _scanner.lastMatch!.end));
continue;
}
// Words
if (_scanner.scan(RegExp(r'\w+'))) {
_HighlightType? type;
String word = _scanner.lastMatch![0]!;
if (word.startsWith('_')) word = word.substring(1);
if (_keywords.contains(word))
type = _HighlightType.keyword;
else if (_builtInTypes.contains(word))
type = _HighlightType.keyword;
else if (_firstLetterIsUpperCase(word))
type = _HighlightType.klass;
else if (word.length >= 2 &&
word.startsWith('k') &&
_firstLetterIsUpperCase(word.substring(1)))
type = _HighlightType.constant;
if (type != null) {
_spans.add(_HighlightSpan(
type, _scanner.lastMatch!.start, _scanner.lastMatch!.end));
}
}
// Check if this loop did anything
if (lastLoopPosition == _scanner.position) {
// Failed to parse this file, abort gracefully
return false;
}
lastLoopPosition = _scanner.position;
}
_simplify();
return true;
}
void _simplify() {
for (int i = _spans.length - 2; i >= 0; i -= 1) {
if (_spans[i].type == _spans[i + 1].type &&
_spans[i].end == _spans[i + 1].start) {
_spans[i] =
_HighlightSpan(_spans[i].type, _spans[i].start, _spans[i + 1].end);
_spans.removeAt(i + 1);
}
}
}
bool _firstLetterIsUpperCase(String str) {
if (str.isNotEmpty) {
final String first = str.substring(0, 1);
return first == first.toUpperCase();
}
return false;
}
}
enum _HighlightType {
number,
comment,
keyword,
string,
punctuation,
klass,
constant
}
class _HighlightSpan {
_HighlightSpan(this.type, this.start, this.end);
final _HighlightType type;
final int start;
final int end;
String textForSpan(String src) {
return src.substring(start, end);
}
TextStyle? textStyle(SyntaxHighlighterStyle? style) {
if (type == _HighlightType.number)
return style!.numberStyle;
else if (type == _HighlightType.comment)
return style!.commentStyle;
else if (type == _HighlightType.keyword)
return style!.keywordStyle;
else if (type == _HighlightType.string)
return style!.stringStyle;
else if (type == _HighlightType.punctuation)
return style!.punctuationStyle;
else if (type == _HighlightType.klass)
return style!.classStyle;
else if (type == _HighlightType.constant)
return style!.constantStyle;
else
return style!.baseStyle;
}
}
@@ -0,0 +1,29 @@
name: flutter_ume_kit_show_code
description: Show Code kit for flutter_ume.
version: 1.0.0
homepage: https://github.com/bytedance/flutter_ume
publish_to: none
environment:
sdk: ">=2.12.0 <4.0.0"
flutter: ">=2.0.0"
dependencies:
flutter:
sdk: flutter
flutter_ume:
path: ../../../flutter_ume
share_plus: ^12.0.0
vm_service: 14.2.1
string_scanner: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter
mockito: ^5.0.12
flutter_coverage_badge:
git:
url: https://github.com/smileShirely/flutter_coverage_badge.git
ref: 59b7580f406bb712e9d9049c8c99212946e34f65
flutter:
@@ -0,0 +1,4 @@
# melos_managed_dependency_overrides: flutter_ume
dependency_overrides:
flutter_ume:
path: ../..
@@ -0,0 +1,75 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
build/
# Android related
**/android/**/gradle-wrapper.jar
**/android/.gradle
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
@@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 02c026b03cd31dd3f867e5faeb7e104cce174c5f
channel: unknown
project_type: package
@@ -0,0 +1,54 @@
# Changelog
## 1.1.1
* image: ^3.0.5 升级到 image: ^4.0.15
* quiver: ^3.0.1 升级到 quiver: ^3.2.1
* mockito: ^5.0.12 升级到 mockito: ^5.3.2
* Bump image from 3.0.5 to 4.0.15
* Bump quiver from 3.0.1 to 3.2.1
* Bump mockito from 5.0.12 to 5.3.2
## 1.1.0
* 适配 Flutter 3.7,不兼容旧版本的适配
* Adapt Flutter 3.7, breaking change.
## 1.0.0
* 正式版
* Normal version.
## 1.0.0-dev.0
* 适配 Flutter 3
* Adapt Flutter 3
## 0.3.0
* 新增 UI 调试插件:cyclop、touch_indicator
* Add new kits: cyclop, touch_indicator
## 0.2.1
* null-safety 正式版本
* Null-Safety formal version.
## 0.2.0-dev.0
* 适配 null-safety
* Adapted Null-Safety.
## 0.1.0
* 发布开源版本。
* Release opensource version.
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 ByteDance Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More