AhmedLSayed9 / dropdown_button2

Flutter's core Dropdown Button widget with steady dropdown menu and many other features.
https://pub.dev/packages/dropdown_button2
MIT License
264 stars 122 forks source link

Ink splashes get displayed over search widget #290

Closed hlvs-apps closed 3 months ago

hlvs-apps commented 3 months ago

If the search widget is set, the ink splashes of half visible top rows get displayed over the search widget. The expected behaviour would be to hide them behind the search widget, because the search widget itself is not the source of the splash and the row that causes the splash itself is hidden behind the search widget.

ink_splash

Code to reproduce this issue:

import 'package:dropdown_button2/dropdown_button2.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
//import 'package:flutterfontchooser/flutterfontchooser.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: DropdownButtonHideUnderline(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              Text("Width: ..."),
              Text("Height: 500px"),
              Text("..."),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                children: [
                  Text("Bold"),
                  Text("Italic"),
                ],
              ),
              FontChooserDropdownMenuWithoutServerAccessAndNoFonts(),
              //FontChooserDropdownMenu(),
              //SizedBox(height: 200),
            ],
          ),
        ),
      ),
    );
  }
}

//a dropdown widget that allows the user to choose a font, and displays the font name in the dropdown.
//the fonts get dynamically loaded from the server
class FontChooserDropdownMenuWithoutServerAccessAndNoFonts
    extends StatefulWidget {
  //const FontChooserDropdownMenu({super.key, super.controller});
  const FontChooserDropdownMenuWithoutServerAccessAndNoFonts({super.key});

  @override
  State<FontChooserDropdownMenuWithoutServerAccessAndNoFonts> createState() =>
      _FontChooserDropdownMenuWithoutServerAccessAndNoFontsState();
}

class _FontChooserDropdownMenuWithoutServerAccessAndNoFontsState
    extends State<FontChooserDropdownMenuWithoutServerAccessAndNoFonts> {
  //final Map<SearchResponseEntryComparable, DropdownItem<SearchResponseEntryComparable>>
  final Map<String, DropdownItem<String>> _items = {};

  void _loadItems() {
    /*Set<SearchResponseEntryComparable> oldItems = _items.keys.toSet();
    bool changed = false;
    for (var value in controller.allFontsIfLoaded) {
      SearchResponseEntryComparable comparable =
      SearchResponseEntryComparable(value);
      if (!oldItems.contains(comparable)) {
        changed = true;
        _items[comparable] = DropdownItem<SearchResponseEntryComparable>(
          value: comparable,
          child: DropdownRowWidget(
            key: Key("${value.family}-dropdown-row-widget"),
            selectedFont: selectedFont,
            thisFont: value,
          ),
        );
      }
      oldItems.remove(comparable);
    }
    for (var item in oldItems) {
      _items.remove(item);
    }
    if (changed && mounted) {
        setState(() {});
    }*/
    for (int i = 0; i < 1500; i++) {
      _items["Font $i"] = DropdownItem<String>(
        value: "Font $i",
        child: DropdownRowWidget(
          key: Key("Font $i-dropdown-row-widget"),
          selectedFont: selectedFont,
          thisFont: "Font $i",
        ),
      );
    }
    setState(() {});
  }

  @override
  void initState() {
    super.initState();
    ServicesBinding.instance.keyboard.addHandler(_onKey);
    Future.microtask(_loadItems);
  }

  /*@override
  void onController() {
    var s = searchResponseEntryComparableFromSearchResponseEntry(
        controller.selectedFont?.searchResponseEntry);
    if (s != selectedFont.value) {
      selectedFont.value = s;
    }
    _loadItems();
  }*/

  @override
  void dispose() {
    ServicesBinding.instance.keyboard.removeHandler(_onKey);
    super.dispose();
  }

  //final ValueNotifier<SearchResponseEntryComparable?> selectedFont =
  final ValueNotifier<String?> selectedFont = ValueNotifier(null);

  TextEditingController searchController = TextEditingController();

  FocusNode searchFocusNode = FocusNode();

  bool _onKey(KeyEvent event) {
    /*setState(() {
      //set random height
      maxHeight = 200 + (100 * (DateTime.now().second % 5));
    });*/
    if (_menuOpen && !searchFocusNode.hasFocus && event is KeyDownEvent) {
      String? key = event.character;
      if (key != null &&
          event.logicalKey != LogicalKeyboardKey.enter &&
          event.logicalKey != LogicalKeyboardKey.space &&
          event.logicalKey != LogicalKeyboardKey.tab) {
        if (event.logicalKey == LogicalKeyboardKey.backspace) {
          if (searchController.text.isNotEmpty) {
            searchController.text = "";
            searchFocusNode.requestFocus();
            return true;
          }
          searchFocusNode.requestFocus();
          return false;
        }
        searchController.text += key;
        searchFocusNode.requestFocus();
        return true;
      }
    }
    return false;
  }

  bool _menuOpen = false;

  @override
  Widget build(BuildContext context) {
    //return DropdownButton2<SearchResponseEntryComparable>(
    return Expanded(child:Column(
      children: [
        DropdownButton2<String>(
          valueListenable: selectedFont,
          barrierCoversButton: false,
          dropdownStyleData: DropdownStyleData(
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(15),
            ),
          ),
          onMenuStateChange: (open) {
            _menuOpen = open;
          },
          buttonStyleData: const ButtonStyleData(
            width: 260,
            height: 50,
          ),
          dropdownSearchData: DropdownSearchData(
            searchController: searchController,
            searchBarWidgetHeight: 50,
            searchBarWidget: Container(
              height: 50,
              padding: const EdgeInsets.only(
                top: 8,
                bottom: 4,
                right: 8,
                left: 8,
              ),
              child: TextFormField(
                maxLines: 1,
                controller: searchController,
                focusNode: searchFocusNode,
                decoration: InputDecoration(
                  isDense: true,
                  contentPadding: const EdgeInsets.symmetric(
                    horizontal: 10,
                    vertical: 8,
                  ),
                  hintText: 'Search for a font...',
                  hintStyle: const TextStyle(fontSize: 12),
                  border: OutlineInputBorder(
                    borderRadius: BorderRadius.circular(8),
                  ),
                ),
              ),
            ),
            noResultsWidget: const Padding(
              padding: EdgeInsets.all(8),
              child: Text('No Font Found!'),
            ),
            searchMatchFn:
                //(DropdownItem<SearchResponseEntryComparable> entry, String search) {
                (DropdownItem<String> entry, String search) {
              //return entry.value?.family
              return entry.value
                      ?.toLowerCase()
                      .contains(search.toLowerCase()) ??
                  false;
            },
          ),
          iconStyleData: const IconStyleData(
            icon: Icon(
              Icons.arrow_drop_down,
              color: Colors.black45,
            ),
          ),
          menuItemStyleData: const MenuItemStyleData(
            padding: EdgeInsets.symmetric(horizontal: 16),
          ),
          hint: const Text(
            'Select a font',
            style: TextStyle(fontSize: 14),
          ),
          /*onChanged: (SearchResponseEntryComparable? newValue) {
        selectedFont.value = newValue;
        if (newValue == null) {
          controller.selectedFont = null;
          return;
        }
        Future.microtask(() async {
          controller.selectedFont =
          await LoadedFont.fromSearchResponseEntry(newValue.entry);
        });
      },*/
          onChanged: (String? newValue) {
            selectedFont.value = newValue;
          },
          items: _items.values.toList(),
        ),
      ],
    ),);
  }
}

