commit 249eb6f6f2c0eb75c42da1f2fa0e469ad26deae0
Author: Vasco James Maia Maile <240422@MacBook-Pro-23.local>
Date: Mon Nov 3 11:41:39 2025 +0000
first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..b233e8c
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Gestão de Despesas
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..b268ef3
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..639c779
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..b2c751a
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..0d73912
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.configuration.updateBuildConfiguration": "interactive"
+}
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d5587e9
--- /dev/null
+++ b/README.md
@@ -0,0 +1,240 @@
+# 💰 Gestão de Despesas - Aplicação Android com SQLite
+
+Uma aplicação Android moderna para gestão pessoal de despesas, desenvolvida com SQLite para armazenamento local de dados e interface Material Design.
+
+## 🎯 Funcionalidades Implementadas
+
+### ✅ **Funcionalidades Principais**
+- **💰 Adicionar Despesas**: Registre novas despesas com descrição, valor, categoria, data e notas
+- **📋 Listar Despesas**: Visualize todas as despesas em uma lista organizada e moderna
+- **✏️ Editar Despesas**: Modifique informações de despesas existentes
+- **🗑️ Excluir Despesas**: Remova despesas com confirmação de segurança
+- **📊 Estatísticas**: Visualize o valor total gasto e número de despesas
+- **🏷️ Categorização**: Organize despesas por categorias personalizadas
+- **🎨 Interface Moderna**: Design Material Design com gradientes e animações
+
+### ✅ **Funcionalidades Avançadas**
+- **📱 Dashboard Principal**: Tela de boas-vindas com estatísticas em tempo real
+- **🔍 Filtros**: Filtre despesas por categoria
+- **📈 Estatísticas Visuais**: Cards com métricas importantes
+- **🎯 Navegação Intuitiva**: Botões e ações bem organizados
+- **🛡️ Tratamento de Erros**: Aplicação robusta que não fecha com erros
+
+## 🏗️ Arquitetura do Projeto
+
+### 📱 **Activities (Telas)**
+- **`MainActivity.java`**: Tela principal com dashboard e estatísticas
+- **`SimpleExpensesActivity.java`**: Lista simplificada de despesas
+- **`AddExpenseActivity.java`**: Formulário para adicionar/editar despesas
+
+### 🗄️ **Banco de Dados**
+- **`DatabaseHelper.java`**: Gerenciador completo do SQLite
+- **`Expense.java`**: Modelo de dados para despesas
+
+### 🎨 **Interface**
+- **`ExpenseAdapter.java`**: Adapter para RecyclerView
+- **Layouts XML**: Design moderno com Material Design
+
+## 🗄️ Banco de Dados SQLite
+
+### 📊 **Tabela: `expenses`**
+
+| Campo | Tipo | Descrição | Obrigatório |
+|-------|------|-----------|-------------|
+| `id` | INTEGER | Chave primária (auto incremento) | ✅ |
+| `description` | TEXT | Descrição da despesa | ✅ |
+| `amount` | REAL | Valor da despesa | ✅ |
+| `category` | TEXT | Categoria da despesa | ✅ |
+| `date` | TEXT | Data da despesa (DD/MM/AAAA) | ✅ |
+| `notes` | TEXT | Notas adicionais | ❌ |
+
+### 🔧 **Operações do DatabaseHelper**
+
+```java
+// Operações CRUD+wsqzxe
+addExpense(Expense) // Adiciona nova despesa
+getAllExpenses() // Retorna todas as despesas
+getExpense(int id) // Retorna despesa específica
+updateExpense(Expense) // Atualiza despesa existente
+deleteExpense(int id) // Remove despesa
+
+// Consultas personalizadas
+getTotalExpenses() // Calcula total de despesas
+getExpensesByCategory(String) // Filtra por categoria
+```
+
+## 🎨 Design e Interface
+
+### 🎯 **Paleta de Cores**
+- **Primária**: Azul moderno (#6366F1)
+- **Secundária**: Azul claro (#A5B4FC)
+- **Acento**: Laranja (#F59E0B)
+- **Sucesso**: Verde (#10B981)
+- **Erro**: Vermelho (#EF4444)GREA
+
+### 🎨 **Elementos Visuais**
+- **Gradientes**: Fundos com transições suaves
+- **Cards**: Design Material com sombras e bordas arredondadas
+- **Ícones**: SVG otimizados para cada função
+- **Animações**: Transições suaves entre telas
+
+## 📱 Como Usar a Aplicação
+
+### 🚀 **Primeiro Uso**
+1. **Abrir a aplicação** → Vê a tela de boas-vindas
+2. **Ver estatísticas** → Total gasto e número de despesas
+3. **Clicar "Começar Agora"** → Vai para adicionar primeira despesa
+
+### 💰 **Gerenciar Despesas**
+1. **Adicionar**: Botão "+" ou "Adicionar" → Formulário completo
+2. **Preencher**: Descrição, valor, categoria, data, notas
+3. **Salvar**: Confirma e retorna à tela anterior
+4. **Ver Lista**: Botão "Ver Despesas" → Lista completa
+
+### ✏️ **Editar/Excluir**
+1. **Na Lista**: Cada despesa tem botões "Editar" e "Excluir"
+2. **Editar**: Abre o formulário com dados preenchidos
+3. **Excluir**: Confirmação de segurança antes de remover
+
+## 🛠️ Tecnologias Utilizadas
+
+- **📱 Android SDK**: Desenvolvimento nativo Android
+- **🗄️ SQLite**: Banco de dados local persistente
+- **🎨 Material Design**: Interface moderna e responsiva
+- **📋 RecyclerView**: Lista otimizada para performance
+- **☕ Java**: Linguagem de programação
+- **🎨 Gradientes**: Design visual atrativo
+
+## 📋 Requisitos do Sistema
+
+- **Android**: API 24+ (Android 7.0 Nougat)
+- **Android Studio**: Última versão estável
+- **Gradle**: 8.13+
+- **Java**: JDK 11+
+
+## 🚀 Instalação e Execução
+
+### 📥 **Instalação**
+```bash
+# 1. Clone o repositório
+git clone [url-do-repositorio]
+
+# 2. Abra no Android Studio
+# 3. Sincronize o Gradle
+# 4. Execute no emulador ou dispositivo
+```
+
+### 🔧 **Compilação**
+```bash
+# Compilar APK de debug
+./gradlew assembleDebug
+
+# Executar testes
+./gradlew test
+```
+
+## 📁 Estrutura de Arquivos
+
+```
+app/src/main/
+├── java/pt/epvc/gestodedespesas/
+│ ├── MainActivity.java # Tela principal com dashboard
+│ ├── SimpleExpensesActivity.java # Lista simplificada de despesas
+│ ├── AddExpenseActivity.java # Formulário de adicionar/editar
+│ ├── Expense.java # Modelo de dados
+│ ├── DatabaseHelper.java # Gerenciador SQLite
+│ └── ExpenseAdapter.java # Adapter do RecyclerView
+│
+├── res/layout/
+│ ├── activity_main.xml # Layout da tela principal
+│ ├── activity_simple_expenses.xml # Layout da lista de despesas
+│ ├── activity_add_expense.xml # Layout do formulário
+│ └── item_expense.xml # Layout do item da lista
+│
+├── res/drawable/ # Ícones e recursos visuais
+├── res/values/
+│ ├── colors.xml # Paleta de cores
+│ └── strings.xml # Textos da aplicação
+└── AndroidManifest.xml # Configuração da aplicação
+```
+
+## 🔧 Funcionalidades Técnicas
+
+### 🛡️ **Tratamento de Erros**
+- **Try-Catch**: Em todas as operações críticas
+- **Verificações de Null**: Para evitar crashes
+- **Mensagens Informativas**: Toast para feedback do usuário
+- **Fallback Seguro**: Aplicação continua funcionando mesmo com erros
+
+### 📊 **Performance**
+- **RecyclerView**: Lista otimizada para grandes quantidades de dados
+- **SQLite**: Consultas eficientes com índices
+- **Lazy Loading**: Carregamento sob demanda
+- **Memory Management**: Gerenciamento eficiente de memória
+
+### 🎨 **Design Responsivo**
+- **Material Design**: Seguindo guidelines do Google
+- **Adaptação**: Funciona em diferentes tamanhos de tela
+- **Acessibilidade**: Elementos grandes e bem contrastados
+- **Navegação**: Fluxo intuitivo entre telas
+
+## 🚀 Próximas Funcionalidades
+
+### 📊 **Análises e Relatórios**
+- Gráficos de gastos por categoria
+- Relatórios mensais/anuais
+- Comparativos entre períodos
+- Exportação para PDF/Excel
+
+### 🔍 **Filtros Avançados**
+- Filtro por período de datas
+- Busca por texto na descrição
+- Ordenação por valor, data, categoria
+- Filtros salvos
+
+### 💾 **Backup e Sincronização**
+- Backup automático na nuvem
+- Sincronização entre dispositivos
+- Restauração de dados
+- Exportação/importação
+
+### 🎨 **Personalização**
+- Modo escuro/claro
+- Temas personalizados
+- Categorias customizáveis
+- Widget para tela inicial
+
+## 📝 Notas de Desenvolvimento
+
+### 🎯 **Decisões de Design**
+- **Interface Simplificada**: Foco na usabilidade
+- **Material Design**: Consistência visual
+- **SQLite Local**: Privacidade e performance
+- **Tratamento de Erros**: Robustez da aplicação
+
+### 🔧 **Implementações Técnicas**
+- **DatabaseHelper**: Padrão Singleton para acesso ao banco
+- **Serializable**: Passagem de objetos entre Activities
+- **RecyclerView**: Performance otimizada para listas
+- **Material Components**: Interface moderna e acessível
+
+### 📱 **Compatibilidade**
+- **Android 7.0+**: Suporte amplo de dispositivos
+- **Edge-to-Edge**: Design moderno
+- **Responsive**: Adapta-se a diferentes telas
+- **Acessibilidade**: Suporte a leitores de tela
+
+---
+
+## 👨💻 Desenvolvido com ❤️
+
+Esta aplicação foi desenvolvida como um exemplo completo de aplicação Android com SQLite, demonstrando:
+- Arquitetura limpa e organizada
+- Interface moderna e responsiva
+- Tratamento robusto de erros
+- Documentação completa em português
+- Código bem comentado e explicado
+
+**Tecnologias**: Android SDK, SQLite, Material Design, Java
+**Design**: Interface moderna com gradientes e animações
+**Funcionalidades**: CRUD completo, estatísticas, filtros, navegação intuitiva
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..a66051d
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,51 @@
+// Configuração do módulo app - aplicação principal Android
+
+// Plugins aplicados a este módulo
+plugins {
+ id 'com.android.application' // Plugin para criar aplicação Android
+}
+
+// Configurações específicas do Android
+android {
+ namespace 'pt.epvc.gestodedespesas' // Namespace único da aplicação
+ compileSdk 35 // Versão do SDK Android para compilação
+
+ // Configurações padrão da aplicação
+ defaultConfig {
+ applicationId 'pt.epvc.gestodedespesas' // ID único da aplicação na Play Store
+ minSdk 24 // Versão mínima do Android suportada (Android 7.0)
+ targetSdk 35 // Versão do Android para a qual a aplicação foi desenvolvida
+ versionCode 1 // Número da versão (incrementa a cada release)
+ versionName '1.0' // Nome da versão exibido aos usuários
+
+ testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' // Runner para testes instrumentados
+ }
+
+ // Configurações de build (Debug/Release)
+ buildTypes {
+ release {
+ minifyEnabled false // Desabilita minificação (otimização de código)
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' // Arquivos ProGuard
+ }
+ }
+
+ // Configurações de compilação Java
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11 // Versão do Java para código fonte
+ targetCompatibility JavaVersion.VERSION_11 // Versão do Java para bytecode compilado
+ }
+}
+
+// Dependências da aplicação
+dependencies {
+ // Bibliotecas AndroidX (suporte moderno)
+ implementation 'androidx.appcompat:appcompat:1.7.1' // Compatibilidade com versões antigas
+ implementation 'com.google.android.material:material:1.10.0' // Material Design Components
+ implementation 'androidx.activity:activity:1.8.2' // Activity base moderna
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4' // Layout com constraints
+
+ // Bibliotecas de teste
+ testImplementation 'junit:junit:4.13.2' // Framework de testes unitários
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5' // Extensões JUnit para Android
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' // Framework de testes de interface
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/pt/epvc/gestodedespesas/ExampleInstrumentedTest.java b/app/src/androidTest/java/pt/epvc/gestodedespesas/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..a072f06
--- /dev/null
+++ b/app/src/androidTest/java/pt/epvc/gestodedespesas/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package pt.epvc.gestodedespesas;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("pt.epvc.gestodedespesas", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..1c5e9e1
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/pt/epvc/gestodedespesas/AddExpenseActivity.java b/app/src/main/java/pt/epvc/gestodedespesas/AddExpenseActivity.java
new file mode 100644
index 0000000..dc3f7be
--- /dev/null
+++ b/app/src/main/java/pt/epvc/gestodedespesas/AddExpenseActivity.java
@@ -0,0 +1,211 @@
+package pt.epvc.gestodedespesas; // Define o pacote onde esta classe está localizada
+
+import android.os.Bundle; // Importa classe para passar dados entre Activities
+import android.view.View; // Importa classe base para elementos visuais
+import android.widget.Toast; // Importa classe para exibir mensagens temporárias
+
+import androidx.appcompat.app.AppCompatActivity; // Importa classe base para Activities modernas
+
+import com.google.android.material.button.MaterialButton; // Importa botão Material Design
+import com.google.android.material.textfield.TextInputEditText; // Importa campo de texto Material Design
+
+import java.text.SimpleDateFormat; // Importa classe para formatação de datas
+import java.util.Date; // Importa classe para trabalhar com datas
+import java.util.Locale; // Importa classe para configurações de localização
+
+import pt.epvc.gestodedespesas.data.DatabaseHelper; // DatabaseHelper movido para pacote data
+
+/**
+ * AddExpenseActivity - Tela de Adicionar/Editar Despesas
+ *
+ * Esta Activity permite ao usuário adicionar novas despesas ou editar despesas existentes.
+ * Funcionalidades implementadas:
+ * - Formulário completo para entrada de dados da despesa
+ * - Validação de todos os campos obrigatórios
+ * - Suporte para edição de despesas existentes
+ * - Data atual como padrão para novas despesas
+ * - Feedback visual com mensagens de sucesso/erro
+ * - Navegação de volta após salvar/cancelar
+ */
+public class AddExpenseActivity extends AppCompatActivity { // Declara classe que estende AppCompatActivity
+
+ // Views da interface - campos de entrada do formulário
+ private TextInputEditText etDescription, etAmount, etCategory, etDate, etNotes; // Declara campos de texto para entrada de dados
+ private MaterialButton btnSave, btnCancel; // Declara botões de ação
+
+ // Objetos para funcionalidade
+ private DatabaseHelper databaseHelper; // Declara helper para operações com SQLite
+ private Expense expenseToEdit = null; // Declara variável para despesa sendo editada (null para nova despesa)
+
+ @Override // Sobrescreve método da classe pai
+ protected void onCreate(Bundle savedInstanceState) { // Método chamado quando Activity é criada
+ super.onCreate(savedInstanceState); // Chama método da classe pai
+ // Define o layout da tela de adicionar despesa (activity_add_expense.xml)
+ setContentView(R.layout.activity_add_expense); // Conecta layout XML com esta Activity
+
+ // Inicializa o helper do banco de dados SQLite
+ databaseHelper = new DatabaseHelper(this); // Cria nova instância do DatabaseHelper
+
+ // Configura a interface e funcionalidades
+ initializeViews(); // Chama método para conectar views com variáveis
+ setupClickListeners(); // Chama método para configurar eventos de clique
+
+ // Verificar se é edição de uma despesa existente
+ if (getIntent().hasExtra("expense")) { // Verifica se Intent contém dados de despesa para edição
+ // Se há uma despesa extra, estamos editando
+ expenseToEdit = (Expense) getIntent().getSerializableExtra("expense"); // Obtém despesa do Intent e converte para Expense
+ populateFields(); // Chama método para preencher campos com dados da despesa
+ } else { // Se não há despesa extra, é uma nova despesa
+ // Para nova despesa, define a data atual como padrão
+ SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()); // Cria formato de data brasileiro
+ etDate.setText(sdf.format(new Date())); // Define data atual no campo de data
+ }
+ }
+
+ /**
+ * Inicializa as views conectando os elementos do layout com as variáveis Java
+ * Este método é chamado no onCreate para preparar a interface
+ */
+ private void initializeViews() { // Declara método privado para inicializar views
+ // Conecta os campos de entrada de texto
+ etDescription = findViewById(R.id.etDescription); // Busca campo de descrição por ID e atribui à variável
+ etAmount = findViewById(R.id.etAmount); // Busca campo de valor por ID e atribui à variável
+ etCategory = findViewById(R.id.etCategory); // Busca campo de categoria por ID e atribui à variável
+ etDate = findViewById(R.id.etDate); // Busca campo de data por ID e atribui à variável
+ etNotes = findViewById(R.id.etNotes); // Busca campo de notas por ID e atribui à variável
+
+ // Conecta os botões de ação
+ btnSave = findViewById(R.id.btnSave); // Busca botão salvar por ID e atribui à variável
+ btnCancel = findViewById(R.id.btnCancel); // Busca botão cancelar por ID e atribui à variável
+ }
+
+ /**
+ * Configura os eventos de clique para os botões da interface
+ * Cada botão executa uma ação específica
+ */
+ private void setupClickListeners() { // Declara método privado para configurar listeners
+ // Botão Salvar - valida e salva a despesa
+ btnSave.setOnClickListener(new View.OnClickListener() { // Define listener de clique para o botão salvar
+ @Override // Sobrescreve método da interface
+ public void onClick(View v) { // Método chamado quando botão é clicado
+ saveExpense(); // Chama método para salvar a despesa
+ }
+ });
+
+ // Botão Cancelar - fecha a tela sem salvar
+ btnCancel.setOnClickListener(new View.OnClickListener() { // Define listener de clique para o botão cancelar
+ @Override // Sobrescreve método da interface
+ public void onClick(View v) { // Método chamado quando botão é clicado
+ finish(); // Fecha a Activity atual
+ }
+ });
+ }
+
+ /**
+ * Preenche os campos do formulário com os dados da despesa sendo editada
+ * Este método é chamado apenas quando estamos editando uma despesa existente
+ */
+ private void populateFields() { // Declara método privado para preencher campos
+ if (expenseToEdit != null) { // Verifica se há despesa para editar
+ // Preenche cada campo com os dados da despesa
+ etDescription.setText(expenseToEdit.getDescription()); // Define texto do campo descrição com dados da despesa
+ etAmount.setText(String.valueOf(expenseToEdit.getAmount())); // Define texto do campo valor convertendo double para string
+ etCategory.setText(expenseToEdit.getCategory()); // Define texto do campo categoria com dados da despesa
+ etDate.setText(expenseToEdit.getDate()); // Define texto do campo data com dados da despesa
+ etNotes.setText(expenseToEdit.getNotes()); // Define texto do campo notas com dados da despesa
+ }
+ }
+
+ /**
+ * Salva a despesa no banco de dados após validar todos os campos
+ * Este método é responsável por:
+ * - Validar todos os campos obrigatórios
+ * - Converter e validar o valor numérico
+ * - Criar ou atualizar a despesa no banco
+ * - Exibir feedback ao usuário
+ * - Fechar a tela em caso de sucesso
+ */
+ private void saveExpense() { // Declara método privado para salvar despesa
+ // Obtém os valores dos campos e remove espaços em branco
+ String description = etDescription.getText().toString().trim(); // Obtém texto do campo descrição e remove espaços
+ String amountStr = etAmount.getText().toString().trim(); // Obtém texto do campo valor e remove espaços
+ String category = etCategory.getText().toString().trim(); // Obtém texto do campo categoria e remove espaços
+ String date = etDate.getText().toString().trim(); // Obtém texto do campo data e remove espaços
+ String notes = etNotes.getText().toString().trim(); // Obtém texto do campo notas e remove espaços
+
+ // VALIDAÇÕES DOS CAMPOS OBRIGATÓRIOS
+
+ // Validação da descrição
+ if (description.isEmpty()) { // Verifica se descrição está vazia
+ etDescription.setError("Descrição é obrigatória"); // Define mensagem de erro no campo
+ etDescription.requestFocus(); // Foca no campo com erro
+ return; // Para a execução se houver erro
+ }
+
+ // Validação do valor
+ if (amountStr.isEmpty()) { // Verifica se valor está vazio
+ etAmount.setError("Valor é obrigatório"); // Define mensagem de erro no campo
+ etAmount.requestFocus(); // Foca no campo com erro
+ return; // Para a execução se houver erro
+ }
+
+ // Conversão e validação do valor numérico
+ double amount; // Declara variável para armazenar valor convertido
+ try { // Inicia bloco try para capturar erros de conversão
+ amount = Double.parseDouble(amountStr); // Converte string para double
+ if (amount <= 0) { // Verifica se o valor é positivo
+ etAmount.setError("Valor deve ser maior que zero"); // Define mensagem de erro
+ etAmount.requestFocus(); // Foca no campo com erro
+ return; // Para a execução se houver erro
+ }
+ } catch (NumberFormatException e) { // Captura erro de conversão
+ etAmount.setError("Valor inválido"); // Define mensagem de erro
+ etAmount.requestFocus(); // Foca no campo com erro
+ return; // Para a execução se houver erro
+ }
+
+ // Validação da categoria
+ if (category.isEmpty()) { // Verifica se categoria está vazia
+ etCategory.setError("Categoria é obrigatória"); // Define mensagem de erro no campo
+ etCategory.requestFocus(); // Foca no campo com erro
+ return; // Para a execução se houver erro
+ }
+
+ // Validação da data
+ if (date.isEmpty()) { // Verifica se data está vazia
+ etDate.setError("Data é obrigatória"); // Define mensagem de erro no campo
+ etDate.requestFocus(); // Foca no campo com erro
+ return; // Para a execução se houver erro
+ }
+
+ // CRIAÇÃO/ATUALIZAÇÃO DA DESPESA
+
+ // Cria um objeto Expense com os dados validados
+ Expense expense = new Expense(description, amount, category, date, notes); // Cria nova instância de Expense com dados validados
+
+ if (expenseToEdit != null) { // Verifica se estamos editando uma despesa existente
+ // EDIÇÃO - Atualiza despesa existente
+ expense.setId(expenseToEdit.getId()); // Define o ID da despesa existente no objeto
+ int result = databaseHelper.updateExpense(expense); // Chama método para atualizar despesa no banco
+
+ if (result > 0) { // Verifica se a atualização foi bem-sucedida (retorna número de linhas afetadas)
+ Toast.makeText(this, "Despesa atualizada com sucesso!", Toast.LENGTH_SHORT).show(); // Exibe mensagem de sucesso
+ setResult(RESULT_OK); // Define resultado de sucesso para Activity pai
+ finish(); // Fecha a Activity atual
+ } else { // Se houve erro na atualização
+ Toast.makeText(this, "Erro ao atualizar despesa", Toast.LENGTH_SHORT).show(); // Exibe mensagem de erro
+ }
+ } else { // Se não estamos editando, é uma nova despesa
+ // NOVA DESPESA - Adiciona nova despesa
+ long id = databaseHelper.addExpense(expense); // Chama método para adicionar despesa no banco
+
+ if (id > 0) { // Verifica se a adição foi bem-sucedida (retorna ID > 0)
+ Toast.makeText(this, "Despesa adicionada com sucesso!", Toast.LENGTH_SHORT).show(); // Exibe mensagem de sucesso
+ setResult(RESULT_OK); // Define resultado de sucesso para Activity pai
+ finish(); // Fecha a Activity atual
+ } else { // Se houve erro na adição
+ Toast.makeText(this, "Erro ao adicionar despesa", Toast.LENGTH_SHORT).show(); // Exibe mensagem de erro
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/pt/epvc/gestodedespesas/Expense.java b/app/src/main/java/pt/epvc/gestodedespesas/Expense.java
new file mode 100644
index 0000000..8aef6e8
--- /dev/null
+++ b/app/src/main/java/pt/epvc/gestodedespesas/Expense.java
@@ -0,0 +1,107 @@
+package pt.epvc.gestodedespesas; // Define o pacote onde esta classe está localizada
+
+import java.io.Serializable; // Importa interface para permitir serialização de objetos
+
+/**
+ * Expense - Modelo de Dados para Despesas
+ *
+ * Esta classe representa uma despesa no sistema.
+ * Implementa Serializable para permitir passagem entre Activities.
+ *
+ * Campos da despesa:
+ * - id: Identificador único (chave primária)
+ * - description: Descrição da despesa
+ * - amount: Valor da despesa
+ * - category: Categoria da despesa
+ * - date: Data da despesa
+ * - notes: Notas adicionais (opcional)
+ */
+public class Expense implements Serializable { // Declara classe que implementa Serializable
+ private int id; // Declara campo privado para ID da despesa
+ private String description; // Declara campo privado para descrição da despesa
+ private double amount; // Declara campo privado para valor da despesa
+ private String category; // Declara campo privado para categoria da despesa
+ private String date; // Declara campo privado para data da despesa
+ private String notes; // Declara campo privado para notas da despesa
+
+ // Construtores
+ public Expense() {} // Construtor padrão sem parâmetros
+
+ public Expense(String description, double amount, String category, String date, String notes) { // Construtor com parâmetros
+ this.description = description; // Define descrição da despesa
+ this.amount = amount; // Define valor da despesa
+ this.category = category; // Define categoria da despesa
+ this.date = date; // Define data da despesa
+ this.notes = notes; // Define notas da despesa
+ }
+
+ public Expense(int id, String description, double amount, String category, String date, String notes) { // Construtor completo com ID
+ this.id = id; // Define ID da despesa
+ this.description = description; // Define descrição da despesa
+ this.amount = amount; // Define valor da despesa
+ this.category = category; // Define categoria da despesa
+ this.date = date; // Define data da despesa
+ this.notes = notes; // Define notas da despesa
+ }
+
+ // Getters e Setters
+ public int getId() { // Declara método público getter para ID
+ return id; // Retorna valor do campo id
+ }
+
+ public void setId(int id) { // Declara método público setter para ID
+ this.id = id; // Define valor do campo id
+ }
+
+ public String getDescription() { // Declara método público getter para descrição
+ return description; // Retorna valor do campo description
+ }
+
+ public void setDescription(String description) { // Declara método público setter para descrição
+ this.description = description; // Define valor do campo description
+ }
+
+ public double getAmount() { // Declara método público getter para valor
+ return amount; // Retorna valor do campo amount
+ }
+
+ public void setAmount(double amount) { // Declara método público setter para valor
+ this.amount = amount; // Define valor do campo amount
+ }
+
+ public String getCategory() { // Declara método público getter para categoria
+ return category; // Retorna valor do campo category
+ }
+
+ public void setCategory(String category) { // Declara método público setter para categoria
+ this.category = category; // Define valor do campo category
+ }
+
+ public String getDate() { // Declara método público getter para data
+ return date; // Retorna valor do campo date
+ }
+
+ public void setDate(String date) { // Declara método público setter para data
+ this.date = date; // Define valor do campo date
+ }
+
+ public String getNotes() { // Declara método público getter para notas
+ return notes; // Retorna valor do campo notes
+ }
+
+ public void setNotes(String notes) { // Declara método público setter para notas
+ this.notes = notes; // Define valor do campo notes
+ }
+
+ @Override // Sobrescreve método da classe Object
+ public String toString() { // Declara método público para converter objeto em string
+ return "Expense{" + // Inicia string de representação do objeto
+ "id=" + id + // Adiciona ID à string
+ ", description='" + description + '\'' + // Adiciona descrição à string
+ ", amount=" + amount + // Adiciona valor à string
+ ", category='" + category + '\'' + // Adiciona categoria à string
+ ", date='" + date + '\'' + // Adiciona data à string
+ ", notes='" + notes + '\'' + // Adiciona notas à string
+ '}'; // Finaliza string de representação do objeto
+ }
+}
diff --git a/app/src/main/java/pt/epvc/gestodedespesas/ExpenseAdapter.java b/app/src/main/java/pt/epvc/gestodedespesas/ExpenseAdapter.java
new file mode 100644
index 0000000..cddddb6
--- /dev/null
+++ b/app/src/main/java/pt/epvc/gestodedespesas/ExpenseAdapter.java
@@ -0,0 +1,161 @@
+package pt.epvc.gestodedespesas; // Define o pacote onde esta classe está localizada
+
+import android.view.LayoutInflater; // Importa classe para inflar layouts XML
+import android.view.View; // Importa classe base para elementos visuais
+import android.view.ViewGroup; // Importa classe para grupos de views
+import android.widget.TextView; // Importa classe para exibir texto
+
+import androidx.annotation.NonNull; // Importa anotação para parâmetros não nulos
+import androidx.recyclerview.widget.RecyclerView; // Importa classe base para adaptadores de lista
+
+import com.google.android.material.button.MaterialButton; // Importa botão Material Design
+
+import java.text.DecimalFormat; // Importa classe para formatação de números
+import java.util.List; // Importa interface para listas
+
+/**
+ * ExpenseAdapter - Adaptador para RecyclerView de Despesas
+ *
+ * Esta classe é responsável por exibir a lista de despesas em um RecyclerView.
+ * Funcionalidades implementadas:
+ * - Exibe cada despesa em um item personalizado
+ * - Formata valores monetários em euros
+ * - Permite editar e excluir despesas através de botões
+ * - Atualiza a lista quando os dados mudam
+ * - Interface para comunicação com a Activity pai
+ */
+public class ExpenseAdapter extends RecyclerView.Adapter { // Declara classe que estende RecyclerView.Adapter
+
+ // Dados e configurações
+ private List expenseList; // Declara lista de despesas para exibir
+ private OnExpenseClickListener listener; // Declara interface para eventos de clique
+ private DecimalFormat currencyFormat = new DecimalFormat("€#,##0.00"); // Cria formato de moeda em euros
+
+ /**
+ * Interface para comunicação com a Activity que usa este adapter
+ * Permite que a Activity responda aos cliques de editar e excluir
+ */
+ public interface OnExpenseClickListener { // Declara interface pública para eventos de clique
+ void onEditClick(Expense expense); // Declara método para evento de clique em editar
+ void onDeleteClick(Expense expense); // Declara método para evento de clique em excluir
+ }
+
+ /**
+ * Construtor do adapter
+ * @param expenseList Lista de despesas para exibir
+ * @param listener Interface para eventos de clique
+ */
+ public ExpenseAdapter(List expenseList, OnExpenseClickListener listener) { // Declara construtor público
+ this.expenseList = expenseList; // Armazena a lista de despesas
+ this.listener = listener; // Armazena o listener para eventos
+ }
+
+ /**
+ * Cria um novo ViewHolder para um item da lista
+ * Este método é chamado pelo RecyclerView quando precisa de um novo item
+ * @param parent ViewGroup pai (o RecyclerView)
+ * @param viewType Tipo da view (não usado neste caso)
+ * @return Novo ExpenseViewHolder
+ */
+ @NonNull // Anotação indicando que o retorno não pode ser nulo
+ @Override // Sobrescreve método da classe pai
+ public ExpenseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { // Declara método para criar ViewHolder
+ // Infla o layout do item (item_expense.xml) e cria o ViewHolder
+ View view = LayoutInflater.from(parent.getContext()) // Obtém LayoutInflater do contexto do pai
+ .inflate(R.layout.item_expense, parent, false); // Infla layout XML para criar view
+ return new ExpenseViewHolder(view); // Retorna novo ViewHolder criado
+ }
+
+ /**
+ * Preenche os dados de um item da lista com os dados da despesa correspondente
+ * Este método é chamado pelo RecyclerView para cada item visível
+ * @param holder ViewHolder que contém as views do item
+ * @param position Posição do item na lista
+ */
+ @Override // Sobrescreve método da classe pai
+ public void onBindViewHolder(@NonNull ExpenseViewHolder holder, int position) { // Declara método para vincular dados ao ViewHolder
+ Expense expense = expenseList.get(position); // Obtém a despesa na posição atual da lista
+
+ // Preenche os campos de texto com os dados da despesa
+ holder.tvDescription.setText(expense.getDescription()); // Define texto da descrição no TextView
+ holder.tvAmount.setText(currencyFormat.format(expense.getAmount())); // Define texto do valor formatado em moeda no TextView
+ holder.tvCategory.setText(expense.getCategory()); // Define texto da categoria no TextView
+ holder.tvDate.setText(expense.getDate()); // Define texto da data no TextView
+
+ // Tratamento especial para notas - só exibe se não estiver vazia
+ if (expense.getNotes() != null && !expense.getNotes().trim().isEmpty()) { // Verifica se notas não são nulas nem vazias
+ holder.tvNotes.setText(expense.getNotes()); // Define texto das notas no TextView
+ holder.tvNotes.setVisibility(View.VISIBLE); // Torna TextView de notas visível
+ } else { // Se notas estão vazias ou nulas
+ holder.tvNotes.setVisibility(View.GONE); // Oculta TextView de notas
+ }
+
+ // Configura o botão Editar
+ holder.btnEdit.setOnClickListener(v -> { // Define listener de clique para botão editar
+ if (listener != null) { // Verifica se listener não é nulo
+ listener.onEditClick(expense); // Chama método da interface para notificar clique em editar
+ }
+ });
+
+ // Configura o botão Excluir
+ holder.btnDelete.setOnClickListener(v -> { // Define listener de clique para botão excluir
+ if (listener != null) { // Verifica se listener não é nulo
+ listener.onDeleteClick(expense); // Chama método da interface para notificar clique em excluir
+ }
+ });
+ }
+
+ /**
+ * Retorna o número total de itens na lista
+ * @return Número de despesas na lista
+ */
+ @Override // Sobrescreve método da classe pai
+ public int getItemCount() { // Declara método para obter número de itens
+ return expenseList.size(); // Retorna o tamanho da lista de despesas
+ }
+
+ /**
+ * Atualiza a lista de despesas e notifica o RecyclerView sobre a mudança
+ * Este método é usado quando os dados mudam (adicionar, editar, excluir)
+ * @param newExpenseList Nova lista de despesas
+ */
+ public void updateExpenses(List newExpenseList) { // Declara método público para atualizar lista
+ this.expenseList = newExpenseList; // Atualiza a lista de despesas com nova lista
+ notifyDataSetChanged(); // Notifica o RecyclerView para atualizar a interface de todos os itens
+ }
+
+ /**
+ * ExpenseViewHolder - Classe interna que mantém referências para as views de cada item
+ *
+ * Esta classe é responsável por:
+ * - Manter referências para todos os elementos visuais do item
+ * - Facilitar o acesso às views durante o binding
+ * - Otimizar a performance do RecyclerView
+ */
+ public static class ExpenseViewHolder extends RecyclerView.ViewHolder { // Declara classe interna estática que estende RecyclerView.ViewHolder
+ // Views de texto para exibir os dados da despesa
+ TextView tvDescription, tvAmount, tvCategory, tvDate, tvNotes; // Declara TextViews para exibir dados da despesa
+
+ // Botões de ação para editar e excluir
+ MaterialButton btnEdit, btnDelete; // Declara botões Material Design para ações
+
+ /**
+ * Construtor do ViewHolder
+ * @param itemView View raiz do item (inflada do layout item_expense.xml)
+ */
+ public ExpenseViewHolder(@NonNull View itemView) { // Declara construtor público com parâmetro não nulo
+ super(itemView); // Chama construtor da classe pai
+
+ // Conecta as views do layout com as variáveis
+ tvDescription = itemView.findViewById(R.id.tvDescription); // Busca TextView de descrição por ID e atribui à variável
+ tvAmount = itemView.findViewById(R.id.tvAmount); // Busca TextView de valor por ID e atribui à variável
+ tvCategory = itemView.findViewById(R.id.tvCategory); // Busca TextView de categoria por ID e atribui à variável
+ tvDate = itemView.findViewById(R.id.tvDate); // Busca TextView de data por ID e atribui à variável
+ tvNotes = itemView.findViewById(R.id.tvNotes); // Busca TextView de notas por ID e atribui à variável
+
+ // Conecta os botões de ação
+ btnEdit = itemView.findViewById(R.id.btnEdit); // Busca botão editar por ID e atribui à variável
+ btnDelete = itemView.findViewById(R.id.btnDelete); // Busca botão excluir por ID e atribui à variável
+ }
+ }
+}
diff --git a/app/src/main/java/pt/epvc/gestodedespesas/ExpensesListActivity.java b/app/src/main/java/pt/epvc/gestodedespesas/ExpensesListActivity.java
new file mode 100644
index 0000000..00d7e67
--- /dev/null
+++ b/app/src/main/java/pt/epvc/gestodedespesas/ExpensesListActivity.java
@@ -0,0 +1,283 @@
+package pt.epvc.gestodedespesas; // Pacote da aplicação
+
+import android.content.Intent; // Para navegar entre Activities
+import android.os.Bundle; // Estado da Activity
+import android.view.View; // Base para listeners de clique
+import android.widget.TextView; // Exibe textos na UI
+import android.widget.Toast; // Mostra mensagens curtas
+
+import androidx.activity.EdgeToEdge; // Suporte a layout de ponta a ponta
+import androidx.appcompat.app.AlertDialog; // Diálogo de opções/confirmar
+import androidx.appcompat.app.AppCompatActivity; // Base para Activities
+import androidx.core.graphics.Insets; // Dimensões de barras do sistema
+import androidx.core.view.ViewCompat; // Utilidades de view
+import androidx.core.view.WindowInsetsCompat; // Insets de janela
+import androidx.recyclerview.widget.LinearLayoutManager; // Layout vertical
+import androidx.recyclerview.widget.RecyclerView; // Lista eficiente
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton; // Botão flutuante
+import com.google.android.material.button.MaterialButton; // Botão Material
+
+import java.text.DecimalFormat; // Formatar valores monetários
+import java.util.List; // Estrutura de lista
+
+import pt.epvc.gestodedespesas.data.DatabaseHelper; // Acesso ao SQLite movido para pacote data
+
+/**
+ * ExpensesListActivity - Tela avançada de listagem e análise de despesas
+ * - Exibe total, contagem e média das despesas
+ * - Permite filtrar por categoria
+ * - Possibilita adicionar, editar e excluir despesas
+ * - Usa Edge-to-Edge e ajusta padding conforme barras do sistema
+ */
+public class ExpensesListActivity extends AppCompatActivity implements ExpenseAdapter.OnExpenseClickListener { // Activity principal da lista
+
+ private RecyclerView recyclerViewExpenses; // Lista de despesas
+ private TextView tvTotal, tvExpenseCount, tvAverage; // Indicadores de total, contagem e média
+ private FloatingActionButton fabAddExpense; // Botão flutuante para adicionar
+ private MaterialButton btnFilter; // Botão para filtrar por categoria
+ private DatabaseHelper databaseHelper; // Acesso ao SQLite
+ private ExpenseAdapter expenseAdapter; // Adapter do RecyclerView
+ private List expenseList; // Dados das despesas
+ private DecimalFormat currencyFormat = new DecimalFormat("€#,##0.00"); // Formatação em euro
+ private boolean isShowingEmptyState = false; // Controle de qual layout está visível (lista ou estado vazio)
+
+ /**
+ * onCreate: configura layout, Edge-to-Edge e listeners de insets, inicializa
+ * dependências (DB/Views/RecyclerView) e carrega dados iniciais.
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) { // Ciclo de vida: criação da Activity
+ super.onCreate(savedInstanceState); // Chama implementação base
+ EdgeToEdge.enable(this); // Ativa layout de ponta a ponta
+ databaseHelper = new DatabaseHelper(this); // Instancia acesso ao DB (SQLiteHelper)
+ renderLayoutByData(); // Decide e aplica o layout conforme existência de despesas
+ }
+
+ /**
+ * Faz o bind das views usadas nesta Activity via findViewById.
+ */
+ private void initializeViews() {
+ try {
+ recyclerViewExpenses = findViewById(R.id.recyclerViewExpenses); // RecyclerView da lista
+ tvTotal = findViewById(R.id.tvTotal); // Texto do total gasto
+ tvExpenseCount = findViewById(R.id.tvExpenseCount); // Texto da contagem
+ tvAverage = findViewById(R.id.tvAverage); // Texto da média
+ fabAddExpense = findViewById(R.id.fabAddExpense); // FAB adicionar
+ btnFilter = findViewById(R.id.btnFilter); // Botão filtrar
+ } catch (Exception e) {
+ Toast.makeText(this, "Erro ao inicializar views: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro
+ }
+ }
+
+ /**
+ * Configura o RecyclerView com LayoutManager linear e o adapter
+ * preenchido com os dados do banco.
+ */
+ private void setupRecyclerView() {
+ try {
+ if (recyclerViewExpenses != null) { // Garante que a view existe
+ expenseList = databaseHelper.getAllExpenses(); // Busca dados no DB
+ expenseAdapter = new ExpenseAdapter(expenseList, this); // Cria adapter
+ recyclerViewExpenses.setLayoutManager(new LinearLayoutManager(this)); // Layout vertical
+ recyclerViewExpenses.setAdapter(expenseAdapter); // Liga adapter
+ }
+ } catch (Exception e) {
+ Toast.makeText(this, "Erro ao configurar RecyclerView: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro
+ }
+ }
+
+ /**
+ * Registra os listeners de clique do FAB (adicionar) e do botão de filtro.
+ */
+ private void setupClickListeners() {
+ try {
+ if (fabAddExpense != null) { // Verifica se a view foi encontrada
+ fabAddExpense.setOnClickListener(new View.OnClickListener() { // Clique do FAB
+ @Override
+ public void onClick(View v) { // Ação ao clicar
+ Intent intent = new Intent(ExpensesListActivity.this, AddExpenseActivity.class); // Ir para adicionar
+ startActivityForResult(intent, 1); // Abrir aguardando resultado
+ }
+ });
+ }
+
+ if (btnFilter != null) { // Verifica existência do botão
+ btnFilter.setOnClickListener(new View.OnClickListener() { // Clique do filtro
+ @Override
+ public void onClick(View v) { // Ação ao clicar
+ showFilterDialog(); // Abre diálogo de categorias
+ }
+ });
+ }
+ } catch (Exception e) {
+ Toast.makeText(this, "Erro ao configurar click listeners: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro
+ }
+ }
+
+ /**
+ * Carrega/recarrega a lista de despesas do banco, atualiza o adapter
+ * e recalcula os totais exibidos.
+ */
+ private void loadExpenses() {
+ try {
+ expenseList = databaseHelper.getAllExpenses(); // Puxa despesas do DB
+ boolean hasData = expenseList != null && !expenseList.isEmpty();
+ if (!hasData) {
+ if (!isShowingEmptyState) {
+ renderEmptyLayout();
+ }
+ return; // Nada para atualizar no layout vazio
+ }
+ if (isShowingEmptyState) {
+ renderMainLayout();
+ }
+ if (expenseAdapter != null) { // Garante adapter existente
+ expenseAdapter.updateExpenses(expenseList); // Atualiza lista na UI
+ }
+ updateTotal(); // Recalcula indicadores
+ } catch (Exception e) {
+ Toast.makeText(this, "Erro ao carregar despesas: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro
+ }
+ }
+
+ /**
+ * Recalcula e exibe total gasto, número de despesas e média por despesa.
+ */
+ private void updateTotal() {
+ try {
+ double total = databaseHelper.getTotalExpenses(); // Soma dos valores
+ int count = expenseList != null ? expenseList.size() : 0; // Quantidade de itens
+ double average = count > 0 ? total / count : 0; // Média simples
+
+ if (tvTotal != null) { // Atualiza total
+ tvTotal.setText(currencyFormat.format(total)); // Exibe em €
+ }
+ if (tvExpenseCount != null) { // Atualiza contagem
+ tvExpenseCount.setText(String.valueOf(count)); // Converte para texto
+ }
+ if (tvAverage != null) { // Atualiza média
+ tvAverage.setText(currencyFormat.format(average)); // Exibe em €
+ }
+ } catch (Exception e) {
+ Toast.makeText(this, "Erro ao atualizar totais: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro
+ }
+ }
+
+ /**
+ * Exibe diálogo para seleção de categoria e aplica o filtro na lista.
+ */
+ private void showFilterDialog() {
+ String[] categories = {"Todas", "Alimentação", "Transporte", "Entretenimento", "Saúde", "Compras", "Outros"}; // Opções
+
+ new AlertDialog.Builder(this) // Constrói diálogo
+ .setTitle("🔍 Filtrar Despesas") // Título
+ .setItems(categories, (dialog, which) -> { // Lista de categorias
+ if (which == 0) { // Opção "Todas"
+ loadExpenses(); // Recarrega lista completa
+ } else { // Categoria específica
+ String category = categories[which]; // Nome da categoria
+ expenseList = databaseHelper.getExpensesByCategory(category); // Busca filtrada
+ if (expenseList == null || expenseList.isEmpty()) {
+ renderEmptyLayout();
+ } else {
+ if (isShowingEmptyState) {
+ renderMainLayout();
+ }
+ expenseAdapter.updateExpenses(expenseList); // Atualiza adapter
+ updateTotal(); // Recalcula totais com filtro
+ }
+ }
+ })
+ .show(); // Exibe o diálogo
+ }
+
+ /**
+ * Recebe resultado de AddExpenseActivity. Recarrega lista quando sucesso.
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) { // Callback de retorno
+ super.onActivityResult(requestCode, resultCode, data); // Chamada base
+ if (requestCode == 1 && resultCode == RESULT_OK) { // Verifica sucesso
+ loadExpenses(); // Recarrega lista
+ }
+ }
+
+ /**
+ * Abre AddExpenseActivity para edição de um item selecionado, enviando-o via Intent.
+ */
+ @Override
+ public void onEditClick(Expense expense) { // Clique em editar no item
+ Intent intent = new Intent(this, AddExpenseActivity.class); // Abre tela de edição
+ intent.putExtra("expense", expense); // Envia objeto selecionado
+ startActivityForResult(intent, 1); // Aguarda resultado
+ }
+
+ /**
+ * Pede confirmação e, se positivo, exclui a despesa, recarregando a lista.
+ */
+ @Override
+ public void onDeleteClick(Expense expense) { // Clique em excluir no item
+ new AlertDialog.Builder(this) // Diálogo de confirmação
+ .setTitle("Confirmar Exclusão") // Título
+ .setMessage("Tem certeza que deseja excluir esta despesa?") // Mensagem
+ .setPositiveButton("Sim", (dialog, which) -> { // Confirma
+ databaseHelper.deleteExpense(expense.getId()); // Remove do DB
+ loadExpenses(); // Recarrega a lista
+ Toast.makeText(this, "Despesa excluída com sucesso!", Toast.LENGTH_SHORT).show(); // Feedback
+ })
+ .setNegativeButton("Não", null) // Cancela
+ .show(); // Exibe diálogo
+ }
+
+ /**
+ * Decide qual layout usar com base nos dados atuais do DB.
+ */
+ private void renderLayoutByData() { // Decide qual layout mostrar com base nos dados
+ try {
+ List initial = databaseHelper.getAllExpenses(); // Consulta inicial ao DB
+ if (initial == null || initial.isEmpty()) { // Sem dados?
+ renderEmptyLayout(); // Mostra estado vazio
+ } else { // Há dados
+ expenseList = initial; // Mantém em memória
+ renderMainLayout(); // Mostra lista principal
+ }
+ } catch (Exception e) { // Qualquer erro
+ renderEmptyLayout(); // Fallback: evita crash mostrando estado vazio
+ }
+ }
+
+ /**
+ * Mostra o layout principal de lista, inicializa views/adapter e listeners.
+ */
+ private void renderMainLayout() { // Exibe layout principal de lista
+ setContentView(R.layout.activity_expenses_list); // Aplica XML de lista
+ isShowingEmptyState = false; // Marca que a lista está ativa
+ try {
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { // Listener de insets
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); // Áreas das barras
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); // Padding seguro
+ return insets; // Continua propagação
+ });
+ } catch (Exception ignored) { } // Se não existir view com id main, ignora
+ initializeViews(); // Associa views do layout
+ setupRecyclerView(); // Configura RecyclerView e adapter
+ setupClickListeners(); // Registra cliques (FAB e filtro)
+ updateTotal(); // Atualiza totais na UI
+ }
+
+ /**
+ * Mostra o layout de estado vazio e configura o botão de adicionar.
+ */
+ private void renderEmptyLayout() { // Exibe layout de estado vazio
+ setContentView(R.layout.empty_state); // Aplica XML vazio
+ isShowingEmptyState = true; // Marca que estado vazio está ativo
+ View add = findViewById(R.id.btnAddFirstExpense); // Botão "Adicionar Despesa"
+ if (add != null) { // Se existir no layout
+ add.setOnClickListener(v -> { // Ao clicar
+ Intent intent = new Intent(ExpensesListActivity.this, AddExpenseActivity.class); // Abre tela de adição
+ startActivityForResult(intent, 1); // Aguarda resultado para recarregar
+ });
+ }
+ }
+}
diff --git a/app/src/main/java/pt/epvc/gestodedespesas/MainActivity.java b/app/src/main/java/pt/epvc/gestodedespesas/MainActivity.java
new file mode 100644
index 0000000..05b1f81
--- /dev/null
+++ b/app/src/main/java/pt/epvc/gestodedespesas/MainActivity.java
@@ -0,0 +1,160 @@
+package pt.epvc.gestodedespesas; // Define o pacote onde esta classe está localizada
+
+import android.content.Intent; // Importa classe para navegação entre Activities
+import android.os.Bundle; // Importa classe para passar dados entre Activities
+import android.view.View; // Importa classe base para elementos visuais
+import android.widget.TextView; // Importa classe para exibir texto
+import android.widget.Toast; // Importa classe para exibir mensagens temporárias
+
+import androidx.activity.EdgeToEdge; // Importa classe para design edge-to-edge
+import androidx.appcompat.app.AlertDialog; // Importa classe para diálogos de alerta
+import androidx.appcompat.app.AppCompatActivity; // Importa classe base para Activities modernas
+import androidx.core.graphics.Insets; // Importa classe para margens do sistema
+import androidx.core.view.ViewCompat; // Importa classe para compatibilidade de views
+import androidx.core.view.WindowInsetsCompat; // Importa classe para insets de janela
+import androidx.recyclerview.widget.LinearLayoutManager; // Importa classe para layout linear
+import androidx.recyclerview.widget.RecyclerView; // Importa classe para listas otimizadas
+
+import com.google.android.material.floatingactionbutton.FloatingActionButton; // Importa botão flutuante Material Design
+import com.google.android.material.button.MaterialButton; // Importa botão Material Design
+
+import java.text.DecimalFormat; // Importa classe para formatação de números
+import java.util.List; // Importa interface para listas
+
+import pt.epvc.gestodedespesas.data.DatabaseHelper; // DatabaseHelper movido para pacote data
+
+/**
+ * MainActivity - Tela Principal da Aplicação de Gestão de Despesas
+ *
+ * Esta é a tela de boas-vindas que serve como dashboard principal da aplicação.
+ * Funcionalidades implementadas:
+ * - Exibe estatísticas rápidas (total gasto e número de despesas)
+ * - Botões para navegar para diferentes funcionalidades
+ * - Interface moderna com Material Design
+ * - Navegação para lista de despesas e formulário de adição
+ */
+public class MainActivity extends AppCompatActivity { // Declara classe que estende AppCompatActivity
+
+ // Views da interface - elementos visuais da tela principal
+ private TextView tvQuickTotal, tvQuickCount; // Declara TextViews para exibir estatísticas
+ private FloatingActionButton fabQuickAdd; // Declara botão flutuante para adicionar despesa
+ private MaterialButton btnGetStarted, btnViewExpenses, btnAddExpense; // Declara botões de navegação
+
+ // Objetos para funcionalidade
+ private DatabaseHelper databaseHelper; // Declara helper para operações com SQLite
+ private DecimalFormat currencyFormat = new DecimalFormat("€#,##0.00"); // Cria formato de moeda
+
+ @Override // Sobrescreve método da classe pai
+ protected void onCreate(Bundle savedInstanceState) { // Método chamado quando Activity é criada
+ super.onCreate(savedInstanceState); // Chama método da classe pai
+ EdgeToEdge.enable(this); // Habilita edge-to-edge para design moderno
+
+ // Define o layout da tela principal (activity_main.xml)
+ setContentView(R.layout.activity_main); // Conecta layout XML com esta Activity
+
+ // Configura insets para adaptar ao sistema (status bar, navigation bar)
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { // Define listener para insets
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); // Obtém insets das barras do sistema
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); // Aplica padding baseado nos insets
+ return insets; // Retorna os insets processados
+ });
+
+ // Inicializa o helper do banco de dados SQLite
+ databaseHelper = new DatabaseHelper(this); // Cria nova instância do DatabaseHelper
+
+ // Configura a interface e funcionalidades
+ initializeViews(); // Chama método para conectar views com variáveis
+ setupClickListeners(); // Chama método para configurar eventos de clique
+ updateQuickStats(); // Chama método para atualizar estatísticas exibidas
+ }
+
+ /**
+ * Inicializa as views conectando os elementos do layout com as variáveis Java
+ * Este método é chamado no onCreate para preparar a interface
+ */
+ private void initializeViews() { // Declara método privado para inicializar views
+ // Conecta os TextViews para exibir estatísticas
+ tvQuickTotal = findViewById(R.id.tvQuickTotal); // Busca TextView por ID e atribui à variável
+ tvQuickCount = findViewById(R.id.tvQuickCount); // Busca TextView por ID e atribui à variável
+
+ // Conecta os botões de ação
+ fabQuickAdd = findViewById(R.id.fabQuickAdd); // Busca FloatingActionButton por ID e atribui à variável
+ btnGetStarted = findViewById(R.id.btnGetStarted); // Busca MaterialButton por ID e atribui à variável
+ btnViewExpenses = findViewById(R.id.btnViewExpenses); // Busca MaterialButton por ID e atribui à variável
+ btnAddExpense = findViewById(R.id.btnAddExpense); // Busca MaterialButton por ID e atribui à variável
+ }
+
+ /**
+ * Configura os eventos de clique para todos os botões da interface
+ * Cada botão navega para uma funcionalidade específica da aplicação
+ */
+ private void setupClickListeners() { // Declara método privado para configurar listeners
+ // Botão flutuante para adicionar despesa rapidamente
+ fabQuickAdd.setOnClickListener(new View.OnClickListener() { // Define listener de clique para o botão flutuante
+ @Override // Sobrescreve método da interface
+ public void onClick(View v) { // Método chamado quando botão é clicado
+ // Navega para a tela de adicionar despesa
+ Intent intent = new Intent(MainActivity.this, AddExpenseActivity.class); // Cria Intent para navegar para AddExpenseActivity
+ startActivityForResult(intent, 1); // Inicia Activity esperando resultado com código 1
+ }
+ });
+
+ // Botão "Começar Agora" - leva direto para adicionar primeira despesa
+ btnGetStarted.setOnClickListener(new View.OnClickListener() { // Define listener de clique para o botão "Começar Agora"
+ @Override // Sobrescreve método da interface
+ public void onClick(View v) { // Método chamado quando botão é clicado
+ Intent intent = new Intent(MainActivity.this, AddExpenseActivity.class); // Cria Intent para navegar para AddExpenseActivity
+ startActivityForResult(intent, 1); // Inicia Activity esperando resultado com código 1
+ }
+ });
+
+ // Botão "Ver Despesas" - navega para a lista completa de despesas
+ btnViewExpenses.setOnClickListener(new View.OnClickListener() { // Define listener de clique para o botão "Ver Despesas"
+ @Override // Sobrescreve método da interface
+ public void onClick(View v) { // Método chamado quando botão é clicado
+ // Navega para a tela simplificada de lista de despesas
+ Intent intent = new Intent(MainActivity.this, SimpleExpensesActivity.class); // Cria Intent para navegar para SimpleExpensesActivity
+ startActivity(intent); // Inicia Activity sem esperar resultado
+ }
+ });
+
+ // Botão "Adicionar" - também leva para o formulário de adicionar despesa
+ btnAddExpense.setOnClickListener(new View.OnClickListener() { // Define listener de clique para o botão "Adicionar"
+ @Override // Sobrescreve método da interface
+ public void onClick(View v) { // Método chamado quando botão é clicado
+ Intent intent = new Intent(MainActivity.this, AddExpenseActivity.class); // Cria Intent para navegar para AddExpenseActivity
+ startActivityForResult(intent, 1); // Inicia Activity esperando resultado com código 1
+ }
+ });
+ }
+
+ /**
+ * Atualiza as estatísticas rápidas exibidas na tela principal
+ * Busca dados do banco SQLite e atualiza os TextViews
+ */
+ private void updateQuickStats() { // Declara método privado para atualizar estatísticas
+ // Busca o total gasto de todas as despesas
+ double total = databaseHelper.getTotalExpenses(); // Chama método do DatabaseHelper para obter total
+
+ // Busca todas as despesas para contar
+ List expenses = databaseHelper.getAllExpenses(); // Chama método do DatabaseHelper para obter todas as despesas
+ int count = expenses.size(); // Obtém o tamanho da lista (número de despesas)
+
+ // Atualiza a interface com os valores formatados
+ tvQuickTotal.setText(currencyFormat.format(total)); // Define texto do TextView com total formatado em moeda
+ tvQuickCount.setText(String.valueOf(count)); // Define texto do TextView com número de despesas convertido para string
+ }
+
+ /**
+ * Método chamado quando retorna de outra Activity
+ * Se uma despesa foi adicionada/editada, atualiza as estatísticas
+ */
+ @Override // Sobrescreve método da classe pai
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) { // Método chamado quando Activity retorna resultado
+ super.onActivityResult(requestCode, resultCode, data); // Chama método da classe pai
+ if (requestCode == 1 && resultCode == RESULT_OK) { // Verifica se é o resultado esperado (código 1) e se foi bem-sucedido
+ // Se uma despesa foi adicionada/editada com sucesso, atualiza as estatísticas
+ updateQuickStats(); // Chama método para atualizar estatísticas na tela
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/pt/epvc/gestodedespesas/SimpleExpensesActivity.java b/app/src/main/java/pt/epvc/gestodedespesas/SimpleExpensesActivity.java
new file mode 100644
index 0000000..16b4643
--- /dev/null
+++ b/app/src/main/java/pt/epvc/gestodedespesas/SimpleExpensesActivity.java
@@ -0,0 +1,178 @@
+package pt.epvc.gestodedespesas; // Este arquivo faz parte do projeto "Gestão de Despesas"
+
+import android.content.Intent; // Usado para abrir outra tela
+import android.os.Bundle; // Guarda informações quando a tela abre
+import android.view.View; // Necessário para saber quando algo foi clicado
+import android.widget.TextView; // Mostra textos na tela
+import android.widget.Toast; // Mostra avisos rápidos na parte de baixo da tela
+
+import androidx.appcompat.app.AppCompatActivity; // Tipo de tela do Android
+import androidx.recyclerview.widget.LinearLayoutManager; // Organiza a lista de cima para baixo
+import androidx.recyclerview.widget.RecyclerView; // Componente que mostra a lista de despesas
+
+import com.google.android.material.button.MaterialButton; // Botões bonitos (Material Design)
+
+import java.text.DecimalFormat; // Deixa valores com cara de dinheiro (ex: €12,34)
+import java.util.List; // Lista de coisas (neste caso, despesas)
+
+import pt.epvc.gestodedespesas.data.DatabaseHelper; // Acesso ao banco movido para o pacote data
+
+/**
+ * O que esta tela faz (em linguagem simples):
+ * - Mostra uma lista com as suas despesas
+ * - Mostra em cima quanto você já gastou no total
+ * - Tem um botão para adicionar uma nova despesa
+ * - Tem um botão para voltar para a tela anterior
+ * - Você pode tocar em uma despesa para editar ou apagar
+ */
+public class SimpleExpensesActivity extends AppCompatActivity implements ExpenseAdapter.OnExpenseClickListener { // Activity simples de lista
+
+ private static final int REQUEST_ADD_EDIT = 1; // Código de requisição para identificar retorno de Add/Edit
+
+ // Itens que aparecem na tela (componentes visuais)
+ private RecyclerView recyclerViewExpenses; // RecyclerView para exibir as despesas
+ private TextView tvTotal; // TextView do valor total
+ private MaterialButton btnAddExpense, btnBack; // Botões adicionar e voltar
+
+ // Parte "por trás das câmeras" (código que faz as coisas funcionarem)
+ private DatabaseHelper databaseHelper; // Fala com o banco de dados do celular
+ private ExpenseAdapter expenseAdapter; // Encaixa cada despesa direitinho na lista
+ private List expenseList; // Onde as despesas ficam guardadas na memória
+ private DecimalFormat currencyFormat = new DecimalFormat("€#,##0.00"); // Deixa o número no formato de euro
+
+ /**
+ * Quando esta tela abre:
+ * - Mostramos o desenho da tela (layout)
+ * - Preparamos o acesso ao banco de dados
+ * - Ligamos os botões e a lista
+ * - Buscamos as despesas já salvas e atualizamos o total gasto
+ */
+ @Override
+ protected void onCreate(Bundle savedInstanceState) { // Ponto de criação da Activity
+ super.onCreate(savedInstanceState); // Chamada da superclasse
+ setContentView(R.layout.activity_simple_expenses); // Aplica o layout simples
+
+ databaseHelper = new DatabaseHelper(this); // Instancia o helper do DB
+ initializeViews(); // Liga views do layout
+ setupRecyclerView(); // Configura a lista
+ setupClickListeners(); // Registra cliques
+ loadExpenses(); // Carrega dados iniciais
+ }
+
+ /**
+ * Faz o bind das views declaradas com os componentes do layout via findViewById.
+ */
+ private void initializeViews() { // Encontramos na tela cada componente que vamos usar
+ recyclerViewExpenses = findViewById(R.id.recyclerViewExpenses); // A lista onde aparecem as despesas
+ tvTotal = findViewById(R.id.tvTotal); // O texto que mostra o valor total gasto
+ btnAddExpense = findViewById(R.id.btnAddExpense); // Botão para adicionar uma nova despesa
+ btnBack = findViewById(R.id.btnBack); // Botão para voltar para a tela anterior
+ }
+
+ /**
+ * Configura o RecyclerView:
+ * - Busca a lista de despesas no banco de dados
+ * - Cria o adapter e define o LayoutManager linear
+ * - Conecta o adapter ao RecyclerView
+ */
+ private void setupRecyclerView() { // Preparamos a lista para aparecer bonitinha
+ try {
+ expenseList = databaseHelper.getAllExpenses(); // Pegamos todas as despesas que já existem
+ expenseAdapter = new ExpenseAdapter(expenseList, this); // Dizemos como cada item da lista deve ser mostrado
+ recyclerViewExpenses.setLayoutManager(new LinearLayoutManager(this)); // A lista vai de cima para baixo
+ recyclerViewExpenses.setAdapter(expenseAdapter); // Colocamos a lista para aparecer na tela
+ } catch (Exception e) {
+ Toast.makeText(this, "Tivemos um problema ao mostrar a lista: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Mostra um aviso se algo der errado
+ }
+ }
+
+ /**
+ * Registra os listeners de clique dos botões:
+ * - Adicionar: abre a tela de cadastro/edição de despesa
+ * - Voltar: finaliza a Activity atual
+ */
+ private void setupClickListeners() { // Dizemos o que acontece quando os botões são clicados
+ btnAddExpense.setOnClickListener(new View.OnClickListener() { // Quando tocar em "Adicionar"
+ @Override
+ public void onClick(View v) { // Ao clicar neste botão
+ Intent intent = new Intent(SimpleExpensesActivity.this, AddExpenseActivity.class); // Vamos para a tela de adicionar despesa
+ startActivityForResult(intent, REQUEST_ADD_EDIT); // Depois que terminar, voltamos aqui com o resultado
+ }
+ });
+
+ btnBack.setOnClickListener(new View.OnClickListener() { // Quando tocar em "Voltar"
+ @Override
+ public void onClick(View v) { // Ao clicar neste botão
+ finish(); // Fechamos esta tela e voltamos para a anterior
+ }
+ });
+ }
+
+ /**
+ * Carrega/recarrega a lista de despesas do banco e atualiza o adapter.
+ * Em seguida, recalcula o total gasto.
+ */
+ private void loadExpenses() { // Busca de novo as despesas para manter a lista atualizada
+ try {
+ expenseList = databaseHelper.getAllExpenses(); // Buscamos as despesas salvas no celular
+ if (expenseAdapter != null) { // Se a lista já está pronta
+ expenseAdapter.updateExpenses(expenseList); // Atualizamos a lista que aparece na tela
+ }
+ updateTotal(); // Atualizamos o valor total mostrado em cima
+ } catch (Exception e) {
+ Toast.makeText(this, "Tivemos um problema ao carregar as despesas: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Aviso de problema
+ }
+ }
+
+ /**
+ * Calcula o total de despesas no banco e atualiza o TextView com formatação em euros.
+ */
+ private void updateTotal() { // Calcula quanto foi gasto no total e mostra na tela
+ try {
+ double total = databaseHelper.getTotalExpenses(); // Somamos os valores de todas as despesas
+ if (tvTotal != null) { // Se o texto do total existe na tela
+ tvTotal.setText(currencyFormat.format(total)); // Mostramos o total com símbolo de euro
+ }
+ } catch (Exception e) {
+ Toast.makeText(this, "Não conseguimos atualizar o total agora: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Aviso de problema
+ }
+ }
+
+ /**
+ * Recebe o retorno da AddExpenseActivity (adicionar/editar).
+ * Se o resultado for OK, recarrega a lista.
+ */
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) { // Quando voltamos da tela de adicionar/editar
+ super.onActivityResult(requestCode, resultCode, data); // Mantém o funcionamento padrão
+ if (requestCode == REQUEST_ADD_EDIT && resultCode == RESULT_OK) { // Deu tudo certo por lá?
+ loadExpenses(); // Atualizamos a lista aqui também
+ }
+ }
+
+ /**
+ * Chamado quando o usuário clica para editar uma despesa no item da lista.
+ * Abre a AddExpenseActivity enviando o objeto Expense via Intent.
+ */
+ @Override
+ public void onEditClick(Expense expense) { // Quando tocar para editar uma despesa
+ Intent intent = new Intent(this, AddExpenseActivity.class); // Abrimos a tela de edição
+ intent.putExtra("expense", expense); // Levamos as informações da despesa para lá
+ startActivityForResult(intent, REQUEST_ADD_EDIT); // Depois voltamos com o resultado
+ }
+
+ /**
+ * Chamado quando o usuário solicita a exclusão de uma despesa.
+ * Remove do banco, recarrega a lista e informa via Toast.
+ */
+ @Override
+ public void onDeleteClick(Expense expense) { // Quando tocar para apagar uma despesa
+ try {
+ databaseHelper.deleteExpense(expense.getId()); // Apagamos do banco de dados
+ loadExpenses(); // Atualizamos a lista na tela
+ Toast.makeText(this, "Despesa excluída com sucesso!", Toast.LENGTH_SHORT).show(); // Mostramos uma mensagem de sucesso
+ } catch (Exception e) {
+ Toast.makeText(this, "Não conseguimos apagar agora: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Aviso de problema
+ }
+ }
+}
diff --git a/app/src/main/java/pt/epvc/gestodedespesas/data/DatabaseHelper.java b/app/src/main/java/pt/epvc/gestodedespesas/data/DatabaseHelper.java
new file mode 100644
index 0000000..be20fa1
--- /dev/null
+++ b/app/src/main/java/pt/epvc/gestodedespesas/data/DatabaseHelper.java
@@ -0,0 +1,184 @@
+package pt.epvc.gestodedespesas.data; // Esta pasta (pacote) guarda tudo que conversa com o banco de dados
+
+import android.content.ContentValues; // Usado para montar os dados antes de salvar
+import android.content.Context; // Precisamos do contexto para abrir/criar o banco
+import android.database.Cursor; // Onde ficam os resultados de uma consulta
+import android.database.sqlite.SQLiteDatabase; // Objeto que permite ler/escrever no banco
+import android.database.sqlite.SQLiteOpenHelper; // Classe que ajuda a criar e atualizar o banco
+
+import java.util.ArrayList; // Lista de tamanho variável
+import java.util.List; // Tipo "lista" em Java
+
+import pt.epvc.gestodedespesas.Expense; // Nossa classe que representa uma despesa
+
+/**
+ * O que esta classe faz (em linguagem simples):
+ * - Cria o arquivo do banco de dados no celular, caso não exista
+ * - Cria a tabela de despesas na primeira vez
+ * - Salva, busca, atualiza e apaga despesas no banco
+ * - Também consegue somar o total gasto e filtrar por categoria
+ */
+public class DatabaseHelper extends SQLiteOpenHelper { // Helper do SQLite
+ private static final String DATABASE_NAME = "expenses.db"; // Nome do arquivo do banco no aparelho
+ private static final int DATABASE_VERSION = 1; // Versão do banco (muda quando a estrutura mudar)
+
+ // Nome da tabela e das colunas (os "campos" da tabela)
+ private static final String TABLE_EXPENSES = "expenses";
+ private static final String COLUMN_ID = "id";
+ private static final String COLUMN_DESCRIPTION = "description";
+ private static final String COLUMN_AMOUNT = "amount";
+ private static final String COLUMN_CATEGORY = "category";
+ private static final String COLUMN_DATE = "date";
+ private static final String COLUMN_NOTES = "notes";
+
+ // Comando que cria a tabela de despesas (rodado somente na primeira vez)
+ private static final String CREATE_TABLE_EXPENSES =
+ "CREATE TABLE " + TABLE_EXPENSES + "(" +
+ COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+ COLUMN_DESCRIPTION + " TEXT NOT NULL," +
+ COLUMN_AMOUNT + " REAL NOT NULL," +
+ COLUMN_CATEGORY + " TEXT NOT NULL," +
+ COLUMN_DATE + " TEXT NOT NULL," +
+ COLUMN_NOTES + " TEXT" +
+ ")";
+
+ public DatabaseHelper(Context context) { // Chamado quando precisamos usar o banco
+ super(context, DATABASE_NAME, null, DATABASE_VERSION); // Diz o nome e a versão do banco
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) { // Roda só na primeira instalação/uso
+ db.execSQL(CREATE_TABLE_EXPENSES); // Cria a tabela de despesas
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Se a versão do banco aumentar
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_EXPENSES); // Apaga a tabela antiga
+ onCreate(db); // Cria de novo com a estrutura atualizada
+ }
+
+ // CRUD = Criar, Ler, Atualizar e Deletar
+ public long addExpense(Expense expense) { // Salva uma nova despesa
+ SQLiteDatabase db = this.getWritableDatabase(); // Abre o banco para escrita
+ ContentValues values = new ContentValues(); // Monta o "pacotinho" de dados
+ values.put(COLUMN_DESCRIPTION, expense.getDescription()); // Descrição
+ values.put(COLUMN_AMOUNT, expense.getAmount()); // Valor
+ values.put(COLUMN_CATEGORY, expense.getCategory()); // Categoria
+ values.put(COLUMN_DATE, expense.getDate()); // Data
+ values.put(COLUMN_NOTES, expense.getNotes()); // Notas
+ long id = db.insert(TABLE_EXPENSES, null, values); // Insere na tabela e devolve o id criado
+ db.close(); // Fecha o banco
+ return id; // Retorna o id da nova despesa
+ }
+
+ public Expense getExpense(int id) { // Busca uma despesa específica pelo id
+ SQLiteDatabase db = this.getReadableDatabase(); // Abre o banco para leitura
+ Cursor cursor = db.query(
+ TABLE_EXPENSES,
+ new String[]{COLUMN_ID, COLUMN_DESCRIPTION, COLUMN_AMOUNT, COLUMN_CATEGORY, COLUMN_DATE, COLUMN_NOTES},
+ COLUMN_ID + "=?",
+ new String[]{String.valueOf(id)}, null, null, null, null
+ );
+
+ if (cursor != null && cursor.moveToFirst()) { // Se achou o registro
+ Expense expense = new Expense( // Monta um objeto Expense com os dados
+ cursor.getInt(0),
+ cursor.getString(1),
+ cursor.getDouble(2),
+ cursor.getString(3),
+ cursor.getString(4),
+ cursor.getString(5)
+ );
+ cursor.close(); // Fecha o resultado
+ db.close(); // Fecha o banco
+ return expense;
+ }
+ if (cursor != null) cursor.close(); // Se não achou, só fecha o cursor
+ db.close(); // Fecha o banco
+ return null; // Diz que não encontrou
+ }
+
+ public List getAllExpenses() { // Busca todas as despesas, mais recentes primeiro
+ List expenseList = new ArrayList<>(); // Cria uma lista vazia
+ String selectQuery = "SELECT * FROM " + TABLE_EXPENSES + " ORDER BY " + COLUMN_DATE + " DESC"; // Consulta
+ SQLiteDatabase db = this.getWritableDatabase(); // Abre o banco
+ Cursor cursor = db.rawQuery(selectQuery, null); // Executa a consulta
+
+ if (cursor.moveToFirst()) { // Se tem pelo menos uma linha
+ do { // Repete para cada linha
+ Expense expense = new Expense(); // Cria o objeto da despesa
+ expense.setId(cursor.getInt(0)); // Pega o id
+ expense.setDescription(cursor.getString(1)); // Pega a descrição
+ expense.setAmount(cursor.getDouble(2)); // Pega o valor
+ expense.setCategory(cursor.getString(3)); // Pega a categoria
+ expense.setDate(cursor.getString(4)); // Pega a data
+ expense.setNotes(cursor.getString(5)); // Pega as notas
+ expenseList.add(expense); // Coloca na lista
+ } while (cursor.moveToNext()); // Vai para a próxima
+ }
+ cursor.close(); // Fecha o resultado
+ db.close(); // Fecha o banco
+ return expenseList; // Devolve a lista pronta
+ }
+
+ public int updateExpense(Expense expense) { // Atualiza uma despesa que já existe
+ SQLiteDatabase db = this.getWritableDatabase(); // Abre para escrita
+ ContentValues values = new ContentValues(); // Novos valores
+ values.put(COLUMN_DESCRIPTION, expense.getDescription());
+ values.put(COLUMN_AMOUNT, expense.getAmount());
+ values.put(COLUMN_CATEGORY, expense.getCategory());
+ values.put(COLUMN_DATE, expense.getDate());
+ values.put(COLUMN_NOTES, expense.getNotes());
+ int result = db.update(TABLE_EXPENSES, values, COLUMN_ID + " = ?", new String[]{String.valueOf(expense.getId())}); // Atualiza onde id = ...
+ db.close(); // Fecha o banco
+ return result; // Diz quantas linhas foram alteradas
+ }
+
+ public void deleteExpense(int id) { // Apaga uma despesa pelo id
+ SQLiteDatabase db = this.getWritableDatabase(); // Abre para escrita
+ db.delete(TABLE_EXPENSES, COLUMN_ID + " = ?", new String[]{String.valueOf(id)}); // Remove a linha do id
+ db.close(); // Fecha o banco
+ }
+
+ public double getTotalExpenses() { // Soma o valor de todas as despesas
+ String selectQuery = "SELECT SUM(" + COLUMN_AMOUNT + ") FROM " + TABLE_EXPENSES; // Consulta de soma
+ SQLiteDatabase db = this.getReadableDatabase(); // Abre para leitura
+ Cursor cursor = db.rawQuery(selectQuery, null); // Executa
+ double total = 0; // Começa em zero
+ if (cursor.moveToFirst()) { // Se teve resultado
+ total = cursor.getDouble(0); // Pega o valor somado
+ }
+ cursor.close(); // Fecha o resultado
+ db.close(); // Fecha o banco
+ return total; // Devolve o total
+ }
+
+ public List getExpensesByCategory(String category) { // Busca despesas só de uma categoria
+ List expenseList = new ArrayList<>(); // Lista vazia
+ SQLiteDatabase db = this.getReadableDatabase(); // Abre para leitura
+ Cursor cursor = db.query( // Faz a consulta com filtro de categoria
+ TABLE_EXPENSES,
+ new String[]{COLUMN_ID, COLUMN_DESCRIPTION, COLUMN_AMOUNT, COLUMN_CATEGORY, COLUMN_DATE, COLUMN_NOTES},
+ COLUMN_CATEGORY + "=?",
+ new String[]{category}, null, null, COLUMN_DATE + " DESC", null
+ );
+
+ if (cursor.moveToFirst()) { // Se achou resultados
+ do { // Percorre cada linha
+ Expense expense = new Expense(); // Cria a despesa
+ expense.setId(cursor.getInt(0)); // id
+ expense.setDescription(cursor.getString(1)); // descrição
+ expense.setAmount(cursor.getDouble(2)); // valor
+ expense.setCategory(cursor.getString(3)); // categoria
+ expense.setDate(cursor.getString(4)); // data
+ expense.setNotes(cursor.getString(5)); // notas
+ expenseList.add(expense); // coloca na lista
+ } while (cursor.moveToNext()); // próxima linha
+ }
+ cursor.close(); // Fecha o resultado
+ db.close(); // Fecha o banco
+ return expenseList; // Devolve lista filtrada
+ }
+}
+
+
diff --git a/app/src/main/res/drawable/average_background.xml b/app/src/main/res/drawable/average_background.xml
new file mode 100644
index 0000000..5a3e07f
--- /dev/null
+++ b/app/src/main/res/drawable/average_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/background_gradient.xml b/app/src/main/res/drawable/background_gradient.xml
new file mode 100644
index 0000000..b8eaf7b
--- /dev/null
+++ b/app/src/main/res/drawable/background_gradient.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/card_animation.xml b/app/src/main/res/drawable/card_animation.xml
new file mode 100644
index 0000000..b5dcbc3
--- /dev/null
+++ b/app/src/main/res/drawable/card_animation.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/category_chip.xml b/app/src/main/res/drawable/category_chip.xml
new file mode 100644
index 0000000..35e98ba
--- /dev/null
+++ b/app/src/main/res/drawable/category_chip.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/empty_state_background.xml b/app/src/main/res/drawable/empty_state_background.xml
new file mode 100644
index 0000000..9fc51e2
--- /dev/null
+++ b/app/src/main/res/drawable/empty_state_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/fab_gradient.xml b/app/src/main/res/drawable/fab_gradient.xml
new file mode 100644
index 0000000..d6fc1f7
--- /dev/null
+++ b/app/src/main/res/drawable/fab_gradient.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/feature_icon_background.xml b/app/src/main/res/drawable/feature_icon_background.xml
new file mode 100644
index 0000000..01722d1
--- /dev/null
+++ b/app/src/main/res/drawable/feature_icon_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/header_icon_background.xml b/app/src/main/res/drawable/header_icon_background.xml
new file mode 100644
index 0000000..01c1288
--- /dev/null
+++ b/app/src/main/res/drawable/header_icon_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_add_white.xml b/app/src/main/res/drawable/ic_add_white.xml
new file mode 100644
index 0000000..aa7a727
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add_white.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_calendar.xml b/app/src/main/res/drawable/ic_calendar.xml
new file mode 100644
index 0000000..3b4d27a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_calendar.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml
new file mode 100644
index 0000000..e551d15
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cancel.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_category.xml b/app/src/main/res/drawable/ic_category.xml
new file mode 100644
index 0000000..62f7b29
--- /dev/null
+++ b/app/src/main/res/drawable/ic_category.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_delete.xml b/app/src/main/res/drawable/ic_delete.xml
new file mode 100644
index 0000000..fb6bd16
--- /dev/null
+++ b/app/src/main/res/drawable/ic_delete.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_description.xml b/app/src/main/res/drawable/ic_description.xml
new file mode 100644
index 0000000..500d9ea
--- /dev/null
+++ b/app/src/main/res/drawable/ic_description.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_edit.xml b/app/src/main/res/drawable/ic_edit.xml
new file mode 100644
index 0000000..7c27a88
--- /dev/null
+++ b/app/src/main/res/drawable/ic_edit.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_empty_wallet.xml b/app/src/main/res/drawable/ic_empty_wallet.xml
new file mode 100644
index 0000000..b3e8218
--- /dev/null
+++ b/app/src/main/res/drawable/ic_empty_wallet.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_euro.xml b/app/src/main/res/drawable/ic_euro.xml
new file mode 100644
index 0000000..904486f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_euro.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml
new file mode 100644
index 0000000..34d28b0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_filter.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_list.xml b/app/src/main/res/drawable/ic_list.xml
new file mode 100644
index 0000000..1e64d98
--- /dev/null
+++ b/app/src/main/res/drawable/ic_list.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_money.xml b/app/src/main/res/drawable/ic_money.xml
new file mode 100644
index 0000000..19cd6a4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_money.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_notes.xml b/app/src/main/res/drawable/ic_notes.xml
new file mode 100644
index 0000000..500d9ea
--- /dev/null
+++ b/app/src/main/res/drawable/ic_notes.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_rocket.xml b/app/src/main/res/drawable/ic_rocket.xml
new file mode 100644
index 0000000..33d335d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_rocket.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_save.xml b/app/src/main/res/drawable/ic_save.xml
new file mode 100644
index 0000000..391eee7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_save.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_wallet.xml b/app/src/main/res/drawable/ic_wallet.xml
new file mode 100644
index 0000000..86e615e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_wallet.xml
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/icon_background.xml b/app/src/main/res/drawable/icon_background.xml
new file mode 100644
index 0000000..a2c3f75
--- /dev/null
+++ b/app/src/main/res/drawable/icon_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/notes_background.xml b/app/src/main/res/drawable/notes_background.xml
new file mode 100644
index 0000000..68f7a61
--- /dev/null
+++ b/app/src/main/res/drawable/notes_background.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/stat_background.xml b/app/src/main/res/drawable/stat_background.xml
new file mode 100644
index 0000000..9b74684
--- /dev/null
+++ b/app/src/main/res/drawable/stat_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/stat_card_background.xml b/app/src/main/res/drawable/stat_card_background.xml
new file mode 100644
index 0000000..9b74684
--- /dev/null
+++ b/app/src/main/res/drawable/stat_card_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/total_background.xml b/app/src/main/res/drawable/total_background.xml
new file mode 100644
index 0000000..2478630
--- /dev/null
+++ b/app/src/main/res/drawable/total_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/welcome_icon_background.xml b/app/src/main/res/drawable/welcome_icon_background.xml
new file mode 100644
index 0000000..d73ef0c
--- /dev/null
+++ b/app/src/main/res/drawable/welcome_icon_background.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_add_expense.xml b/app/src/main/res/layout/activity_add_expense.xml
new file mode 100644
index 0000000..38d7b42
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_expense.xml
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_expenses_list.xml b/app/src/main/res/layout/activity_expenses_list.xml
new file mode 100644
index 0000000..9b0c03b
--- /dev/null
+++ b/app/src/main/res/layout/activity_expenses_list.xml
@@ -0,0 +1,247 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..2976fe1
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,436 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_simple_expenses.xml b/app/src/main/res/layout/activity_simple_expenses.xml
new file mode 100644
index 0000000..1bb14e2
--- /dev/null
+++ b/app/src/main/res/layout/activity_simple_expenses.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/empty_state.xml b/app/src/main/res/layout/empty_state.xml
new file mode 100644
index 0000000..d955dc0
--- /dev/null
+++ b/app/src/main/res/layout/empty_state.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_expense.xml b/app/src/main/res/layout/item_expense.xml
new file mode 100644
index 0000000..7df24ae
--- /dev/null
+++ b/app/src/main/res/layout/item_expense.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..76ffda8
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..4003997
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,35 @@
+
+
+ #FF000000
+ #FFFFFFFF
+
+
+ #6366F1
+ #4F46E5
+ #A5B4FC
+ #F59E0B
+
+
+ #F8FAFC
+ #FFFFFF
+ #667EEA
+ #764BA2
+
+
+ #1F2937
+ #6B7280
+ #9CA3AF
+
+
+ #10B981
+ #EF4444
+ #F59E0B
+
+
+ #F59E0B
+ #3B82F6
+ #8B5CF6
+ #EF4444
+ #10B981
+ #6B7280
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0bc6316
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Gestão de Despesas
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..67865a2
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..4df9255
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/pt/epvc/gestodedespesas/ExampleUnitTest.java b/app/src/test/java/pt/epvc/gestodedespesas/ExampleUnitTest.java
new file mode 100644
index 0000000..8714e8d
--- /dev/null
+++ b/app/src/test/java/pt/epvc/gestodedespesas/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package pt.epvc.gestodedespesas;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..6adebfd
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,7 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+// Configuração de plugins para todo o projeto
+plugins {
+ // Plugin Android Application - versão 8.13.0 (aplicado apenas aos módulos que o solicitarem)
+ id 'com.android.application' version '8.13.0' apply false
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..ffaa94d
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,33 @@
+# Configurações globais do Gradle para todo o projeto
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Configurações da JVM do daemon Gradle
+# Especifica os argumentos JVM usados para o processo daemon.
+# Esta configuração é particularmente útil para ajustar configurações de memória.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+
+# Configurações de build paralelo
+# Quando configurado, o Gradle executará em modo paralelo incubating.
+# Esta opção deve ser usada apenas com projetos desacoplados. Para mais detalhes, visite
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+
+# Configurações AndroidX
+# Estrutura de pacotes AndroidX para tornar mais claro quais pacotes são agrupados com o
+# sistema operacional Android, e quais são empacotados com o APK da sua aplicação
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+
+# Configurações de recursos
+# Habilita namespacing da classe R de cada biblioteca para que sua classe R inclua apenas os
+# recursos declarados na própria biblioteca e nenhum das dependências da biblioteca,
+# reduzindo assim o tamanho da classe R para essa biblioteca
+android.nonTransitiveRClass=true
+
+# Configurações de compilação
+android.suppressUnsupportedCompileSdk=35
+android.javaCompile.suppressSourceTargetDeprecationWarning=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..4548451
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,28 @@
+# Arquivo de versões centralizadas para dependências e plugins
+# Este arquivo permite gerenciar todas as versões em um local central
+
+# Seção de versões - define as versões de todas as dependências e plugins
+[versions]
+agp = "8.5.2" # Android Gradle Plugin - versão estável
+junit = "4.13.2" # JUnit - framework de testes unitários
+junitVersion = "1.1.5" # AndroidX JUnit - extensões JUnit para Android
+espressoCore = "3.5.1" # Espresso - framework de testes de interface
+appcompat = "1.7.1" # AppCompat - compatibilidade com versões antigas
+material = "1.10.0" # Material Design Components
+activity = "1.8.2" # Activity - biblioteca base para Activities
+constraintlayout = "2.1.4" # ConstraintLayout - sistema de layout com constraints
+
+# Seção de bibliotecas - define as dependências usando as versões acima
+[libraries]
+junit = { group = "junit", name = "junit", version.ref = "junit" } # JUnit para testes unitários
+ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } # Extensões JUnit Android
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } # Espresso para testes UI
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } # AppCompat
+material = { group = "com.google.android.material", name = "material", version.ref = "material" } # Material Design
+activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } # Activity
+constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } # ConstraintLayout
+
+# Seção de plugins - define os plugins usando as versões acima
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" } # Plugin Android Application
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..ab851d5
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,16 @@
+# Configurações do Gradle Wrapper
+# Este arquivo define qual versão do Gradle será usada para construir o projeto
+
+# Data de criação do arquivo
+#Thu Oct 23 15:58:53 WEST 2025
+
+# Configurações de distribuição do Gradle
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+
+# URL da distribuição Gradle - versão 8.14 (suporte Java 24)
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
+
+# Configurações de armazenamento
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..0769e96
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,30 @@
+// Configurações do projeto Gradle - define repositórios e módulos
+
+// Configuração de gerenciamento de plugins
+pluginManagement {
+ repositories {
+ // Repositório Google - plugins Android e Google
+ google {
+ content {
+ includeGroupByRegex 'com\\.android.*' // Inclui grupos Android
+ includeGroupByRegex 'com\\.google.*' // Inclui grupos Google
+ includeGroupByRegex 'androidx.*' // Inclui grupos AndroidX
+ }
+ }
+ mavenCentral() // Repositório Maven Central
+ gradlePluginPortal() // Portal de plugins Gradle
+ }
+}
+
+// Configuração de resolução de dependências
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) // Falha se repositórios forem definidos em módulos
+ repositories {
+ google() // Repositório Google para dependências Android
+ mavenCentral() // Repositório Maven Central para dependências Java
+ }
+}
+
+// Configuração do projeto raiz
+rootProject.name = 'Gestão de Despesas' // Nome do projeto
+include ':app' // Inclui o módulo app no build