Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions .github/workflows/build-apk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Build Android APK

on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
workflow_dispatch:

env:
DOTNET_VERSION: '8.0.x'
PROJECT_PATH: src/VoiceNotesAI/VoiceNotesAI.csproj

jobs:
build:
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Install MAUI workload
run: dotnet workload install maui-android

- name: Setup Java JDK
uses: actions/setup-java@v4
with:
distribution: 'microsoft'
java-version: '17'

- name: Create appsettings.json from secret
run: |
cat > src/VoiceNotesAI/appsettings.json << 'SETTINGS'
{
"OpenAI": {
"ApiKey": "${{ secrets.OPENAI_API_KEY }}",
"Model": "gpt-4o",
"WhisperModel": "whisper-1"
}
}
SETTINGS

- name: Restore dependencies
run: dotnet restore ${{ env.PROJECT_PATH }}

- name: Build APK
run: >
dotnet publish ${{ env.PROJECT_PATH }}
-c Release
-f net8.0-android
-o output

- name: Upload APK artifact
uses: actions/upload-artifact@v4
with:
name: VoiceNotesAI-APK
path: output/**/*-Signed.apk
if-no-files-found: warn
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*.userosscache
*.sln.docstates
*.env
appsettings.json

# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
Expand Down
25 changes: 25 additions & 0 deletions VoiceNotesAI.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoiceNotesAI", "src\VoiceNotesAI\VoiceNotesAI.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VoiceNotesAI.Tests", "tests\VoiceNotesAI.Tests\VoiceNotesAI.Tests.csproj", "{B2C3D4E5-F6A7-8901-BCDE-F12345678901}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B2C3D4E5-F6A7-8901-BCDE-F12345678901}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
29 changes: 29 additions & 0 deletions src/VoiceNotesAI/App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:converters="clr-namespace:VoiceNotesAI.Converters"
x:Class="VoiceNotesAI.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>

<!-- Converters -->
<converters:InvertedBoolConverter x:Key="InvertedBoolConverter" />
<converters:RecordButtonTextConverter x:Key="RecordButtonTextConverter" />
<converters:RecordButtonColorConverter x:Key="RecordButtonColorConverter" />

<!-- Chip Button Style -->
<Style x:Key="ChipButton" TargetType="Button">
<Setter Property="FontSize" Value="13" />
<Setter Property="HeightRequest" Value="34" />
<Setter Property="CornerRadius" Value="17" />
<Setter Property="Padding" Value="14,0" />
<Setter Property="BackgroundColor" Value="{AppThemeBinding Light=#EDE7F6, Dark=#2D2040}" />
<Setter Property="TextColor" Value="{AppThemeBinding Light=#6750A4, Dark=#D0BCFF}" />
</Style>
</ResourceDictionary>
</Application.Resources>
</Application>
18 changes: 18 additions & 0 deletions src/VoiceNotesAI/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using VoiceNotesAI.Data;

namespace VoiceNotesAI;

public partial class App : Application
{
public App(AppDatabase database)
{
InitializeComponent();

Task.Run(async () => await database.InitializeAsync());
}

protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}
16 changes: 16 additions & 0 deletions src/VoiceNotesAI/AppShell.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:VoiceNotesAI.Pages"
x:Class="VoiceNotesAI.AppShell"
Title="VoiceNotes AI"
Shell.BackgroundColor="{AppThemeBinding Light=#6750A4, Dark=#1C1B1F}"
Shell.ForegroundColor="{AppThemeBinding Light=White, Dark=#D0BCFF}"
Shell.TitleColor="{AppThemeBinding Light=White, Dark=#D0BCFF}">

<ShellContent
Title="Notas"
ContentTemplate="{DataTemplate pages:NoteListPage}"
Route="NoteListPage" />

</Shell>
15 changes: 15 additions & 0 deletions src/VoiceNotesAI/AppShell.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using VoiceNotesAI.Pages;

namespace VoiceNotesAI;

