felixmccuaig / flutter-autocomplete-textfield

An autocomplete Textfield for flutter
MIT License
181 stars 131 forks source link

Keyboard covering dropdown on auto-complete #128

Open Sofiety opened 5 months ago

Sofiety commented 5 months ago

Hello,

My keyboard keeps covering the dropdown list for the suggestions, this is a issue as the user can't see the suggestions unless they scroll the main listview down.

I've tried everything and multiple stacoverflows for hours, but my keyboard keeps covering the dropdown.

In the end, I now have resizeToAvoidBottomInset: true, In the scaffold, as it makes my other fields work. I've tried it off and with other solutions.

I also noticed .up direction is not working, but it is a open flutter issue:

https://github.com/flutter/flutter/issues/147483

So I can't even make it go up.

My only temporary fix is to put it at the top of my screen to give some space below, which gives bad UX, and barely works on smaller phones.

Is anyone else experiencing this issue? Any ideas where I can start?

Pasting my main file with scaffold, and then my code for the autocomplete below.

class AddAdvicePage extends StatefulWidget { const AddAdvicePage({super.key});

static Page page() => const MaterialPage(child: AddAdvicePage());

static Route route() { return MaterialPageRoute(builder: (_) => const AddAdvicePage()); }

@override State createState() => _AddAdvicePageState(); }

