first commit

main
Vasco James Maia Maile 2025-11-03 11:41:39 +00:00
commit 249eb6f6f2
90 changed files with 3928 additions and 0 deletions

15
.gitignore vendored Normal file
View File

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

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

1
.idea/.name Normal file
View File

@ -0,0 +1 @@
Gestão de Despesas

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetSelector">
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
</selectionStates>
</component>
</project>

19
.idea/gradle.xml Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

10
.idea/migrations.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

9
.idea/misc.xml Normal file
View File

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
</set>
</option>
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

240
README.md Normal file
View File

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

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

51
app/build.gradle Normal file
View File

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

21
app/proguard-rules.pro vendored Normal file
View File

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

View File

@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@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());
}
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.GestãoDeDespesas">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".AddExpenseActivity"
android:exported="false" />
<activity
android:name=".ExpensesListActivity"
android:exported="false" />
<activity
android:name=".SimpleExpensesActivity"
android:exported="false" />
</application>
</manifest>

View File

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

View File

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

View File

@ -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<ExpenseAdapter.ExpenseViewHolder> { // Declara classe que estende RecyclerView.Adapter
// Dados e configurações
private List<Expense> 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<Expense> 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<Expense> 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
}
}
}

View File

@ -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<Expense> 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<Expense> 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
});
}
}
}

View File

@ -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<Expense> 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
}
}
}

View File

@ -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<Expense> 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
}
}
}

View File

