parseWidgetTree function
Parses the Flutter widget tree and returns a list of widget data maps.
element
: The root element of the widget tree to parse.
Implementation
Future<List<Map<String, dynamic>>> parseWidgetTree(Element element) async {
final widgetTree = <Map<String, dynamic>>[];
final List<AccessiblePosition?> accessiblePositionList = [];
AccessiblePosition? accessibility;
/// All controls excluding the type 10 root node
final List<Map<String, dynamic>> allControlsList = [];
// Element parentElement;
try {
// Recursively parse the widget tree
void traverse(Element element, [int depth = 0]) {
final widget = element.widget;
final type = widget.runtimeType.toString();
/// Build type 10 object
if (widget is Semantics ||
widget is TextField ||
widget is Text ||
widget is ElevatedButton ||
widget is TextFormField ||
widget is TextField ||
widget is Checkbox ||
widget is CheckboxListTile ||
widget is Switch ||
widget is SwitchListTile ||
widget is Slider ||
widget is Radio ||
widget is RadioListTile ||
widget is DropdownButton ||
widget is DropdownMenuItem ||
widget is AlertDialog ||
widget is SnackBar ||
widget is Image ||
widget is Icon) {
RenderBox? renderObject = element.renderObject as RenderBox?;
if (renderObject != null && renderObject.hasSize) {
// Access properties or methods specific to RenderBox
// final renderObject = element.renderObject as RenderBox;
final position = renderObject.localToGlobal(Offset.zero);
final size = renderObject.size;
Map<String, dynamic>? aStyle;
Map<String, dynamic>? font;
Map<String, dynamic>? image;
String? text = "";
if (widget is Text) {
final TextStyle style = widget.style ?? TextStyle();
final TextAlign align = widget.textAlign ?? TextAlign.left;
Widget currentWidget = widget;
Padding padding;
font = {
'family': style.fontFamily,
'size': style.fontSize.toString(),
'bold': (style.fontWeight != null &&
FontWeight.values.indexOf(style.fontWeight!) >
FontWeight.values.indexOf(FontWeight.normal))
.toString(),
'italic': (style.fontStyle == FontStyle.italic).toString()
};
double top = 0, bottom = 0, left = 0, right = 0;
/// Get Padding
element.visitAncestorElements((ancestor) {
currentWidget = ancestor.widget;
if (currentWidget is Padding) {
padding = currentWidget as Padding;
if (padding.padding is EdgeInsets) {
final EdgeInsets eig = padding.padding as EdgeInsets;
top = eig.top;
bottom = eig.bottom;
left = eig.left;
right = eig.right;
}
return false;
}
return true;
});
aStyle = {
'textColor': ((style.color?.value ?? 0) & 0xFFFFFF).toString(),
'textAlphaColor': (style.color?.alpha ?? 0).toString(),
'textAlphaBGColor':
(style.backgroundColor?.alpha ?? 0).toString(),
'textAlign': align.toString().split('.').last,
'paddingBottom': bottom.toInt().toString(),
'paddingTop': top.toInt().toString(),
'paddingLeft': left.toInt().toString(),
'paddingRight': right.toInt().toString(),
'hidden': (style.color?.opacity == 1.0).toString(),
'colorPrimary': (style.foreground?.color ?? 0).toString(),
'colorPrimaryDark': 0.toString(), // TBD: Dark theme??
'colorAccent': (style.decorationColor?.value ?? 0).toString(),
};
}
/// Get Semantics
if (widget is Semantics) {
final Semantics semantics = widget;
if (semantics.properties.label?.isNotEmpty == true ||
semantics.properties.label?.isNotEmpty == true) {
final String? hint = semantics.properties.hint;
final String? label = semantics.properties.label;
print(
'Tealeaf - Widget is a semantic type: ${semantics.properties}');
/// Get Accessibility object, and its position for masking purpose
accessibility = AccessiblePosition(
id: element.toStringShort(),
label: label ?? '',
hint: hint ?? '',
dx: position.dx,
dy: position.dy,
width: size.width,
height: size.height,
);
accessiblePositionList.add(accessibility);
}
} else {
text = widget is Text ? widget.data : '';
final widgetData = {
'type': type,
'text': text,
'position':
'x: ${position.dx}, y: ${position.dy}, width: ${size.width}, height: ${size.height}',
};
// tlLogger.v('WidgetData - ${widget.toString()}');
widgetTree.add(widgetData);
Map<String, dynamic> accessibilityMap = {
'id': accessibility?.id,
'label': accessibility?.label,
'hint': accessibility?.hint,
};
final masked = (accessibility != null) ? true : false;
final widgetId =
widget.runtimeType.toString() + widget.hashCode.toString();
/// Add the control as map to the list
allControlsList.add(<String, dynamic>{
'id': widgetId,
'cssId': widgetId,
'idType': (-4).toString(),
// ignore: unnecessary_null_comparison
'tlType': (image != null)
? 'image'
: (text != null && text.contains('\n')
? 'textArea'
: 'label'),
'type': type,
'subType': widget.runtimeType.toString(),
'position': <String, String>{
'x': position.dx.toInt().toString(),
'y': position.dy.toInt().toString(),
'width': renderObject.size.width.toInt().toString(),
'height': renderObject.size.height.toInt().toString(),
},
'zIndex': "501",
'currState': <String, dynamic>{'text': text, 'font': font},
if (aStyle != null) 'style': aStyle,
if (accessibility != null) 'accessibility': accessibilityMap,
'originalId': "",
'masked': '$masked'
});
/// Reset
if (accessibility != null) {
accessibility = null;
}
}
}
}
/// Recursively call to wall down the tree, only Visible children
element.visitChildren((child) {
bool visible = true;
if (widget is Visibility) {
final visibility = widget;
if (!visibility.visible) {
visible = false;
}
}
/// Skip invisible Widgets
if (visible) {
// parentElement = element;
// tlLogger.v('Parent widget - $parentElement.');
traverse(child, depth + 1);
}
return;
});
}
/// Starting to parse tree
traverse(element, 0);
// Encode the JSON object
String jsonString = jsonEncode(widgetTree);
PluginTealeaf.tlApplicationCustomEvent(eventName: jsonString);
} catch (error) {
// Handle errors using try-catch block
tlLogger.v('Error caught in try-catch: $error');
}
return allControlsList;
}