Home / Aplicativo móvel / Introdução ao controle de botão de fala para texto .NET MAUI

Introdução ao controle de botão de fala para texto .NET MAUI

Introdução ao controle de botão de fala para texto .NET MAUI

Adicione opções de conversão de fala em texto às suas anotações, bate-papo, reunião ou muitos outros tipos de aplicativos, para que seus usuários possam pular a digitação manual em seu aplicativo .NET MAUI.

Há vários anos, um problema comum para usuários de dispositivos móveis tem sido a digitação rápida de texto. Embora recursos inovadores como digitação e preenchimento automático de palavras tenham sido implementados, sem dúvida um dos métodos mais confortáveis ​​continua sendo o ditado por voz.

No .NET MAUI, você pode aproveitar as vantagens da UI Progress Telerik para .NET MAUI SpeechToTextButton controle, que permite converter fala em texto. Vamos ver como usá-lo em seus próprios aplicativos .NET MAUI!

Conhecendo o controle Telerik SpeechToTextButton para .NET MAUI

O controle SpeechToTextButton usa serviços de reconhecimento de voz específicos da plataforma para realizar a conversão de fala em texto, incluindo WinUI. A estrutura do controle é simples e baseia-se em um botão composto por um Conteúdo do SpeechToTextButton e um SpeechToTextButtonque você pode ver na imagem a seguir:

Diagrama mostrando a estrutura do controle SpeechToTextButton da Telerik para .NET MAUI

Alguns possíveis casos de uso para o controle são:

  • Anotações de voz
  • Aplicativos de bate-papo
  • Pesquisa por voz
  • Controle de comando via voz
  • Transcrição da reunião
  • Entre muitos outros

Na realidade, a gama de casos de uso é bastante grande. Agora vamos analisar o controle com mais profundidade.

Criando um caso prático

Vamos começar criando um aplicativo de demonstração, que nos ajudará a ver os diferentes recursos do controle de fala para texto. A ideia do aplicativo é ser um aplicativo de anotações pessoais que permite ações como editar texto, salvar notas e compartilhá-las.

Para este exemplo, usaremos o CommunityToolkit.Mvvm pacote, que permite a criação rápida de modelos de visualização. Abaixo mostro o código inicial da aplicação caso você queira replicá-la, que é o seguinte:

TextEditorPage.xaml:

