getSelection<T> method

  1. @override
Future<T> getSelection<T>(
  1. List<T> choices,
  2. MessageBuilder builder,
  3. {ResponseLevel? level,
  4. Duration? timeout,
  5. bool authorOnly = true,
  6. FutureOr<SelectMenuOptionBuilder> toSelectMenuOption(
    1. T
    )?,
  7. Converter<T>? converterOverride}
)
inherited

Present the user with a drop-down menu of choices and return the selected choice.

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.

converterOverride can be set to change how each value is converted to a multi-select option. The default is to use Converter.toSelectMenuOption on the default converter for T.

You might also be interested in:

Implementation

@override
Future<T> getSelection<T>(
  List<T> choices,
  MessageBuilder builder, {
  ResponseLevel? level,
  Duration? timeout,
  bool authorOnly = true,
  FutureOr<SelectMenuOptionBuilder> Function(T)? toSelectMenuOption,
  Converter<T>? converterOverride,
}) async {
  if (_delegate != null) {
    return _delegate!.getSelection(
      choices,
      builder,
      authorOnly: authorOnly,
      converterOverride: converterOverride,
      level: level,
      timeout: timeout,
      toSelectMenuOption: toSelectMenuOption,
    );
  }

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

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

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

  Map<String, T> idToValue = {};
  List<SelectMenuOptionBuilder> options = await Future.wait(choices.map(
    (value) async {
      SelectMenuOptionBuilder builder = await toSelectMenuOption!(value);
      idToValue[builder.value] = value;
      return builder;
    },
  ));

  SelectMenuOptionBuilder prevPageOption = SelectMenuOptionBuilder(
    label: 'Previous page',
    value: ComponentId.generate().toString(),
  );

  SelectMenuOptionBuilder nextPageOption = SelectMenuOptionBuilder(
    label: 'Next page',
    value: ComponentId.generate().toString(),
  );

  SelectMenuContext<List<String>>? context;
  int currentOffset = 0;

  SelectMenuBuilder? menu;
  Message? message;
  InteractiveContext? responseContext;

  try {
    do {
      bool hasPreviousPage = currentOffset != 0;
      int itemsPerPage = hasPreviousPage ? 24 : 25;
      bool hasNextPage = currentOffset + itemsPerPage < options.length;

      if (hasNextPage) {
        itemsPerPage -= 1;
      }

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

      menu = SelectMenuBuilder(
        type: MessageComponentType.stringSelect,
        customId: menuId.toString(),
        options: [
          if (hasPreviousPage) prevPageOption,
          ...options.skip(currentOffset).take(itemsPerPage),
          if (hasNextPage) nextPageOption,
        ],
      );

      ActionRowBuilder row = ActionRowBuilder(components: [menu]);
      if (context == null) {
        // This is the first time we're sending a message, just append the component row.
        (builder.components ??= []).add(row);

        message = await respond(builder, level: level);
        responseContext = this;
      } else {
        // On later iterations, replace the last row with our newly created one.
        List<ActionRowBuilder> rows = builder.components!;

        rows[rows.length - 1] = row;

        await context.respond(
          builder,
          level: (level ?? _nearestCommandContext.command.resolvedOptions.defaultResponseLevel)!
              .copyWith(preserveComponentMessages: false),
        );
        responseContext = context;
      }

      context = await commands.eventManager.nextSelectMenuEvent(menuId);

      if (context.selected.single == nextPageOption.value) {
        currentOffset += itemsPerPage;
      } else if (context.selected.single == prevPageOption.value) {
        currentOffset -= itemsPerPage;
      }
    } while (context.selected.single == nextPageOption.value ||
        context.selected.single == prevPageOption.value);

    context._parent = this;
    _delegate = context;

    final result = idToValue[context.selected.single] as T;

    final matchingOptionIndex = menu.options!.indexWhere(
      (option) => option.value == context!.selected.single,
    );

    if (matchingOptionIndex >= 0) {
      menu.options![matchingOptionIndex].isDefault = true;
    }

    return result;
  } on TimeoutException catch (e, s) {
    throw InteractionTimeoutException(
      'Timed out waiting for selection',
      _nearestCommandContext,
    )..stackTrace = s;
  } finally {
    if (menu != null && message != null && responseContext != null) {
      menu.isDisabled = true;
      await _updateMessage(this, message, MessageCreateUpdateBuilder.fromMessageBuilder(builder));
    }
  }
}