getButtonSelection<T> method
- List<
T> values, - MessageBuilder builder,
- {Map<
T, ButtonStyle> ? styles, - bool authorOnly = true,
- ResponseLevel? level,
- Duration? timeout,
- FutureOr<
ButtonBuilder> toButton(- T
- 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));
}
}