<Grid
    Padding="20"
    RowDefinitions="Auto, Auto, *, Auto"
    RowSpacing="16">

    

    <Label
        FontAttributes="Bold"
        FontSize="28"
        Text="Fast Note"
        TextColor="#1A1A2E" />

    
    <Border
        Grid.Row="1"
        Padding="16,12"
        BackgroundColor="#F3F4F6"
        StrokeShape="RoundRectangle 12"
        StrokeThickness="0">
        <Grid ColumnDefinitions="Auto, *, Auto">
            <Label
                Grid.Column="2"
                FontSize="12"
                Text="{Binding CharacterCount}"
                TextColor="#9CA3AF"
                VerticalOptions="Center" />
        Grid>
    Border>

    
    <Border
        Grid.Row="2"
        Padding="0"
        BackgroundColor="#FFFFFF"
        Stroke="#E5E7EB"
        StrokeShape="RoundRectangle 16"
        StrokeThickness="1">
        <Grid RowDefinitions="*, Auto">
            <Editor
                x:Name="MainEditor"
                Margin="16"
                AutoSize="TextChanges"
                BackgroundColor="Transparent"
                FontSize="16"
                Placeholder="Start typing or use voice input..."
                PlaceholderColor="#9CA3AF"
                Text="{Binding EditorText, Mode=TwoWay}"
                TextColor="#1F2937" />

            
            <Border
                Grid.Row="1"
                Padding="12,0"
                BackgroundColor="#F9FAFB"
                HeightRequest="56"
                StrokeThickness="0">
                <Grid ColumnDefinitions="*, Auto, Auto">
                    
                    <VerticalStackLayout VerticalOptions="Center">
                        <Label
                            FontSize="12"
                            Text="{Binding WordCount}"
                            TextColor="#6B7280" />
                    VerticalStackLayout>

                    
                    <Button
                        Grid.Column="1"
                        Padding="12,8"
                        BackgroundColor="Transparent"
                        BorderWidth="0"
                        Command="{Binding ClearTextCommand}"
                        FontSize="14"
                        IsEnabled="{Binding HasText}"
                        Text="Clear"
                        TextColor="#EF4444" />

                    
                    <Button
                        Grid.Column="2"
                        Padding="12,8"
                        BackgroundColor="Transparent"
                        BorderWidth="0"
                        Command="{Binding CopyTextCommand}"
                        FontSize="14"
                        IsEnabled="{Binding HasText}"
                        Text="Copy"
                        TextColor="#3B82F6" />
                Grid>
            Border>
        Grid>
    Border>

    
    <Grid
        Grid.Row="3"
        ColumnDefinitions="*, *, *"
        ColumnSpacing="12">
        <Button
            BackgroundColor="#E5E7EB"
            BorderWidth="0"
            Command="{Binding NewDocumentCommand}"
            CornerRadius="10"
            FontSize="14"
            HeightRequest="44"
            IsEnabled="{Binding HasText}"
            Text="New"
            TextColor="#374151" />
        <Button
            Grid.Column="1"
            BackgroundColor="#10B981"
            BorderWidth="0"
            Command="{Binding SaveTextCommand}"
            CornerRadius="10"
            FontSize="14"
            HeightRequest="44"
            IsEnabled="{Binding HasText}"
            Text="Save"
            TextColor="#FFFFFF" />
        <Button
            Grid.Column="2"
            BackgroundColor="#8B5CF6"
            BorderWidth="0"
            Command="{Binding ShareTextCommand}"
            CornerRadius="10"
            FontSize="14"
            HeightRequest="44"
            IsEnabled="{Binding HasText}"
            Text="Share"
            TextColor="#FFFFFF" />
    Grid>

Grid>

TextEditorPage.xaml.cs:

public partial class TextEditorPage : ContentPage
{
    private readonly TextEditorViewModel _viewModel;

    public TextEditorPage()
    {
        InitializeComponent();
        _viewModel = new TextEditorViewModel();
        BindingContext = _viewModel;
    }
}

TextEditorViewModel.cs:

public partial class TextEditorViewModel : ObservableObject
{
    (ObservableProperty)
    (NotifyPropertyChangedFor(nameof(CharacterCount)))
    (NotifyPropertyChangedFor(nameof(WordCount)))
    (NotifyPropertyChangedFor(nameof(HasText)))
    private string _editorText = string.Empty;

    (ObservableProperty)
    private string _statusText = "Ready";

    (ObservableProperty)
    private Color _statusColor = Colors.Gray;

    public string CharacterCount => $"{EditorText?.Length ?? 0} characters";

    public string WordCount
    {
        get
        {
            int words = string.IsNullOrWhiteSpace(EditorText)
                ? 0
                : EditorText.Split((' ', '\n', '\r'), StringSplitOptions.RemoveEmptyEntries).Length;
            return $"{words} words";
        }
    }

    public bool HasText => !string.IsNullOrEmpty(EditorText);

    #region Commands

    (RelayCommand)
    private void ClearText()
    {
        EditorText = string.Empty;
        UpdateStatus("Text cleared", null);
    }

    (RelayCommand)
    private async Task CopyTextAsync()
    {
        if (!HasText) return;

        await Clipboard.Default.SetTextAsync(EditorText);
        UpdateStatus("Copied to clipboard", "#10B981");

        await Task.Delay(2000);
        UpdateStatus("Ready", null);
    }

