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 @@ + + + + +