-
-
Notifications
You must be signed in to change notification settings - Fork 118
Support Custom Feedback Prompts and Data Types #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…lled' is ignored, all else pass
@ueman This is still WIP. It's buggy and I haven't added any tests for the new logic yet. However, while I'm fixing it and adding tests, if you have any time to glance over the high level approach I've taken I'd love to hear if you have any feedback! Also, the golden images are failing in a trivial manner-some elements that had one pixel borders now don't and some images that didn't have one pixel borders now do. I assume this is a change to Flutter? But will await your input before making any updates to the golden images. |
test/feedback_test.dart
Outdated
@@ -123,7 +129,7 @@ void main() { | |||
await tester.tap(submitFeedbackButton); | |||
await tester.pumpAndSettle(); | |||
}); | |||
}, skip: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bug was slipping through the tests because this one was being skipped. I unskipped it and added await tester.pumpAndSettle();
to fix the bug in the test itself that was (presumably) why it was being skipped.
I'm not sure if this is the right approach to the problem. Why not just include a Dropdown with options for However I really appreciate your PR. Having a categorization seems like a great addition. |
They need to be created/tested on a macOS system. They're a little bit different on Windows/Linux. I should really document that somewhere 😅 |
My reasoning for letting users provide arbitrary widgets (as opposed to just providing them with a prebuilt drop down and configuration) is I think that the set of features rapidly becomes too expansive to realistically build and provide configuration for every one. For example, here's just with my use case:
Providing the option for custom bottom sheets means any time a user has a request that's too niche to add to the base API, they can just build it themselves for their project. That said, the custom widget solution does have downsides:
Anyway, if this doesn't convince you no hard feelings! I'll just use a local copy of this fork in my project. |
Ah okay I see, thanks for your elaborate answer. I think you could avoid a lot of those generics by just making a Though it's not necessarily a better approach. A solution for the bottom sheet being too small would be a two step submit process. First getting the annotated screen and then select type and input a message. |
Yeah, either a subclass or Another option is to merge the classes back together and retain the generics but force the user to specify In the end it's a value judgement-let me know which solution you prefer and I can update the PR to follow it! For the bottom sheet, are you comfortable leaving that to a follow up PR? Perhaps we could publish the two PRs at the same time as a custom bottom sheet may not be every helpful if your bottom sheet is extremely size constrained. |
Using generics with two separate classes proved prohibitively messy in tests and the example app-I've moved to using This is non-ideal as it requires users to cast |
@@ -112,16 +131,16 @@ class MyHomePage extends StatelessWidget { | |||
ElevatedButton( | |||
child: const Text('Provide feedback'), | |||
onPressed: () { | |||
BetterFeedback.of(context)?.show( | |||
BetterFeedback.of(context)!.show( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo !
is preferable as it'll fail loudly instead of silently (ran into a few bugs that I would have identified faster with !
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's definitly something I want to tackle, along with making the image not nullable.
In the past I wasn't comfortable making it non nullable but now it's quite popular and nobody complained that it fails.
@@ -179,6 +198,19 @@ class MyHomePage extends StatelessWidget { | |||
), | |||
), | |||
), | |||
floatingActionButton: MaterialButton( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A floating action button could look a little sloppy, but I figured this should be fine?
child: MaterialApp( | ||
title: 'Feedback Demo', | ||
theme: ThemeData( | ||
primarySwatch: _useCustomFeedback ? Colors.green : Colors.blue, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change colors to emphasize current feedback mode
// Typically, the navigator would be provided by a `MaterialApp`, but | ||
// `BetterFeedback` is used above `MaterialApp` in the widget tree so that | ||
// the nested navigation in navigate mode works properly. | ||
return Navigator( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This used to be an Overlay
. I switched to Navigator
as DropDownButton
and several other material widgets require a navigator-not just an overlay (all navigators also provide an overlay).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. At first look this seems like the better approach.
Does that impact the navigator of your *App
?
In the past I had problems with overlays not being at the correct position because I used a MaterialApp
inside this library. MaterialApp also provides a Navigator.
An simple test for that would be to show a dialog and open the feedback view iirc.
I'll hope that this change does not reintroduce that bug.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah it probably doesn't because it's just used in the FeedbackBottomSheet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From messing around manually with the sample app this worked well. I'm not quite sure what the right test to verify this is-but if you have ideas for test scenarios to ensure proper behavior I can try writing them up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thing that could be useful: A test that opens the feedback in navigate mode, clicks a couple buttons, and then hits back a couple times and verifies that the appropriate widgets are present between each action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added!
lib/src/feedback_bottom_sheet.dart
Outdated
}, | ||
), | ||
], | ||
child: SingleChildScrollView( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a scroll view here to prevent overflow, but maybe we should leave this up to users? Generally, I tried to make getFeedback
easy to write but slightly less customizable.
lib/src/feedback_bottom_sheet.dart
Outdated
], | ||
child: SingleChildScrollView( | ||
child: Container( | ||
padding: const EdgeInsets.all(30), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could also be pushed into getFeedback
so if users wanted to customize padding they could do so there.
Or, probably better, make this configurable via FeedbackTheme
?
// height should be screen size minus the bottom edge of | ||
// screenshot widget: | ||
// 1 - (scaleOrigin + height*scaleFactor) | ||
(1 - (.35 / 2 + 1.65 / 2 * .65)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This one's a doozy, but it ensures that the bottom sheet is programmatically positioned relative to the screenshot.
@ueman This one ended up being pretty big! Let me know what you think when you have the chance to look at it. Happy to make any changes you see necessary. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitly need to check this out and see it in action.
|
||
/// A function that returns a Widget that prompts the user for feedback and | ||
/// calls [OnSubmit] when the user wants to submit their feedback. | ||
typedef GetFeedback = Widget Function(OnSubmit); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should also supply a context to the user, because it's kinda like a Builder
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I thought about that and went without it as users can just use a builder
if they want access to a BuildContext
-it'll just pollute the function arguments otherwise. That said, it's pretty conventional in Flutter to pass a BuildContext
in this kind of situation.
How about I rename GetFeedback
to FeedbackBuilder
and add a BuildContext
argument?
if (widget.isFeedbackVisible) | ||
// only display if feedback is visible or this widget is still | ||
// animating out | ||
if (widget.isFeedbackVisible || !animation.isDismissed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could probably be simplified to just if (!animation.isDismissed)
. In fact, you could replace bool isFeedbackVisible
with bool get isFeedbackVisible => !animation.isDismissed
(this will be slightly broader: it'll return true when the feedback is displayed AND while it's still animating out).
All that said, I think either works well and this way it's a little more obvious what the code is doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. Thanks!
I'm going to merge this via command line because I want to update the golden images first.
Oh crap, I forgot to change |
I changed it 👍 |
📜 Description
Allows users to customize the contents of
FeedbackBottomSheet
so that they can prompt users for feedback more complex than a singleString
. This also enables them to freely stylize the bottom sheet beyond the options inFeedbackTheme
.The biggest change is the addition of a
getFeedback
argument toBetterFeedback
. This is where users provide their custom widgets. It defaults togetStringFeedback
which mirrors the existingBetterFeedback
implementation.THIS IS A BREAKING CHANGE. Specifically, the
feedback
argument ofOnSubmit
is now anObject
rather than aString
to accommodate custom feedback types. Existing users will get a compile time error and will have to addas String
to any reference tofeedback
inBetterFeedback.of(context).show((feedback) { ... })
.Alternatives Considered:
Object
forfeedback
Because you can't have a default type, this would require splitting
BetterFeedback
into two separate classes, one for default behavior withString
feedback, and one with custom behavior and parameterized feedback.This became exceedingly messy and bug prone, especially for writing tests and the sample app.
Feedback
class that contains anObject content
member fieldThis didn't seem to provide any clear advantages over feedback being an object.
💡 Motivation and Context
See #77.
💚 How did you test it?
TBD
📝 Checklist
🔮 Next steps