    (RelayCommand)
    private async Task NewDocumentAsync()
    {
        if (HasText)
        {
            if (Application.Current?.Windows.FirstOrDefault()?.Page is Page page)
            {
                bool confirm = await page.DisplayAlertAsync("New Document",
                    "Do you want to create a new document? Current text will be lost.",
                    "Yes", "No");

                if (!confirm) return;
            }
        }

        EditorText = string.Empty;
        UpdateStatus("New document created", "#10B981");
    }

    (RelayCommand)
    private async Task SaveTextAsync()
    {
        var page = Application.Current?.Windows.FirstOrDefault()?.Page;
        if (page == null) return;

        if (!HasText)
        {
            await page.DisplayAlertAsync("Save", "There is no text to save.", "OK");
            return;
        }

        try
        {
            string fileName = $"VoiceNote_{DateTime.Now:yyyyMMdd_HHmmss}.txt";
            string filePath = Path.Combine(FileSystem.AppDataDirectory, fileName);

            await File.WriteAllTextAsync(filePath, EditorText);

            UpdateStatus("Document saved", "#10B981");

            await page.DisplayAlertAsync("Saved", $"Document saved as {fileName}", "OK");
        }
        catch (Exception ex)
        {
            await page.DisplayAlertAsync("Error", $"Could not save document: {ex.Message}", "OK");
        }
    }

    (RelayCommand)
    private async Task ShareTextAsync()
    {
        if (!HasText)
        {
            if (Application.Current?.Windows.FirstOrDefault()?.Page is Page page)
            {
                await page.DisplayAlertAsync("Share", "There is no text to share.", "OK");
            }
            return;
        }

        await Share.Default.RequestAsync(new ShareTextRequest
        {
            Text = EditorText,
            Title = "Share Voice Note"
        });
    }

    #endregion

    #region Helper Methods

    private void UpdateStatus(string text, string? colorHex)
    {
        StatusText = text;
        StatusColor = string.IsNullOrEmpty(colorHex) ? Colors.Gray : Color.FromArgb(colorHex);
    }

    public void UpdateButtonState(Telerik.Maui.SpeechRecognizer.SpeechRecognizerState state)
    {
        switch (state)
        {
            case Telerik.Maui.SpeechRecognizer.SpeechRecognizerState.Listening:
                UpdateStatus("Listening...", "#2563EB");
                break;
            case Telerik.Maui.SpeechRecognizer.SpeechRecognizerState.Initializing:
                UpdateStatus("Initializing...", "#F59E0B");
                break;
            default:
                UpdateStatus("Ready", null);
                break;
        }
    }

    #endregion
}

O código acima resulta na seguinte aplicação quando executado:

Mostrando a interface de um aplicativo de anotações sendo usado por meio de um teclado

Na imagem anterior você confere o aplicativo, que só permite fazer anotações via teclado, o que pode ser tedioso, lento e enfadonho. Portanto, usaremos o controle de fala para texto para melhorar a rapidez na tomada de notas.

Integrando o controle SpeechToTextButton ao aplicativo

A primeira coisa que você precisa fazer para usar o controle de fala para texto é seguir as Guia de instalação dos controles Telerik .NET MAUI. Depois de fazer isso, adicione o seguinte namespace no arquivo ContentPage onde você deseja usar o controle:

xmlns:telerik="http://schemas.telerik.com/2022/xaml/maui"

Em seguida, adicione o controle usando o RadSpeechToTextButton marque onde deseja colocá-lo; em nosso exemplo, ele estará localizado onde o Barra de ferramentas do editor comentário é:

<telerik:RadSpeechToTextButton
    x:Name="SpeechButton"
    Grid.Column="3"
    VerticalOptions="Center" />

Como utilizaremos o microfone do aplicativo para obtenção da transcrição, é necessário conceder o permissões necessárias de acordo com a plataforma para permitir gravação de áudio e reconhecimento de voz. Por exemplo, para Android, você precisa adicionar a seguinte linha ao AndroidManifest.xml:

A integração do controle fornece um botão com as funcionalidades necessárias para iniciar o processo de conversão de fala em texto:

Mostrando o botão que ativa a funcionalidade de fala para texto, com o microfone sendo ativado automaticamente

