diff --git a/lib/home/bloc/home_bloc.dart b/lib/home/bloc/home_bloc.dart index f576dfd..5440813 100644 --- a/lib/home/bloc/home_bloc.dart +++ b/lib/home/bloc/home_bloc.dart @@ -44,7 +44,12 @@ class HomeBloc extends Bloc { QuestionAsked event, Emitter emit, ) async { - emit(state.copyWith(status: Status.askQuestionToThinking)); + emit( + state.copyWith( + status: Status.askQuestionToThinking, + submittedQuery: event.submittedQuery, + ), + ); final result = await _questionsRepository.getVertexResponse(state.query); emit( state.copyWith( diff --git a/lib/home/bloc/home_event.dart b/lib/home/bloc/home_event.dart index 923b513..a40716e 100644 --- a/lib/home/bloc/home_event.dart +++ b/lib/home/bloc/home_event.dart @@ -24,7 +24,12 @@ class QueryUpdated extends HomeEvent { } class QuestionAsked extends HomeEvent { - const QuestionAsked(); + const QuestionAsked(this.submittedQuery); + + final String submittedQuery; + + @override + List get props => [submittedQuery]; } class Results extends HomeEvent { diff --git a/lib/home/bloc/home_state.dart b/lib/home/bloc/home_state.dart index 7e287c7..a565abb 100644 --- a/lib/home/bloc/home_state.dart +++ b/lib/home/bloc/home_state.dart @@ -18,11 +18,13 @@ class HomeState extends Equatable { this.status = Status.welcome, this.query = '', this.vertexResponse = const VertexResponse.empty(), + this.submittedQuery, }); final Status status; final String query; final VertexResponse vertexResponse; + final String? submittedQuery; bool get isWelcomeVisible => status == Status.welcome || status == Status.welcomeToAskQuestion; @@ -52,11 +54,13 @@ class HomeState extends Equatable { Status? status, String? query, VertexResponse? vertexResponse, + String? submittedQuery, }) { return HomeState( status: status ?? this.status, query: query ?? this.query, vertexResponse: vertexResponse ?? this.vertexResponse, + submittedQuery: submittedQuery ?? this.submittedQuery, ); } diff --git a/lib/home/widgets/search_box.dart b/lib/home/widgets/search_box.dart index 0562676..c9831ac 100644 --- a/lib/home/widgets/search_box.dart +++ b/lib/home/widgets/search_box.dart @@ -10,16 +10,22 @@ class SearchBox extends StatelessWidget { @override Widget build(BuildContext context) { final l10n = context.l10n; - final searchQuery = context.select((HomeBloc bloc) => bloc.state.query); - return QuestionInputTextField( - icon: vertexIcons.stars.image(), - hint: l10n.questionHint, - actionText: l10n.ask, - onTextUpdated: (String query) => - context.read().add(QueryUpdated(query: query)), - onActionPressed: () => - context.read().add(const QuestionAsked()), - text: searchQuery.isEmpty ? null : searchQuery, + return BlocBuilder( + builder: (BuildContext context, HomeState state) { + final searchQuery = state.query; + final submittedQuery = state.submittedQuery; + return QuestionInputTextField( + shouldDisplayClearTextButton: searchQuery == submittedQuery, + icon: vertexIcons.stars.image(), + hint: l10n.questionHint, + actionText: l10n.ask, + onTextUpdated: (String query) => + context.read().add(QueryUpdated(query: query)), + onActionPressed: () => + context.read().add(QuestionAsked(searchQuery)), + text: searchQuery.isEmpty ? null : searchQuery, + ); + }, ); } } diff --git a/packages/app_ui/lib/src/widgets/question_input_text_field.dart b/packages/app_ui/lib/src/widgets/question_input_text_field.dart index e78e267..02127bf 100644 --- a/packages/app_ui/lib/src/widgets/question_input_text_field.dart +++ b/packages/app_ui/lib/src/widgets/question_input_text_field.dart @@ -13,6 +13,7 @@ class QuestionInputTextField extends StatefulWidget { required this.actionText, required this.onTextUpdated, required this.onActionPressed, + this.shouldDisplayClearTextButton = false, this.text, super.key, }); @@ -29,12 +30,16 @@ class QuestionInputTextField extends StatefulWidget { /// Function called when text is updated final ValueChanged onTextUpdated; - /// + /// Function called when the action widget is pressed final VoidCallback onActionPressed; /// Initial text displayed in the text field final String? text; + /// Set to `true` when instead of showing the action, you expect a button + /// that clears the text field + final bool shouldDisplayClearTextButton; + @override State createState() => _QuestionTextFieldState(); } @@ -77,10 +82,17 @@ class _QuestionTextFieldState extends State { hintText: widget.hint, suffixIcon: Padding( padding: const EdgeInsets.only(right: 12), - child: PrimaryCTA( - label: widget.actionText, - onPressed: () => widget.onActionPressed(), - ), + child: widget.shouldDisplayClearTextButton + ? IconButton( + onPressed: () { + _controller.clear(); + }, + icon: const Icon(Icons.close), + ) + : PrimaryCTA( + label: widget.actionText, + onPressed: () => widget.onActionPressed(), + ), ), ), ), diff --git a/packages/app_ui/test/src/widgets/question_input_text_field_test.dart b/packages/app_ui/test/src/widgets/question_input_text_field_test.dart index 9921063..e4ae191 100644 --- a/packages/app_ui/test/src/widgets/question_input_text_field_test.dart +++ b/packages/app_ui/test/src/widgets/question_input_text_field_test.dart @@ -42,24 +42,50 @@ void main() { expect(text, equals('test')); }); - testWidgets('calls onActionPressed clicking on PrimaryCTA', (tester) async { - var called = false; - await tester.pumpApp( - Material( - child: QuestionInputTextField( - icon: SizedBox.shrink(), - hint: 'hint', - actionText: 'actionText', - onActionPressed: () { - called = true; - }, - onTextUpdated: (_) {}, + group('when shouldDisplayClearTextButton is false', () { + testWidgets('calls onActionPressed clicking on PrimaryCTA', + (tester) async { + var called = false; + await tester.pumpApp( + Material( + child: QuestionInputTextField( + icon: SizedBox.shrink(), + hint: 'hint', + actionText: 'actionText', + onActionPressed: () { + called = true; + }, + onTextUpdated: (_) {}, + ), ), - ), - ); - await tester.tap(find.byType(PrimaryCTA)); + ); + await tester.tap(find.byType(PrimaryCTA)); - expect(called, equals(true)); + expect(called, equals(true)); + }); + }); + + group('when shouldDisplayClearTextButton is true', () { + testWidgets('should display a clear button that clears the text field', + (tester) async { + await tester.pumpApp( + Material( + child: QuestionInputTextField( + text: 'hello world', + shouldDisplayClearTextButton: true, + icon: SizedBox.shrink(), + hint: 'hint', + actionText: 'actionText', + onActionPressed: () {}, + onTextUpdated: (_) {}, + ), + ), + ); + expect(find.text('actionText'), findsNothing); + await tester.tap(find.byIcon(Icons.close)); + await tester.pumpAndSettle(); + expect(find.text('hello world'), findsNothing); + }); }); }); } diff --git a/test/home/bloc/home_bloc_test.dart b/test/home/bloc/home_bloc_test.dart index 7b04f44..acba156 100644 --- a/test/home/bloc/home_bloc_test.dart +++ b/test/home/bloc/home_bloc_test.dart @@ -69,12 +69,13 @@ void main() { .thenAnswer((_) async => VertexResponse.empty()); }, build: buildBloc, - act: (bloc) => bloc.add(QuestionAsked()), + act: (bloc) => bloc.add(QuestionAsked('query')), expect: () => [ HomeState(status: Status.askQuestionToThinking), HomeState( status: Status.thinkingToResults, vertexResponse: VertexResponse.empty(), + submittedQuery: 'query', ), ], ); diff --git a/test/home/bloc/home_event_test.dart b/test/home/bloc/home_event_test.dart index ba56be7..812b7c2 100644 --- a/test/home/bloc/home_event_test.dart +++ b/test/home/bloc/home_event_test.dart @@ -26,8 +26,8 @@ void main() { test('QuestionAsked supports value equality', () { expect( - QuestionAsked(), - equals(QuestionAsked()), + QuestionAsked('query'), + equals(QuestionAsked('query')), ); }); diff --git a/test/home/widgets/question_view_test.dart b/test/home/widgets/question_view_test.dart index 073b147..982daf4 100644 --- a/test/home/widgets/question_view_test.dart +++ b/test/home/widgets/question_view_test.dart @@ -79,7 +79,7 @@ void main() { (WidgetTester tester) async { await tester.pumpApp(bootstrap()); await tester.tap(find.byType(PrimaryCTA)); - verify(() => homeBloc.add(QuestionAsked())).called(1); + verify(() => homeBloc.add(QuestionAsked(''))).called(1); }, ); });