From 736dff6b14e7de926232e46d6ced53c208cc5628 Mon Sep 17 00:00:00 2001 From: polemarkh Date: Sat, 15 Mar 2025 18:10:24 -0400 Subject: [PATCH 1/2] Add features to the base message-input keyboard (#428) --- commet/lib/ui/molecules/message_input.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/commet/lib/ui/molecules/message_input.dart b/commet/lib/ui/molecules/message_input.dart index 42922978b..12900c291 100644 --- a/commet/lib/ui/molecules/message_input.dart +++ b/commet/lib/ui/molecules/message_input.dart @@ -584,6 +584,8 @@ class MessageInputState extends State { style: Theme.of(context).textTheme.bodyMedium!, maxLines: null, contextMenuBuilder: contextMenuBuilder, + keyboardType: TextInputType.text, + textCapitalization: TextCapitalization.sentences, decoration: InputDecoration( contentPadding: EdgeInsets.fromLTRB(8, padding / 2, 4, padding / 2), From 97fecf82febee3cd77e67b75dce2255edc0af8fc Mon Sep 17 00:00:00 2001 From: Airyzz <36567925+Airyzz@users.noreply.github.com> Date: Sun, 16 Mar 2025 08:42:10 +1030 Subject: [PATCH 2/2] Add more dev tools (#430) --- commet/.gitignore | 1 + commet/lib/client/client_manager.dart | 2 + commet/lib/client/matrix/matrix_client.dart | 4 + commet/lib/client/matrix/matrix_profile.dart | 2 + commet/lib/client/matrix/matrix_room.dart | 2 + commet/lib/client/matrix/matrix_space.dart | 2 + commet/lib/client/matrix/matrix_timeline.dart | 2 + .../app_inspector/app_inspector_page.dart | 40 +++++ .../app_inspector/field_inspector.dart | 143 ++++++++++++++++++ .../app_inspector/reflectable_extensions.dart | 10 ++ .../reflectable_matrix_client.dart | 40 +++++ .../app_inspector/value_reflector_widget.dart | 100 ++++++++++++ .../developer/developer_settings_page.dart | 24 +++ commet/pubspec.lock | 8 + commet/pubspec.yaml | 1 + 15 files changed, 381 insertions(+) create mode 100644 commet/lib/ui/pages/developer/app_inspector/app_inspector_page.dart create mode 100644 commet/lib/ui/pages/developer/app_inspector/field_inspector.dart create mode 100644 commet/lib/ui/pages/developer/app_inspector/reflectable_extensions.dart create mode 100644 commet/lib/ui/pages/developer/app_inspector/reflectable_matrix_client.dart create mode 100644 commet/lib/ui/pages/developer/app_inspector/value_reflector_widget.dart diff --git a/commet/.gitignore b/commet/.gitignore index 5b1f87c42..ef6ac7264 100644 --- a/commet/.gitignore +++ b/commet/.gitignore @@ -9,6 +9,7 @@ .history .svn/ *.g.dart +*.reflectable.dart *.widgetbook.dart migrate_working_dir/ diff --git a/commet/lib/client/client_manager.dart b/commet/lib/client/client_manager.dart index c8f7d4ff0..f2d7934a5 100644 --- a/commet/lib/client/client_manager.dart +++ b/commet/lib/client/client_manager.dart @@ -8,8 +8,10 @@ import 'package:commet/client/matrix/matrix_client.dart'; import 'package:commet/client/stale_info.dart'; import 'package:commet/client/tasks/client_connection_status_task.dart'; import 'package:commet/main.dart'; +import 'package:commet/ui/pages/developer/app_inspector/value_reflector_widget.dart'; import 'package:commet/utils/notifying_list.dart'; +@Reflector() class ClientManager { final Map _clients = {}; diff --git a/commet/lib/client/matrix/matrix_client.dart b/commet/lib/client/matrix/matrix_client.dart index 0565a2dfe..d10c47003 100644 --- a/commet/lib/client/matrix/matrix_client.dart +++ b/commet/lib/client/matrix/matrix_client.dart @@ -18,6 +18,7 @@ import 'package:commet/debug/log.dart'; import 'package:commet/diagnostic/diagnostics.dart'; import 'package:commet/main.dart'; import 'package:commet/ui/navigation/adaptive_dialog.dart'; +import 'package:commet/ui/pages/developer/app_inspector/value_reflector_widget.dart'; import 'package:commet/ui/pages/matrix/authentication/matrix_uia_request.dart'; import 'package:commet/utils/list_extension.dart'; import 'package:commet/utils/notifying_list.dart'; @@ -37,6 +38,7 @@ import 'matrix_room.dart'; import 'matrix_space.dart'; import 'package:olm/olm.dart' as olm; +@Reflector() class MatrixClient extends Client { late matrix.Client _matrixClient; late final List> componentsInternal; @@ -45,6 +47,8 @@ class MatrixClient extends Client { matrix.ServerConfig? config; + matrix.Client get matrixClient => _matrixClient; + late String _id; final NotifyingList _rooms = NotifyingList.empty( diff --git a/commet/lib/client/matrix/matrix_profile.dart b/commet/lib/client/matrix/matrix_profile.dart index 669cae0d3..edb07d355 100644 --- a/commet/lib/client/matrix/matrix_profile.dart +++ b/commet/lib/client/matrix/matrix_profile.dart @@ -1,9 +1,11 @@ import 'package:commet/client/matrix/matrix_member.dart'; import 'package:commet/client/matrix/matrix_mxc_image_provider.dart'; import 'package:commet/client/profile.dart'; +import 'package:commet/ui/pages/developer/app_inspector/value_reflector_widget.dart'; import 'package:flutter/widgets.dart'; import 'package:matrix/matrix.dart' as matrix; +@Reflector() class MatrixProfile implements Profile { matrix.Profile profile; matrix.Client client; diff --git a/commet/lib/client/matrix/matrix_room.dart b/commet/lib/client/matrix/matrix_room.dart index 8260fc8c4..7ff5aa8f4 100644 --- a/commet/lib/client/matrix/matrix_room.dart +++ b/commet/lib/client/matrix/matrix_room.dart @@ -35,6 +35,7 @@ import 'package:commet/client/timeline_events/timeline_event_message.dart'; import 'package:commet/config/build_config.dart'; import 'package:commet/debug/log.dart'; import 'package:commet/main.dart'; +import 'package:commet/ui/pages/developer/app_inspector/value_reflector_widget.dart'; import 'package:commet/utils/image_utils.dart'; import 'package:commet/utils/mime.dart'; import 'package:flutter/material.dart'; @@ -49,6 +50,7 @@ import '../attachment.dart'; import '../client.dart'; import 'package:matrix/matrix.dart' as matrix; +@Reflector() class MatrixRoom extends Room { late matrix.Room _matrixRoom; diff --git a/commet/lib/client/matrix/matrix_space.dart b/commet/lib/client/matrix/matrix_space.dart index b09fea4c4..7da73a440 100644 --- a/commet/lib/client/matrix/matrix_space.dart +++ b/commet/lib/client/matrix/matrix_space.dart @@ -11,12 +11,14 @@ import 'package:commet/client/matrix/matrix_room_permissions.dart'; import 'package:commet/client/matrix/matrix_room_preview.dart'; import 'package:commet/client/permissions.dart'; import 'package:commet/client/room_preview.dart'; +import 'package:commet/ui/pages/developer/app_inspector/value_reflector_widget.dart'; import 'package:commet/utils/notifying_list.dart'; import 'package:flutter/material.dart'; import 'package:matrix/matrix.dart' as matrix; import 'matrix_peer.dart'; +@Reflector() class MatrixSpace extends Space { late matrix.Room _matrixRoom; late matrix.Client _matrixClient; diff --git a/commet/lib/client/matrix/matrix_timeline.dart b/commet/lib/client/matrix/matrix_timeline.dart index e0b621156..ee43e6da7 100644 --- a/commet/lib/client/matrix/matrix_timeline.dart +++ b/commet/lib/client/matrix/matrix_timeline.dart @@ -6,10 +6,12 @@ import 'package:commet/client/matrix/timeline_events/matrix_timeline_event.dart' import 'package:commet/client/timeline_events/timeline_event.dart'; import 'package:commet/client/timeline_events/timeline_event_message.dart'; import 'package:commet/client/timeline_events/timeline_event_sticker.dart'; +import 'package:commet/ui/pages/developer/app_inspector/value_reflector_widget.dart'; import '../client.dart'; import 'package:matrix/matrix.dart' as matrix; +@Reflector() class MatrixTimeline extends Timeline { matrix.Timeline? _matrixTimeline; late matrix.Room _matrixRoom; diff --git a/commet/lib/ui/pages/developer/app_inspector/app_inspector_page.dart b/commet/lib/ui/pages/developer/app_inspector/app_inspector_page.dart new file mode 100644 index 000000000..cfece26f6 --- /dev/null +++ b/commet/lib/ui/pages/developer/app_inspector/app_inspector_page.dart @@ -0,0 +1,40 @@ +import 'package:commet/main.dart'; +import 'package:commet/ui/atoms/scaled_safe_area.dart'; +import 'package:commet/ui/pages/developer/app_inspector/value_reflector_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:tiamat/tiamat.dart' as tiamat; + +class AppInspectorPage extends StatelessWidget { + const AppInspectorPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + child: ScaledSafeArea( + child: SelectionArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: tiamat.CircleButton( + icon: Icons.arrow_back, + radius: 27, + onPressed: () => Navigator.of(context).pop(), + ), + ), + const Padding( + padding: EdgeInsets.all(20.0), + child: tiamat.Text.body( + "Warning: This inspector allows you to view lots of information that would typically be hidden, some of which could be sensitive. It is recommended that you do not view this page if your screen may be visible to others\n\nWhile effort has been made to redact sensitive information, we cannot guarantee that we have caught everything. Continue at your own risk."), + ), + ValueReflectorWidget(value: clientManager!) + ], + ), + ), + ), + ), + ); + } +} diff --git a/commet/lib/ui/pages/developer/app_inspector/field_inspector.dart b/commet/lib/ui/pages/developer/app_inspector/field_inspector.dart new file mode 100644 index 000000000..1a02c4550 --- /dev/null +++ b/commet/lib/ui/pages/developer/app_inspector/field_inspector.dart @@ -0,0 +1,143 @@ +import 'package:commet/ui/pages/developer/app_inspector/reflectable_extensions.dart'; +import 'package:commet/ui/pages/developer/app_inspector/value_reflector_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:reflectable/reflectable.dart'; +import 'package:tiamat/tiamat.dart' as tiamat; + +class FieldInspector extends StatefulWidget { + const FieldInspector( + {required this.declaration, required this.instance, super.key}); + final MethodMirror declaration; + final dynamic instance; + @override + State createState() => FieldInspectorState(); +} + +class FieldInspectorState extends State { + dynamic fieldValue; + + @override + void initState() { + try { + final result = ReflectableExtensions.invoke( + widget.instance, widget.declaration.simpleName); + fieldValue = result; + } catch (_) {} + + super.initState(); + } + + static String displayValue(dynamic value) { + if (value == null) { + return "null"; + } + + if (value is String) { + return value; + } + + try { + var v = "$value name: ${value.name}"; + return v; + } catch (_) {} + + try { + var v = "$value displayname: ${value.displayname}"; + return v; + } catch (_) {} + + try { + var v = "$value displayName: ${value.displayName}"; + return v; + } catch (_) {} + + try { + var v = "$value id: ${value.id}"; + return v; + } catch (_) {} + + return value.toString(); + } + + @override + Widget build(BuildContext context) { + final color = (Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black) + .withAlpha(7); + + return ExpansionTile( + expandedCrossAxisAlignment: CrossAxisAlignment.start, + expandedAlignment: Alignment.topLeft, + backgroundColor: color, + dense: true, + title: Row( + children: [ + tiamat.Text(widget.declaration.simpleName.toString()), + const SizedBox(width: 20), + Flexible( + child: tiamat.Text.labelLow( + displayValue(fieldValue), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ) + ], + ), + children: [buildValue(fieldValue)], + ); + } + + Widget buildValue(dynamic value, {int index = 0}) { + if (value == null) { + return const tiamat.Text("null"); + } + + if (value is List) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: (value) + .mapIndexed((e, i) => Container(child: buildValue(e))) + .toList(), + ); + } + + if (value is Map) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: value.keys.mapIndexed((e, i) { + var label = displayValue(e); + if (label == "") { + label = "\"\""; + } + return Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 0, 0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + tiamat.Text.labelLow( + "$label:", + ), + Padding( + padding: const EdgeInsets.fromLTRB(10, 0, 0, 0), + child: buildValue(value[e], index: i), + ) + ], + ), + ); + }).toList(), + ); + } + + return Padding( + padding: const EdgeInsets.fromLTRB(20, 0, 0, 0), + child: ValueReflectorWidget( + value: value, + index: index, + ), + ); + } +} diff --git a/commet/lib/ui/pages/developer/app_inspector/reflectable_extensions.dart b/commet/lib/ui/pages/developer/app_inspector/reflectable_extensions.dart new file mode 100644 index 000000000..53a234ec5 --- /dev/null +++ b/commet/lib/ui/pages/developer/app_inspector/reflectable_extensions.dart @@ -0,0 +1,10 @@ +// ignore: implementation_imports +import 'package:reflectable/src/reflectable_builder_based.dart' as builder; + +class ReflectableExtensions { + static Object? invoke(dynamic object, String name) { + var getter = builder.data.values.first.getters[name]!; + var result = getter(object); + return result; + } +} diff --git a/commet/lib/ui/pages/developer/app_inspector/reflectable_matrix_client.dart b/commet/lib/ui/pages/developer/app_inspector/reflectable_matrix_client.dart new file mode 100644 index 000000000..bbd9ae436 --- /dev/null +++ b/commet/lib/ui/pages/developer/app_inspector/reflectable_matrix_client.dart @@ -0,0 +1,40 @@ +import 'package:commet/ui/pages/developer/app_inspector/value_reflector_widget.dart'; +import 'package:matrix/matrix.dart' as matrix; +// ignore: implementation_imports +import 'package:matrix/src/utils/space_child.dart'; + +@Reflector() +class ReflectableMatrixClient extends matrix.Client { + ReflectableMatrixClient(super.clientName); +} + +@Reflector() +class ReflectableMatrixRoom extends matrix.Room { + ReflectableMatrixRoom({required super.id, required super.client}); +} + +@Reflector() +class ReflectableMatrixEvent extends matrix.Event { + ReflectableMatrixEvent( + {required super.content, + required super.type, + required super.eventId, + required super.senderId, + required super.originServerTs, + required super.room}); +} + +@Reflector() +class ReflectableMatrixSpaceChild extends SpaceChild { + ReflectableMatrixSpaceChild.fromState(super.state) : super.fromState(); +} + +@Reflector() +class ReflectableMatrixSpaceParent extends SpaceParent { + ReflectableMatrixSpaceParent.fromState(super.state) : super.fromState(); +} + +@Reflector() +class ReflectableMatrixBasicEvent extends matrix.BasicEvent { + ReflectableMatrixBasicEvent({required super.type, required super.content}); +} diff --git a/commet/lib/ui/pages/developer/app_inspector/value_reflector_widget.dart b/commet/lib/ui/pages/developer/app_inspector/value_reflector_widget.dart new file mode 100644 index 000000000..ceaa07ff4 --- /dev/null +++ b/commet/lib/ui/pages/developer/app_inspector/value_reflector_widget.dart @@ -0,0 +1,100 @@ +import 'package:commet/main.reflectable.dart'; +import 'package:commet/ui/pages/developer/app_inspector/field_inspector.dart'; +import 'package:commet/ui/pages/developer/app_inspector/reflectable_matrix_client.dart'; +import 'package:flutter/material.dart'; +import 'package:reflectable/reflectable.dart'; +import 'package:tiamat/tiamat.dart' as tiamat; +import 'package:matrix/matrix.dart' as matrix; + +// ignore: implementation_imports +import 'package:matrix/src/utils/space_child.dart'; + +class Reflector extends Reflectable { + const Reflector() + : super(typingCapability, invokingCapability, + libraryDependenciesCapability); +} + +const reflector = Reflector(); + +class ValueReflectorWidget extends StatefulWidget { + const ValueReflectorWidget({required this.value, super.key, this.index = 0}); + final dynamic value; + final int index; + + @override + State createState() => _ValueReflectorWidgetState(); +} + +class _ValueReflectorWidgetState extends State { + ClassMirror? classMirror; + late List keys; + @override + void initState() { + initializeReflectable(); + + var type = { + matrix.Client: ReflectableMatrixClient, + matrix.Room: ReflectableMatrixRoom, + matrix.Event: ReflectableMatrixEvent, + SpaceChild: ReflectableMatrixSpaceChild, + SpaceParent: ReflectableMatrixSpaceParent, + matrix.BasicEvent: ReflectableMatrixBasicEvent + }[widget.value.runtimeType] ?? + widget.value.runtimeType; + + if (reflector.canReflectType(type)) { + classMirror = reflector.reflectType(type) as ClassMirror; + keys = classMirror!.instanceMembers.keys.toList() + ..sort((a, b) => a.compareTo(b)) + ..removeWhere((e) => + classMirror!.instanceMembers[e]!.isRegularMethod || + classMirror!.instanceMembers[e]!.isSetter); + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (classMirror == null) { + return tiamat.Text.labelLow(widget.value.toString()); + } + + final color = (Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black) + .withAlpha(7); + + return Padding( + padding: const EdgeInsets.all(2), + child: ExpansionTile( + backgroundColor: color, + title: + tiamat.Text.label(FieldInspectorState.displayValue(widget.value)), + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: keys.mapIndexed((e, i) { + if ([ + "accessToken", + "bearerToken", + "fingerprintKey", + "identityKey" + ].contains(e)) { + return Padding( + padding: const EdgeInsets.fromLTRB(8, 4, 0, 4), + child: tiamat.Text.error("$e (Redacted)"), + ); + } + + return FieldInspector( + declaration: classMirror!.instanceMembers[e]!, + instance: widget.value, + ); + }).toList()), + ]), + ); + } +} diff --git a/commet/lib/ui/pages/settings/categories/developer/developer_settings_page.dart b/commet/lib/ui/pages/settings/categories/developer/developer_settings_page.dart index 3d2a793f2..191bffe8c 100644 --- a/commet/lib/ui/pages/settings/categories/developer/developer_settings_page.dart +++ b/commet/lib/ui/pages/settings/categories/developer/developer_settings_page.dart @@ -5,6 +5,7 @@ import 'package:commet/config/app_config.dart'; import 'package:commet/diagnostic/diagnostics.dart'; import 'package:commet/main.dart'; import 'package:commet/ui/navigation/navigation_utils.dart'; +import 'package:commet/ui/pages/developer/app_inspector/app_inspector_page.dart'; import 'package:commet/ui/pages/developer/benchmarks/timeline_viewer_benchmark.dart'; import 'package:commet/ui/pages/settings/categories/developer/cumulative_diagnostics_widget.dart'; import 'package:commet/utils/background_tasks/background_task_manager.dart'; @@ -30,6 +31,7 @@ class _DeveloperSettingsPageState extends State { children: [ performance(), benchmarks(), + inspect(), windowSize(), notificationTests(), rendering(), @@ -88,6 +90,28 @@ class _DeveloperSettingsPageState extends State { ]); } + Widget inspect() { + return ExpansionTile( + title: const tiamat.Text.labelEmphasised("Inspect"), + initiallyExpanded: false, + backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, + collapsedBackgroundColor: + Theme.of(context).colorScheme.surfaceContainerLow, + children: [ + Wrap( + spacing: 8, + runSpacing: 8, + children: [ + tiamat.Button( + text: "Inspect App State", + onTap: () => NavigationUtils.navigateTo( + context, const AppInspectorPage()), + ) + ], + ), + ]); + } + Widget benchmarks() { return ExpansionTile( title: const tiamat.Text.labelEmphasised("Benchmarks"), diff --git a/commet/pubspec.lock b/commet/pubspec.lock index 683a02bb5..708335df8 100644 --- a/commet/pubspec.lock +++ b/commet/pubspec.lock @@ -1329,6 +1329,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.5" + reflectable: + dependency: "direct main" + description: + name: reflectable + sha256: f6abe4c7779c6c88f77b79a4317d9277c870c7292a3d6c2e56624c4846124368 + url: "https://pub.dev" + source: hosted + version: "4.0.10" safe_local_storage: dependency: transitive description: diff --git a/commet/pubspec.yaml b/commet/pubspec.yaml index bcf0009f9..6fea80dcd 100644 --- a/commet/pubspec.yaml +++ b/commet/pubspec.yaml @@ -90,6 +90,7 @@ dependencies: archive: ^3.6.1 exif: ^3.3.0 image: ^4.2.0 + reflectable: ^4.0.10 ## ---- Putting some extra lines in here to help git with the diff ## ---- Probably good to keep this stuff at the bottom of the list