public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();

Routing.RegisterRoute("RecordingPage", typeof(RecordingPage));
Routing.RegisterRoute("NoteResultPage", typeof(NoteResultPage));
Routing.RegisterRoute("NoteDetailPage", typeof(NoteDetailPage));
}
}
20 changes: 20 additions & 0 deletions src/VoiceNotesAI/Converters/InvertedBoolConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System.Globalization;

namespace VoiceNotesAI.Converters;

public class InvertedBoolConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool boolValue)
return !boolValue;
return value;
}

public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool boolValue)
return !boolValue;
return value;
}
}
18 changes: 18 additions & 0 deletions src/VoiceNotesAI/Converters/RecordButtonColorConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Globalization;

namespace VoiceNotesAI.Converters;

public class RecordButtonColorConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool isRecording)
return isRecording ? Color.FromArgb("#E53935") : Color.FromArgb("#6750A4");
return Color.FromArgb("#6750A4");
}

public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
18 changes: 18 additions & 0 deletions src/VoiceNotesAI/Converters/RecordButtonTextConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Globalization;

namespace VoiceNotesAI.Converters;

public class RecordButtonTextConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is bool isRecording)
return isRecording ? "⏹️ Parar Gravação" : "🎙️ Iniciar Gravação";
return "🎙️ Iniciar Gravação";
}

public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
46 changes: 46 additions & 0 deletions src/VoiceNotesAI/Data/AppDatabase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using SQLite;
using VoiceNotesAI.Models;

namespace VoiceNotesAI.Data;

public class AppDatabase
{
private readonly SQLiteAsyncConnection _database;

public AppDatabase(string dbPath)
{
_database = new SQLiteAsyncConnection(dbPath);
}

public SQLiteAsyncConnection Connection => _database;

public async Task InitializeAsync()
{
await _database.CreateTableAsync<Note>();
await _database.CreateTableAsync<Category>();

await SeedCategoriesAsync();
}

private async Task SeedCategoriesAsync()
{
var count = await _database.Table<Category>().CountAsync();
if (count > 0)
return;

var defaultCategories = new[]
{
"Tarefas",
"Ideias",
"Lembretes",
"Trabalho",
"Pessoal",
"Outros"
};

foreach (var name in defaultCategories)
{
await _database.InsertAsync(new Category { Name = name });
}
}
}
8 changes: 8 additions & 0 deletions src/VoiceNotesAI/Helpers/AppSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace VoiceNotesAI.Helpers;

public class OpenAISettings
{
public string ApiKey { get; set; } = string.Empty;
public string Model { get; set; } = "gpt-4o";
public string WhisperModel { get; set; } = "whisper-1";
}
38 changes: 38 additions & 0 deletions src/VoiceNotesAI/Helpers/PromptTemplates.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
namespace VoiceNotesAI.Helpers;

public static class PromptTemplates
{
public const string NoteInterpretation = """
You are an intelligent note-taking assistant.

The user will provide a transcribed audio text in Portuguese (Brazil).

Your job is to:
1. Understand the content and intent of the message
2. Generate a structured note from it

Return ONLY a valid JSON object with this exact structure:

{
"title": "Short and objective title (max 60 characters)",
"description": "Full and clear description of the note, preserving the original meaning",
"category": "One of: Tarefas | Ideias | Lembretes | Trabalho | Pessoal | Outros"
}

Rules:
- Do NOT add any explanation outside the JSON
- Do NOT wrap in markdown code blocks
- Title must be concise and meaningful
- Category must match exactly one of the allowed values
- Always respond in Portuguese (Brazil)
- If the content is unclear, do your best to interpret the intent

Transcribed text:
{{TRANSCRIBED_TEXT}}
""";

public static string BuildNotePrompt(string transcribedText)
{
return NoteInterpretation.Replace("{{TRANSCRIBED_TEXT}}", transcribedText);
}
}
Loading
Loading