combos 1.1.0 copy "combos: ^1.1.0" to clipboard
combos: ^1.1.0 copied to clipboard

outdated

Combo Widgets for Flutter. Includes Combo, AwaitCombo, ListCombo, SelectorCombo, TypeaheadCombo, MenuItemCombo

example/lib/main.dart

import 'dart:math' as math;

import 'package:combos/combos.dart';
import 'package:demo_items/demo_items.dart';
import 'package:editors/editors.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

const _customAnimationDurationMs = 150;

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) => MaterialApp(
        title: 'Combo Samples',
        home: HomePage(),
      );
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _comboKey = GlobalKey<ComboState>();
  final _awaitComboKey = GlobalKey<ComboState>();
  GlobalKey<_TestPopupState> _popupKey2;
  GlobalKey<_TestPopupState> _awaitPopupKey2;

  final _comboProperties = ComboProperties();
  final _awaitComboProperties = AwaitComboProperties();
  final _listComboProperties = ListProperties();
  final _selectorProperties = SelectorProperties();
  final _typeaheadProperties = TypeaheadProperties();
  final _menuProperties = MenuProperties();

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text('Combo Samples'),
        ),
        body: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
              // combo
              DemoItem<ComboProperties>(
                properties: _comboProperties,
                childBuilder: (properties) => SizedBox(
                  width: properties.comboWidth.value?.toDouble(),
                  child: Combo(
                    key: _comboKey,
                    child: ListTile(title: Text('Combo')),
                    popupBuilder: (context, mirrored) => TestPopup(
                      key: _popupKey2 = GlobalKey<_TestPopupState>(),
                      mirrored: mirrored,
                      animated:
                          properties.animation.value == PopupAnimation.custom,
                      itemsCount: properties.itemsCount.value ?? 0,
                      onClose: () => _comboKey.currentState.close(),
                      width: properties.popupWidth.value?.toDouble(),
                    ),
                    openedChanged: (isOpened) {
                      if (!isOpened &&
                          properties.animation.value == PopupAnimation.custom) {
                        _popupKey2.currentState?.animatedClose();
                      }
                    },
                  ),
                ),
              ),

              const SizedBox(height: 16),

              // await combo
              DemoItem<AwaitComboProperties>(
                properties: _awaitComboProperties,
                childBuilder: (properties) => SizedBox(
                  width: properties.comboWidth.value?.toDouble(),
                  child: AwaitCombo(
                    key: _awaitComboKey,
                    child: ListTile(title: Text('Await Combo')),
                    popupBuilder: (context) async {
                      await Future.delayed(const Duration(milliseconds: 500));
                      return TestPopup(
                        key: _awaitPopupKey2 = GlobalKey<_TestPopupState>(),
                        mirrored: false,
                        animated:
                            properties.animation.value == PopupAnimation.custom,
                        itemsCount: properties.itemsCount.value ?? 0,
                        onClose: () => _awaitComboKey.currentState.close(),
                        width: properties.popupWidth.value?.toDouble(),
                      );
                    },
                    openedChanged: (isOpened) {
                      if (!isOpened &&
                          properties.animation.value == PopupAnimation.custom) {
                        _awaitPopupKey2.currentState?.animatedClose();
                      }
                    },
                  ),
                ),
              ),

              const SizedBox(height: 16),

              // list
              DemoItem<ListProperties>(
                properties: _listComboProperties,
                childBuilder: (properties) => SizedBox(
                  width: properties.comboWidth.value?.toDouble(),
                  child: ComboContext(
                    parameters: ComboParameters(
                      popupContraints: properties.hasSize
                          ? null
                          : BoxConstraints(
                              maxWidth: properties.popupWidth.value.toDouble()),
                    ),
                    child: ListCombo<String>(
                      getList: () async {
                        await Future.delayed(const Duration(milliseconds: 500));
                        return Iterable.generate(properties.itemsCount.value)
                            .map((e) => 'Item ${e + 1}')
                            .toList();
                      },
                      itemBuilder: (context, parameters, item) =>
                          ListTile(title: Text(item ?? '')),
                      child: ListTile(title: Text('List Combo')),
                      onItemTapped: (value) {},
                    ),
                  ),
                ),
              ),

              const SizedBox(height: 16),

              // selector
              DemoItem<SelectorProperties>(
                properties: _selectorProperties,
                childBuilder: (properties) => SizedBox(
                  width: properties.comboWidth.value?.toDouble(),
                  child: ComboContext(
                    parameters: ComboParameters(
                      popupContraints: properties.hasSize
                          ? null
                          : BoxConstraints(
                              maxWidth: properties.popupWidth.value.toDouble()),
                    ),
                    child: SelectorCombo<String>(
                      getList: () async {
                        await Future.delayed(const Duration(milliseconds: 500));
                        return Iterable.generate(properties.itemsCount.value)
                            .map((e) => 'Item ${e + 1}')
                            .toList();
                      },
                      selected: properties.selected.value,
                      itemBuilder: (context, parameters, item) =>
                          ListTile(title: Text(item ?? '')),
                      childBuilder: (context, parameters, item) =>
                          ListTile(title: Text(item ?? 'Selector Combo')),
                      onItemTapped: (value) =>
                          setState(() => properties.selected.value = value),
                    ),
                  ),
                ),
              ),

              const SizedBox(height: 12),

              // typeahead
              DemoItem<TypeaheadProperties>(
                properties: _typeaheadProperties,
                childBuilder: (properties) => SizedBox(
                  width: properties.comboWidth.value?.toDouble(),
                  child: ComboContext(
                    parameters: ComboParameters(
                      inputThrottle: Duration(
                          milliseconds: properties.inputThrottleMs.value),
                      popupContraints: properties.hasSize
                          ? null
                          : BoxConstraints(
                              maxWidth: properties.popupWidth.value.toDouble()),
                    ),
                    child: TypeaheadCombo<String>(
                      getList: (text) async {
                        await Future.delayed(const Duration(milliseconds: 500));
                        return Iterable.generate(properties.itemsCount.value)
                            .map((e) => 'Item ${e + 1}')
                            .toList();
                      },
                      minTextLength: properties.minTextLength.value,
                      cleanAfterSelection: properties.cleanAfterSelection.value,
                      decoration: InputDecoration(labelText: 'Typeahead Combo'),
                      selected: properties.selected.value,
                      itemBuilder: (context, parameters, item) =>
                          ListTile(title: Text(item ?? '')),
                      getItemText: (item) => item,
                      onItemTapped: (value) =>
                          setState(() => properties.selected.value = value),
                    ),
                  ),
                ),
              ),

              const SizedBox(height: 12),

              // menu
              DemoItem<MenuProperties>(
                properties: _menuProperties,
                childBuilder: (properties) => SizedBox(
                  width: properties.comboWidth.value?.toDouble(),
                  child: MenuItemCombo<String>(
                    itemBuilder: (context, parameters, item) => Padding(
                      padding: const EdgeInsets.all(16),
                      child: Text(item.item),
                    ),
                    onItemTapped: (value) {
                      final dialog =
                          AlertDialog(content: Text('${value.item} tapped!'));
                      showDialog(context: context, builder: (_) => dialog);
                    },
                    item: MenuItem(
                        'Menu Item Combo',
                        () => [
                              MenuItem('New'),
                              MenuItem.separator,
                              MenuItem('Open'),
                              MenuItem('Save'),
                              MenuItem('Save As...'),
                              MenuItem.separator,
                              MenuItem(
                                  'Recent',
                                  () => [
                                        MenuItem('Folders', () async {
                                          await Future.delayed(
                                              Duration(milliseconds: 500));
                                          return [
                                            MenuItem('Folder 1'),
                                            MenuItem('Folder 2'),
                                            MenuItem('Folder 3'),
                                          ];
                                        }),
                                        MenuItem('Files', () async {
                                          await Future.delayed(
                                              Duration(milliseconds: 500));
                                          return [
                                            MenuItem('File 1'),
                                            MenuItem('File 2'),
                                            MenuItem('File 3'),
                                          ];
                                        }),
                                        MenuItem('Documents', () async {
                                          await Future.delayed(
                                              Duration(milliseconds: 500));
                                          return [];
                                        }),
                                      ]),
                              MenuItem.separator,
                              MenuItem('Exit'),
                            ]),
                  ),
                ),
              ),
            ]),
          ],
        ),
      );
}

class TestPopup extends StatefulWidget {
  const TestPopup({
    Key key,
    @required this.mirrored,
    @required this.width,
    @required this.itemsCount,
    @required this.onClose,
    @required this.animated,
    this.radius = Radius.zero,
  }) : super(key: key);

  final bool mirrored;
  final double width;
  final int itemsCount;
  final VoidCallback onClose;
  final bool animated;
  final Radius radius;

  @override
  _TestPopupState createState() => _TestPopupState(width, itemsCount);
}

class _TestPopupState extends State<TestPopup>
    with SingleTickerProviderStateMixin {
  _TestPopupState(this._width, this._itemsCount);

  double _width;
  final int _itemsCount;
  AnimationController _controller;

  void animatedClose() => _controller.animateBack(0.0);

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: _customAnimationDurationMs),
      vsync: this,
      value: widget.animated ? 0.0 : 1.0,
    );
    _controller.animateTo(1.0);
  }

  @override
  Widget build(BuildContext context) {
    final close = Ink(
      decoration: BoxDecoration(
        color: Colors.blueAccent,
        borderRadius: BorderRadius.only(
          topLeft: !widget.mirrored ? widget.radius : Radius.zero,
          topRight: !widget.mirrored ? widget.radius : Radius.zero,
          bottomLeft: widget.mirrored ? widget.radius : Radius.zero,
          bottomRight: widget.mirrored ? widget.radius : Radius.zero,
        ),
      ),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          IconButton(
            icon: Icon(Icons.close, color: Colors.white),
            onPressed: widget.onClose,
          ),
        ],
      ),
    );
    final size = Ink(
      decoration: BoxDecoration(color: Colors.blueAccent.withOpacity(0.7)),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          IconButton(
            icon: Icon(Icons.remove, color: Colors.white),
            onPressed: () =>
                setState(() => _width = math.max(_width - 24, 108)),
          ),
          IconButton(
            icon: Icon(Icons.add, color: Colors.white),
            onPressed: () => setState(() => _width += 24),
          ),
        ],
      ),
    );
    final content = Column(
        children: Iterable.generate(_itemsCount)
            .map((_) => ListTile(title: Text('Item ${_ + 1}'), onTap: () {}))
            .toList());

    return Material(
      elevation: 4,
      borderRadius: BorderRadius.all(widget.radius),
      child: AnimatedContainer(
        width: _width,
        duration: const Duration(milliseconds: _customAnimationDurationMs),
        decoration:
            BoxDecoration(borderRadius: BorderRadius.all(widget.radius)),
        child: ScaleTransition(
          scale: _controller,
          child: Column(children: [
            if (widget.mirrored) ...[
              content,
              size,
              close
            ] else ...[
              close,
              size,
              content
            ],
          ]),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

class DemoItem<TProperties extends ComboProperties>
    extends DemoItemBase<TProperties> {
  const DemoItem({
    Key key,
    @required TProperties properties,
    @required ChildBuilder<TProperties> childBuilder,
  }) : super(key: key, properties: properties, childBuilder: childBuilder);
  @override
  _DemoItemState<TProperties> createState() => _DemoItemState<TProperties>();
}

class _DemoItemState<TProperties extends ComboProperties>
    extends DemoItemStateBase<TProperties> {
  Widget _buildChildDecoration(Widget child) => Container(
        child: Material(
          color: Colors.transparent,
          borderRadius: BorderRadius.circular(16),
          clipBehavior: Clip.antiAlias,
          child: child,
        ),
        clipBehavior: Clip.antiAlias,
        decoration: BoxDecoration(
          border: Border.all(color: Colors.blueAccent),
          borderRadius: BorderRadius.circular(16),
        ),
      );
  Widget _buildPopupDecoration(Widget child) => Container(
        child: Material(
          elevation: 4,
          borderRadius: BorderRadius.circular(16),
          clipBehavior: Clip.antiAlias,
          child: child,
        ),
      );

  @override
  Widget buildChild() {
    final properties = widget.properties;
    final AwaitComboProperties awaitProperties =
        properties is AwaitComboProperties ? properties : null;
    final MenuProperties menuProperties =
        properties is MenuProperties ? properties : null;
    return ComboContext(
        parameters: ComboParameters(
          position: properties.position.value,
          offset: Offset(
            properties.offsetX.value?.toDouble(),
            properties.offsetY.value?.toDouble(),
          ),
          autoMirror: properties.autoMirror.value,
          screenPadding: EdgeInsets.symmetric(
            horizontal: properties.screenPaddingHorizontal.value.toDouble(),
            vertical: properties.screenPaddingVertical.value.toDouble(),
          ),
          autoOpen: properties.autoOpen.value,
          autoClose: properties.autoClose.value,
          enabled: properties.enabled.value,
          animation: properties.animation.value,
          animationDuration:
              Duration(milliseconds: properties.animationDurationMs.value),
          childDecoratorBuilder:
              properties.useChildDecorator.value ? _buildChildDecoration : null,
          popupDecoratorBuilder:
              properties.usePopupDecorator.value ? _buildPopupDecoration : null,
          refreshOnOpened: awaitProperties?.refreshOnOpened?.value ?? false,
          progressPosition: awaitProperties?.progressPosition?.value ??
              ProgressPosition.popup,
          menuShowArrows: menuProperties?.showArrows?.value,
          menuCanTapOnFolder: menuProperties?.canTapOnFolder?.value,
          menuRefreshOnOpened: menuProperties?.menuRefreshOnOpened?.value,
          menuProgressPosition: menuProperties?.menuProgressPosition?.value,
        ),
        child: super.buildChild());
  }

  @override
  Widget buildProperties() {
    final editors = widget.properties.editors;
    return EditorsContext(
      child: ListView.separated(
        padding: const EdgeInsets.all(16),
        shrinkWrap: true,
        physics: ClampingScrollPhysics(),
        itemCount: editors.length,
        itemBuilder: (context, index) => editors[index].build(),
        separatorBuilder: (context, index) => const SizedBox(height: 8),
      ),
    );
  }
}

class ComboProperties {
  final comboWidth = IntEditor(title: 'Combo Width', value: 200);
  final popupWidth = IntEditor(title: 'Popup Width', value: 300);
  final itemsCount = IntEditor(title: 'Items Count', value: 3);
  final position = EnumEditor<PopupPosition>(
      title: 'Position',
      value: PopupPosition.bottomMinMatch,
      getList: () => PopupPosition.values);

  bool get hasSize =>
      position.value == PopupPosition.bottomMatch ||
      position.value == PopupPosition.topMatch;

  final offsetX = IntEditor(title: 'Offset X', value: 0);
  final offsetY = IntEditor(title: 'Offset Y', value: 0);
  final autoMirror = BoolEditor(title: 'Auto Mirror', value: true);
  final requiredSpace = IntEditor(title: 'Req. Space');
  final screenPaddingHorizontal = IntEditor(title: 'S. Padding X', value: 16);
  final screenPaddingVertical = IntEditor(title: 'S. Padding Y', value: 16);
  final autoOpen = EnumEditor<ComboAutoOpen>(
      title: 'Auto Open',
      value: ComboAutoOpen.tap,
      getList: () => ComboAutoOpen.values);
  final autoClose = EnumEditor<ComboAutoClose>(
      title: 'Auto Close',
      value: ComboAutoClose.tapOutsideWithChildIgnorePointer,
      getList: () => ComboAutoClose.values);
  final enabled = BoolEditor(title: 'Enabled', value: true);
  final animation = EnumEditor<PopupAnimation>(
      title: 'Animation',
      value: PopupAnimation.fade,
      getList: () => PopupAnimation.values);
  final animationDurationMs = IntEditor(
      title: 'A. Duration',
      value: ComboParameters.defaultAnimationDuration.inMilliseconds);
  final useChildDecorator =
      BoolEditor(title: 'Use Child Decorator', value: true);
  final usePopupDecorator =
      BoolEditor(title: 'Use Popup Decorator', value: false);

  List<Editor> get editors => [
        comboWidth,
        popupWidth,
        itemsCount,
        position,
        offsetX,
        offsetY,
        autoMirror,
        requiredSpace,
        screenPaddingHorizontal,
        screenPaddingVertical,
        autoOpen,
        autoClose,
        enabled,
        animation,
        animationDurationMs,
        useChildDecorator,
        usePopupDecorator,
      ];
}

class AwaitComboProperties extends ComboProperties {
  final refreshOnOpened = BoolEditor(title: 'Refresh On Opened', value: false);
  final progressPosition = EnumEditor<ProgressPosition>(
      title: 'Progress Position',
      getList: () => ProgressPosition.values,
      value: ProgressPosition.popup);

  @override
  List<Editor> get editors =>
      [refreshOnOpened, progressPosition, ...super.editors];
}

class ListProperties extends AwaitComboProperties {}

class SelectorProperties extends ListProperties {
  SelectorProperties() {
    _selected = EnumEditor<String>(
        title: 'Selected',
        getList: () => Iterable.generate(itemsCount.value)
            .map((e) => 'Item ${e + 1}')
            .toList());
  }

  EnumEditor<String> _selected;
  EnumEditor<String> get selected => _selected;

  @override
  List<Editor> get editors => [selected, ...super.editors];
}

class TypeaheadProperties extends SelectorProperties {
  TypeaheadProperties() {
    _excludes = {autoOpen, autoClose, refreshOnOpened, useChildDecorator};
  }
  Set<Editor> _excludes;

  final minTextLength =
      IntEditor(title: 'Min Text Length', minValue: 0, value: 1);
  final inputThrottleMs =
      IntEditor(title: 'Throttle (ms)', minValue: 0, value: 300);
  final cleanAfterSelection =
      BoolEditor(title: 'Clean After Selection', value: false);

  @override
  List<Editor> get editors => [
        minTextLength,
        inputThrottleMs,
        cleanAfterSelection,
        ...super.editors.where((e) => !_excludes.contains(e))
      ];
}

class MenuProperties extends ListProperties {
  final showArrows = BoolEditor(title: 'Show Arrows', value: true);
  final canTapOnFolder = BoolEditor(title: 'Can Tap On Folder', value: false);
  final menuRefreshOnOpened =
      BoolEditor(title: 'Menu Refresh On Opened', value: false);
  final menuProgressPosition = EnumEditor<ProgressPosition>(
      title: 'Menu Progress Position',
      getList: () => ProgressPosition.values,
      value: ProgressPosition.child);
  @override
  List<Editor> get editors => [
        showArrows,
        canTapOnFolder,
        menuRefreshOnOpened,
        menuProgressPosition,
        ...super.editors.where((e) => e != useChildDecorator)
      ];
}
21
likes
0
pub points
53%
popularity

Publisher

verified publishercreomobile.com

Combo Widgets for Flutter. Includes Combo, AwaitCombo, ListCombo, SelectorCombo, TypeaheadCombo, MenuItemCombo

Repository (GitHub)
View/report issues

License

unknown (LICENSE)

Dependencies

flutter

More

Packages that depend on combos