From ce1a463d756327752c1f7a13955672d72573044f Mon Sep 17 00:00:00 2001 From: Manas Hejmadi Date: Thu, 3 Jul 2025 03:46:50 +0530 Subject: [PATCH] SSE onDone cleanup fix & moved SSEDisplay to its own file --- lib/widgets/response_body_success.dart | 132 +----------------- lib/widgets/sse_display.dart | 87 ++++++++++++ .../lib/services/http_service.dart | 7 +- 3 files changed, 92 insertions(+), 134 deletions(-) create mode 100644 lib/widgets/sse_display.dart diff --git a/lib/widgets/response_body_success.dart b/lib/widgets/response_body_success.dart index cdc1a33f..5192e3d5 100644 --- a/lib/widgets/response_body_success.dart +++ b/lib/widgets/response_body_success.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:apidash/widgets/sse_display.dart'; import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/foundation.dart'; @@ -164,134 +165,3 @@ class _ResponseBodySuccessState extends State { ); } } - -//MOVE THIS SOMEWHERE ELSE -class SSEDisplay extends StatefulWidget { - final String sseOutput; - const SSEDisplay({super.key, required this.sseOutput}); - - @override - State createState() => _SSEDisplayState(); -} - -class _SSEDisplayState extends State { - final _scrollController = ScrollController(); - bool autoScrollEnabled = true; - bool _isScrolling = false; - - @override - void initState() { - super.initState(); - - _scrollController.addListener(() { - final position = _scrollController.position; - final atBottom = position.pixels >= position.maxScrollExtent - 50; - - if (autoScrollEnabled && !atBottom) { - // User scrolled up manually - setState(() => autoScrollEnabled = false); - } else if (!autoScrollEnabled && atBottom) { - // User scrolled back to bottom - setState(() => autoScrollEnabled = true); - } - }); - } - - @override - void didUpdateWidget(covariant SSEDisplay oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.sseOutput != widget.sseOutput && - autoScrollEnabled && - !_isScrolling) { - WidgetsBinding.instance.addPostFrameCallback((_) async { - if (_scrollController.hasClients) { - _isScrolling = true; - _scrollController.jumpTo( - _scrollController.position.maxScrollExtent, - ); - _isScrolling = false; - } - }); - } - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - List sse; - try { - sse = jsonDecode(widget.sseOutput); - } catch (e) { - return Text( - 'Invalid SSE output', - style: theme.textTheme.bodyMedium?.copyWith(color: Colors.red), - ); - } - - return SingleChildScrollView( - controller: _scrollController, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: sse.map((chunk) { - Map? parsedJson; - try { - parsedJson = jsonDecode(chunk); - } catch (_) {} - - return Card( - margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: parsedJson != null - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: parsedJson.entries.map((entry) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '${entry.key}: ', - style: theme.textTheme.bodyMedium?.copyWith( - fontWeight: FontWeight.bold, - color: kColorGQL, - ), - ), - const SizedBox(width: 4), - Expanded( - child: Text( - entry.value.toString(), - style: theme.textTheme.bodyMedium?.copyWith( - fontFamily: 'monospace', - ), - ), - ), - ], - ), - ); - }).toList(), - ) - : Text( - chunk.toString(), - style: theme.textTheme.bodyMedium?.copyWith( - fontFamily: 'monospace', - ), - ), - ), - ); - }).toList(), - ), - ); - } -} diff --git a/lib/widgets/sse_display.dart b/lib/widgets/sse_display.dart new file mode 100644 index 00000000..0d73871e --- /dev/null +++ b/lib/widgets/sse_display.dart @@ -0,0 +1,87 @@ +import 'dart:convert'; +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; + +class SSEDisplay extends StatefulWidget { + final String sseOutput; + const SSEDisplay({super.key, required this.sseOutput}); + + @override + State createState() => _SSEDisplayState(); +} + +class _SSEDisplayState extends State { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + List sse; + try { + sse = jsonDecode(widget.sseOutput); + } catch (e) { + return Text( + 'Invalid SSE output', + style: theme.textTheme.bodyMedium?.copyWith(color: Colors.red), + ); + } + + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: sse.reversed.map((chunk) { + Map? parsedJson; + try { + parsedJson = jsonDecode(chunk); + } catch (_) {} + + return Card( + color: Colors.white, + margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: parsedJson != null + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: parsedJson.entries.map((entry) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${entry.key}: ', + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.bold, + color: kColorGQL, + ), + ), + const SizedBox(width: 4), + Expanded( + child: Text( + entry.value.toString(), + style: theme.textTheme.bodyMedium?.copyWith( + fontFamily: 'monospace', + ), + ), + ), + ], + ), + ); + }).toList(), + ) + : Text( + chunk.toString().trim(), + style: theme.textTheme.bodyMedium?.copyWith( + fontFamily: 'monospace', + ), + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/packages/better_networking/lib/services/http_service.dart b/packages/better_networking/lib/services/http_service.dart index 0fc31fcd..72520ce1 100644 --- a/packages/better_networking/lib/services/http_service.dart +++ b/packages/better_networking/lib/services/http_service.dart @@ -347,9 +347,10 @@ Future> streamHttpRequest( //handle cases where response is larger than a TCP packet and cuts mid-way if (!hasEmitted && !controller.isClosed) { final response = getResponseFromBytes(buffer.toString().codeUnits); - if (response.body.trim().isEmpty) return; - final isStreaming = kStreamingResponseTypes.contains(contentType); - controller.add((isStreaming, response, stopwatch.elapsed, null)); + if (response.body.trim().isNotEmpty) { + final isStreaming = kStreamingResponseTypes.contains(contentType); + controller.add((isStreaming, response, stopwatch.elapsed, null)); + } } cleanup(); },