Skip to content
Client v5: BLE, BLE Hosting, HTTP, Jobs - Linux, MacOS, & Blazor Support! Full AOT, RX on BLE only & MANY other features! Power up!

ChatView | Custom Actions

The old ChatEntryTool / ChatBubbleTool FAB tool tree is gone. ChatView now derives its built-in actions from permissions and ownership, and exposes two lightweight hooks for genuinely app-specific verbs.

Built-in bubble and input actions are derived automatically from Info.Permissions + ownership — there is no per-tool wiring:

ActionAppears when
ReactCanReactToMessages (picker filtered to PermittedEmojis)
EditCanEditMessages and the message is the current user’s own
DeleteCanDeleteMessages and the message is the current user’s own
Copyalways available on text bubbles (client-side)
Attach imageCanSendImages (input bar)

See Permissions for the full map. You don’t register these — set the right permissions on your ChatSessionInfo and they appear.

For app-specific verbs, add lightweight actions to two collections:

PropertyTypeDescription
InputActionsIList<ChatInputAction>Custom input-bar actions (surfaced via the overflow button)
CustomBubbleActionsIList<ChatBubbleAction>Custom bubble actions appended to the built-in permission-driven set
public class ChatInputAction : BindableObject
{
public string? Text { get; set; }
public ImageSource? Icon { get; set; }
public Func<ChatView, Task>? Handler { get; set; }
public event EventHandler<ChatView>? Clicked;
public virtual Task InvokeAsync(ChatView chatView); // overridable
}
public class ChatBubbleAction : BindableObject
{
public string? Text { get; set; }
public ImageSource? Icon { get; set; }
public Func<ChatMessage, Task>? Handler { get; set; }
public event EventHandler<ChatMessage>? Clicked;
public virtual Task InvokeAsync(ChatMessage message); // overridable
}
public partial class ChatViewModel : ObservableObject
{
public ChatViewModel(IChatSessionProvider provider)
{
this.Provider = provider;
this.InputActions =
[
new ChatInputAction
{
Text = "Insert Greeting",
Handler = view =>
{
var existing = view.EntryText?.Trim();
view.EntryText = string.IsNullOrEmpty(existing) ? "Hello 👋" : $"{existing} Hello 👋";
return Task.CompletedTask;
}
}
];
this.CustomBubbleActions =
[
new ChatBubbleAction
{
Text = "Translate",
Handler = async msg =>
await Shell.Current.DisplayAlert("Translate", $"Translating: {msg.Body}", "OK")
}
];
}
public IChatSessionProvider Provider { get; }
public string SessionId => "demo";
public IList<ChatInputAction> InputActions { get; }
public IList<ChatBubbleAction> CustomBubbleActions { get; }
}
<shiny:ChatView Provider="{Binding Provider}"
SessionId="{Binding SessionId}"
InputActions="{Binding InputActions}"
CustomBubbleActions="{Binding CustomBubbleActions}" />

A ChatInputAction receives the ChatView (handy with EntryText / SubmitEntry()); a ChatBubbleAction receives the ChatMessage it was invoked on. Override InvokeAsync to build a reusable, stateful action instead of using the Handler delegate.

Shiny.Maui.Controls.SpeechAddins ships two ready-made actions:

  • SpeechToTextTool : ChatInputAction — listens via ISpeechToTextService and backfills the entry; optional AutoSend.
  • TextToSpeechBubbleTool : ChatBubbleAction — reads a message’s Body aloud via ITextToSpeechService.

Add the package and register Shiny Speech, then add them like any other action. They are registered under the same http://shiny.net/maui/controls XAML namespace:

<shiny:ChatView Provider="{Binding Provider}" SessionId="{Binding SessionId}">
<shiny:ChatView.InputActions>
<shiny:SpeechToTextTool AutoSend="False" SilenceTimeout="00:00:03" />
</shiny:ChatView.InputActions>
<shiny:ChatView.CustomBubbleActions>
<shiny:TextToSpeechBubbleTool />
</shiny:ChatView.CustomBubbleActions>
</shiny:ChatView>

Or in code:

using Shiny.Maui.Controls.SpeechAddins.Chat;
this.InputActions = [ new SpeechToTextTool { AutoSend = false, SilenceTimeout = TimeSpan.FromSeconds(3) } ];
this.CustomBubbleActions = [ new TextToSpeechBubbleTool() ];
SpeechToTextToolDefault
AutoSendfalsesubmit the entry after recognition completes
SilenceTimeout2ssilence before recognition is considered done
Culturenullrecognition culture (e.g. "en-US")
PreferOnDevicefalseprefer on-device recognition
TextToSpeechBubbleToolDefault
SpeechRate / Pitch / Volume1.0
Culture / VoiceNamenull (system default)