From bfc02bf5f5f15737ba3e45ec3d6ae082eb28ceb3 Mon Sep 17 00:00:00 2001 From: Tyler Norbury Date: Fri, 12 May 2023 21:36:42 -0700 Subject: [PATCH 01/12] update SDK constraint to make pub happy on next upload --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 39e82c0..b500295 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,7 +14,7 @@ platforms: environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=2.12.0 <4.0.0" flutter: ">=1.17.0" dependencies: From 498e1c25532cf62ef7fe81b19da6c7fbd4f58752 Mon Sep 17 00:00:00 2001 From: d1y Date: Sun, 11 Jun 2023 17:41:56 +0800 Subject: [PATCH 02/12] feat: add focusNode parameter (#33) --- lib/src/command_palette.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/src/command_palette.dart b/lib/src/command_palette.dart index d88428b..fc28217 100644 --- a/lib/src/command_palette.dart +++ b/lib/src/command_palette.dart @@ -24,6 +24,8 @@ class CommandPalette extends InheritedWidget { /// functional configuration final CommandPaletteConfig config; + final FocusNode? focusNode; + late final _CommandPaletteToggler _toggler; late final CommandPaletteController _controller; @@ -33,6 +35,7 @@ class CommandPalette extends InheritedWidget { CommandPaletteConfig? config, required this.actions, required Widget child, + this.focusNode, }) : config = config ?? CommandPaletteConfig(), super( key: key, @@ -45,6 +48,7 @@ class CommandPalette extends InheritedWidget { ), config: config ?? CommandPaletteConfig(), toggler: _CommandPaletteToggler(false), + focusNode: focusNode, child: child, ), ) { @@ -109,7 +113,7 @@ class _CommandPaletteInner extends StatefulWidget { final CommandPaletteConfig config; final _CommandPaletteToggler toggler; final CommandPaletteController controller; - + final FocusNode? focusNode; const _CommandPaletteInner({ Key? key, required this.child, @@ -117,6 +121,7 @@ class _CommandPaletteInner extends StatefulWidget { required this.config, required this.toggler, required this.controller, + required this.focusNode, }) : super(key: key); @override @@ -239,6 +244,7 @@ class _CommandPaletteInnerState extends State<_CommandPaletteInner> { ) }, child: Focus( + focusNode: widget.focusNode, autofocus: true, child: widget.child, ), From a463fb747a1fb30e3c0dc4bc2694f261c383628f Mon Sep 17 00:00:00 2001 From: Tyler Norbury Date: Fri, 29 Sep 2023 09:58:58 -0400 Subject: [PATCH 03/12] changelog for PR #34 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b397ea..c6342bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased +### Added +- Add FocusNode parameter which is passed to underlying Focus widget + ## 0.7.3 - 2023-05-12 ### Changed - Upgrade to Flutter 3.10.0 From efb986b4b5e0a36daf84f481d90c68f834554dbf Mon Sep 17 00:00:00 2001 From: Pedro Nishiyama Date: Mon, 26 Jun 2023 01:14:20 -0300 Subject: [PATCH 04/12] implement input action --- .../command_palette_controller.dart | 26 ++++++++++++-- lib/src/models/command_palette_action.dart | 24 +++++++++++-- .../matched_command_palette_action.dart | 1 + .../widgets/command_palette_instructions.dart | 35 +++++++++++-------- .../widgets/command_palette_text_field.dart | 6 ++-- 5 files changed, 71 insertions(+), 21 deletions(-) diff --git a/lib/src/controller/command_palette_controller.dart b/lib/src/controller/command_palette_controller.dart index 2db77a8..5269db7 100644 --- a/lib/src/controller/command_palette_controller.dart +++ b/lib/src/controller/command_palette_controller.dart @@ -85,6 +85,10 @@ class CommandPaletteController extends ChangeNotifier { /// Listens to [textEditingController] and is called whenever it changes. void _onTextControllerChange() { + if (currentlySelectedAction?.actionType == CommandPaletteActionType.input) { + return; + } + if (_enteredQuery != textEditingController.text) { _enteredQuery = textEditingController.text; _actionsNeedRefiltered = true; @@ -96,7 +100,8 @@ class CommandPaletteController extends ChangeNotifier { CommandPaletteAction? get currentlySelectedAction => _currentlySelectedAction; set currentlySelectedAction(CommandPaletteAction? newAction) { assert(newAction == null || - newAction.actionType == CommandPaletteActionType.nested); + newAction.actionType == CommandPaletteActionType.nested || + newAction.actionType == CommandPaletteActionType.input); _currentlySelectedAction = newAction; _actionsNeedRefiltered = true; textEditingController.clear(); @@ -118,6 +123,9 @@ class CommandPaletteController extends ChangeNotifier { if (currentlySelectedAction?.actionType == CommandPaletteActionType.nested) { filteredActions = currentlySelectedAction!.childrenActions!; + } else if (currentlySelectedAction?.actionType == + CommandPaletteActionType.input) { + filteredActions = []; } else { filteredActions = actions; } @@ -193,14 +201,26 @@ class CommandPaletteController extends ChangeNotifier { // nested items we set this item as the selected which in turn // will display its children. - else if (action.actionType == CommandPaletteActionType.nested) { + else { currentlySelectedAction = action; } } + void handleActionInput(BuildContext context) { + _currentlySelectedAction?.onConfirmInput!(textEditingController.text); + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } + } + /// performs the action which is currently selected by [highlightedAction] void performHighlightedAction(BuildContext context) { - handleAction(context, action: _filteredActionsCache[highlightedAction]); + if (_currentlySelectedAction?.actionType == + CommandPaletteActionType.input) { + handleActionInput(context); + } else { + handleAction(context, action: _filteredActionsCache[highlightedAction]); + } } } diff --git a/lib/src/models/command_palette_action.dart b/lib/src/models/command_palette_action.dart index 3e237e7..46b4031 100644 --- a/lib/src/models/command_palette_action.dart +++ b/lib/src/models/command_palette_action.dart @@ -8,7 +8,11 @@ enum CommandPaletteActionType { /// Upon being selected a nested action will change the state of the command /// palette so that it only shows its children - nested + nested, + + /// Upon being selected the user will be given a text field that they can enter text into and submit. + /// The entered text will then be passed to the relevant callback + input } /// Action that is presented in the command palette. These are the things the @@ -25,6 +29,10 @@ class CommandPaletteAction { /// Specifies what type of action this is final CommandPaletteActionType actionType; + /// Required when [actionType] set to [CommandPaletteActionType.input]. This + /// function is called when the action is confirmed + ValueChanged? onConfirmInput; + /// Required when [actionType] set to [CommandPaletteActionType.single]. This /// function is called when the action is selected VoidCallback? onSelect; @@ -68,13 +76,16 @@ class CommandPaletteAction { required this.actionType, this.onSelect, this.childrenActions, + this.onConfirmInput, this.shortcut, this.id, this.leading, }) : assert((actionType == CommandPaletteActionType.single && onSelect != null) || (actionType == CommandPaletteActionType.nested && - (childrenActions?.isNotEmpty ?? false))) { + (childrenActions?.isNotEmpty ?? false)) || + (actionType == CommandPaletteActionType.input && + (onConfirmInput != null))) { // give all our children "us" as a parent. if (actionType == CommandPaletteActionType.nested) { for (final child in childrenActions!) { @@ -106,6 +117,15 @@ class CommandPaletteAction { } } + CommandPaletteAction.input({ + required this.label, + this.description, + required this.onConfirmInput, + this.shortcut, + this.id, + this.leading, + }) : actionType = CommandPaletteActionType.input; + @override bool operator ==(Object other) { if (identical(this, other)) return true; diff --git a/lib/src/models/matched_command_palette_action.dart b/lib/src/models/matched_command_palette_action.dart index b86dd21..bf3cf38 100644 --- a/lib/src/models/matched_command_palette_action.dart +++ b/lib/src/models/matched_command_palette_action.dart @@ -13,6 +13,7 @@ class MatchedCommandPaletteAction extends CommandPaletteAction { childrenActions: action.childrenActions, description: action.description, onSelect: action.onSelect, + onConfirmInput: action.onConfirmInput, shortcut: action.shortcut, id: action.id, leading: action.leading, diff --git a/lib/src/widgets/command_palette_instructions.dart b/lib/src/widgets/command_palette_instructions.dart index f4769e8..5e11c51 100644 --- a/lib/src/widgets/command_palette_instructions.dart +++ b/lib/src/widgets/command_palette_instructions.dart @@ -1,4 +1,5 @@ import 'package:command_palette/src/controller/command_palette_controller.dart'; +import 'package:command_palette/src/models/command_palette_action.dart'; import 'package:command_palette/src/widgets/keyboard_key_icon.dart'; import 'package:flutter/material.dart'; @@ -34,21 +35,27 @@ class CommandPaletteInstructions extends StatelessWidget { color: color, ), ], - instruction: "to select", - ), - _KeyboardInstruction( - icons: [ - KeyboardKeyIcon( - icon: Icons.arrow_upward, - color: color, - ), - KeyboardKeyIcon( - icon: Icons.arrow_downward, - color: color, - ), - ], - instruction: "to navigate", + instruction: + (controller.currentlySelectedAction?.actionType == + CommandPaletteActionType.input) + ? "to confirm" + : "to select", ), + if (controller.currentlySelectedAction?.actionType != + CommandPaletteActionType.input) + _KeyboardInstruction( + icons: [ + KeyboardKeyIcon( + icon: Icons.arrow_upward, + color: color, + ), + KeyboardKeyIcon( + icon: Icons.arrow_downward, + color: color, + ), + ], + instruction: "to navigate", + ), if (controller.currentlySelectedAction != null) _KeyboardInstruction( icons: [ diff --git a/lib/src/widgets/command_palette_text_field.dart b/lib/src/widgets/command_palette_text_field.dart index 8d420a3..92366d3 100644 --- a/lib/src/widgets/command_palette_text_field.dart +++ b/lib/src/widgets/command_palette_text_field.dart @@ -65,8 +65,10 @@ class _CommandPaletteTextFieldState extends State { inputDecoration.prefixText == null; if (styleHasNoPrefix && style.prefixNestedActions && - controller.currentlySelectedAction?.actionType == - CommandPaletteActionType.nested) { + (controller.currentlySelectedAction?.actionType == + CommandPaletteActionType.nested || + controller.currentlySelectedAction?.actionType == + CommandPaletteActionType.input)) { inputDecoration = inputDecoration.copyWith( prefixText: "${controller.currentlySelectedAction!.label}: ", hintText: "", From a57f26e3d6a2b19686e7ffe95084448e846813d2 Mon Sep 17 00:00:00 2001 From: Pedro Nishiyama Date: Fri, 29 Sep 2023 13:36:15 -0300 Subject: [PATCH 05/12] add input action example --- example/lib/main.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/example/lib/main.dart b/example/lib/main.dart index 92afc70..661912b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -99,6 +99,16 @@ class _MyHomePageState extends State { ), ], ), + CommandPaletteAction.input( + id: "new-user", + label: "New User", + shortcut: ["ctrl", "shift", "n"], + leading: Icon(Icons.add), + onConfirmInput: (value) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Created user: $value'))); + }, + ), CommandPaletteAction.nested( id: 1, // or numbers (or really anything...) label: "Set User", From e8621f42734b9f24824b3c9655a451ebbbb46bf5 Mon Sep 17 00:00:00 2001 From: Tyler Norbury Date: Mon, 30 Oct 2023 15:46:30 -0700 Subject: [PATCH 06/12] format --- lib/src/command_palette.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/command_palette.dart b/lib/src/command_palette.dart index fc28217..e03feed 100644 --- a/lib/src/command_palette.dart +++ b/lib/src/command_palette.dart @@ -48,7 +48,7 @@ class CommandPalette extends InheritedWidget { ), config: config ?? CommandPaletteConfig(), toggler: _CommandPaletteToggler(false), - focusNode: focusNode, + focusNode: focusNode, child: child, ), ) { From 2031a4f90a10eac695a1d77726becda5169d371c Mon Sep 17 00:00:00 2001 From: Tyler Norbury Date: Mon, 30 Oct 2023 15:50:54 -0700 Subject: [PATCH 07/12] changelog for PR #35 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6342bb..27a817f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Added - Add FocusNode parameter which is passed to underlying Focus widget +- Added input as a type for an action. This type of action will give the user a text field and accept the processed text, passing it to a callback ## 0.7.3 - 2023-05-12 ### Changed From f95d4ede6379180067e759b858decd2fd54aa01c Mon Sep 17 00:00:00 2001 From: Tyler Norbury Date: Mon, 30 Oct 2023 16:22:26 -0700 Subject: [PATCH 08/12] Add test for input action --- test/input_action_test.dart | 130 ++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 test/input_action_test.dart diff --git a/test/input_action_test.dart b/test/input_action_test.dart new file mode 100644 index 0000000..3958c11 --- /dev/null +++ b/test/input_action_test.dart @@ -0,0 +1,130 @@ +import 'package:command_palette/command_palette.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'utils.dart'; + +void main() { + const String expectedText = "This is a secret message!"; + testWidgets( + "Input action passes result to callback", + (WidgetTester tester) async { + String enteredText = ""; + await tester.pumpWidget( + MyApp( + actions: [ + CommandPaletteAction.input( + label: "Enter Text", + onConfirmInput: (value) { + enteredText = value; + }, + ), + CommandPaletteAction.single( + label: "Some other action", + onSelect: () {}, + ), + ], + ), + ); + + await openPalette(tester); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + expect(find.text("Enter Text: "), findsOneWidget); + expect(find.text("Some other action"), findsNothing); + + // enter text + await tester.enterText(find.byType(TextField), expectedText); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + expect(enteredText, expectedText); + }, + ); + + testWidgets( + "Input text is reset after closing palette", + (WidgetTester tester) async { + await tester.pumpWidget( + MyApp( + actions: [ + CommandPaletteAction.input( + label: "Enter Text", + onConfirmInput: (value) {}, + ), + ], + ), + ); + + await openPalette(tester); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + // enter text + await tester.enterText(find.byType(TextField), expectedText); + + await closePalette(tester); + await openPalette(tester); + + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + expect(find.text(expectedText), findsNothing); + }, + ); + + testWidgets( + "Enter button label is changed when input action is selected", + (WidgetTester tester) async { + await tester.pumpWidget( + MyApp( + actions: [ + CommandPaletteAction.input( + label: "Enter Text", + onConfirmInput: (value) {}, + ), + ], + ), + ); + + await openPalette(tester); + await tester.sendKeyEvent(LogicalKeyboardKey.enter); + await tester.pumpAndSettle(); + + expect(find.text("to confirm"), findsOneWidget); + }, + ); +} + +class MyApp extends StatelessWidget { + final List actions; + + const MyApp({ + Key? key, + required this.actions, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: CommandPalette( + actions: actions, + config: CommandPaletteConfig( + showInstructions: true, + builder: (context, style, action, isHighlighted, onSelected, + searchTerms) { + return Text(action.label); + }, + style: const CommandPaletteStyle( + highlightedLabelTextStyle: TextStyle(color: Colors.pink))), + child: const Scaffold( + body: Center( + child: Text('Hello World'), + ), + ), + ), + ); + } +} From 8d55da30b0cdd4513b01b0b8eb54cf26dc3fa381 Mon Sep 17 00:00:00 2001 From: Tyler Norbury Date: Mon, 30 Oct 2023 16:22:35 -0700 Subject: [PATCH 09/12] update comment --- lib/src/models/command_palette_config.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/models/command_palette_config.dart b/lib/src/models/command_palette_config.dart index bf46d60..851b983 100644 --- a/lib/src/models/command_palette_config.dart +++ b/lib/src/models/command_palette_config.dart @@ -91,7 +91,7 @@ class CommandPaletteConfig { /// keyboard /// /// The current instructions are: - /// * enter/return: to select + /// * enter/return: to select, (or if an input action) to confirm /// * up/down arrow: to navigate /// * escape: to close /// From 1753f97b81cfdeb1477856119f20fe6c30b5d929 Mon Sep 17 00:00:00 2001 From: Tyler Norbury Date: Mon, 30 Oct 2023 16:25:06 -0700 Subject: [PATCH 10/12] don't scroll actions when returning from input action --- lib/src/widgets/options/command_palette_body.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/options/command_palette_body.dart b/lib/src/widgets/options/command_palette_body.dart index 19191be..7ba6854 100644 --- a/lib/src/widgets/options/command_palette_body.dart +++ b/lib/src/widgets/options/command_palette_body.dart @@ -38,7 +38,8 @@ class _CommandPaletteBodyState extends State { double posToScrollTo = -1; if (selectedItemTop < scrollViewTopOffset) { posToScrollTo = selectedItemTop; - } else if (selectedItemBottom > scrollViewBottomOffset) { + } else if (scrollViewBottomOffset != 0 && + selectedItemBottom > scrollViewBottomOffset) { // align bottom of item to bottom posToScrollTo = selectedItemBottom - scrollViewHeight; } From 4666e6cf3f625de97e8a712d1ea979bb95e019c2 Mon Sep 17 00:00:00 2001 From: Tyler Norbury Date: Mon, 30 Oct 2023 16:34:22 -0700 Subject: [PATCH 11/12] update CI triggers --- .github/workflows/ci.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b04600..a09f5b4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,10 +8,7 @@ on: - main - development pull_request: - types: - - ready_for_review - - synchronize - - opened + types: [synchronize, opened, reopened] env: flutter_version: 3.10.0 From c3ccd8a9160976e9820539d375462f3a8f27fa1e Mon Sep 17 00:00:00 2001 From: Tyler Norbury Date: Mon, 30 Oct 2023 16:38:55 -0700 Subject: [PATCH 12/12] 0.7.4 release prep --- CHANGELOG.md | 4 ++-- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27a817f..d89957a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Unreleased +## 0.7.4 - 2023-10-30 ### Added - Add FocusNode parameter which is passed to underlying Focus widget - Added input as a type for an action. This type of action will give the user a text field and accept the processed text, passing it to a callback @@ -96,4 +96,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 0.1.0 - 2021-11-03 ### Added -- initial release \ No newline at end of file +- initial release diff --git a/example/pubspec.lock b/example/pubspec.lock index 76c59e2..f1b9495 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -47,7 +47,7 @@ packages: path: ".." relative: true source: path - version: "0.7.3" + version: "0.7.4" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b500295..de9eec8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: command_palette description: Flutter implementation of a Command Palette. Can be brought up via a keyboard shortcut. -version: 0.7.3 +version: 0.7.4 homepage: https://github.com/TNorbury/command_palette issue_tracker: https://github.com/TNorbury/command_palette/issues repository: https://github.com/TNorbury/command_palette