esentis / multiple_search_selection

A highly customizable multiple selection widget with fuzzy search functionality.
https://pub.dev/packages/multiple_search_selection
BSD 3-Clause "New" or "Revised" License
11 stars 13 forks source link

The focus is messed up if there are multiple search selection widgets or textfields in a page #46

Closed becjit closed 6 months ago

becjit commented 7 months ago

Current Behavior

create more than one multiple search selection widgets in a page Noticed following behaviours 1) The keyboard disappears if you focus on search selection 2) If you focus on some other textfield and then click search selection it is likely to work 3) Sometimes the focus keep jumping from one search selection to other

Mostly the behaviour is inconsistent I have tried with same or different focus nodes . Nothing seems to work. I am no expert in focus nodes though :)

My current set up that is relatively stable

I have a page with 1 textfield and two search selections I have added separate global keys to both search selections

If I open the page and immediately focus on search selection the keyboard disappears however if I focus on the textfield and then focus on the search selection it seems to work

I may be missing something basic.

Expected behavior/code

Environment

Possible Solution

Only if you have suggestions on a fix for the bug

Additional context/Screenshots

Add any other context about the problem here. If applicable, add screenshots to help explain.

esentis commented 7 months ago

Hello hello! Interesting finding, FocusNodes are quite tricky may I say. I'll dive into it to see what's going on 🥲 Can you please provide a minimal reproducable code because I can't seem to reproduce the issue.

becjit commented 6 months ago

hi, I will try to come up with a reproducable code soon! My apologies for not getting back earlier

becjit commented 6 months ago

Attaching a reproducible code and the screen recording. I can confirm as of ^2.5.6 this bug exists. You can see in the recording that the cursor is blinking on the both textfields

class TestScreen extends StatefulWidget {

  TestScreen({
    Key? key,
  }) : super(key: key);

  @override
  State<TestScreen> createState() => _TestScreenState();
}

class _TestScreenState extends State<TestScreen> {
  List<String> pediatricConditions = ['test3', 'test4'];
  Set<String> selectedPediatricConditions = {};

  List<String> vaccines = ['test1', 'test2'];
  Set<String> selectedVaccines = {};

  @override
  void initState() {

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    ThemeData theme = Theme.of(context);
    return Scaffold(
      resizeToAvoidBottomInset: false,

      body: Column(
        children: [
          Expanded(
            child: Column(mainAxisSize: MainAxisSize.min, children: [

              Padding(
                padding: spacer.x.sm,
                child: MultipleSearchSelection<String?>.creatable(
                  sortPickedItems: true,
                  sortShowedItems: true,
                  clearSearchFieldOnSelect: true,
                  pickedItemsBoxDecoration: const BoxDecoration(border: Border()),
                  searchFieldInputDecoration: InputDecoration(
                    hintText: 'Type to search',
                    hintStyle: theme.textTheme.bodySmall,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: theme.primaryColorDark),
                    ),
                  ),
                  searchFieldBoxDecoration: const BoxDecoration(
                    //borderRadius: BorderRadius.all(Radius.circular(20.r)),
                    color: Colors.white,
                    border: Border(
                      bottom: BorderSide(width: 1.0),
                    ),
                  ),
                  showClearAllButton: false,
                  onItemAdded: (c) {
                    if (c != null) selectedPediatricConditions.add(c);
                  },
                  showClearSearchFieldButton: true,
                  onItemRemoved: (p0) {
                    selectedPediatricConditions.remove(p0);
                  },
                  createOptions: CreateOptions(
                    createItem: (text) {
                      // var newVal = PediatricConditions(conditionName: text);
                      // if (selectedPediatricConditions.contains(newVal)) {
                      //   return null;
                      // }
                      return text;
                    },
                    onItemCreated: (c) {},
                    createItemBuilder: (text) => Align(
                      alignment: Alignment.centerLeft,
                      child: Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Text(text),
                      ),
                    ),
                    pickCreatedItem: true,
                  ),
                  items: pediatricConditions, // List<Country>
                  fieldToCheck: (c) {
                    return c!;
                  },
                  itemBuilder: (condition, index) {
                    return Padding(
                      padding: const EdgeInsets.all(6.0),
                      child: Text(condition!),
                    );
                  },
                  pickedItemBuilder: (condition) {
                    if (condition != null && condition.isNotEmpty) {
                      return MultipleSearchSelectionChip(
                        label: condition,
                      );
                    }
                    return const SizedBox.shrink();
                  },

                  caseSensitiveSearch: false,
                  fuzzySearch: FuzzySearch.none,
                  itemsVisibility: ShowedItemsVisibility.onType,
                  showSelectAllButton: false,
                  maximumShowItemsHeight: 120,
                ),
              ),
              Gap(48.h),
              Padding(
                padding: spacer.x.sm,
                child: MultipleSearchSelection<String?>(
                  sortPickedItems: true,
                  sortShowedItems: true,
                  clearSearchFieldOnSelect: true,
                  pickedItemsBoxDecoration: const BoxDecoration(border: Border()),
                  searchFieldInputDecoration: InputDecoration(
                    hintText: 'Type to search',
                    hintStyle: theme.textTheme.bodySmall,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: theme.primaryColorDark),
                    ),
                  ),
                  searchFieldBoxDecoration: const BoxDecoration(
                    color: Colors.white,
                    border: Border(
                      bottom: BorderSide(width: 1.0),
                    ),
                  ),
                  showClearAllButton: false,
                  onItemAdded: (c) {
                    if (c != null) selectedVaccines.add(c);
                  },
                  showClearSearchFieldButton: true,
                  onItemRemoved: (p0) {
                    selectedVaccines.remove(p0);
                  },

                  items: vaccines, // List<Country>
                  fieldToCheck: (vaccine) {
                    return vaccine!;
                  },
                  itemBuilder: (vaccine, index) {
                    return Padding(
                      padding: const EdgeInsets.all(6.0),
                      child: Text('$vaccine'),
                    );
                  },
                  pickedItemBuilder: (vaccine) {
                    if (vaccine != null) {
                      return MultipleSearchSelectionChip(
                        label: vaccine,
                      );
                    }
                    return const SizedBox.shrink();
                  },

                  caseSensitiveSearch: false,
                  fuzzySearch: FuzzySearch.none,
                  itemsVisibility: ShowedItemsVisibility.onType,
                  showSelectAllButton: false,
                  maximumShowItemsHeight: 120,
                ),
              ), //
              // This trailing comma makes auto-formatting nicer for build methods.
            ]),
          ),

          Gap(48.h),
        ],
      ),
    );
  }
}