@ -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<Expense> getAllExpenses() { // Busca todas as despesas, mais recentes primeiro
List<Expense> 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<Expense> getExpensesByCategory(String category) { // Busca despesas só de uma categoria
List<Expense> 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
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/accent_color"
android:endColor="@color/warning_color"
android:angle="90" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="@color/warning_color" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/background_gradient_start"
android:endColor="@color/background_gradient_end"
android:angle="135" />
</shape>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:duration="200"
android:fromXScale="0.95"
android:fromYScale="0.95"
android:toXScale="1.0"
android:toYScale="1.0"
android:pivotX="50%"
android:pivotY="50%" />
<alpha
android:duration="200"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/primary_light" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="@color/primary_color" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/background_light"
android:endColor="@color/primary_light"
android:angle="45" />
<corners android:radius="16dp" />
<stroke
android:width="2dp"
android:color="@color/primary_light" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient
android:startColor="@color/primary_color"
android:endColor="@color/primary_dark"
android:angle="45" />
<stroke
android:width="2dp"
android:color="@color/white" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/primary_light" />
<stroke
android:width="1dp"
android:color="@color/primary_color" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient
android:startColor="@color/primary_light"
android:endColor="@color/primary_color"
android:angle="45" />
<stroke
android:width="2dp"
android:color="@color/white" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/white">
<path
android:fillColor="@color/white"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M19,3H18V1H16V3H8V1H6V3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3M19,19H5V8H19V19Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M12,2L13.09,8.26L22,9L13.09,9.74L12,16L10.91,9.74L2,9L10.91,8.26L12,2Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/white">
<path
android:fillColor="@color/white"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/white">
<path
android:fillColor="@color/white"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="40dp"
android:height="40dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M21,18V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V6H12C10.89,6 10,6.89 10,8V16A2,2 0 0,0 12,18H21M12,16V8H22V16H12M16,13.5A1.5,1.5 0 0,1 14.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,12A1.5,1.5 0 0,1 16,13.5Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M7.07,11L6,10L4.5,11.5L3,10L1.93,11L3.5,12.5L1.93,14L3,15L4.5,13.5L6,15L7.07,14L5.5,12.5L7.07,11M15,5H7A1,1 0 0,0 6,6V18A1,1 0 0,0 7,19H15A1,1 0 0,0 16,18V6A1,1 0 0,0 15,5M15,18H7V6H15V18Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M10,18H14V16H10V18M3,6V8H21V6H3M6,13H18V11H6V13Z"/>
</vector>

View File

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

View File

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M3,13H5V11H3V13M3,17H5V15H3V17M3,9H5V7H3V9M7,13H21V11H7V13M7,17H21V15H7V17M7,7V9H21V7H7Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M14,2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18A2,2 0 0,0 20,20V8L14,2M18,20H6V4H13V9H18V20Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/white">
<path
android:fillColor="@color/white"
android:pathData="M2.81,14.12L5.64,11.29L8.17,10.79C11.39,6.41 17.55,4.22 19.78,4.22C19.78,6.45 17.59,12.61 13.21,15.83L12.71,18.36L9.88,21.19C9.88,21.19 9.88,21.19 9.88,21.19L2.81,14.12M7.76,6.94L6.34,8.36L7.76,9.78L9.18,8.36L7.76,6.94M11.29,8.17L9.88,9.58L11.29,11L12.71,9.58L11.29,8.17M14.83,11.71L13.41,13.12L14.83,14.54L16.24,13.12L14.83,11.71Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/white">
<path
android:fillColor="@color/white"
android:pathData="M15,9H5V5H15M12,19A3,3 0 0,1 9,16A3,3 0 0,1 12,13A3,3 0 0,1 15,16A3,3 0 0,1 12,19M17,3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V7L17,3Z"/>
</vector>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="@color/primary_color">
<path
android:fillColor="@color/primary_color"
android:pathData="M21,18V19A2,2 0 0,1 19,21H5C3.89,21 3,20.1 3,19V5A2,2 0 0,1 5,3H19A2,2 0 0,1 21,5V6H12C10.89,6 10,6.89 10,8V16A2,2 0 0,0 12,18H21M12,16V8H22V16H12M16,13.5A1.5,1.5 0 0,1 14.5,12A1.5,1.5 0 0,1 16,10.5A1.5,1.5 0 0,1 17.5,12A1.5,1.5 0 0,1 16,13.5Z"/>
</vector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/primary_light" />
<stroke
android:width="2dp"
android:color="@color/primary_color" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/background_light" />
<corners android:radius="8dp" />
<stroke
android:width="1dp"
android:color="@color/primary_light" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/background_light"
android:endColor="@color/primary_light"
android:angle="135" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="@color/primary_light" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/background_light"
android:endColor="@color/primary_light"
android:angle="135" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="@color/primary_light" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:startColor="@color/primary_light"
android:endColor="@color/primary_color"
android:angle="90" />
<corners android:radius="12dp" />
<stroke
android:width="1dp"
android:color="@color/primary_dark" />
</shape>

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<gradient
android:startColor="@color/primary_light"
android:endColor="@color/primary_color"
android:angle="45" />
<stroke
android:width="4dp"
android:color="@color/white" />
</shape>

View File

@ -0,0 +1,184 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_gradient"
android:padding="16dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="20dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@color/background_card">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<!-- Header -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="24dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="💰 Adicionar Despesa"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/text_primary" />
<ImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/ic_money"
android:background="@drawable/icon_background"
android:padding="6dp" />
</LinearLayout>
<!-- Formulário -->
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="Descrição da despesa"
app:startIconDrawable="@drawable/ic_description"
app:boxStrokeColor="@color/primary_color"
app:hintTextColor="@color/primary_color">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textColor="@color/text_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="Valor (€)"
app:startIconDrawable="@drawable/ic_euro"
app:boxStrokeColor="@color/primary_color"
app:hintTextColor="@color/primary_color">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etAmount"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:textColor="@color/text_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="Categoria"
app:startIconDrawable="@drawable/ic_category"
app:boxStrokeColor="@color/primary_color"
app:hintTextColor="@color/primary_color">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etCategory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text"
android:textColor="@color/text_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="Data (DD/MM/AAAA)"
app:startIconDrawable="@drawable/ic_calendar"
app:boxStrokeColor="@color/primary_color"
app:hintTextColor="@color/primary_color">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etDate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="date"
android:textColor="@color/text_primary" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:hint="Notas (opcional)"
app:startIconDrawable="@drawable/ic_notes"
app:boxStrokeColor="@color/primary_color"
app:hintTextColor="@color/primary_color">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etNotes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textMultiLine"
android:maxLines="3"
android:textColor="@color/text_primary" />
</com.google.android.material.textfield.TextInputLayout>
<!-- Botões -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSave"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:text="💾 Salvar"
android:textSize="16sp"
android:textColor="@color/white"
app:backgroundTint="@color/primary_color"
app:cornerRadius="28dp"
app:icon="@drawable/ic_save"
app:iconTint="@color/white"
app:elevation="4dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnCancel"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:text="❌ Cancelar"
android:textSize="16sp"
android:textColor="@color/primary_color"
app:backgroundTint="@android:color/transparent"
app:strokeColor="@color/primary_color"
app:strokeWidth="2dp"
app:cornerRadius="28dp"
app:icon="@drawable/ic_cancel"
app:iconTint="@color/primary_color" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</ScrollView>

View File

@ -0,0 +1,247 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_gradient">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- Header Principal -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="20dp"
app:cardElevation="12dp"
app:cardBackgroundColor="@color/background_card"
app:strokeColor="@color/primary_light"
app:strokeWidth="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<!-- Título e ícone -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="20dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="💰 Minhas Despesas"
android:textSize="28sp"
android:textStyle="bold"
android:textColor="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Gerencie seus gastos de forma inteligente"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:layout_marginTop="4dp" />
</LinearLayout>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/ic_wallet"
android:background="@drawable/header_icon_background"
android:padding="12dp" />
</LinearLayout>
<!-- Estatísticas principais -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<!-- Total gasto -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:background="@drawable/stat_card_background"
android:padding="16dp"
android:layout_marginEnd="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="💰 Total Gasto"
android:textSize="12sp"
android:textColor="@color/text_secondary"
android:textStyle="bold" />
<TextView
android:id="@+id/tvTotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="€0.00"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/error_color"
android:layout_marginTop="4dp" />
</LinearLayout>
<!-- Número de despesas -->
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:background="@drawable/stat_card_background"
android:padding="16dp"
android:layout_marginStart="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="📊 Despesas"
android:textSize="12sp"
android:textColor="@color/text_secondary"
android:textStyle="bold" />
<TextView
android:id="@+id/tvExpenseCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/primary_color"
android:layout_marginTop="4dp" />
</LinearLayout>
</LinearLayout>
<!-- Média por despesa -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center"
android:background="@drawable/average_background"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="📈 Média por despesa: "
android:textSize="14sp"
android:textColor="@color/text_primary"
android:textStyle="bold" />
<TextView
android:id="@+id/tvAverage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="€0.00"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/accent_color" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Filtros e ações -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="6dp"
app:cardBackgroundColor="@color/background_card">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="📋 Suas Despesas"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/text_primary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnFilter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🔍 Filtrar"
android:textSize="12sp"
android:paddingStart="12dp"
android:paddingEnd="12dp"
app:backgroundTint="@color/primary_light"
app:cornerRadius="16dp"
app:icon="@drawable/ic_filter"
app:iconSize="16dp"
app:iconTint="@color/primary_color" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Lista de despesas -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewExpenses"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="80dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- Floating Action Button -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabAddExpense"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/ic_add_white"
app:backgroundTint="@color/primary_color"
app:borderWidth="0dp"
app:elevation="12dp"
app:fabSize="normal"
app:tint="@color/white"
app:rippleColor="@color/primary_dark" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,436 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/background_gradient"
tools:context=".MainActivity">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- Header de Boas-vindas -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="24dp"
app:cardElevation="12dp"
app:cardBackgroundColor="@color/background_card"
app:strokeColor="@color/primary_light"
app:strokeWidth="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="32dp"
android:gravity="center">
<!-- Ícone principal -->
<ImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/ic_wallet"
android:background="@drawable/welcome_icon_background"
android:padding="24dp"
android:layout_marginBottom="24dp" />
<!-- Título principal -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="💰 Gestão de Despesas"
android:textSize="32sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:gravity="center"
android:layout_marginBottom="12dp" />
<!-- Subtítulo -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Controle seus gastos de forma inteligente e organizada"
android:textSize="16sp"
android:textColor="@color/text_secondary"
android:gravity="center"
android:lineSpacingExtra="4dp"
android:layout_marginBottom="24dp" />
<!-- Botão principal -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnGetStarted"
android:layout_width="match_parent"
android:layout_height="56dp"
android:text="🚀 Começar Agora"
android:textSize="18sp"
android:textColor="@color/white"
app:backgroundTint="@color/primary_color"
app:cornerRadius="28dp"
app:icon="@drawable/ic_rocket"
app:iconTint="@color/white"
app:iconSize="24dp"
app:elevation="6dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Recursos da aplicação -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="✨ Recursos Principais"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:layout_marginBottom="16dp" />
<!-- Lista de recursos -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Recurso 1 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@color/background_card">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_money"
android:background="@drawable/feature_icon_background"
android:padding="8dp"
android:layout_marginEnd="16dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="💰 Controle Total"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Registre e acompanhe todas as suas despesas"
android:textSize="14sp"
android:textColor="@color/text_secondary" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Recurso 2 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@color/background_card">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_category"
android:background="@drawable/feature_icon_background"
android:padding="8dp"
android:layout_marginEnd="16dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="📊 Categorização"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Organize por categorias e visualize padrões"
android:textSize="14sp"
android:textColor="@color/text_secondary" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Recurso 3 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:cardBackgroundColor="@color/background_card">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="40dp"
android:layout_height="40dp"
android:src="@drawable/ic_calendar"
android:background="@drawable/feature_icon_background"
android:padding="8dp"
android:layout_marginEnd="16dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="📅 Histórico Completo"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/text_primary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Acompanhe seu histórico de gastos ao longo do tempo"
android:textSize="14sp"
android:textColor="@color/text_secondary" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>
<!-- Estatísticas rápidas -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardCornerRadius="20dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@color/background_card">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="📈 Estatísticas em Tempo Real"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:layout_marginBottom="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/stat_background"
android:padding="16dp"
android:layout_marginEnd="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="💰"
android:textSize="24sp"
android:layout_marginBottom="4dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Total Gasto"
android:textSize="12sp"
android:textColor="@color/text_secondary"
android:textStyle="bold" />
<TextView
android:id="@+id/tvQuickTotal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="€0.00"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/error_color"
android:layout_marginTop="4dp" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center"
android:background="@drawable/stat_background"
android:padding="16dp"
android:layout_marginStart="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="📊"
android:textSize="24sp"
android:layout_marginBottom="4dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Despesas"
android:textSize="12sp"
android:textColor="@color/text_secondary"
android:textStyle="bold" />
<TextView
android:id="@+id/tvQuickCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/primary_color"
android:layout_marginTop="4dp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Botões de ação -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="24dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnViewExpenses"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:text="📋 Ver Despesas"
android:textSize="16sp"
android:textColor="@color/primary_color"
app:backgroundTint="@android:color/transparent"
app:strokeColor="@color/primary_color"
app:strokeWidth="2dp"
app:cornerRadius="28dp"
app:icon="@drawable/ic_list"
app:iconTint="@color/primary_color" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAddExpense"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:text=" Adicionar"
android:textSize="16sp"
android:textColor="@color/white"
app:backgroundTint="@color/primary_color"
app:cornerRadius="28dp"
app:icon="@drawable/ic_add_white"
app:iconTint="@color/white" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<!-- Floating Action Button -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabQuickAdd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
android:src="@drawable/ic_add_white"
app:backgroundTint="@color/primary_color"
app:borderWidth="0dp"
app:elevation="12dp"
app:fabSize="normal"
app:tint="@color/white"
app:rippleColor="@color/primary_dark" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/background_gradient"
android:padding="16dp">
<!-- Header simples -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="💰 Minhas Despesas"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:gravity="center"
android:layout_marginBottom="16dp" />
<!-- Estatísticas simples -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Total: "
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:gravity="center" />
<TextView
android:id="@+id/tvTotal"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="€0.00"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="@color/error_color"
android:gravity="center" />
</LinearLayout>
<!-- Lista de despesas -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewExpenses"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginBottom="16dp" />
<!-- Botões de ação -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAddExpense"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:text=" Adicionar"
android:textColor="@color/white"
app:backgroundTint="@color/primary_color"
app:cornerRadius="20dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnBack"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:text="🔙 Voltar"
android:textColor="@color/primary_color"
app:backgroundTint="@android:color/transparent"
app:strokeColor="@color/primary_color"
app:strokeWidth="2dp"
app:cornerRadius="20dp" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:padding="32dp"
android:background="@drawable/empty_state_background">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/ic_empty_wallet"
android:background="@drawable/header_icon_background"
android:padding="20dp"
android:layout_marginBottom="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="💰 Nenhuma despesa ainda"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:layout_marginBottom="8dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Toque no botão + para adicionar sua primeira despesa"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:gravity="center"
android:layout_marginBottom="16dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAddFirstExpense"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" Adicionar Despesa"
android:textColor="@color/white"
app:backgroundTint="@color/primary_color"
app:cornerRadius="20dp"
app:icon="@drawable/ic_add_white"
app:iconTint="@color/white" />
</LinearLayout>

View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
app:cardCornerRadius="16dp"
app:cardElevation="6dp"
app:cardBackgroundColor="@color/background_card"
app:strokeColor="@color/primary_light"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<!-- Header com descrição e valor -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Descrição"
android:textSize="18sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:maxLines="2"
android:ellipsize="end" />
<TextView
android:id="@+id/tvCategory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Categoria"
android:textSize="14sp"
android:textColor="@color/primary_color"
android:background="@drawable/category_chip"
android:padding="6dp"
android:layout_marginTop="4dp" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="end">
<TextView
android:id="@+id/tvAmount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="€0.00"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="@color/error_color" />
<TextView
android:id="@+id/tvDate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Data"
android:textSize="12sp"
android:textColor="@color/text_light"
android:layout_marginTop="4dp" />
</LinearLayout>
</LinearLayout>
<!-- Notas (se existirem) -->
<TextView
android:id="@+id/tvNotes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Notas"
android:textSize="14sp"
android:textColor="@color/text_secondary"
android:background="@drawable/notes_background"
android:padding="12dp"
android:layout_marginTop="8dp"
android:visibility="gone" />
<!-- Botões de ação -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="16dp"
android:gravity="end">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="✏️ Editar"
android:textSize="12sp"
android:layout_marginEnd="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:backgroundTint="@color/primary_color"
app:cornerRadius="20dp"
app:icon="@drawable/ic_edit"
app:iconSize="16dp"
app:iconTint="@color/white" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="🗑️ Excluir"
android:textSize="12sp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:backgroundTint="@color/error_color"
app:cornerRadius="20dp"
app:icon="@drawable/ic_delete"
app:iconSize="16dp"
app:iconTint="@color/white" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 982 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -0,0 +1,7 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.GestãoDeDespesas" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your dark theme here. -->
<!-- <item name="colorPrimary">@color/my_dark_primary</item> -->
</style>
</resources>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<!-- Cores principais da aplicação -->
<color name="primary_color">#6366F1</color>
<color name="primary_dark">#4F46E5</color>
<color name="primary_light">#A5B4FC</color>
<color name="accent_color">#F59E0B</color>
<!-- Cores de fundo -->
<color name="background_light">#F8FAFC</color>
<color name="background_card">#FFFFFF</color>
<color name="background_gradient_start">#667EEA</color>
<color name="background_gradient_end">#764BA2</color>
<!-- Cores de texto -->
<color name="text_primary">#1F2937</color>
<color name="text_secondary">#6B7280</color>
<color name="text_light">#9CA3AF</color>
<!-- Cores de status -->
<color name="success_color">#10B981</color>
<color name="error_color">#EF4444</color>
<color name="warning_color">#F59E0B</color>
<!-- Cores de categoria -->
<color name="category_food">#F59E0B</color>
<color name="category_transport">#3B82F6</color>
<color name="category_entertainment">#8B5CF6</color>
<color name="category_health">#EF4444</color>
<color name="category_shopping">#10B981</color>
<color name="category_other">#6B7280</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Gestão de Despesas</string>
</resources>

View File

@ -0,0 +1,9 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Base.Theme.GestãoDeDespesas" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Customize your light theme here. -->
<!-- <item name="colorPrimary">@color/my_light_primary</item> -->
</style>
<style name="Theme.GestãoDeDespesas" parent="Base.Theme.GestãoDeDespesas" />
</resources>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample backup rules file; uncomment and customize as necessary.
See https://developer.android.com/guide/topics/data/autobackup
for details.
Note: This file is ignored for devices older than API 31
See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
<!--
<include domain="sharedpref" path="."/>
<exclude domain="sharedpref" path="device.xml"/>
-->
</full-backup-content>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
Sample data extraction rules file; uncomment and customize as necessary.
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
for details.
-->
<data-extraction-rules>
<cloud-backup>
<!-- TODO: Use <include> and <exclude> to control what is backed up.
<include .../>
<exclude .../>
-->
</cloud-backup>
<!--
<device-transfer>
<include .../>
<exclude .../>
</device-transfer>
-->
</data-extraction-rules>

View File

@ -0,0 +1,17 @@
package pt.epvc.gestodedespesas;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

7
build.gradle Normal file
View File

@ -0,0 +1,7 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
// Configuração de plugins para todo o projeto
plugins {
// Plugin Android Application - versão 8.13.0 (aplicado apenas aos módulos que o solicitarem)
id 'com.android.application' version '8.13.0' apply false
}

33
gradle.properties Normal file
View File

@ -0,0 +1,33 @@
# Configurações globais do Gradle para todo o projeto
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Configurações da JVM do daemon Gradle
# Especifica os argumentos JVM usados para o processo daemon.
# Esta configuração é particularmente útil para ajustar configurações de memória.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# Configurações de build paralelo
# Quando configurado, o Gradle executará em modo paralelo incubating.
# Esta opção deve ser usada apenas com projetos desacoplados. Para mais detalhes, visite
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# Configurações AndroidX
# Estrutura de pacotes AndroidX para tornar mais claro quais pacotes são agrupados com o
# sistema operacional Android, e quais são empacotados com o APK da sua aplicação
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Configurações de recursos
# Habilita namespacing da classe R de cada biblioteca para que sua classe R inclua apenas os
# recursos declarados na própria biblioteca e nenhum das dependências da biblioteca,
# reduzindo assim o tamanho da classe R para essa biblioteca
android.nonTransitiveRClass=true
# Configurações de compilação
android.suppressUnsupportedCompileSdk=35
android.javaCompile.suppressSourceTargetDeprecationWarning=true

28
gradle/libs.versions.toml Normal file
View File

@ -0,0 +1,28 @@
# Arquivo de versões centralizadas para dependências e plugins
# Este arquivo permite gerenciar todas as versões em um local central
# Seção de versões - define as versões de todas as dependências e plugins
[versions]
agp = "8.5.2" # Android Gradle Plugin - versão estável
junit = "4.13.2" # JUnit - framework de testes unitários
junitVersion = "1.1.5" # AndroidX JUnit - extensões JUnit para Android
espressoCore = "3.5.1" # Espresso - framework de testes de interface
appcompat = "1.7.1" # AppCompat - compatibilidade com versões antigas
material = "1.10.0" # Material Design Components
activity = "1.8.2" # Activity - biblioteca base para Activities
constraintlayout = "2.1.4" # ConstraintLayout - sistema de layout com constraints
# Seção de bibliotecas - define as dependências usando as versões acima
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" } # JUnit para testes unitários
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } # Extensões JUnit Android
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } # Espresso para testes UI
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } # AppCompat
material = { group = "com.google.android.material", name = "material", version.ref = "material" } # Material Design
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" } # Activity
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } # ConstraintLayout
# Seção de plugins - define os plugins usando as versões acima
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" } # Plugin Android Application

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,16 @@
# Configurações do Gradle Wrapper
# Este arquivo define qual versão do Gradle será usada para construir o projeto
# Data de criação do arquivo
#Thu Oct 23 15:58:53 WEST 2025
# Configurações de distribuição do Gradle
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
# URL da distribuição Gradle - versão 8.14 (suporte Java 24)
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
# Configurações de armazenamento
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

30
settings.gradle Normal file
View File

@ -0,0 +1,30 @@
// Configurações do projeto Gradle - define repositórios e módulos
// Configuração de gerenciamento de plugins
pluginManagement {
repositories {
// Repositório Google - plugins Android e Google
google {
content {
includeGroupByRegex 'com\\.android.*' // Inclui grupos Android
includeGroupByRegex 'com\\.google.*' // Inclui grupos Google
includeGroupByRegex 'androidx.*' // Inclui grupos AndroidX
}
}
mavenCentral() // Repositório Maven Central
gradlePluginPortal() // Portal de plugins Gradle
}
}
// Configuração de resolução de dependências
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) // Falha se repositórios forem definidos em módulos
repositories {
google() // Repositório Google para dependências Android
mavenCentral() // Repositório Maven Central para dependências Java
}
}
// Configuração do projeto raiz
rootProject.name = 'Gestão de Despesas' // Nome do projeto
include ':app' // Inclui o módulo app no build