Line data Source code
1 : import 'dart:async';
2 : import 'dart:convert';
3 : import 'dart:math';
4 : import 'package:flutter/material.dart';
5 : import 'package:flutter_ume_kit_ui/components/hit_test.dart';
6 : import 'package:flutter_ume/flutter_ume.dart';
7 : import 'search_bar.dart';
8 : import 'icon.dart' as icon;
9 :
10 : class WidgetDetailInspector extends StatelessWidget implements Pluggable {
11 2 : const WidgetDetailInspector({Key? key}) : super(key: key);
12 :
13 1 : @override
14 : Widget build(BuildContext context) {
15 1 : return MaterialApp(
16 1 : theme: ThemeData(primaryColor: Colors.white),
17 1 : home: _DetailPage(),
18 : );
19 : }
20 :
21 1 : @override
22 : Widget buildWidget(BuildContext? context) => this;
23 :
24 1 : @override
25 : ImageProvider<Object> get iconImageProvider =>
26 2 : MemoryImage(base64Decode(icon.iconData));
27 :
28 1 : @override
29 : String get name => 'WidgetDetail';
30 :
31 0 : @override
32 : String get displayName => 'WidgetDetail';
33 :
34 1 : @override
35 : void onTrigger() {}
36 : }
37 :
38 : class _DetailPage extends StatefulWidget {
39 2 : const _DetailPage({Key? key}) : super(key: key);
40 :
41 1 : @override
42 1 : _DetailPageState createState() => _DetailPageState();
43 : }
44 :
45 : class _DetailPageState extends State<_DetailPage> with WidgetsBindingObserver {
46 3 : _DetailPageState() : selection = WidgetInspectorService.instance.selection;
47 :
48 : final window = WidgetsBinding.instance!.window;
49 :
50 : Offset? _lastPointerLocation;
51 :
52 : final InspectorSelection selection;
53 :
54 1 : void _inspectAt(Offset? position) {
55 1 : final List<RenderObject> selected = HitTest.hitTest(position);
56 2 : setState(() {
57 2 : selection.candidates = selected;
58 : });
59 : }
60 :
61 1 : void _handlePanDown(DragDownDetails event) {
62 2 : _lastPointerLocation = event.globalPosition;
63 2 : _inspectAt(event.globalPosition);
64 : }
65 :
66 1 : void _handleTap() {
67 1 : if (_lastPointerLocation != null) {
68 2 : _inspectAt(_lastPointerLocation);
69 : }
70 3 : Future.delayed(Duration(milliseconds: 100), () {
71 5 : Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
72 1 : return _InfoPage(
73 3 : elements: selection.currentElement!.debugGetDiagnosticChain());
74 : }));
75 : });
76 : }
77 :
78 1 : @override
79 : void initState() {
80 1 : super.initState();
81 2 : selection.clear();
82 : }
83 :
84 1 : @override
85 : Widget build(BuildContext context) {
86 1 : List<Widget> children = <Widget>[];
87 1 : GestureDetector gesture = GestureDetector(
88 1 : onTap: _handleTap,
89 1 : onPanDown: _handlePanDown,
90 : behavior: HitTestBehavior.translucent,
91 1 : child: IgnorePointer(
92 1 : child: Container(
93 3 : width: MediaQuery.of(context).size.width,
94 3 : height: MediaQuery.of(context).size.height),
95 : ),
96 : );
97 1 : children.add(gesture);
98 2 : children.add(InspectorOverlay(
99 1 : selection: selection, needDescription: false, needEdges: false));
100 1 : return Stack(children: children, textDirection: TextDirection.ltr);
101 : }
102 : }
103 :
104 : class _DetailModel {
105 : List<int> colors = [
106 : Random().nextInt(256),
107 : Random().nextInt(256),
108 : Random().nextInt(256)
109 : ];
110 : Element element;
111 1 : _DetailModel(this.element);
112 : }
113 :
114 : class _InfoPage extends StatefulWidget {
115 1 : const _InfoPage({Key? key, required this.elements})
116 0 : : assert(elements != null),
117 1 : super(key: key);
118 :
119 : final List<Element> elements;
120 :
121 1 : @override
122 1 : __InfoPageState createState() => __InfoPageState();
123 : }
124 :
125 : class __InfoPageState extends State<_InfoPage> {
126 : List<_DetailModel> _showList = <_DetailModel>[];
127 : late List<_DetailModel> _originalList;
128 :
129 1 : @override
130 : void initState() {
131 1 : super.initState();
132 4 : widget.elements.forEach((f) {
133 3 : _showList.add(_DetailModel(f));
134 : });
135 2 : _originalList = _showList;
136 : }
137 :
138 1 : Widget _dot(List<int> colors) {
139 4 : Color randomColor = Color.fromARGB(255, colors[0], colors[1], colors[2]);
140 1 : return Container(
141 1 : decoration: BoxDecoration(
142 : shape: BoxShape.circle,
143 : color: randomColor,
144 : ),
145 : width: 10,
146 : height: 10,
147 : );
148 : }
149 :
150 1 : Widget _cell(_DetailModel model, BuildContext context) {
151 1 : return GestureDetector(
152 1 : onTap: () {
153 4 : Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
154 1 : return Scaffold(
155 2 : body: _DetailContent(element: model.element),
156 1 : appBar: PreferredSize(
157 2 : child: AppBar(elevation: 0.0, title: Text("widget_detail")),
158 1 : preferredSize: Size.fromHeight(44)));
159 : }));
160 : },
161 1 : child: Container(
162 : margin: const EdgeInsets.only(top: 6, bottom: 6, left: 12, right: 12),
163 1 : child: Row(
164 1 : children: [
165 1 : Padding(
166 : padding: const EdgeInsets.only(right: 12),
167 2 : child: _dot(model.colors)),
168 1 : Expanded(
169 1 : child: Text(
170 4 : "${model.element.widget.toStringShort()}",
171 1 : style: TextStyle(fontSize: 15),
172 : ))
173 : ],
174 : ),
175 : ),
176 : );
177 : }
178 :
179 0 : void _textChange(String query) {
180 0 : query = query.trim();
181 0 : List<_DetailModel> infoList = [];
182 0 : _originalList.forEach((_DetailModel model) {
183 0 : var regExp = RegExp("$query", caseSensitive: false);
184 0 : if (regExp.hasMatch(model.element.widget.toStringShort())) {
185 0 : infoList.add(model);
186 : }
187 : });
188 0 : setState(() {
189 0 : _showList = infoList;
190 : });
191 : }
192 :
193 1 : @override
194 : Widget build(BuildContext context) {
195 1 : return Scaffold(
196 : resizeToAvoidBottomInset: false,
197 1 : body: Container(
198 1 : child: Column(
199 1 : children: <Widget>[
200 1 : Padding(
201 : padding: const EdgeInsets.only(
202 : left: 12, right: 12, top: 10, bottom: 10),
203 1 : child: SearchBar(
204 1 : placeHolder: '请输入要搜索的widget', onChangeHandle: _textChange),
205 : ),
206 1 : Expanded(
207 1 : child: GestureDetector(
208 1 : onPanDown: (_) {
209 3 : FocusScope.of(context).requestFocus(FocusNode());
210 : },
211 1 : child: ListView.builder(
212 1 : physics: BouncingScrollPhysics(),
213 1 : itemBuilder: (_, index) {
214 2 : _DetailModel model = _showList[index];
215 1 : return _cell(model, context);
216 : },
217 2 : itemCount: _showList.length),
218 : ),
219 : ),
220 : ],
221 : ),
222 : ),
223 1 : appBar: PreferredSize(
224 1 : child: AppBar(
225 : elevation: 0.0,
226 1 : title: RichText(
227 1 : text: TextSpan(
228 : text: 'widget_build_chain',
229 1 : style: TextStyle(
230 : color: Colors.black,
231 : fontSize: 19,
232 : fontWeight: FontWeight.w500,
233 : fontFamily: "PingFang SC"),
234 1 : children: <TextSpan>[
235 1 : TextSpan(
236 4 : text: ' depth: ${widget.elements.length}',
237 1 : style: TextStyle(color: Colors.black, fontSize: 11)),
238 : ],
239 : ),
240 : )),
241 1 : preferredSize: Size.fromHeight(44)));
242 : }
243 : }
244 :
245 : class _DetailContent extends StatelessWidget {
246 1 : const _DetailContent({Key? key, required this.element})
247 0 : : assert(element != null),
248 1 : super(key: key);
249 :
250 : final Element element;
251 :
252 1 : Widget _titleWidget(String title) {
253 1 : return Padding(
254 : padding: const EdgeInsets.only(top: 12, left: 12),
255 1 : child: Text(
256 : title,
257 : style: const TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
258 : ),
259 : );
260 : }
261 :
262 1 : Future<List<String>> getInfo() async {
263 1 : Completer<List<String>> completer = Completer();
264 3 : String string = element.renderObject!.toStringDeep();
265 1 : List<String> list = string.split("\n");
266 1 : completer.complete(list);
267 1 : return completer.future;
268 : }
269 :
270 1 : @override
271 : Widget build(BuildContext context) {
272 1 : return FutureBuilder(
273 1 : future: getInfo(),
274 1 : builder: (_, snapshot) {
275 2 : if (snapshot.connectionState == ConnectionState.waiting) {
276 1 : return Center(
277 1 : child: CircularProgressIndicator(
278 1 : valueColor: AlwaysStoppedAnimation<Color>(Colors.red)));
279 2 : } else if (snapshot.connectionState == ConnectionState.done) {
280 1 : return Container(
281 1 : child: Column(
282 : crossAxisAlignment: CrossAxisAlignment.start,
283 1 : children: [
284 1 : _titleWidget("Widget Description"),
285 1 : Container(
286 1 : constraints: BoxConstraints(maxHeight: 200),
287 : padding:
288 : const EdgeInsets.only(top: 12, left: 12, right: 12),
289 1 : child: SingleChildScrollView(
290 4 : child: Text(element.widget.toStringDeep()))),
291 1 : _titleWidget("RenderObject Description"),
292 1 : Expanded(
293 1 : child: Padding(
294 : padding: const EdgeInsets.only(
295 : top: 12, right: 12, left: 12),
296 1 : child: ListView.builder(
297 1 : physics: BouncingScrollPhysics(),
298 1 : itemBuilder: (_, index) {
299 1 : return SingleChildScrollView(
300 1 : child: Text(
301 2 : (snapshot.data as List<String>)[index]),
302 : scrollDirection: Axis.horizontal);
303 : },
304 : itemCount:
305 2 : (snapshot.data as List<String>).length))),
306 : ],
307 : ),
308 : );
309 : } else {
310 0 : return Container();
311 : }
312 : });
313 : }
314 : }
|