diff --git a/feedback/example/ios/Flutter/AppFrameworkInfo.plist b/feedback/example/ios/Flutter/AppFrameworkInfo.plist
index f2872cf4..8c6e5614 100644
--- a/feedback/example/ios/Flutter/AppFrameworkInfo.plist
+++ b/feedback/example/ios/Flutter/AppFrameworkInfo.plist
@@ -21,6 +21,6 @@
CFBundleVersion
1.0
MinimumOSVersion
- 9.0
+ 12.0
diff --git a/feedback/example/ios/Podfile b/feedback/example/ios/Podfile
index 1e8c3c90..279576f3 100644
--- a/feedback/example/ios/Podfile
+++ b/feedback/example/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-# platform :ios, '9.0'
+# platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/feedback/example/ios/Runner.xcodeproj/project.pbxproj b/feedback/example/ios/Runner.xcodeproj/project.pbxproj
index 74406c5c..071f1101 100644
--- a/feedback/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/feedback/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 50;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -164,7 +164,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
- LastUpgradeCheck = 1300;
+ LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -214,14 +214,14 @@
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/flutter_email_sender/flutter_email_sender.framework",
- "${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
+ "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework",
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_email_sender.framework",
- "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
);
@@ -232,10 +232,12 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
+ "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
@@ -246,6 +248,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -355,7 +358,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
@@ -440,7 +443,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
@@ -489,7 +492,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 12.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
diff --git a/feedback/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/feedback/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
index 3db53b6e..e67b2808 100644
--- a/feedback/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
+++ b/feedback/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -1,6 +1,6 @@
CADisableMinimumFrameDurationOnPhone
+ UIApplicationSupportsIndirectInputEvents
+
diff --git a/feedback/example/lib/custom_feedback.dart b/feedback/example/lib/custom_feedback.dart
index d3d32d0c..cdc4757f 100644
--- a/feedback/example/lib/custom_feedback.dart
+++ b/feedback/example/lib/custom_feedback.dart
@@ -1,4 +1,7 @@
+import 'dart:typed_data';
+
import 'package:feedback/feedback.dart';
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/// A data type holding user feedback consisting of a feedback type, free from
@@ -53,11 +56,11 @@ class CustomFeedbackForm extends StatefulWidget {
const CustomFeedbackForm({
super.key,
required this.onSubmit,
- required this.scrollController,
+ required this.formController,
});
final OnSubmit onSubmit;
- final ScrollController? scrollController;
+ final FeedbackFormController formController;
@override
State createState() => _CustomFeedbackFormState();
@@ -73,13 +76,11 @@ class _CustomFeedbackFormState extends State {
Expanded(
child: Stack(
children: [
- if (widget.scrollController != null)
- const FeedbackSheetDragHandle(),
+ if (widget.formController.scrollController != null) const FeedbackSheetDragHandle(),
ListView(
- controller: widget.scrollController,
+ controller: widget.formController.scrollController,
// Pad the top by 20 to match the corner radius if drag enabled.
- padding: EdgeInsets.fromLTRB(
- 16, widget.scrollController != null ? 20 : 16, 16, 0),
+ padding: EdgeInsets.fromLTRB(16, widget.formController.scrollController != null ? 20 : 16, 16, 0),
children: [
const Text('What kind of feedback do you want to give?'),
Row(
@@ -99,16 +100,11 @@ class _CustomFeedbackFormState extends State {
.map(
(type) => DropdownMenuItem(
value: type,
- child: Text(type
- .toString()
- .split('.')
- .last
- .replaceAll('_', ' ')),
+ child: Text(type.toString().split('.').last.replaceAll('_', ' ')),
),
)
.toList(),
- onChanged: (feedbackType) => setState(() =>
- _customFeedback.feedbackType = feedbackType),
+ onChanged: (feedbackType) => setState(() => _customFeedback.feedbackType = feedbackType),
),
ElevatedButton(
child: const Text('Open Dialog #2'),
@@ -132,8 +128,7 @@ class _CustomFeedbackFormState extends State {
const SizedBox(height: 16),
const Text('What is your feedback?'),
TextField(
- onChanged: (newFeedback) =>
- _customFeedback.feedbackText = newFeedback,
+ onChanged: (newFeedback) => _customFeedback.feedbackText = newFeedback,
),
const SizedBox(height: 16),
const Text('How does this make you feel?'),
@@ -149,10 +144,21 @@ class _CustomFeedbackFormState extends State {
TextButton(
// disable this button until the user has specified a feedback type
onPressed: _customFeedback.feedbackType != null
- ? () => widget.onSubmit(
- _customFeedback.feedbackText ?? '',
- extras: _customFeedback.toMap(),
- )
+ ? () async {
+ final Uint8List screenshot = await widget.formController.takeScreenshot(context);
+ if (!context.mounted) {
+ // User popped the page while screenshotting, abort.
+ return;
+ }
+ await widget.onSubmit(
+ context,
+ UserFeedback(
+ text: _customFeedback.feedbackText ?? '',
+ screenshot: screenshot,
+ extra: _customFeedback.toMap(),
+ ),
+ );
+ }
: null,
child: const Text('submit'),
),
diff --git a/feedback/example/lib/feedback_functions.dart b/feedback/example/lib/feedback_functions.dart
index e5835615..224e5e4c 100644
--- a/feedback/example/lib/feedback_functions.dart
+++ b/feedback/example/lib/feedback_functions.dart
@@ -1,7 +1,62 @@
// ignore_for_file: avoid_print
+import 'dart:convert';
+import 'dart:io';
+import 'dart:typed_data';
+
import 'package:feedback/feedback.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_email_sender/flutter_email_sender.dart';
+import 'package:http/http.dart' as http;
+import 'package:path_provider/path_provider.dart';
+import 'package:share_plus/share_plus.dart';
+
+/// Map from a string route to the underlying feedback route and it's corresponding `onSubmit`.
+/// This is just here to support the legacy `simpleFeedback` API.
+Future onSubmitWithStringRoute(
+ BuildContext context,
+ String? route,
+ UserFeedback feedback,
+) {
+ return switch (route) {
+ 'alert_dialog' => onSubmitAlertDialog(context, feedback),
+ 'email' => onSubmitEmail(context, feedback),
+ 'platform_sharing' => onSubmitPlatformSharing(context, feedback),
+ _ => throw UnsupportedError('Unsupported route: `$route`'),
+ };
+}
+
+Future onSubmitEmail(BuildContext context, UserFeedback feedback) async {
+ // draft an email and send to developer
+ final screenshotFilePath = await writeImageToStorage(feedback.screenshot);
+
+ final Email email = Email(
+ body: feedback.text,
+ subject: 'App Feedback',
+ recipients: ['john.doe@flutter.dev'],
+ attachmentPaths: [screenshotFilePath],
+ isHTML: false,
+ );
+ await FlutterEmailSender.send(email);
+}
+
+Future onSubmitPlatformSharing(BuildContext context, UserFeedback feedback) async {
+ final screenshotFilePath = await writeImageToStorage(feedback.screenshot);
+
+ // ignore: deprecated_member_use
+ await Share.shareFiles(
+ [screenshotFilePath],
+ text: feedback.text,
+ );
+}
+
+Future writeImageToStorage(Uint8List feedbackScreenshot) async {
+ final Directory output = await getTemporaryDirectory();
+ final String screenshotFilePath = '${output.path}/feedback.png';
+ final File screenshotFile = File(screenshotFilePath);
+ await screenshotFile.writeAsBytes(feedbackScreenshot);
+ return screenshotFilePath;
+}
/// Prints the given feedback to the console.
/// This is useful for debugging purposes.
@@ -17,14 +72,9 @@ void consoleFeedbackFunction(
}
}
-/// Shows an [AlertDialog] with the given feedback.
-/// This is useful for debugging purposes.
-void alertFeedbackFunction(
- BuildContext outerContext,
- UserFeedback feedback,
-) {
- showDialog(
- context: outerContext,
+Future onSubmitAlertDialog(BuildContext context, UserFeedback feedback) async {
+ await showDialog(
+ context: context,
builder: (context) {
return AlertDialog(
title: Text(feedback.text),
@@ -54,3 +104,45 @@ void alertFeedbackFunction(
},
);
}
+
+Future createGitlabIssueFromFeedback(BuildContext context, UserFeedback feedback) async {
+ const projectId = 'your-gitlab-project-id';
+ const apiToken = 'your-gitlab-api-token';
+
+ final screenshotFilePath = await writeImageToStorage(feedback.screenshot);
+
+ // Upload screenshot
+ final uploadRequest = http.MultipartRequest(
+ 'POST',
+ Uri.https(
+ 'gitlab.com',
+ '/api/v4/projects/$projectId/uploads',
+ ),
+ )
+ ..files.add(await http.MultipartFile.fromPath(
+ 'file',
+ screenshotFilePath,
+ ))
+ ..headers.putIfAbsent('PRIVATE-TOKEN', () => apiToken);
+ final uploadResponse = await uploadRequest.send();
+
+ final dynamic uploadResponseMap = jsonDecode(
+ await uploadResponse.stream.bytesToString(),
+ );
+
+ // Create issue
+ await http.post(
+ Uri.https(
+ 'gitlab.com',
+ '/api/v4/projects/$projectId/issues',
+ {
+ 'title': feedback.text.padRight(80),
+ 'description': '${feedback.text}\n'
+ "${uploadResponseMap["markdown"] ?? "Missing image!"}",
+ },
+ ),
+ headers: {
+ 'PRIVATE-TOKEN': apiToken,
+ },
+ );
+}
diff --git a/feedback/example/lib/main.dart b/feedback/example/lib/main.dart
index 0f0fda02..22d6e1ce 100644
--- a/feedback/example/lib/main.dart
+++ b/feedback/example/lib/main.dart
@@ -1,17 +1,11 @@
-import 'dart:convert';
import 'dart:io';
-import 'dart:typed_data';
import 'package:example/feedback_functions.dart';
import 'package:feedback/feedback.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
-import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
-import 'package:http/http.dart' as http;
-import 'package:path_provider/path_provider.dart';
-import 'package:share_plus/share_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'custom_feedback.dart';
@@ -32,35 +26,47 @@ bool _useCustomFeedback = false;
class _MyAppState extends State {
@override
Widget build(BuildContext context) {
- return BetterFeedback(
- // If custom feedback is not enabled, supply null and the default text
- // feedback form will be used.
- feedbackBuilder: _useCustomFeedback
- ? (context, onSubmit, scrollController) => CustomFeedbackForm(
- onSubmit: onSubmit,
- scrollController: scrollController,
- )
- : null,
- theme: FeedbackThemeData(
- background: Colors.grey,
- feedbackSheetColor: Colors.grey[50]!,
- drawColors: [
- Colors.red,
- Colors.green,
- Colors.blue,
- Colors.yellow,
- ],
- ),
- darkTheme: FeedbackThemeData.dark(),
- localizationsDelegates: [
- GlobalFeedbackLocalizationsDelegate(),
- GlobalMaterialLocalizations.delegate,
- GlobalCupertinoLocalizations.delegate,
- GlobalWidgetsLocalizations.delegate,
+ final theme = FeedbackThemeData(
+ background: Colors.grey,
+ feedbackSheetColor: Colors.grey[50]!,
+ drawColors: [
+ Colors.red,
+ Colors.green,
+ Colors.blue,
+ Colors.yellow,
],
+ );
+ final delegates = >[
+ GlobalFeedbackLocalizationsDelegate(),
+ GlobalMaterialLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ ];
+ if (_useCustomFeedback) {
+ return BetterFeedback.customFeedback(
+ feedbackBuilder: (context, onSubmit, formController) => CustomFeedbackForm(
+ onSubmit: onSubmit,
+ formController: formController,
+ ),
+ theme: theme,
+ darkTheme: FeedbackThemeData.dark(),
+ localizationsDelegates: delegates,
+ localeOverride: const Locale('en'),
+ mode: FeedbackMode.draw,
+ child: MaterialApp(
+ title: 'Feedback Demo',
+ theme: ThemeData(
+ primarySwatch: _useCustomFeedback ? Colors.green : Colors.blue,
+ ),
+ home: MyHomePage(_toggleCustomizedFeedback),
+ ),
+ );
+ }
+ return BetterFeedback.simpleFeedback(
+ darkTheme: FeedbackThemeData.dark(),
+ localizationsDelegates: delegates,
localeOverride: const Locale('en'),
mode: FeedbackMode.draw,
- pixelRatio: 1,
child: MaterialApp(
title: 'Feedback Demo',
theme: ThemeData(
@@ -71,8 +77,7 @@ class _MyAppState extends State {
);
}
- void _toggleCustomizedFeedback() =>
- setState(() => _useCustomFeedback = !_useCustomFeedback);
+ void _toggleCustomizedFeedback() => setState(() => _useCustomFeedback = !_useCustomFeedback);
}
class MyHomePage extends StatelessWidget {
@@ -84,9 +89,7 @@ class MyHomePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
- title: Text(_useCustomFeedback
- ? '(Custom) Feedback Example'
- : 'Feedback Example'),
+ title: Text(_useCustomFeedback ? '(Custom) Feedback Example' : 'Feedback Example'),
),
drawer: Drawer(
child: Container(color: Colors.blue),
@@ -126,17 +129,13 @@ class MyHomePage extends StatelessWidget {
const Divider(),
ElevatedButton(
child: const Text('Provide feedback'),
- onPressed: () {
- BetterFeedback.of(context).show(
- (feedback) async {
- // upload to server, share whatever
- // for example purposes just show it to the user
- alertFeedbackFunction(
- context,
- feedback,
- );
- },
- );
+ onPressed: () async {
+ // We're going to open the alert dialog after the feedback is complete, so
+ // there's no need to provide an `onSubmit` callback.
+ final feedback = await BetterFeedback.simpleFeedbackOf(context).show(null);
+ // User cancelled feedback before submitting and/or navigated away.
+ if (feedback == null || !context.mounted) return;
+ onSubmitAlertDialog(context, feedback);
},
),
const SizedBox(height: 10),
@@ -144,20 +143,7 @@ class MyHomePage extends StatelessWidget {
TextButton(
child: const Text('Provide E-Mail feedback'),
onPressed: () {
- BetterFeedback.of(context).show((feedback) async {
- // draft an email and send to developer
- final screenshotFilePath =
- await writeImageToStorage(feedback.screenshot);
-
- final Email email = Email(
- body: feedback.text,
- subject: 'App Feedback',
- recipients: ['john.doe@flutter.dev'],
- attachmentPaths: [screenshotFilePath],
- isHTML: false,
- );
- await FlutterEmailSender.send(email);
- });
+ BetterFeedback.simpleFeedbackOf(context).show(onSubmitEmail);
},
),
const SizedBox(height: 10),
@@ -165,18 +151,7 @@ class MyHomePage extends StatelessWidget {
ElevatedButton(
child: const Text('Provide feedback via platform sharing'),
onPressed: () {
- BetterFeedback.of(context).show(
- (feedback) async {
- final screenshotFilePath =
- await writeImageToStorage(feedback.screenshot);
-
- // ignore: deprecated_member_use
- await Share.shareFiles(
- [screenshotFilePath],
- text: feedback.text,
- );
- },
- );
+ BetterFeedback.simpleFeedbackOf(context).show(onSubmitPlatformSharing);
},
),
const Divider(),
@@ -209,13 +184,11 @@ class MyHomePage extends StatelessWidget {
),
floatingActionButton: MaterialButton(
color: Theme.of(context).primaryColor,
- shape: const RoundedRectangleBorder(
- borderRadius: BorderRadius.all(Radius.circular(20))),
- child: const Text('toggle feedback mode',
- style: TextStyle(color: Colors.white)),
+ shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20))),
+ child: const Text('toggle feedback mode', style: TextStyle(color: Colors.white)),
onPressed: () {
// don't toggle the feedback mode if it's currently visible
- if (!BetterFeedback.of(context).isVisible) {
+ if (!BetterFeedback.simpleFeedbackOf(context).isVisible) {
toggleCustomizedFeedback();
}
},
@@ -237,55 +210,3 @@ class _SecondaryScaffold extends StatelessWidget {
);
}
}
-
-Future writeImageToStorage(Uint8List feedbackScreenshot) async {
- final Directory output = await getTemporaryDirectory();
- final String screenshotFilePath = '${output.path}/feedback.png';
- final File screenshotFile = File(screenshotFilePath);
- await screenshotFile.writeAsBytes(feedbackScreenshot);
- return screenshotFilePath;
-}
-
-Future createGitlabIssueFromFeedback(BuildContext context) async {
- BetterFeedback.of(context).show((feedback) async {
- const projectId = 'your-gitlab-project-id';
- const apiToken = 'your-gitlab-api-token';
-
- final screenshotFilePath = await writeImageToStorage(feedback.screenshot);
-
- // Upload screenshot
- final uploadRequest = http.MultipartRequest(
- 'POST',
- Uri.https(
- 'gitlab.com',
- '/api/v4/projects/$projectId/uploads',
- ),
- )
- ..files.add(await http.MultipartFile.fromPath(
- 'file',
- screenshotFilePath,
- ))
- ..headers.putIfAbsent('PRIVATE-TOKEN', () => apiToken);
- final uploadResponse = await uploadRequest.send();
-
- final dynamic uploadResponseMap = jsonDecode(
- await uploadResponse.stream.bytesToString(),
- );
-
- // Create issue
- await http.post(
- Uri.https(
- 'gitlab.com',
- '/api/v4/projects/$projectId/issues',
- {
- 'title': feedback.text.padRight(80),
- 'description': '${feedback.text}\n'
- "${uploadResponseMap["markdown"] ?? "Missing image!"}",
- },
- ),
- headers: {
- 'PRIVATE-TOKEN': apiToken,
- },
- );
- });
-}
diff --git a/feedback/lib/feedback.dart b/feedback/lib/feedback.dart
index 121146be..5d1198f2 100644
--- a/feedback/lib/feedback.dart
+++ b/feedback/lib/feedback.dart
@@ -8,3 +8,4 @@ export 'src/feedback_mode.dart';
export 'src/l18n/translation.dart';
export 'src/theme/feedback_theme.dart' show FeedbackThemeData;
export 'src/user_feedback.dart';
+export 'src/feedback_form_controller.dart';
diff --git a/feedback/lib/src/better_feedback.dart b/feedback/lib/src/better_feedback.dart
index 9657add6..f85a2895 100644
--- a/feedback/lib/src/better_feedback.dart
+++ b/feedback/lib/src/better_feedback.dart
@@ -11,11 +11,7 @@ import 'package:feedback/src/utilities/renderer/renderer.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
-/// The function to be called when the user submits his feedback.
-typedef OnSubmit = Future Function(
- String feedback, {
- Map? extras,
-});
+typedef OnSubmit = FutureOr Function(BuildContext context, UserFeedback feedback);
/// A function that returns a Widget that prompts the user for feedback and
/// calls [OnSubmit] when the user wants to submit their feedback.
@@ -27,10 +23,11 @@ typedef OnSubmit = Future Function(
/// scrolled. Typically, this will be a `ListView` or `SingleChildScrollView`
/// wrapping the feedback sheet's content.
/// See: [FeedbackThemeData.sheetIsDraggable] and [DraggableScrollableSheet].
-typedef FeedbackBuilder = Widget Function(
- BuildContext,
- OnSubmit,
- ScrollController?,
+typedef FeedbackBuilder = Widget Function(
+ BuildContext context,
+ T route,
+ // All the callbacks the sheet needs to communicate with `BetterFeedback`.
+ FeedbackFormController formController,
);
/// A drag handle to be placed at the top of a draggable feedback sheet.
@@ -71,12 +68,6 @@ class FeedbackSheetDragHandle extends StatelessWidget {
}
}
-/// Function which gets called when the user submits his feedback.
-/// [feedback] is the user generated feedback. A string, by default.
-/// [screenshot] is a raw png encoded image.
-/// [OnFeedbackCallback] should cast [feedback] to the appropriate type.
-typedef OnFeedbackCallback = FutureOr Function(UserFeedback);
-
/// A feedback widget that uses a custom widget and data type for
/// prompting the user for their feedback. This widget should be the root of
/// your widget tree. Specifically, it should be above any [Navigator] widgets,
@@ -90,32 +81,39 @@ typedef OnFeedbackCallback = FutureOr Function(UserFeedback);
/// home: MyHomePage(),
/// );
/// ```
-///
-class BetterFeedback extends StatefulWidget {
- /// Creates a [BetterFeedback].
- ///
- /// /// ```dart
- /// BetterFeedback(
- /// child: MaterialApp(
- /// title: 'App',
- /// home: MyHomePage(),
- /// );
- /// ```
- const BetterFeedback({
+class BetterFeedback extends StatefulWidget {
+ static BetterFeedback simpleFeedback({
+ Key? key,
+ required Widget child,
+ ThemeMode? themeMode,
+ FeedbackThemeData? theme,
+ FeedbackThemeData? darkTheme,
+ List>? localizationsDelegates,
+ Locale? localeOverride,
+ FeedbackMode mode = FeedbackMode.draw,
+ }) =>
+ BetterFeedback.customFeedback(
+ key: key,
+ feedbackBuilder: simpleFeedbackBuilder,
+ themeMode: themeMode,
+ darkTheme: darkTheme,
+ localizationsDelegates: localizationsDelegates,
+ localeOverride: localeOverride,
+ mode: mode,
+ child: child,
+ );
+
+ const BetterFeedback.customFeedback({
super.key,
required this.child,
- this.feedbackBuilder,
+ required this.feedbackBuilder,
this.themeMode,
this.theme,
this.darkTheme,
this.localizationsDelegates,
this.localeOverride,
this.mode = FeedbackMode.draw,
- this.pixelRatio = 3.0,
- }) : assert(
- pixelRatio > 0,
- 'pixelRatio needs to be larger than 0',
- );
+ });
/// The application to wrap, typically a [MaterialApp].
final Widget child;
@@ -125,7 +123,7 @@ class BetterFeedback extends StatefulWidget {
/// some form fields and a submit button that calls [OnSubmit] when pressed.
/// Defaults to [StringFeedback] which uses a single editable text field to
/// prompt for input.
- final FeedbackBuilder? feedbackBuilder;
+ final FeedbackBuilder feedbackBuilder;
/// Determines which theme will be used by the Feedback UI.
/// If set to [ThemeMode.system], the choice of which theme to use will be based
@@ -166,16 +164,6 @@ class BetterFeedback extends StatefulWidget {
/// See [FeedbackMode] for other options.
final FeedbackMode mode;
- /// The pixelRatio describes the scale between
- /// the logical pixels and the size of the output image.
- /// Specifying 1.0 will give you a 1:1 mapping between
- /// logical pixels and the output pixels in the image.
- /// The default is a pixel ration of 3 and a value below 1 is not recommended.
- ///
- /// See [RenderRepaintBoundary](https://api.flutter.dev/flutter/rendering/RenderRepaintBoundary/toImage.html)
- /// for information on the underlying implementation.
- final double pixelRatio;
-
/// Call `BetterFeedback.of(context)` to get an
/// instance of [FeedbackData] on which you can call `.show()` or `.hide()`
/// to enable or disable the feedback view.
@@ -185,22 +173,25 @@ class BetterFeedback extends StatefulWidget {
/// BetterFeedback.of(context).show(...);
/// BetterFeedback.of(context).hide(...);
/// ```
- static FeedbackController of(BuildContext context) {
- final feedbackData =
- context.dependOnInheritedWidgetOfExactType();
+ static FeedbackController of(BuildContext context) {
+ final feedbackData = context.dependOnInheritedWidgetOfExactType>();
assert(
feedbackData != null,
- 'You need to add a $BetterFeedback widget above this context!',
+ 'You need to add a ${BetterFeedback} widget above this context!',
);
return feedbackData!.controller;
}
+ static FeedbackController simpleFeedbackOf(BuildContext context) {
+ return of(context);
+ }
+
@override
- State createState() => _BetterFeedbackState();
+ State> createState() => _BetterFeedbackState();
}
-class _BetterFeedbackState extends State {
- FeedbackController controller = FeedbackController();
+class _BetterFeedbackState extends State> {
+ FeedbackController controller = FeedbackController();
@override
void initState() {
@@ -229,13 +220,12 @@ class _BetterFeedbackState extends State {
child: Builder(
builder: (context) {
assert(debugCheckHasFeedbackLocalizations(context));
- return FeedbackWidget(
- isFeedbackVisible: controller.isVisible,
+ return FeedbackWidget(
+ isVisible: controller.isVisible,
+ route: controller.currentRoute,
drawColors: FeedbackTheme.of(context).drawColors,
mode: widget.mode,
- pixelRatio: widget.pixelRatio,
- feedbackBuilder:
- widget.feedbackBuilder ?? simpleFeedbackBuilder,
+ feedbackBuilder: widget.feedbackBuilder,
child: widget.child,
);
},
diff --git a/feedback/lib/src/feedback_bottom_sheet.dart b/feedback/lib/src/feedback_bottom_sheet.dart
index 8a09c097..ba39bfe5 100644
--- a/feedback/lib/src/feedback_bottom_sheet.dart
+++ b/feedback/lib/src/feedback_bottom_sheet.dart
@@ -1,30 +1,35 @@
// ignore_for_file: public_member_api_docs
import 'package:feedback/src/better_feedback.dart';
+import 'package:feedback/src/feedback_form_controller.dart';
+import 'package:feedback/src/screenshot.dart';
import 'package:feedback/src/theme/feedback_theme.dart';
import 'package:feedback/src/utilities/back_button_interceptor.dart';
import 'package:flutter/material.dart';
/// Shows the text input in which the user can describe his feedback.
-class FeedbackBottomSheet extends StatelessWidget {
+class FeedbackBottomSheet extends StatelessWidget {
const FeedbackBottomSheet({
super.key,
+ required this.route,
+ required this.screenshotController,
required this.feedbackBuilder,
- required this.onSubmit,
required this.sheetProgress,
});
- final FeedbackBuilder feedbackBuilder;
- final OnSubmit onSubmit;
+ final T route;
+ final ScreenshotController screenshotController;
+ final FeedbackBuilder feedbackBuilder;
final ValueNotifier sheetProgress;
@override
Widget build(BuildContext context) {
if (FeedbackTheme.of(context).sheetIsDraggable) {
return DraggableScrollableActuator(
- child: _DraggableFeedbackSheet(
+ child: _DraggableFeedbackSheet(
+ route: route,
+ screenshotController: screenshotController,
feedbackBuilder: feedbackBuilder,
- onSubmit: onSubmit,
sheetProgress: sheetProgress,
),
);
@@ -43,8 +48,8 @@ class FeedbackBottomSheet extends StatelessWidget {
return MaterialPageRoute(
builder: (_) => feedbackBuilder(
context,
- onSubmit,
- null,
+ route,
+ FeedbackFormController(screenshotController),
),
);
},
@@ -55,23 +60,25 @@ class FeedbackBottomSheet extends StatelessWidget {
}
}
-class _DraggableFeedbackSheet extends StatefulWidget {
+class _DraggableFeedbackSheet extends StatefulWidget {
const _DraggableFeedbackSheet({
+ required this.route,
+ required this.screenshotController,
required this.feedbackBuilder,
- required this.onSubmit,
required this.sheetProgress,
});
- final FeedbackBuilder feedbackBuilder;
- final OnSubmit onSubmit;
+ final T route;
+ final ScreenshotController screenshotController;
+ final FeedbackBuilder feedbackBuilder;
final ValueNotifier sheetProgress;
@override
- State<_DraggableFeedbackSheet> createState() =>
+ State<_DraggableFeedbackSheet> createState() =>
_DraggableFeedbackSheetState();
}
-class _DraggableFeedbackSheetState extends State<_DraggableFeedbackSheet> {
+class _DraggableFeedbackSheetState extends State<_DraggableFeedbackSheet> {
@override
void initState() {
super.initState();
@@ -111,7 +118,7 @@ class _DraggableFeedbackSheetState extends State<_DraggableFeedbackSheet> {
),
Expanded(
child: DraggableScrollableSheet(
- controller: BetterFeedback.of(context).sheetController,
+ controller: BetterFeedback.of(context).sheetController,
snap: true,
minChildSize: collapsedHeight,
initialChildSize: collapsedHeight,
@@ -136,8 +143,8 @@ class _DraggableFeedbackSheetState extends State<_DraggableFeedbackSheet> {
return MaterialPageRoute(
builder: (_) => widget.feedbackBuilder(
context,
- widget.onSubmit,
- scrollController,
+ widget.route,
+ FeedbackFormController(widget.screenshotController, scrollController),
),
);
},
diff --git a/feedback/lib/src/feedback_builder/string_feedback.dart b/feedback/lib/src/feedback_builder/string_feedback.dart
index 78c9ea2b..a188723f 100644
--- a/feedback/lib/src/feedback_builder/string_feedback.dart
+++ b/feedback/lib/src/feedback_builder/string_feedback.dart
@@ -1,15 +1,18 @@
-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/foundation.dart';
import 'package:flutter/material.dart';
/// Prompt the user for feedback using `StringFeedback`.
Widget simpleFeedbackBuilder(
BuildContext context,
- OnSubmit onSubmit,
- ScrollController? scrollController,
+ OnSubmit? onSubmit,
+ FeedbackFormController formController,
) =>
- StringFeedback(onSubmit: onSubmit, scrollController: scrollController);
+ StringFeedback(
+ onSubmit: onSubmit,
+ formController: formController,
+ );
/// A form that prompts the user for feedback with a single text field.
/// This is the default feedback widget used by [BetterFeedback].
@@ -19,18 +22,11 @@ class StringFeedback extends StatefulWidget {
const StringFeedback({
super.key,
required this.onSubmit,
- required this.scrollController,
+ required this.formController,
});
- /// Should be called when the user taps the submit button.
- final OnSubmit onSubmit;
-
- /// A scroll controller that expands the sheet when it's attached to a
- /// scrollable widget and that widget is scrolled.
- ///
- /// Non null if the sheet is draggable.
- /// See: [FeedbackThemeData.sheetIsDraggable].
- final ScrollController? scrollController;
+ final OnSubmit? onSubmit;
+ final FeedbackFormController formController;
@override
State createState() => _StringFeedbackState();
@@ -51,6 +47,10 @@ class _StringFeedbackState extends State {
controller = TextEditingController();
}
+ Future? submitting;
+
+ FeedbackFormController get formController => widget.formController;
+
@override
Widget build(BuildContext context) {
return Column(
@@ -59,16 +59,14 @@ class _StringFeedbackState extends State {
child: Stack(
children: [
ListView(
- controller: widget.scrollController,
+ controller: formController.scrollController,
// Pad the top by 20 to match the corner radius if drag enabled.
- padding: EdgeInsets.fromLTRB(
- 16, widget.scrollController != null ? 20 : 16, 16, 0),
+ padding: EdgeInsets.fromLTRB(16, formController.scrollController != null ? 20 : 16, 16, 0),
children: [
Text(
FeedbackLocalizations.of(context).feedbackDescriptionText,
maxLines: 2,
- style:
- FeedbackTheme.of(context).bottomSheetDescriptionStyle,
+ style: FeedbackTheme.of(context).bottomSheetDescriptionStyle,
),
TextField(
style: FeedbackTheme.of(context).bottomSheetTextInputStyle,
@@ -83,23 +81,72 @@ class _StringFeedbackState extends State {
),
],
),
- if (widget.scrollController != null)
- const FeedbackSheetDragHandle(),
+ if (formController.scrollController != null) const FeedbackSheetDragHandle(),
],
),
),
- TextButton(
- key: const Key('submit_feedback_button'),
- child: Text(
- FeedbackLocalizations.of(context).submitButtonText,
- style: TextStyle(
- color: FeedbackTheme.of(context).activeFeedbackModeColor,
- ),
- ),
- onPressed: () => widget.onSubmit(controller.text),
+ _FeedbackSubmitButton(
+ onTap: () async {
+ final Uint8List screenshot = await formController.takeScreenshot(context);
+ if (!context.mounted) return;
+
+ final feedback = UserFeedback(text: controller.text, screenshot: screenshot);
+ await widget.onSubmit?.call(context, feedback);
+ if (!context.mounted) return;
+
+ BetterFeedback.simpleFeedbackOf(context).hide(feedback);
+ },
),
const SizedBox(height: 8),
],
);
}
}
+
+class _FeedbackSubmitButton extends StatefulWidget {
+ const _FeedbackSubmitButton({required this.onTap});
+
+ final AsyncCallback onTap;
+
+ @override
+ State<_FeedbackSubmitButton> createState() => _FeedbackSubmitButtonState();
+}
+
+class _FeedbackSubmitButtonState extends State<_FeedbackSubmitButton> {
+ Future? submitting;
+
+ @override
+ Widget build(BuildContext context) {
+ return FutureBuilder(
+ future: submitting,
+ builder: (context, snap) {
+ return TextButton(
+ key: const Key('submit_feedback_button'),
+ child: AnimatedSwitcher(
+ duration: const Duration(milliseconds: 50),
+ child: snap.connectionState == ConnectionState.waiting
+ ? Container(
+ padding: const EdgeInsets.all(2),
+ alignment: Alignment.center,
+ child: CircularProgressIndicator(
+ color: FeedbackTheme.of(context).activeFeedbackModeColor,
+ ),
+ )
+ : Text(
+ FeedbackLocalizations.of(context).submitButtonText,
+ style: TextStyle(
+ color: FeedbackTheme.of(context).activeFeedbackModeColor,
+ ),
+ ),
+ ),
+ onPressed: () async {
+ setState(() {
+ submitting = widget.onTap();
+ });
+ await submitting;
+ },
+ );
+ },
+ );
+ }
+}
diff --git a/feedback/lib/src/feedback_controller.dart b/feedback/lib/src/feedback_controller.dart
index f8f68f9b..eaa2f1b5 100644
--- a/feedback/lib/src/feedback_controller.dart
+++ b/feedback/lib/src/feedback_controller.dart
@@ -1,30 +1,33 @@
+import 'dart:async';
+
import 'package:feedback/feedback.dart';
import 'package:flutter/material.dart';
/// Controls the state of the feeback ui.
-class FeedbackController extends ChangeNotifier {
- bool _isVisible = false;
+class FeedbackController extends ChangeNotifier {
+ _FeedbackSession? _session;
- /// Whether the feedback ui is currently visible.
- bool get isVisible => _isVisible;
+ /// The current route, if any.
+ T? get currentRoute => _session?.route;
- /// This function is called when the user submits his feedback.
- OnFeedbackCallback? onFeedback;
+ /// Whether the feedback ui is currently visible.
+ bool get isVisible => _session != null;
/// Open the feedback ui.
/// After the user submitted his feedback [onFeedback] is called.
/// If the user aborts the process of giving feedback, [onFeedback] is
/// not called.
- void show(OnFeedbackCallback onFeedback) {
- _isVisible = true;
- this.onFeedback = onFeedback;
+ Future show(T route) async {
+ _session = _FeedbackSession(route);
notifyListeners();
+ return _session!.result.future;
}
/// Hides the feedback ui.
- /// Typically, this does not need to be called by the user of this library
- void hide() {
- _isVisible = false;
+ void hide([R? result]) {
+ if (_session == null) return;
+ _session!.result.complete(result);
+ _session = null;
notifyListeners();
}
@@ -35,3 +38,11 @@ class FeedbackController extends ChangeNotifier {
final DraggableScrollableController sheetController =
DraggableScrollableController();
}
+
+
+class _FeedbackSession {
+ _FeedbackSession(this.route);
+
+ final T route;
+ final Completer result = Completer();
+}
\ No newline at end of file
diff --git a/feedback/lib/src/feedback_data.dart b/feedback/lib/src/feedback_data.dart
index fb11ee68..dd2372d3 100644
--- a/feedback/lib/src/feedback_data.dart
+++ b/feedback/lib/src/feedback_data.dart
@@ -3,17 +3,17 @@
import 'package:feedback/src/feedback_controller.dart';
import 'package:flutter/material.dart';
-class FeedbackData extends InheritedWidget {
+class FeedbackData extends InheritedWidget {
const FeedbackData({
super.key,
required super.child,
required this.controller,
});
- final FeedbackController controller;
+ final FeedbackController controller;
@override
- bool updateShouldNotify(FeedbackData oldWidget) {
+ bool updateShouldNotify(FeedbackData oldWidget) {
return oldWidget.controller != controller;
}
}
diff --git a/feedback/lib/src/feedback_form_controller.dart b/feedback/lib/src/feedback_form_controller.dart
new file mode 100644
index 00000000..215e7994
--- /dev/null
+++ b/feedback/lib/src/feedback_form_controller.dart
@@ -0,0 +1,27 @@
+import 'dart:typed_data';
+
+import 'package:feedback/src/screenshot.dart';
+import 'package:flutter/material.dart';
+
+class FeedbackFormController {
+ const FeedbackFormController(this._screenshotController, [this.scrollController]);
+
+ final ScreenshotController _screenshotController;
+
+ final ScrollController? scrollController;
+
+ Future takeScreenshot(BuildContext context, {
+ bool showKeyboard = false,
+ double pixelRatio = 3.0,
+ Duration delay = const Duration(milliseconds: 2000),
+ }) async {
+ if (!showKeyboard) {
+ FocusScope.of(context).requestFocus(FocusNode());
+ }
+ await Future.delayed(delay);
+ return _screenshotController.capture(
+ pixelRatio: pixelRatio,
+ delay: Duration.zero,
+ );
+ }
+}
diff --git a/feedback/lib/src/feedback_widget.dart b/feedback/lib/src/feedback_widget.dart
index f22acedb..44c2b179 100644
--- a/feedback/lib/src/feedback_widget.dart
+++ b/feedback/lib/src/feedback_widget.dart
@@ -18,14 +18,14 @@ typedef FeedbackButtonPress = void Function(BuildContext context);
const kScaleOrigin = Alignment(-.3, -.65);
const kScaleFactor = .65;
-class FeedbackWidget extends StatefulWidget {
+class FeedbackWidget extends StatefulWidget {
const FeedbackWidget({
super.key,
+ required this.route,
+ required this.isVisible,
required this.child,
- required this.isFeedbackVisible,
required this.drawColors,
required this.mode,
- required this.pixelRatio,
required this.feedbackBuilder,
}) : assert(
// This way, we can have a const constructor
@@ -34,20 +34,22 @@ class FeedbackWidget extends StatefulWidget {
'There must be at least one color to draw',
);
- final bool isFeedbackVisible;
+ // Note that we need both `isVisible` and `route` as route may be
+ // null even if the feedback is visible as route is nullable.
+ final T? route;
+ final bool isVisible;
final FeedbackMode mode;
- final double pixelRatio;
final Widget child;
final List drawColors;
- final FeedbackBuilder feedbackBuilder;
+ final FeedbackBuilder feedbackBuilder;
@override
- FeedbackWidgetState createState() => FeedbackWidgetState();
+ FeedbackWidgetState createState() => FeedbackWidgetState();
}
@visibleForTesting
-class FeedbackWidgetState extends State
+class FeedbackWidgetState extends State>
with SingleTickerProviderStateMixin {
// Padding to put around the interactive screenshot preview.
final double padding = 8;
@@ -65,12 +67,16 @@ class FeedbackWidgetState extends State
ScreenshotController screenshotController = ScreenshotController();
TextEditingController textEditingController = TextEditingController();
+ late final FeedbackController feedbackController = BetterFeedback.of(context);
+
late FeedbackMode mode = widget.mode;
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
+ T? lastSeenRoute;
+
PainterController create() {
final controller = PainterController();
controller.thickness = 5.0;
@@ -94,11 +100,11 @@ class FeedbackWidgetState extends State
@visibleForTesting
bool backButtonIntercept() {
- if (mode == FeedbackMode.draw && widget.isFeedbackVisible) {
+ if (mode == FeedbackMode.draw && feedbackController.isVisible) {
if (painterController.getStepCount() > 0) {
painterController.undo();
} else {
- BetterFeedback.of(context).hide();
+ BetterFeedback.of(context).hide();
}
return true;
}
@@ -106,21 +112,24 @@ class FeedbackWidgetState extends State
}
@override
- void didUpdateWidget(FeedbackWidget oldWidget) {
+ void didUpdateWidget(FeedbackWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// update feedback mode with the initial value
mode = widget.mode;
- if (oldWidget.isFeedbackVisible != widget.isFeedbackVisible &&
- oldWidget.isFeedbackVisible == false) {
+ if (oldWidget.isVisible != widget.isVisible &&
+ oldWidget.isVisible == false) {
// Feedback is now visible,
- // start animation to show it.
+ // start animation to show it and update the route.
+ lastSeenRoute = widget.route;
_controller.forward();
}
- if (oldWidget.isFeedbackVisible != widget.isFeedbackVisible &&
- oldWidget.isFeedbackVisible == true) {
+ if (oldWidget.isVisible != widget.isVisible &&
+ oldWidget.isVisible == true) {
// Feedback is no longer visible,
// reverse animation to hide it.
+ // Note that we do not clear the last seen route as the bottom sheet will
+ // still need to reference it as it animates out.
_controller.reverse();
// Reset the sheet progress so the fade is no longer applied.
sheetProgress.value = 0;
@@ -157,7 +166,7 @@ class FeedbackWidgetState extends State
child: PaintOnChild(
controller: painterController,
isPaintingActive:
- mode == FeedbackMode.draw && widget.isFeedbackVisible,
+ mode == FeedbackMode.draw && feedbackController.isVisible,
child: widget.child,
),
),
@@ -242,7 +251,7 @@ class FeedbackWidgetState extends State
},
onCloseFeedback: () {
_hideKeyboard(context);
- BetterFeedback.of(context).hide();
+ BetterFeedback.of(context).hide();
},
),
),
@@ -260,23 +269,13 @@ class FeedbackWidgetState extends State
notification.minExtent);
return false;
},
- child: FeedbackBottomSheet(
+ child: FeedbackBottomSheet(
key: const Key('feedback_bottom_sheet'),
+ // We need to forcibly cast to T to handle that current route is
+ // nullable and T itself may or may not be nullable
+ route: BetterFeedback.of(context).currentRoute as T,
+ screenshotController: screenshotController,
feedbackBuilder: widget.feedbackBuilder,
- onSubmit: (
- String feedback, {
- Map? extras,
- }) async {
- await _sendFeedback(
- context,
- BetterFeedback.of(context).onFeedback!,
- screenshotController,
- feedback,
- widget.pixelRatio,
- extras: extras,
- );
- painterController.clear();
- },
sheetProgress: sheetProgress,
),
),
@@ -292,66 +291,6 @@ class FeedbackWidgetState extends State
);
}
- @visibleForTesting
- static Future sendFeedback(
- OnFeedbackCallback onFeedbackSubmitted,
- ScreenshotController controller,
- String feedback,
- double pixelRatio, {
- Duration delay = const Duration(milliseconds: 2000),
- Map? extras,
- }) async {
- // Wait for the keyboard to be closed, and then proceed
- // to take a screenshot
- await Future.delayed(
- delay,
- () async {
- // Take high resolution screenshot
- final screenshot = await controller.capture(
- pixelRatio: pixelRatio,
- delay: const Duration(milliseconds: 0),
- );
-
- // Give it to the developer
- // to do something with it.
- await onFeedbackSubmitted(
- UserFeedback(
- text: feedback,
- screenshot: screenshot,
- extra: extras,
- ),
- );
- },
- );
- }
-
- static Future _sendFeedback(
- BuildContext context,
- OnFeedbackCallback onFeedbackSubmitted,
- ScreenshotController controller,
- String feedback,
- double pixelRatio, {
- Duration delay = const Duration(milliseconds: 200),
- bool showKeyboard = false,
- Map? extras,
- }) async {
- if (!showKeyboard) {
- _hideKeyboard(context);
- }
- await sendFeedback(
- onFeedbackSubmitted,
- controller,
- feedback,
- pixelRatio,
- delay: delay,
- extras: extras,
- );
-
- // Close feedback mode
- // ignore: use_build_context_synchronously
- BetterFeedback.of(context).hide();
- }
-
static void _hideKeyboard(BuildContext context) {
FocusScope.of(context).requestFocus(FocusNode());
}
diff --git a/feedback/pubspec.yaml b/feedback/pubspec.yaml
index 83d13d8f..63fc9ab9 100644
--- a/feedback/pubspec.yaml
+++ b/feedback/pubspec.yaml
@@ -17,6 +17,7 @@ dependencies:
sdk: flutter
flutter_localizations:
sdk: flutter
+ fpdart: ^1.1.1
dev_dependencies:
flutter_lints: ^3.0.0
diff --git a/feedback/test/feedback_test.dart b/feedback/test/feedback_test.dart
index d55e950a..68508f5a 100644
--- a/feedback/test/feedback_test.dart
+++ b/feedback/test/feedback_test.dart
@@ -12,7 +12,7 @@ import 'test_app.dart';
void main() {
group('BetterFeedback', () {
testWidgets('can open feedback with default settings', (tester) async {
- final widget = BetterFeedback(
+ final widget = BetterFeedback.simpleFeedback(
child: Builder(
builder: (context) {
return const MyTestApp();
@@ -41,7 +41,7 @@ void main() {
});
testWidgets('can open feedback in drawing mode', (tester) async {
- final widget = BetterFeedback(
+ final widget = BetterFeedback.simpleFeedback(
mode: FeedbackMode.draw,
child: Builder(
builder: (context) {
@@ -71,7 +71,7 @@ void main() {
});
testWidgets('can open feedback in navigation mode', (tester) async {
- final widget = BetterFeedback(
+ final widget = BetterFeedback.simpleFeedback(
mode: FeedbackMode.navigate,
child: Builder(
builder: (context) {