https://github.com/esentis/multiple_search_selection/assets/2213422/3cc169f9-5660-458c-ade1-c9779f7d5d3a

becjit commented 6 months ago

Hi , I have one more request. Could you confirm if this bug exists in the latest version. If it still exists I will hold on to upgrade.

Second question: I see you have introduced overlay constructor in the latest version. Does it support cretable

esentis commented 6 months ago

@becjit Will check it and get back at you.

Yes the new overlay constructor does support creating items when not in the list. You can check the OverlayOptions parameter, you will find a CreateOptions parameter there.

esentis commented 6 months ago

@becjit The bug is not reproduced in the latest version. Here is the refactored version of the code provided :

class TestScreen extends StatefulWidget {
  TestScreen({
    Key? key,
  }) : super(key: key);

  @override
  State<TestScreen> createState() => _TestScreenState();
}

class _TestScreenState extends State<TestScreen> {
  List<String> pediatricConditions = ['test3', 'test4'];
  Set<String> selectedPediatricConditions = {};

  List<String> vaccines = ['test1', 'test2'];
  Set<String> selectedVaccines = {};

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    ThemeData theme = Theme.of(context);
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: Column(
        children: [
          Expanded(
            child: Column(mainAxisSize: MainAxisSize.min, children: [
              MultipleSearchSelection<String?>.creatable(
                sortPickedItems: true,
                sortShowedItems: true,
                clearSearchFieldOnSelect: true,
                pickedItemsBoxDecoration: const BoxDecoration(border: Border()),
                searchField: TextField(
                  decoration: InputDecoration(
                    hintText: 'Type to search',
                    hintStyle: theme.textTheme.bodySmall,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: theme.primaryColorDark),
                    ),
                  ),
                ),

                showClearAllButton: false,
                onItemAdded: (c) {
                  if (c != null) selectedPediatricConditions.add(c);
                },

                onItemRemoved: (p0) {
                  selectedPediatricConditions.remove(p0);
                },
                createOptions: CreateOptions(
                  create: (text) {
                    // var newVal = PediatricConditions(conditionName: text);
                    // if (selectedPediatricConditions.contains(newVal)) {
                    //   return null;
                    // }
                    return text;
                  },
                  onCreated: (c) {},
                  createBuilder: (text) => Align(
                    alignment: Alignment.centerLeft,
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text(text),
                    ),
                  ),
                  pickCreated: true,
                ),
                items: pediatricConditions, // List<Country>
                fieldToCheck: (c) {
                  return c!;
                },
                itemBuilder: (condition, index) {
                  return Padding(
                    padding: const EdgeInsets.all(6.0),
                    child: Text(condition!),
                  );
                },
                pickedItemBuilder: (condition) {
                  if (condition != null && condition.isNotEmpty) {
                    return Text(
                      condition,
                    );
                  }
                  return const SizedBox.shrink();
                },

                caseSensitiveSearch: false,
                fuzzySearch: FuzzySearch.none,
                itemsVisibility: ShowedItemsVisibility.onType,
                showSelectAllButton: false,
                maximumShowItemsHeight: 120,
              ),

              MultipleSearchSelection<String?>(
                sortPickedItems: true,
                sortShowedItems: true,
                clearSearchFieldOnSelect: true,
                pickedItemsBoxDecoration: const BoxDecoration(border: Border()),
                showClearAllButton: false,
                searchField: TextField(
                  decoration: InputDecoration(
                    hintText: 'Type to search',
                    hintStyle: theme.textTheme.bodySmall,
                    focusedBorder: UnderlineInputBorder(
                      borderSide: BorderSide(color: theme.primaryColorDark),
                    ),
                  ),
                ),
                onItemAdded: (c) {
                  if (c != null) selectedVaccines.add(c);
                },

                onItemRemoved: (p0) {
                  selectedVaccines.remove(p0);
                },

                items: vaccines, // List<Country>
                fieldToCheck: (vaccine) {
                  return vaccine!;
                },
                itemBuilder: (vaccine, index) {
                  return Padding(
                    padding: const EdgeInsets.all(6.0),
                    child: Text('$vaccine'),
                  );
                },
                pickedItemBuilder: (vaccine) {
                  if (vaccine != null) {
                    return Text(
                      vaccine,
                    );
                  }
                  return const SizedBox.shrink();
                },

                caseSensitiveSearch: false,
                fuzzySearch: FuzzySearch.none,
                itemsVisibility: ShowedItemsVisibility.onType,
                showSelectAllButton: false,
                maximumShowItemsHeight: 120,
              ), //
              // This trailing comma makes auto-formatting nicer for build methods.
            ]),
          ),
        ],
      ),
    );
  }
}
becjit commented 6 months ago

I can confirm that in the latest version this bug is not there