Improve share intake diagnostics
This commit is contained in:
@@ -520,11 +520,12 @@ class _ShareDiagnosticsDialog extends StatelessWidget {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: events
|
children: events
|
||||||
.map(
|
.map(
|
||||||
(ShareIntakeDebugEvent event) => Padding(
|
(
|
||||||
padding: const EdgeInsets.only(bottom: 10),
|
ShareIntakeDebugEvent event,
|
||||||
child: SelectableText(
|
) => _ShareDiagnosticsEventTile(
|
||||||
'${_debugTimeLabel(event.at)} ${event.stage}\n${event.message}',
|
event: event,
|
||||||
),
|
label:
|
||||||
|
'${_debugTimeLabel(event.at)} ${event.stage}\n${event.message}',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(growable: false),
|
.toList(growable: false),
|
||||||
@@ -545,6 +546,51 @@ class _ShareDiagnosticsDialog extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ShareDiagnosticsEventTile extends StatelessWidget {
|
||||||
|
const _ShareDiagnosticsEventTile({required this.event, required this.label});
|
||||||
|
|
||||||
|
final ShareIntakeDebugEvent event;
|
||||||
|
final String label;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final String? details = event.details;
|
||||||
|
if (details == null || details.isEmpty) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
|
child: SelectableText(label),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 10),
|
||||||
|
child: ExpansionTile(
|
||||||
|
tilePadding: EdgeInsets.zero,
|
||||||
|
childrenPadding: const EdgeInsets.only(top: 6, bottom: 8),
|
||||||
|
title: SelectableText(label),
|
||||||
|
subtitle: Text(
|
||||||
|
'Tap to show raw shared text (${details.length} chars).',
|
||||||
|
),
|
||||||
|
children: <Widget>[
|
||||||
|
DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: Theme.of(context).dividerColor),
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(10),
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: SelectableText(details),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String _debugTimeLabel(DateTime value) {
|
String _debugTimeLabel(DateTime value) {
|
||||||
return '${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}:${value.second.toString().padLeft(2, '0')}';
|
return '${value.hour.toString().padLeft(2, '0')}:${value.minute.toString().padLeft(2, '0')}:${value.second.toString().padLeft(2, '0')}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||||||
|
|
||||||
const String _shareIntakeDebugLogKey = 'share_intake_debug_log_v1';
|
const String _shareIntakeDebugLogKey = 'share_intake_debug_log_v1';
|
||||||
const int _maxShareIntakeDebugEvents = 40;
|
const int _maxShareIntakeDebugEvents = 40;
|
||||||
|
const int _maxShareIntakeDebugDetailChars = 6000;
|
||||||
|
|
||||||
class ShareIntakeDebugEvent {
|
class ShareIntakeDebugEvent {
|
||||||
const ShareIntakeDebugEvent({
|
const ShareIntakeDebugEvent({
|
||||||
required this.at,
|
required this.at,
|
||||||
required this.stage,
|
required this.stage,
|
||||||
required this.message,
|
required this.message,
|
||||||
|
this.details,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ShareIntakeDebugEvent.fromJson(Map<String, dynamic> json) {
|
factory ShareIntakeDebugEvent.fromJson(Map<String, dynamic> json) {
|
||||||
@@ -19,18 +21,21 @@ class ShareIntakeDebugEvent {
|
|||||||
).toLocal(),
|
).toLocal(),
|
||||||
stage: json['stage'] as String? ?? 'unknown',
|
stage: json['stage'] as String? ?? 'unknown',
|
||||||
message: json['message'] as String? ?? '',
|
message: json['message'] as String? ?? '',
|
||||||
|
details: json['details'] as String?,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final DateTime at;
|
final DateTime at;
|
||||||
final String stage;
|
final String stage;
|
||||||
final String message;
|
final String message;
|
||||||
|
final String? details;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
return <String, dynamic>{
|
return <String, dynamic>{
|
||||||
'at': at.toUtc().toIso8601String(),
|
'at': at.toUtc().toIso8601String(),
|
||||||
'stage': stage,
|
'stage': stage,
|
||||||
'message': message,
|
'message': message,
|
||||||
|
if (details != null) 'details': details,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -55,11 +60,16 @@ class ShareIntakeDebugLog {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> record(String stage, String message) async {
|
Future<void> record(String stage, String message, {String? details}) async {
|
||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
final List<ShareIntakeDebugEvent> current = await read();
|
final List<ShareIntakeDebugEvent> current = await read();
|
||||||
final List<ShareIntakeDebugEvent> next = <ShareIntakeDebugEvent>[
|
final List<ShareIntakeDebugEvent> next = <ShareIntakeDebugEvent>[
|
||||||
ShareIntakeDebugEvent(at: DateTime.now(), stage: stage, message: message),
|
ShareIntakeDebugEvent(
|
||||||
|
at: DateTime.now(),
|
||||||
|
stage: stage,
|
||||||
|
message: message,
|
||||||
|
details: _compactDetails(details),
|
||||||
|
),
|
||||||
...current,
|
...current,
|
||||||
].take(_maxShareIntakeDebugEvents).toList(growable: false);
|
].take(_maxShareIntakeDebugEvents).toList(growable: false);
|
||||||
await prefs.setString(
|
await prefs.setString(
|
||||||
@@ -77,3 +87,14 @@ class ShareIntakeDebugLog {
|
|||||||
await prefs.remove(_shareIntakeDebugLogKey);
|
await prefs.remove(_shareIntakeDebugLogKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? _compactDetails(String? value) {
|
||||||
|
final String? trimmed = value?.trim();
|
||||||
|
if (trimmed == null || trimmed.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (trimmed.length <= _maxShareIntakeDebugDetailChars) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
return '${trimmed.substring(0, _maxShareIntakeDebugDetailChars)}\n[truncated]';
|
||||||
|
}
|
||||||
|
|||||||
@@ -159,6 +159,12 @@ class _IncomingShareListenerState extends ConsumerState<IncomingShareListener> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _debugLog.record(
|
||||||
|
'raw_share_text',
|
||||||
|
'Captured raw share text (${trimmed.length} chars).',
|
||||||
|
details: trimmed,
|
||||||
|
);
|
||||||
|
|
||||||
final SharedPayload? payload = buildSharedPayloadFromRawText(
|
final SharedPayload? payload = buildSharedPayloadFromRawText(
|
||||||
rawText: trimmed,
|
rawText: trimmed,
|
||||||
platform: defaultTargetPlatform.name,
|
platform: defaultTargetPlatform.name,
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:relationship_saver/features/share_intake/application/share_intake_debug_log.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
setUp(() {
|
||||||
|
SharedPreferences.setMockInitialValues(<String, Object>{});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('records optional share details for diagnostics', () async {
|
||||||
|
const ShareIntakeDebugLog log = ShareIntakeDebugLog();
|
||||||
|
|
||||||
|
await log.record(
|
||||||
|
'raw_share_text',
|
||||||
|
'Captured raw share text (18 chars).',
|
||||||
|
details: 'Alex: likes cycling',
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<ShareIntakeDebugEvent> events = await log.read();
|
||||||
|
|
||||||
|
expect(events, hasLength(1));
|
||||||
|
expect(events.single.stage, 'raw_share_text');
|
||||||
|
expect(events.single.details, 'Alex: likes cycling');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('trims very large details before storing', () async {
|
||||||
|
const ShareIntakeDebugLog log = ShareIntakeDebugLog();
|
||||||
|
|
||||||
|
await log.record(
|
||||||
|
'raw_share_text',
|
||||||
|
'large',
|
||||||
|
details: List<String>.filled(7000, 'x').join(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<ShareIntakeDebugEvent> events = await log.read();
|
||||||
|
|
||||||
|
expect(events.single.details, endsWith('[truncated]'));
|
||||||
|
expect(events.single.details!.length, lessThan(7000));
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user