Na imagem acima, você pode ver que ao pressionar o botão para iniciar a gravação, o microfone é ativado automaticamente na barra de status (se for a primeira execução, provavelmente solicitará acesso ao microfone), indicando que um processo de gravação foi iniciado.

Agora precisamos tratar os eventos ou comandos para obter a transcrição, que veremos a seguir.

Eventos e comandos disponíveis no controle de fala em texto para .NET MAUI

O controle de fala para texto possui eventos e comandos que podemos usar para reagir a diferentes situações. Para os eventos, temos disponíveis:

  • SpeechRecognized: Isso ocorre quando há um reconhecimento de fala bem-sucedido e inclui um argumento SpeechRecognizerSpeechRecognizedEventArgsque contém o FullText obtido a partir do reconhecimento e FullTextConfidenceScore indicando o nível de confiança do reconhecimento.
  • ErrorOccurred: Isso ocorre quando há um erro no processo de reconhecimento, inclui o argumento SpeechRecognizerErrorOccurredEventArgsque contém o Message propriedade com a mensagem de erro, o Exception associado ao erro e Handled para determinar se o erro foi tratado.
  • StateChanged: Isto é acionado assim que há uma mudança no estado do reconhecedor de fala.

Por outro lado, também temos alguns comandos que nos permitirão tratar eventos diretamente do viewmodel se precisarmos, como é o nosso caso. Para conseguir isso, podemos modificar o rótulo do RadSpeechToTextButton controle adicionando os comandos SpeechRecognizedCommand e ErrorOccurredCommand conforme mostrado no exemplo a seguir:

 
 <Border...>
    <Grid ColumnDefinitions="*, Auto, Auto, Auto">
        ...
        <telerik:RadSpeechToTextButton
            x:Name="SpeechButton"
            Grid.Column="3"
            ErrorOccurredCommand="{Binding ErrorOccurredCommand}"
            SpeechRecognizedCommand="{Binding SpeechRecognizedCommand}"
            VerticalOptions="Center" />
    Grid>
 Border>

A seguir, precisamos lidar com os comandos do viewmodel conforme mostrado abaixo:

#region Speech Recognition Commands

(RelayCommand)
private void SpeechRecognized(SpeechToTextButtonSpeechRecognizedCommandContext context)
{
    EditorText = context.FullText;
    UpdateStatus("Speech recognized", "#10B981");
}

(RelayCommand)
private async Task ErrorOccurredAsync(SpeechToTextButtonErrorOccurredCommandContext context)
{
    UpdateStatus("Error occurred", "#EF4444");

    if (Application.Current?.Windows.FirstOrDefault()?.Page is Page page)
    {
        await page.DisplayAlertAsync("Voice Input Error",
            $"Unable to process voice input: {context.Message}",
            "OK");
    }
}

#endregion

No código anterior, você pode ver que os comandos recebem contextos semelhantes aos argumentos dos eventos. O primeiro, SpeechToTextButtonSpeechRecognizedCommandContextpermite obter o FullText e FullTextConfidenceScore quando há reconhecimento de fala. O segundo, SpeechToTextButtonErrorOccurredCommandContextcontém o Message e Exception propriedades quando há um erro no reconhecimento de fala.

Você também pode ver como FullText é atribuído ao EditorText propriedade, que está vinculada ao controle do tipo Editor. Isso permite que a transcrição obtida seja exibida na interface gráfica, resultando na seguinte execução:

Demonstração ao vivo do controle Speech-to-Text da Telerik para .NET MAUI, permitindo transcrição de voz em tempo real

Agora podemos obter a transcrição; porém, há necessidade de indicadores visuais que permitam aos usuários saber o que está acontecendo na aplicação, e podemos resolver isso através dos estados do controle. Vamos ver como.

Estados do controle de fala para texto

Uma coisa que é apreciada como desenvolvedor é que a documentação do Progress Telerik é muito clara em relação à arquitetura e ao ciclo de vida do controle .NET MAUI SpeechToTextButton, como pode ser visto na imagem a seguir:

