getButtonSelection<T> method

  1. @override
Future<T> getButtonSelection<T>(
  1. List<T> values,
  2. MessageBuilder builder,
  3. {Map<T, ButtonStyle>? styles,
  4. bool authorOnly = true,
  5. ResponseLevel? level,
  6. Duration? timeout,
  7. FutureOr<ButtonBuilder> toButton(
    1. T
    )?,
  8. Converter<T>? converterOverride}
)
inherited

Get a selection from a user, presenting the options as an array of buttons.

If styles is set, the style of a button presenting a given option will depend on the value set in the map.

If timeout is set, this method will complete with an error after timeout has passed.

If authorOnly is set, only the author of this interaction will be able to interact with a button.

level will change the level at which the message is sent, similarly to respond.

toButton and converterOverride can be set to change how each value is converted to a button. At most one of them may be set, and the default is to use Converter.toButton on the default conversion for T.

You might also be interested in:

  • getButtonPress, for getting a button press from any button on a message;
  • getSelection, for getting a selection from a multi-select menu;
  • getConfirmation, for getting a basic true/false selection from the user.

Implementation

@override
Future<T> getButtonSelection<T>(
  List<T> values,
  MessageBuilder builder, {
  Map<T, ButtonStyle>? styles,
  bool authorOnly = true,
  ResponseLevel? level,
  Duration? timeout,
  FutureOr<ButtonBuilder> Function(T)? toButton,
  Converter<T>? converterOverride,
}) async {
  if (_delegate != null) {
    return _delegate!.getButtonSelection(
      values,
      builder,
      authorOnly: authorOnly,
      converterOverride: converterOverride,
      level: level,
      styles: styles,
      timeout: timeout,
      toButton: toButton,
    );
  }

  assert(
    toButton == null || converterOverride == null,
    'Cannot specify both toButton and converterOverride.',
  );

  toButton ??= converterOverride?.toButton;
  toButton ??= commands.getConverter(RuntimeType<T>())?.toButton;

  if (toButton == null) {
    throw UncaughtCommandsException(
      'No suitable method found for converting $T to ButtonBuilder.',
      _nearestCommandContext,
    );
  }

  Map<ComponentId, T> idToValue = {};

  List<ButtonBuilder> buttons = await Future.wait(values.map((value) async {
    ButtonBuilder builder = await toButton!(value);
    ButtonStyle? style = styles?[value];

    ComponentId id = ComponentId.generate(
      expirationTime: timeout,
      allowedUser: authorOnly ? user.id : null,
    );

    idToValue[id] = value;

    // We have to copy since the fields on ButtonBuilder are final.
    return ButtonBuilder(
      style: style ?? builder.style,
      label: builder.label,
      emoji: builder.emoji,
      customId: id.toString(),
      isDisabled: builder.isDisabled,
    );
  }));

  final activeComponentRows = [...?builder.components];
  final disabledComponentRows = [...?builder.components];

  while (buttons.isNotEmpty) {
    // Max 5 buttons per row
    int count = min(5, buttons.length);

    ActionRowBuilder activeRow = ActionRowBuilder(components: []);
    ActionRowBuilder disabledRow = ActionRowBuilder(components: []);

    for (final button in buttons.take(count)) {
      activeRow.components.add(button);

      disabledRow.components.add(
        ButtonBuilder(
          style: button.style,
          label: button.label,
          emoji: button.emoji,
          customId: button.customId,
          isDisabled: true,
        ),
      );
    }

    activeComponentRows.add(activeRow);
    disabledComponentRows.add(disabledRow);

    buttons.removeRange(0, count);
  }

  builder.components = activeComponentRows;
  final message = await respond(builder, level: level);

  final listeners =
      idToValue.keys.map((id) => commands.eventManager.nextButtonEvent(id)).toList();

  try {
    ButtonComponentContext context = await Future.any(listeners);

    context._parent = this;
    _delegate = context;

    return idToValue[context.parsedComponentId]!;
  } on TimeoutException catch (e, s) {
    throw InteractionTimeoutException(
      'Timed out waiting for button selection',
      _nearestCommandContext,
    )..stackTrace = s;
  } finally {
    for (final id in idToValue.keys) {
      commands.eventManager.stopListeningFor(id);
    }

    await _updateMessage(this, message, MessageUpdateBuilder(components: disabledComponentRows));
  }
}