From 9e0222c6c60856184ebfa9877532149083145e0c Mon Sep 17 00:00:00 2001 From: Sadra Kafiri Date: Thu, 14 Aug 2025 17:44:48 +0330 Subject: [PATCH] pass ScreenshotController to feedbackBuilder --- feedback/example/lib/custom_feedback.dart | 157 +++++++++++++++++- feedback/example/lib/main.dart | 4 +- feedback/lib/feedback.dart | 1 + feedback/lib/src/better_feedback.dart | 1 + feedback/lib/src/feedback_bottom_sheet.dart | 8 + .../src/feedback_builder/string_feedback.dart | 12 +- feedback/lib/src/feedback_widget.dart | 2 +- feedback/test/feedback_test.dart | 2 +- 8 files changed, 176 insertions(+), 11 deletions(-) diff --git a/feedback/example/lib/custom_feedback.dart b/feedback/example/lib/custom_feedback.dart index d3d32d0c..c851211b 100644 --- a/feedback/example/lib/custom_feedback.dart +++ b/feedback/example/lib/custom_feedback.dart @@ -1,3 +1,6 @@ +import 'dart:developer'; +import 'dart:typed_data'; + import 'package:feedback/feedback.dart'; import 'package:flutter/material.dart'; @@ -54,10 +57,12 @@ class CustomFeedbackForm extends StatefulWidget { super.key, required this.onSubmit, required this.scrollController, + required this.screenshotController, }); final OnSubmit onSubmit; final ScrollController? scrollController; + final ScreenshotController? screenshotController; @override State createState() => _CustomFeedbackFormState(); @@ -66,6 +71,9 @@ class CustomFeedbackForm extends StatefulWidget { class _CustomFeedbackFormState extends State { final CustomFeedback _customFeedback = CustomFeedback(); + Uint8List? _screenshotBytes; + bool _isCapturing = false; + @override Widget build(BuildContext context) { return Column( @@ -141,20 +149,95 @@ class _CustomFeedbackFormState extends State { mainAxisAlignment: MainAxisAlignment.center, children: FeedbackRating.values.map(_ratingToIcon).toList(), ), + + // ---------- Screenshot preview area ---------- + const SizedBox(height: 16), + if (_screenshotBytes != null) ...[ + Text('Screenshot preview:', + style: Theme.of(context).textTheme.bodyMedium), + const SizedBox(height: 8), + GestureDetector( + onTap: () => _openPreviewDialog(context), + child: Container( + constraints: BoxConstraints( + maxHeight: 220, + maxWidth: double.infinity, + ), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.hardEdge, + child: Image.memory( + _screenshotBytes!, + fit: BoxFit.contain, + gaplessPlayback: true, + ), + ), + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextButton.icon( + onPressed: _isCapturing ? null : _retakeScreenshot, + icon: const Icon(Icons.refresh), + label: const Text('Retake'), + ), + const SizedBox(width: 12), + TextButton.icon( + onPressed: _isCapturing ? null : _clearScreenshot, + icon: const Icon(Icons.delete_outline), + label: const Text('Remove'), + ), + ], + ), + ], + // ---------- end preview ---------- ], ), ], ), ), + + // Capture button + Padding( + padding: const EdgeInsets.symmetric(vertical: 6.0), + child: TextButton( + onPressed: _isCapturing ? null : _handleTakeScreenshot, + child: _isCapturing + ? Row( + mainAxisSize: MainAxisSize.min, + children: const [ + SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2)), + SizedBox(width: 8), + Text('Capturing...'), + ], + ) + : const Text('Take screenshot'), + ), + ), + + // Submit button TextButton( - // disable this button until the user has specified a feedback type onPressed: _customFeedback.feedbackType != null - ? () => widget.onSubmit( + ? () async { + // Prepare extras: merge custom feedback map and optionally screenshot + final extras = + Map.from(_customFeedback.toMap()); + if (_screenshotBytes != null) { + extras['screenshot'] = _screenshotBytes; + } + await widget.onSubmit( _customFeedback.feedbackText ?? '', - extras: _customFeedback.toMap(), - ) + extras: extras, + ); + } : null, - child: const Text('submit'), + child: const Text('Submit'), ), const SizedBox(height: 8), ], @@ -182,4 +265,68 @@ class _CustomFeedbackFormState extends State { iconSize: 36, ); } + + Future _handleTakeScreenshot() async { + if (widget.screenshotController == null) { + _showSnack('Screenshot controller is not available.'); + return; + } + + setState(() { + _isCapturing = true; + }); + + try { + // Capture with sensible default pixelRatio; you can adjust this + final bytes = await widget.screenshotController!.capture(pixelRatio: 2.0); + if (!mounted) return; + setState(() { + _screenshotBytes = bytes; + }); + log('Screenshot captured: ${bytes.lengthInBytes} bytes'); + } catch (e, st) { + log('Error capturing screenshot: $e\n$st'); + _showSnack('Could not capture screenshot.'); + } finally { + if (mounted) { + setState(() { + _isCapturing = false; + }); + } + } + } + + Future _retakeScreenshot() async { + // small convenience wrapper to retake + await _handleTakeScreenshot(); + } + + void _clearScreenshot() { + setState(() { + _screenshotBytes = null; + }); + } + + void _openPreviewDialog(BuildContext context) { + print('object'); + if (_screenshotBytes == null) return; + showDialog( + context: context, + builder: (ctx) { + return Dialog( + insetPadding: const EdgeInsets.all(12), + child: InteractiveViewer( + child: Image.memory(_screenshotBytes!), + ), + ); + }, + ); + } + + void _showSnack(String text) { + final messenger = ScaffoldMessenger.maybeOf(context); + if (messenger != null) { + messenger.showSnackBar(SnackBar(content: Text(text))); + } + } } diff --git a/feedback/example/lib/main.dart b/feedback/example/lib/main.dart index 20cc49b4..98255879 100644 --- a/feedback/example/lib/main.dart +++ b/feedback/example/lib/main.dart @@ -36,9 +36,11 @@ class _MyAppState extends State { // If custom feedback is not enabled, supply null and the default text // feedback form will be used. feedbackBuilder: _useCustomFeedback - ? (context, onSubmit, scrollController) => CustomFeedbackForm( + ? (context, onSubmit, scrollController, screenshotController) => + CustomFeedbackForm( onSubmit: onSubmit, scrollController: scrollController, + screenshotController: screenshotController, ) : null, theme: FeedbackThemeData( diff --git a/feedback/lib/feedback.dart b/feedback/lib/feedback.dart index 121146be..2c298399 100644 --- a/feedback/lib/feedback.dart +++ b/feedback/lib/feedback.dart @@ -5,6 +5,7 @@ library feedback; export 'src/better_feedback.dart'; export 'src/feedback_controller.dart'; export 'src/feedback_mode.dart'; +export 'src/screenshot.dart'; export 'src/l18n/translation.dart'; export 'src/theme/feedback_theme.dart' show FeedbackThemeData; export 'src/user_feedback.dart'; diff --git a/feedback/lib/src/better_feedback.dart b/feedback/lib/src/better_feedback.dart index 9657add6..50d437c8 100644 --- a/feedback/lib/src/better_feedback.dart +++ b/feedback/lib/src/better_feedback.dart @@ -31,6 +31,7 @@ typedef FeedbackBuilder = Widget Function( BuildContext, OnSubmit, ScrollController?, + ScreenshotController?, ); /// A drag handle to be placed at the top of a draggable feedback sheet. diff --git a/feedback/lib/src/feedback_bottom_sheet.dart b/feedback/lib/src/feedback_bottom_sheet.dart index 8a09c097..83cff4aa 100644 --- a/feedback/lib/src/feedback_bottom_sheet.dart +++ b/feedback/lib/src/feedback_bottom_sheet.dart @@ -1,5 +1,6 @@ // ignore_for_file: public_member_api_docs +import 'package:feedback/feedback.dart'; import 'package:feedback/src/better_feedback.dart'; import 'package:feedback/src/theme/feedback_theme.dart'; import 'package:feedback/src/utilities/back_button_interceptor.dart'; @@ -12,11 +13,13 @@ class FeedbackBottomSheet extends StatelessWidget { required this.feedbackBuilder, required this.onSubmit, required this.sheetProgress, + required this.screenshotController, }); final FeedbackBuilder feedbackBuilder; final OnSubmit onSubmit; final ValueNotifier sheetProgress; + final ScreenshotController? screenshotController; @override Widget build(BuildContext context) { @@ -26,6 +29,7 @@ class FeedbackBottomSheet extends StatelessWidget { feedbackBuilder: feedbackBuilder, onSubmit: onSubmit, sheetProgress: sheetProgress, + screenshotController: screenshotController, ), ); } @@ -45,6 +49,7 @@ class FeedbackBottomSheet extends StatelessWidget { context, onSubmit, null, + screenshotController, ), ); }, @@ -60,11 +65,13 @@ class _DraggableFeedbackSheet extends StatefulWidget { required this.feedbackBuilder, required this.onSubmit, required this.sheetProgress, + required this.screenshotController, }); final FeedbackBuilder feedbackBuilder; final OnSubmit onSubmit; final ValueNotifier sheetProgress; + final ScreenshotController? screenshotController; @override State<_DraggableFeedbackSheet> createState() => @@ -138,6 +145,7 @@ class _DraggableFeedbackSheetState extends State<_DraggableFeedbackSheet> { context, widget.onSubmit, scrollController, + widget.screenshotController, ), ); }, diff --git a/feedback/lib/src/feedback_builder/string_feedback.dart b/feedback/lib/src/feedback_builder/string_feedback.dart index 78c9ea2b..d49cc283 100644 --- a/feedback/lib/src/feedback_builder/string_feedback.dart +++ b/feedback/lib/src/feedback_builder/string_feedback.dart @@ -1,5 +1,4 @@ -import 'package:feedback/src/better_feedback.dart'; -import 'package:feedback/src/l18n/translation.dart'; +import 'package:feedback/feedback.dart'; import 'package:feedback/src/theme/feedback_theme.dart'; import 'package:flutter/material.dart'; @@ -8,8 +7,13 @@ Widget simpleFeedbackBuilder( BuildContext context, OnSubmit onSubmit, ScrollController? scrollController, + ScreenshotController? screenshotController, ) => - StringFeedback(onSubmit: onSubmit, scrollController: scrollController); + StringFeedback( + onSubmit: onSubmit, + scrollController: scrollController, + screenshotController: screenshotController, + ); /// A form that prompts the user for feedback with a single text field. /// This is the default feedback widget used by [BetterFeedback]. @@ -20,6 +24,7 @@ class StringFeedback extends StatefulWidget { super.key, required this.onSubmit, required this.scrollController, + this.screenshotController, }); /// Should be called when the user taps the submit button. @@ -31,6 +36,7 @@ class StringFeedback extends StatefulWidget { /// Non null if the sheet is draggable. /// See: [FeedbackThemeData.sheetIsDraggable]. final ScrollController? scrollController; + final ScreenshotController? screenshotController; @override State createState() => _StringFeedbackState(); diff --git a/feedback/lib/src/feedback_widget.dart b/feedback/lib/src/feedback_widget.dart index 5a9a2715..188a0f6d 100644 --- a/feedback/lib/src/feedback_widget.dart +++ b/feedback/lib/src/feedback_widget.dart @@ -275,9 +275,9 @@ class FeedbackWidgetState extends State widget.pixelRatio, extras: extras, ); - painterController.clear(); }, sheetProgress: sheetProgress, + screenshotController: screenshotController, ), ), ), diff --git a/feedback/test/feedback_test.dart b/feedback/test/feedback_test.dart index d55e950a..fc95c8d0 100644 --- a/feedback/test/feedback_test.dart +++ b/feedback/test/feedback_test.dart @@ -224,7 +224,7 @@ void main() { submittedFeedback = feedback; }, ), - feedbackBuilder: (context, onSubmit, controller) { + feedbackBuilder: (context, onSubmit, controller, screenshotController) { return SingleChildScrollView( controller: controller, child: TextButton(