diff --git a/lib/src/models/network_logman_record.dart b/lib/src/models/network_logman_record.dart index b72e810..0957493 100644 --- a/lib/src/models/network_logman_record.dart +++ b/lib/src/models/network_logman_record.dart @@ -20,7 +20,14 @@ class NetworkLogmanRecord extends LogmanRecord { @override String toString() { - return ''; + return 'NetworkRequestLogmanRecord(request: $request, response: $response)'; + } + + Map toJson() { + return { + 'request': request.toJson(), + 'response': response?.toJson(), + }; } } @@ -51,6 +58,17 @@ class NetworkRequestLogmanRecord { .formatJson(); return 'NetworkRequestLogmanRecord $readableJson'; } + + Map toJson() { + return { + 'id': id, + 'url': url, + 'method': method, + 'headers': headers, + 'body': body, + 'sentAt': sentAt?.toIso8601String(), + }; + } } class NetworkResponseLogmanRecord { @@ -78,4 +96,14 @@ class NetworkResponseLogmanRecord { .formatJson(); return 'NetworkResponseLogmanRecord $readableJson'; } + + Map toJson() { + return { + 'id': id, + 'statusCode': statusCode, + 'headers': headers, + 'body': body, + 'receivedAt': receivedAt?.toIso8601String(), + }; + } } diff --git a/lib/src/models/simple_logman_record.dart b/lib/src/models/simple_logman_record.dart index 3bf9b8a..3b4eaac 100644 --- a/lib/src/models/simple_logman_record.dart +++ b/lib/src/models/simple_logman_record.dart @@ -13,6 +13,14 @@ class SimpleLogmanRecord extends LogmanRecord { @override String toString() { - return 'SimpleLogmanRecord (message: $message, source: $source, isError: $isError)'; + return 'SimpleLogmanRecord(message: $message, source: $source, isError: $isError)'; + } + + Map toJson() { + return { + 'message': message, + 'source': source, + 'isError': isError, + }; } } diff --git a/lib/src/presentation/notifier/network_record_notifier.dart b/lib/src/presentation/notifier/network_record_notifier.dart new file mode 100644 index 0000000..38b30d9 --- /dev/null +++ b/lib/src/presentation/notifier/network_record_notifier.dart @@ -0,0 +1,38 @@ +import 'package:flutter/cupertino.dart'; +import 'package:logman/src/models/models.dart'; +import 'package:logman/src/presentation/pages/pages.dart'; + +class NetworkRecordNotifier extends ValueNotifier> { + NetworkRecordNotifier(List initialValue) + : super(initialValue) { + _originalNetworkRecords = List.from(initialValue); + } + + late final List _originalNetworkRecords; + + void filterNetworkRecords(NetworkStatus status) { + value = _originalNetworkRecords.where((networkRecord) { + switch (status) { + case NetworkStatus.success: + return _isSuccessStatusCode(networkRecord); + case NetworkStatus.error: + return _isErrorStatusCode(networkRecord); + case NetworkStatus.all: + default: + return true; + } + }).toList(); + notifyListeners(); + } + + bool _isSuccessStatusCode(NetworkLogmanRecord record) { + final statusCode = record.response?.statusCode; + return statusCode != null && statusCode >= 200 && statusCode < 300; + } + + bool _isErrorStatusCode(NetworkLogmanRecord record) { + final statusCode = record.response?.statusCode; + return statusCode != null && (statusCode >= 400 && statusCode <= 500) || + statusCode == 0; + } +} diff --git a/lib/src/presentation/notifier/notifier.dart b/lib/src/presentation/notifier/notifier.dart new file mode 100644 index 0000000..cff13a8 --- /dev/null +++ b/lib/src/presentation/notifier/notifier.dart @@ -0,0 +1 @@ +export 'network_record_notifier.dart'; diff --git a/lib/src/presentation/pages/logman_dashboard_page.dart b/lib/src/presentation/pages/logman_dashboard_page.dart index 5be90f2..9985569 100644 --- a/lib/src/presentation/pages/logman_dashboard_page.dart +++ b/lib/src/presentation/pages/logman_dashboard_page.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:logman/logman.dart'; +import 'package:logman/src/presentation/notifier/notifier.dart'; import 'package:logman/src/presentation/presentation.dart'; +enum NetworkStatus { all, error, success } + class LogmanDashboardPage extends StatefulWidget { final Widget? debugPage; final Logman logman; @@ -32,11 +35,25 @@ class LogmanDashboardPage extends StatefulWidget { class _LogmanDashboardPageState extends State with SingleTickerProviderStateMixin { + int currentIndex = 0; // To track tab page index + + late final NetworkRecordNotifier _recordNotifier; late final _tabController = TabController( + initialIndex: currentIndex, length: widget.debugPage != null ? 5 : 4, vsync: this, ); + @override + void initState() { + super.initState(); + // Get all NetworkLogmanRecords from logman record list + final networkRecords = + widget.logman.records.value.whereType().toList(); + + _recordNotifier = NetworkRecordNotifier(networkRecords); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -54,6 +71,8 @@ class _LogmanDashboardPageState extends State controller: _tabController, isScrollable: true, tabAlignment: TabAlignment.center, + onTap: (value) => + setState(() => currentIndex = value), // Update currentIndex value tabs: [ const Tab(text: 'All'), const Tab(text: 'Logs'), @@ -63,6 +82,21 @@ class _LogmanDashboardPageState extends State ], ), actions: [ + // Only show these action widgets when network tab is active + Visibility( + visible: currentIndex == 2, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // todo implement search functionality + /*IconButton( + onPressed: () {}, + icon: const Icon(Icons.search_rounded), + ),*/ + _NetworkFilterButton(recordsNotifier: _recordNotifier), + ], + ), + ), IconButton( onPressed: () { Navigator.of(context).pop(); @@ -82,7 +116,7 @@ class _LogmanDashboardPageState extends State children: [ AllRecordsPage(records: records), SimpleRecordsPage(records: records), - NetworkRecordsPage(records: records), + NetworkRecordsPage(networkRecordNotifier: _recordNotifier), NavigationRecordsPage(records: records), if (widget.debugPage != null) widget.debugPage!, ], @@ -92,3 +126,73 @@ class _LogmanDashboardPageState extends State ); } } + +class _NetworkFilterButton extends StatefulWidget { + const _NetworkFilterButton({required this.recordsNotifier}); + + final NetworkRecordNotifier recordsNotifier; + + @override + State<_NetworkFilterButton> createState() => _NetworkFilterButtonState(); +} + +class _NetworkFilterButtonState extends State<_NetworkFilterButton> { + late final NetworkRecordNotifier _recordNotifier; + + @override + void initState() { + super.initState(); + + _recordNotifier = widget.recordsNotifier; + } + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + position: PopupMenuPosition.under, + surfaceTintColor: Theme.of(context).scaffoldBackgroundColor, + onSelected: (value) => _recordNotifier.filterNetworkRecords(value), + itemBuilder: (BuildContext context) => [ + PopupMenuItem( + value: NetworkStatus.all, + child: filterPopupButton( + icon: Icons.public, + title: 'All', + ), + ), + PopupMenuItem( + value: NetworkStatus.error, + child: filterPopupButton( + icon: Icons.public_off, + title: 'Error', + color: Colors.red, + ), + ), + PopupMenuItem( + value: NetworkStatus.success, + child: filterPopupButton( + icon: Icons.public, + title: 'Success', + color: Colors.green, + ), + ), + ], + child: const Icon(Icons.filter_alt_rounded), + ); + } + + Row filterPopupButton({ + required IconData icon, + required String title, + Color? color, + }) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(icon, color: color, size: 16), + const SizedBox(width: 10), + Text(title), + ], + ); + } +} diff --git a/lib/src/presentation/pages/network_records_page.dart b/lib/src/presentation/pages/network_records_page.dart index c20e5c1..59a150a 100644 --- a/lib/src/presentation/pages/network_records_page.dart +++ b/lib/src/presentation/pages/network_records_page.dart @@ -1,32 +1,33 @@ import 'package:flutter/material.dart'; import 'package:logman/logman.dart'; +import 'package:logman/src/presentation/notifier/notifier.dart'; import 'package:logman/src/presentation/presentation.dart'; class NetworkRecordsPage extends StatelessWidget { - final List records; + final NetworkRecordNotifier networkRecordNotifier; - const NetworkRecordsPage({super.key, required this.records}); + const NetworkRecordsPage({super.key, required this.networkRecordNotifier}); @override Widget build(BuildContext context) { - final simpleRecords = List.from(records) - ..retainWhere( - (element) => element is NetworkLogmanRecord, - ); + return ValueListenableBuilder>( + valueListenable: networkRecordNotifier, + builder: (context, networkRecords, child) { + if (networkRecords.isEmpty) { + return const Center( + child: Text('No Network calls recorded yet!'), + ); + } - if (simpleRecords.isEmpty) { - return const Center( - child: Text('No Network calls recorded yet!'), - ); - } - - return ListView.separated( - itemCount: simpleRecords.length, - itemBuilder: (context, index) { - final record = simpleRecords[index] as NetworkLogmanRecord; - return NetworkRecordItem(record: record); + return ListView.separated( + itemCount: networkRecords.length, + itemBuilder: (context, index) { + final networkRecord = networkRecords[index]; + return NetworkRecordItem(record: networkRecord); + }, + separatorBuilder: (context, index) => const CustomDivider(), + ); }, - separatorBuilder: (context, index) => const CustomDivider(), ); } }