Arquitetura e ciclo de vida do controle SpeechToTextButton

O diagrama acima nos mostra uma série de estados que podemos detectar graças ao StateChanged evento como visto abaixo:

<telerik:RadSpeechToTextButton
    x:Name="SpeechButton"
    Grid.Column="3"
    ErrorOccurredCommand="{Binding ErrorOccurredCommand}"
    SpeechRecognizedCommand="{Binding SpeechRecognizedCommand}"
    StateChanged="OnStateChanged"
    VerticalOptions="Center" />

No código por trás, podemos invocar um método do viewmodel que nos permite determinar o que fazer com o estado detectado:

private void OnStateChanged(object sender, EventArgs e)
{
    if (sender is RadSpeechToTextButton button)
    {
        MainThread.BeginInvokeOnMainThread(() =>
        {
            _viewModel.UpdateButtonState(button.State);
        });
    }
}

Dentro do viewmodel, compararemos o estado desejado usando qualquer um dos estados definidos no Telerik.Maui.SpeechRecognizer.SpeechRecognizerState enumeração.

Por exemplo, para reagir a mudanças de estado Listening e Initializingo código seria o seguinte:

public void UpdateButtonState(Telerik.Maui.SpeechRecognizer.SpeechRecognizerState state)
{        
    switch (state)
    {            
        case Telerik.Maui.SpeechRecognizer.SpeechRecognizerState.Listening:
            UpdateStatus("Listening...", "#2563EB");
            break;
        case Telerik.Maui.SpeechRecognizer.SpeechRecognizerState.Initializing:
            UpdateStatus("Initializing...", "#F59E0B");
            break;
        default:
            UpdateStatus("Ready", null);
            break;
    }
}

Por fim, no código da interface gráfica podemos adicionar indicadores visuais que fornecem feedback ao usuário sobre o estado do controle. Em nosso exemplo, fazemos isso na seção de comentários Barra de status:


<Border
    Grid.Row="1"
    Padding="16,12"
    BackgroundColor="#F3F4F6"
    StrokeShape="RoundRectangle 12"
    StrokeThickness="0">
    <Grid ColumnDefinitions="Auto, *, Auto">
        <Ellipse
            BackgroundColor="{Binding StatusColor}"
            HeightRequest="10"
            VerticalOptions="Center"
            WidthRequest="10" />        
        <Label
            Grid.Column="1"
            Margin="12,0,0,0"
            FontSize="14"
            Text="{Binding StatusText}"
            TextColor="#374151"
            VerticalOptions="Center" />
        <Label
            Grid.Column="2"
            FontSize="12"
            Text="{Binding CharacterCount}"
            TextColor="#9CA3AF"
            VerticalOptions="Center" />
    Grid>
Border>

Uma vez substituído o código anterior, veremos graficamente as mudanças de estado, para indicar aos usuários que o componente está pronto para iniciar a transcrição, se está ouvindo a fala ou se a fala foi reconhecida corretamente:

Lidar com a validação de estado do controle Speech-to-Text, fornecendo feedback ao usuário sobre o que está acontecendo na aplicação

Na imagem acima você pode ver as mudanças de estado em ação, de acordo com as ações realizadas pelo usuário no controle de fala para texto.

Conclusão

Ao longo deste artigo, você aprendeu sobre o controle SpeechToTextButton do Telerik para aplicativos .NET MAUI.

A implementação desse tipo de componente em seus aplicativos móveis pode simplificar muito o atrito que existe com os usuários quando se trata de inserir textos longos ou rápidos, algo muito moderno hoje em dia com aplicativos baseados em IA. Recomendo que você experimente e ajude seus usuários a aproveitar ao máximo seus aplicativos.

Se você ainda não estiver usando o Telerik UI para .NET MAUI, ele vem com uma avaliação gratuita de 30 dias:

Experimente agora

Deixe um Comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *