first commit
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
51
app/build.gradle
Normal 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
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
34
app/src/main/AndroidManifest.xml
Normal 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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
107
app/src/main/java/pt/epvc/gestodedespesas/Expense.java
Normal 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
|
||||
}
|
||||
}
|
||||
161
app/src/main/java/pt/epvc/gestodedespesas/ExpenseAdapter.java
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
160
app/src/main/java/pt/epvc/gestodedespesas/MainActivity.java
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
11
app/src/main/res/drawable/average_background.xml
Normal 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>
|
||||
7
app/src/main/res/drawable/background_gradient.xml
Normal 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>
|
||||
15
app/src/main/res/drawable/card_animation.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/category_chip.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/empty_state_background.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/fab_gradient.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/feature_icon_background.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/header_icon_background.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_add_white.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_calendar.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_cancel.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_category.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_delete.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_description.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_edit.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_empty_wallet.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_euro.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_filter.xml
Normal 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>
|
||||
170
app/src/main/res/drawable/ic_launcher_background.xml
Normal 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>
|
||||
30
app/src/main/res/drawable/ic_launcher_foreground.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_list.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_money.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_notes.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_rocket.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_save.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/ic_wallet.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/icon_background.xml
Normal 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>
|
||||
8
app/src/main/res/drawable/notes_background.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/stat_background.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/stat_card_background.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/total_background.xml
Normal 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>
|
||||
11
app/src/main/res/drawable/welcome_icon_background.xml
Normal 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>
|
||||
184
app/src/main/res/layout/activity_add_expense.xml
Normal 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>
|
||||
247
app/src/main/res/layout/activity_expenses_list.xml
Normal 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>
|
||||
436
app/src/main/res/layout/activity_main.xml
Normal 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>
|
||||
91
app/src/main/res/layout/activity_simple_expenses.xml
Normal 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>
|
||||
48
app/src/main/res/layout/empty_state.xml
Normal 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>
|
||||
139
app/src/main/res/layout/item_expense.xml
Normal 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>
|
||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal 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>
|
||||
6
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal 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>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 982 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
7
app/src/main/res/values-night/themes.xml
Normal 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>
|
||||
35
app/src/main/res/values/colors.xml
Normal 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>
|
||||
3
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Gestão de Despesas</string>
|
||||
</resources>
|
||||
9
app/src/main/res/values/themes.xml
Normal 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>
|
||||
13
app/src/main/res/xml/backup_rules.xml
Normal 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>
|
||||
19
app/src/main/res/xml/data_extraction_rules.xml
Normal 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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||