class _AddAdvicePageState extends State {

final scrollController = ScrollController();

Widget build(BuildContext context) { final user = context.select((AppBloc bloc) => bloc.state.user);

return BlocProvider(
  create: (context) => UploadAdviceCubit(),
  child: BlocListener<UploadAdviceCubit, UploadAdviceState>(
    listener: (context, state) {
      }
    },
    child: GestureDetector(
      onTap: () {
        FocusScopeNode currentFocus = FocusScope.of(context);

        if (!currentFocus.hasPrimaryFocus) {
          FocusManager.instance.primaryFocus?.unfocus();
        }
      },
      child: Scaffold(
        primary: true,
        extendBodyBehindAppBar: true,
        **resizeToAvoidBottomInset: true,**
        bottomNavigationBar: buildMoltenBottomNavigationBar(context, 1,
            context.select((AppBloc bloc) => bloc.state.status)),
        appBar: MyAppBar(
          title: "Contribute",
        ),
        body: Container(
          margin: EdgeInsets.fromLTRB(7, 15, 5, 7),
          child: Form(
            key: _formKey,
            child: ListView(
              controller: scrollController,
              shrinkWrap: true,
              scrollDirection: Axis.vertical,
              children: [
                const SizedBox(height: 10),
                titleForm(),
                Padding(
                  padding: const EdgeInsets.fromLTRB(0, 16, 0, 5),
                  child: mainCategories(context),
                ),
                Padding(
                  padding: const EdgeInsets.fromLTRB(0, 15, 0, 12),
                  child: StringAutoCompleteTags(
                    mainStringTagController: stringTagController,
                  ),
                ),

import 'package:flutter/material.dart';
import 'package:flutter_firebase_login/global_widgets/available_categories.dart';
import 'package:textfield_tags/textfield_tags.dart';

//String Tags with AutoComplete
class StringAutoCompleteTags extends StatefulWidget {
  StringAutoCompleteTags({
    Key? key,
    required this.mainStringTagController,
  }) : super(key: key);

  StringTagController mainStringTagController;
  @override
  State<StringAutoCompleteTags> createState() => _StringAutoCompleteTagsState();
}

class _StringAutoCompleteTagsState extends State<StringAutoCompleteTags> {
  late double _distanceToField;

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    _distanceToField = MediaQuery.of(context).size.width;
  }

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

  @override
  void dispose() {
    //widget.stringTagController.dispose();
    super.dispose();
  }

  void addCategoriesToStaticList() {
    categoriesData.forEach((category) {
      if (category.categoryString != "all") {
        _initialTags.add(category.categoryString);
      }
    });
  }

  List<String> _initialTags = <String>[];

  List notAllowed = ['error', 'all', 'murder'];

  @override
  Widget build(BuildContext context) {
    return Autocomplete<String>(
      optionsViewOpenDirection: OptionsViewOpenDirection.down,
      optionsViewBuilder: (context, onSelected, options) {
        return Align(
          alignment: Alignment.topCenter,
          child: Material(
            elevation: 4.0,
            child: ConstrainedBox(
              constraints: const BoxConstraints(maxHeight: 600),
              child: ListView.builder(
                // Dropdown auto select
                reverse: false,
                padding: EdgeInsets.zero,
                shrinkWrap: true,
                itemCount: options.length,
                itemBuilder: (BuildContext context, int index) {
                  final String option = options.elementAt(index);
                  return TextButton(
                    onPressed: () {
                      onSelected(option);
                    },
                    child: Align(
                      alignment: Alignment.centerLeft,
                      child: Text(
                        // Autocomplete options
                        '#$option',
                        textAlign: TextAlign.left,
                        style: const TextStyle(
                          fontSize: 18,
                          color: Color.fromARGB(255, 74, 137, 92),
                        ),
                      ),
                    ),
                  );
                },
              ),
            ),
          ),
        );
      },
      optionsBuilder: (TextEditingValue textEditingValue) {
        if (textEditingValue.text == '') {
          return const Iterable<String>.empty();
        }
        return _initialTags.where((String option) {
          return option.contains(textEditingValue.text.toLowerCase());
        });
      },
      onSelected: (String selectedTag) {
        widget.mainStringTagController.onTagSubmitted(selectedTag);
      },
      fieldViewBuilder:
          (context, textEditingController, focusNode, onFieldSubmitted) {
        return TextFieldTags<String>(
          textEditingController: textEditingController,
          focusNode: focusNode,
          textfieldTagsController: widget.mainStringTagController,
          /*              initialTags: const [
            'yaml',
            'gradle',
          ],*/
          textSeparators: const [' ', ','],
          letterCase: LetterCase.small, // Force small letters
          validator: (String tag) {
            bool isErrorOrLol = notAllowed.contains(tag);
            if (isErrorOrLol) {
              // Categories we don't want to see
              return 'This tag is not allowed';
            } else if (widget.mainStringTagController.getTags!.contains(tag)) {
              return 'You\'ve already entered that';
            }
            return null;
          },
          inputFieldBuilder: (context, inputFieldValues) {
            return TextFormField(
              onEditingComplete: () {},
              textInputAction: TextInputAction.none,
              validator: (value) {
                //print("VALUE: $value");
                int? tagCount = widget.mainStringTagController.getTags?.length;
                //print("Tag count = $tagCount");
                if (tagCount == null || tagCount == 0) {
                  //print("FAIL!!");
                  return '    Minimum one tag.';
                }
                return null;
              },
              controller: inputFieldValues.textEditingController,
              focusNode: inputFieldValues.focusNode,
              decoration: InputDecoration(
                isDense: true,
                enabledBorder: OutlineInputBorder(
                  borderSide: BorderSide(
                    color: Colors.white
                        .withOpacity(0.1), //Color.fromARGB(255, 74, 137, 92),
                    width: 1.0,
                  ),
                ),
                focusedBorder: const OutlineInputBorder(
                  borderSide: BorderSide(
                    color: Color.fromARGB(255, 74, 137, 92),
                    width: 1.0,
                  ),
                ),
                //helperText: 'Enter language...',
                helperStyle: const TextStyle(
                  color: Color.fromARGB(255, 74, 137, 92),
                ),
                labelText: 'Tags',
                floatingLabelBehavior: FloatingLabelBehavior.always,
                labelStyle: TextStyle(
                    color: Colors.white,
                    fontSize: 25,
                    fontWeight: FontWeight.bold),
                fillColor: Color(0xFF0D112C),
                filled: true,
                hintText: inputFieldValues.tags.isNotEmpty
                    ? ''
                    : '"housing, renting, deposit, photos"...',
                hintStyle: TextStyle(
                    fontSize: 12,
                    color: Colors.grey,
                    fontStyle: FontStyle.italic),
                errorText: inputFieldValues.error,
                prefixIconConstraints:
                    BoxConstraints(maxWidth: _distanceToField * 0.74),
                prefixIcon: inputFieldValues.tags.isNotEmpty
                    ? SingleChildScrollView(
                        controller: inputFieldValues.tagScrollController,
                        scrollDirection: Axis.horizontal,
                        child: Row(
                            children: inputFieldValues.tags.map((String tag) {
                          return Container(
                            decoration: const BoxDecoration(
                              borderRadius: BorderRadius.all(
                                Radius.circular(20.0),
                              ),
                              color: Color.fromARGB(255, 74, 137, 92),
                            ),
                            margin: const EdgeInsets.only(left: 7.0),
                            padding: const EdgeInsets.symmetric(
                                horizontal: 10.0, vertical: 4.0),
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                InkWell(
                                  child: Text(
                                    '#$tag',
                                    style: const TextStyle(color: Colors.white),
                                  ),
                                  onTap: () {
                                    //print("$tag selected");
                                  },
                                ),
                                const SizedBox(width: 4.0),
                                InkWell(
                                  child: const Icon(
                                    Icons.cancel,
                                    size: 14.0,
                                    color: Color.fromARGB(255, 233, 233, 233),
                                  ),
                                  onTap: () {
                                    inputFieldValues.onTagRemoved(tag);
                                  },
                                )
                              ],
                            ),
                          );
                        }).toList()),
                      )
                    : null,
              ),
              onChanged: inputFieldValues.onTagChanged,
              onFieldSubmitted: inputFieldValues.onTagSubmitted,
            );
          },
        );
      },
    );
  }
}
`