class DropdownRowWidget extends StatefulWidget {
  //final SearchResponseEntry thisFont;
  final String thisFont;

  //final ValueNotifier<SearchResponseEntryComparable?> selectedFont;
  final ValueNotifier<String?> selectedFont;
  final double width;

  const DropdownRowWidget(
      {super.key,
      required this.thisFont,
      this.width = 200,
      required this.selectedFont});

  @override
  State<DropdownRowWidget> createState() => _DropdownRowWidgetState();
}

class _DropdownRowWidgetState extends State<DropdownRowWidget> {
  bool _isFontLoaded = false;
  bool _isLoading = false;

  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        SizedBox(
          width: widget.width,
          child: Text(
            widget.thisFont,
            /*style: Theme.of(context)
                  .textTheme
                  .apply(fontFamily: widget.thisFont.menuFontFamily)
                  .bodyMedium*/
          ),
        ),
        if (!_isFontLoaded && !_isLoading)
          const Icon(
            Icons.cloud_download_outlined,
            color: Colors.grey,
            size: 24,
          ),
        /*if (!_isFontLoaded && _isLoading)
          LoadingAnimationWidget.dotsTriangle(
            color: Colors.grey,
            size: 24,
          ),*/
      ],
    );
  }

  void _check() {
    if (!mounted) {
      return;
    }
    bool s = false;
    //bool i = isFontLoaded(widget.thisFont);
    bool i = false;
    if (i != _isFontLoaded) {
      _isFontLoaded = i;
      s = true;
    }
    //i = isLoadingFont(widget.thisFont);
    i = false;
    if (i != _isLoading) {
      _isLoading = i;
      s = true;
    }
    if (s) {
      setState(() {});
    }
  }

  @override
  void initState() {
    super.initState();
    _check();
    //FontLoaderNotifier.instance.addListener(_check);
    widget.selectedFont.addListener(_check);
  }

  @override
  void didUpdateWidget(DropdownRowWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    _check();
  }

  @override
  void dispose() {
    //FontLoaderNotifier.instance.removeListener(_check);
    super.dispose();
  }
}
hlvs-apps commented 3 months ago

Solved in PR #291

AhmedLSayed9 commented 3 months ago

This is an issue in flutter which is being tracked in https://github.com/flutter/flutter/issues/86584 & https://github.com/flutter/flutter/issues/73315 but, we can accept the temporary solution in the PR for now.