Alterei o design para ficar m ais fluido mas falta alterar a cor dos botões e adicionar um botão de voltar no activity_expenses_list.xml e tambem adicionei mais explicações

main
240422 2025-11-03 12:09:35 +00:00
parent 249eb6f6f2
commit f021060585
9 changed files with 145 additions and 153 deletions

View File

@ -8,12 +8,16 @@ import androidx.appcompat.app.AppCompatActivity; // Importa classe base para Act
import com.google.android.material.button.MaterialButton; // Importa botão Material Design 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 com.google.android.material.textfield.TextInputEditText; // Importa campo de texto Material Design
import com.google.android.material.textfield.MaterialAutoCompleteTextView; // Campo com menu suspenso
import java.text.SimpleDateFormat; // Importa classe para formatação de datas import java.text.SimpleDateFormat; // Importa classe para formatação de datas
import java.util.Date; // Importa classe para trabalhar com datas import java.util.Date; // Importa classe para trabalhar com datas
import java.util.Locale; // Importa classe para configurações de localização import java.util.Locale; // Importa classe para configurações de localização
import java.util.Arrays; // Utilitário para trabalhar com arrays
import java.util.ArrayList; // Lista mutável
import pt.epvc.gestodedespesas.data.DatabaseHelper; // DatabaseHelper movido para pacote data import pt.epvc.gestodedespesas.data.DatabaseHelper; // DatabaseHelper movido para pacote data
import android.widget.ArrayAdapter; // Adapter para preencher o dropdown
/** /**
* AddExpenseActivity - Tela de Adicionar/Editar Despesas * AddExpenseActivity - Tela de Adicionar/Editar Despesas
@ -30,7 +34,8 @@ import pt.epvc.gestodedespesas.data.DatabaseHelper; // DatabaseHelper movido par
public class AddExpenseActivity extends AppCompatActivity { // Declara classe que estende AppCompatActivity public class AddExpenseActivity extends AppCompatActivity { // Declara classe que estende AppCompatActivity
// Views da interface - campos de entrada do formulário // 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 TextInputEditText etDescription, etAmount, etDate, etNotes; // Declara campos de texto para entrada de dados
private MaterialAutoCompleteTextView etCategory; // Campo de categoria com dropdown
private MaterialButton btnSave, btnCancel; // Declara botões de ação private MaterialButton btnSave, btnCancel; // Declara botões de ação
// Objetos para funcionalidade // Objetos para funcionalidade
@ -59,6 +64,8 @@ public class AddExpenseActivity extends AppCompatActivity { // Declara classe qu
// Para nova despesa, define a data atual como padrão // Para nova despesa, define a data atual como padrão
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()); // Cria formato de data brasileiro 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 etDate.setText(sdf.format(new Date())); // Define data atual no campo de data
// Categoria padrão opcional (ex.: "Outros") para facilitar
etCategory.setText("Outros", false);
} }
} }
@ -70,13 +77,26 @@ public class AddExpenseActivity extends AppCompatActivity { // Declara classe qu
// Conecta os campos de entrada de texto // Conecta os campos de entrada de texto
etDescription = findViewById(R.id.etDescription); // Busca campo de descrição por ID e atribui à variável 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 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 etCategory = findViewById(R.id.etCategory); // Busca campo de categoria (dropdown) por ID
etDate = findViewById(R.id.etDate); // Busca campo de data 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 etNotes = findViewById(R.id.etNotes); // Busca campo de notas por ID e atribui à variável
// Conecta os botões de ação // Conecta os botões de ação
btnSave = findViewById(R.id.btnSave); // Busca botão salvar por ID e atribui à variável 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 btnCancel = findViewById(R.id.btnCancel); // Busca botão cancelar por ID e atribui à variável
// Configura o dropdown de categorias com as mesmas opções do filtro
String[] allCategories = getResources().getStringArray(R.array.expense_categories); // Inclui "Todas"
ArrayList<String> formCategories = new ArrayList<>(Arrays.asList(allCategories));
formCategories.remove("Todas"); // Remove "Todas" do formulário (apenas para filtro)
ArrayAdapter<String> categoryAdapter = new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
formCategories
);
etCategory.setAdapter(categoryAdapter);
etCategory.setOnClickListener(v -> etCategory.showDropDown()); // Abre ao tocar
etCategory.setOnFocusChangeListener((v, hasFocus) -> { if (hasFocus) etCategory.showDropDown(); });
} }
/** /**

View File

@ -15,7 +15,6 @@ import androidx.core.view.WindowInsetsCompat; // Insets de janela
import androidx.recyclerview.widget.LinearLayoutManager; // Layout vertical import androidx.recyclerview.widget.LinearLayoutManager; // Layout vertical
import androidx.recyclerview.widget.RecyclerView; // Lista eficiente 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 com.google.android.material.button.MaterialButton; // Botão Material
import java.text.DecimalFormat; // Formatar valores monetários import java.text.DecimalFormat; // Formatar valores monetários
@ -34,13 +33,12 @@ public class ExpensesListActivity extends AppCompatActivity implements ExpenseAd
private RecyclerView recyclerViewExpenses; // Lista de despesas private RecyclerView recyclerViewExpenses; // Lista de despesas
private TextView tvTotal, tvExpenseCount, tvAverage; // Indicadores de total, contagem e média 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 MaterialButton btnFilter; // Botão para filtrar por categoria
private DatabaseHelper databaseHelper; // Acesso ao SQLite private DatabaseHelper databaseHelper; // Acesso ao SQLite
private ExpenseAdapter expenseAdapter; // Adapter do RecyclerView private ExpenseAdapter expenseAdapter; // Adapter do RecyclerView
private List<Expense> expenseList; // Dados das despesas private List<Expense> expenseList; // Dados das despesas
private DecimalFormat currencyFormat = new DecimalFormat("€#,##0.00"); // Formatação em euro 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) private View emptyStateView; // Container do empty state incluído no layout
/** /**
* onCreate: configura layout, Edge-to-Edge e listeners de insets, inicializa * onCreate: configura layout, Edge-to-Edge e listeners de insets, inicializa
@ -50,8 +48,19 @@ public class ExpensesListActivity extends AppCompatActivity implements ExpenseAd
protected void onCreate(Bundle savedInstanceState) { // Ciclo de vida: criação da Activity protected void onCreate(Bundle savedInstanceState) { // Ciclo de vida: criação da Activity
super.onCreate(savedInstanceState); // Chama implementação base super.onCreate(savedInstanceState); // Chama implementação base
EdgeToEdge.enable(this); // Ativa layout de ponta a ponta EdgeToEdge.enable(this); // Ativa layout de ponta a ponta
databaseHelper = new DatabaseHelper(this); // Instancia acesso ao DB (SQLiteHelper) setContentView(R.layout.activity_expenses_list); // Usa layout principal sempre
renderLayoutByData(); // Decide e aplica o layout conforme existência de despesas try {
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
} catch (Exception ignored) {}
databaseHelper = new DatabaseHelper(this); // Instancia acesso ao DB
initializeViews(); // Faz bind das views (inclui empty state)
setupRecyclerView(); // Configura lista
setupClickListeners(); // Configura cliques (filtro e empty state)
loadExpenses(); // Carrega dados iniciais
} }
/** /**
@ -63,8 +72,8 @@ public class ExpensesListActivity extends AppCompatActivity implements ExpenseAd
tvTotal = findViewById(R.id.tvTotal); // Texto do total gasto tvTotal = findViewById(R.id.tvTotal); // Texto do total gasto
tvExpenseCount = findViewById(R.id.tvExpenseCount); // Texto da contagem tvExpenseCount = findViewById(R.id.tvExpenseCount); // Texto da contagem
tvAverage = findViewById(R.id.tvAverage); // Texto da média tvAverage = findViewById(R.id.tvAverage); // Texto da média
fabAddExpense = findViewById(R.id.fabAddExpense); // FAB adicionar
btnFilter = findViewById(R.id.btnFilter); // Botão filtrar btnFilter = findViewById(R.id.btnFilter); // Botão filtrar
emptyStateView = findViewById(R.id.emptyStateContainer); // Empty state incluído
} catch (Exception e) { } catch (Exception e) {
Toast.makeText(this, "Erro ao inicializar views: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro Toast.makeText(this, "Erro ao inicializar views: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro
} }
@ -92,16 +101,6 @@ public class ExpensesListActivity extends AppCompatActivity implements ExpenseAd
*/ */
private void setupClickListeners() { private void setupClickListeners() {
try { 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 if (btnFilter != null) { // Verifica existência do botão
btnFilter.setOnClickListener(new View.OnClickListener() { // Clique do filtro btnFilter.setOnClickListener(new View.OnClickListener() { // Clique do filtro
@Override @Override
@ -110,6 +109,18 @@ public class ExpensesListActivity extends AppCompatActivity implements ExpenseAd
} }
}); });
} }
// Botões do empty state (se presentes no include)
View add = findViewById(R.id.btnAddFirstExpense);
if (add != null) {
add.setOnClickListener(v -> {
Intent intent = new Intent(ExpensesListActivity.this, AddExpenseActivity.class);
startActivityForResult(intent, 1);
});
}
View back = findViewById(R.id.btnBackFromEmpty);
if (back != null) {
back.setOnClickListener(v -> finish());
}
} catch (Exception e) { } catch (Exception e) {
Toast.makeText(this, "Erro ao configurar click listeners: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro Toast.makeText(this, "Erro ao configurar click listeners: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro
} }
@ -122,20 +133,15 @@ public class ExpensesListActivity extends AppCompatActivity implements ExpenseAd
private void loadExpenses() { private void loadExpenses() {
try { try {
expenseList = databaseHelper.getAllExpenses(); // Puxa despesas do DB expenseList = databaseHelper.getAllExpenses(); // Puxa despesas do DB
boolean hasData = expenseList != null && !expenseList.isEmpty(); if (expenseAdapter != null) { // Atualiza a lista
if (!hasData) { expenseAdapter.updateExpenses(expenseList);
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 updateTotal(); // Recalcula indicadores
// Mostra/oculta o empty state no fim da tela
boolean isEmpty = expenseList == null || expenseList.isEmpty();
if (emptyStateView != null) {
emptyStateView.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
}
} catch (Exception e) { } catch (Exception e) {
Toast.makeText(this, "Erro ao carregar despesas: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro Toast.makeText(this, "Erro ao carregar despesas: " + e.getMessage(), Toast.LENGTH_LONG).show(); // Feedback de erro
} }
@ -178,14 +184,11 @@ public class ExpensesListActivity extends AppCompatActivity implements ExpenseAd
} else { // Categoria específica } else { // Categoria específica
String category = categories[which]; // Nome da categoria String category = categories[which]; // Nome da categoria
expenseList = databaseHelper.getExpensesByCategory(category); // Busca filtrada expenseList = databaseHelper.getExpensesByCategory(category); // Busca filtrada
if (expenseList == null || expenseList.isEmpty()) { expenseAdapter.updateExpenses(expenseList); // Atualiza adapter (lista pode ficar vazia)
renderEmptyLayout();
} else {
if (isShowingEmptyState) {
renderMainLayout();
}
expenseAdapter.updateExpenses(expenseList); // Atualiza adapter
updateTotal(); // Recalcula totais com filtro updateTotal(); // Recalcula totais com filtro
boolean isEmpty = expenseList == null || expenseList.isEmpty();
if (emptyStateView != null) {
emptyStateView.setVisibility(isEmpty ? View.VISIBLE : View.GONE);
} }
} }
}) })
@ -230,54 +233,5 @@ public class ExpensesListActivity extends AppCompatActivity implements ExpenseAd
.show(); // Exibe diálogo .show(); // Exibe diálogo
} }
/** // Métodos de alternância de layout foram substituídos por um include do empty state no XML
* Decide qual layout usar com base nos dados atuais do DB.
*/
private void renderLayoutByData() { // Decide qual layout mostrar com base nos dados
try {
List<Expense> initial = databaseHelper.getAllExpenses(); // Consulta inicial ao DB
if (initial == null || initial.isEmpty()) { // Sem dados?
renderEmptyLayout(); // Mostra estado vazio
} else { // Há dados
expenseList = initial; // Mantém em memória
renderMainLayout(); // Mostra lista principal
}
} catch (Exception e) { // Qualquer erro
renderEmptyLayout(); // Fallback: evita crash mostrando estado vazio
}
}
/**
* Mostra o layout principal de lista, inicializa views/adapter e listeners.
*/
private void renderMainLayout() { // Exibe layout principal de lista
setContentView(R.layout.activity_expenses_list); // Aplica XML de lista
isShowingEmptyState = false; // Marca que a lista está ativa
try {
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { // Listener de insets
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); // Áreas das barras
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); // Padding seguro
return insets; // Continua propagação
});
} catch (Exception ignored) { } // Se não existir view com id main, ignora
initializeViews(); // Associa views do layout
setupRecyclerView(); // Configura RecyclerView e adapter
setupClickListeners(); // Registra cliques (FAB e filtro)
updateTotal(); // Atualiza totais na UI
}
/**
* Mostra o layout de estado vazio e configura o botão de adicionar.
*/
private void renderEmptyLayout() { // Exibe layout de estado vazio
setContentView(R.layout.empty_state); // Aplica XML vazio
isShowingEmptyState = true; // Marca que estado vazio está ativo
View add = findViewById(R.id.btnAddFirstExpense); // Botão "Adicionar Despesa"
if (add != null) { // Se existir no layout
add.setOnClickListener(v -> { // Ao clicar
Intent intent = new Intent(ExpensesListActivity.this, AddExpenseActivity.class); // Abre tela de adição
startActivityForResult(intent, 1); // Aguarda resultado para recarregar
});
}
}
} }

View File

@ -112,8 +112,8 @@ public class MainActivity extends AppCompatActivity { // Declara classe que este
btnViewExpenses.setOnClickListener(new View.OnClickListener() { // Define listener de clique para o botão "Ver Despesas" btnViewExpenses.setOnClickListener(new View.OnClickListener() { // Define listener de clique para o botão "Ver Despesas"
@Override // Sobrescreve método da interface @Override // Sobrescreve método da interface
public void onClick(View v) { // Método chamado quando botão é clicado public void onClick(View v) { // Método chamado quando botão é clicado
// Navega para a tela simplificada de lista de despesas // Navega para a tela avançada de lista de despesas (usa empty_state e activity_expenses_list)
Intent intent = new Intent(MainActivity.this, SimpleExpensesActivity.class); // Cria Intent para navegar para SimpleExpensesActivity Intent intent = new Intent(MainActivity.this, ExpensesListActivity.class); // Cria Intent para ExpensesListActivity
startActivity(intent); // Inicia Activity sem esperar resultado startActivity(intent); // Inicia Activity sem esperar resultado
} }
}); });

View File

@ -6,8 +6,8 @@ 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.SQLiteDatabase; // Objeto que permite ler/escrever no banco
import android.database.sqlite.SQLiteOpenHelper; // Classe que ajuda a criar e atualizar o 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.ArrayList; // Lista de tamanho variável (pode crescer conforme necessário)
import java.util.List; // Tipo "lista" em Java import java.util.List; // Tipo "lista" em Java (coleção de itens)
import pt.epvc.gestodedespesas.Expense; // Nossa classe que representa uma despesa import pt.epvc.gestodedespesas.Expense; // Nossa classe que representa uma despesa
@ -23,24 +23,24 @@ public class DatabaseHelper extends SQLiteOpenHelper { // Helper do SQLite
private static final int DATABASE_VERSION = 1; // Versão do banco (muda quando a estrutura mudar) 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) // Nome da tabela e das colunas (os "campos" da tabela)
private static final String TABLE_EXPENSES = "expenses"; private static final String TABLE_EXPENSES = "expenses"; // Nome da tabela no banco
private static final String COLUMN_ID = "id"; private static final String COLUMN_ID = "id"; // Coluna: identificador único da despesa
private static final String COLUMN_DESCRIPTION = "description"; private static final String COLUMN_DESCRIPTION = "description"; // Coluna: descrição do gasto
private static final String COLUMN_AMOUNT = "amount"; private static final String COLUMN_AMOUNT = "amount"; // Coluna: valor do gasto
private static final String COLUMN_CATEGORY = "category"; private static final String COLUMN_CATEGORY = "category"; // Coluna: categoria do gasto
private static final String COLUMN_DATE = "date"; private static final String COLUMN_DATE = "date"; // Coluna: data do gasto (texto formatado)
private static final String COLUMN_NOTES = "notes"; private static final String COLUMN_NOTES = "notes"; // Coluna: notas/opcional
// Comando que cria a tabela de despesas (rodado somente na primeira vez) // Comando que cria a tabela de despesas (rodado somente na primeira vez)
private static final String CREATE_TABLE_EXPENSES = private static final String CREATE_TABLE_EXPENSES = // Texto do comando para criar a tabela
"CREATE TABLE " + TABLE_EXPENSES + "(" + "CREATE TABLE " + TABLE_EXPENSES + "(" + // Começa criando a tabela "expenses"
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + // id numérico que cresce sozinho
COLUMN_DESCRIPTION + " TEXT NOT NULL," + COLUMN_DESCRIPTION + " TEXT NOT NULL," + // descrição obrigatória
COLUMN_AMOUNT + " REAL NOT NULL," + COLUMN_AMOUNT + " REAL NOT NULL," + // valor obrigatório (número)
COLUMN_CATEGORY + " TEXT NOT NULL," + COLUMN_CATEGORY + " TEXT NOT NULL," + // categoria obrigatória
COLUMN_DATE + " TEXT NOT NULL," + COLUMN_DATE + " TEXT NOT NULL," + // data obrigatória (guardada como texto)
COLUMN_NOTES + " TEXT" + COLUMN_NOTES + " TEXT" + // notas opcionais
")"; ")"; // fecha o comando
public DatabaseHelper(Context context) { // Chamado quando precisamos usar o banco 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 super(context, DATABASE_NAME, null, DATABASE_VERSION); // Diz o nome e a versão do banco
@ -73,11 +73,11 @@ public class DatabaseHelper extends SQLiteOpenHelper { // Helper do SQLite
public Expense getExpense(int id) { // Busca uma despesa específica pelo id public Expense getExpense(int id) { // Busca uma despesa específica pelo id
SQLiteDatabase db = this.getReadableDatabase(); // Abre o banco para leitura SQLiteDatabase db = this.getReadableDatabase(); // Abre o banco para leitura
Cursor cursor = db.query( Cursor cursor = db.query( // Faz uma consulta procurando pelo id
TABLE_EXPENSES, TABLE_EXPENSES, // na tabela de despesas
new String[]{COLUMN_ID, COLUMN_DESCRIPTION, COLUMN_AMOUNT, COLUMN_CATEGORY, COLUMN_DATE, COLUMN_NOTES}, new String[]{COLUMN_ID, COLUMN_DESCRIPTION, COLUMN_AMOUNT, COLUMN_CATEGORY, COLUMN_DATE, COLUMN_NOTES}, // colunas desejadas
COLUMN_ID + "=?", COLUMN_ID + "=?", // condição: id igual ao informado
new String[]{String.valueOf(id)}, null, null, null, null new String[]{String.valueOf(id)}, null, null, null, null // valor do id
); );
if (cursor != null && cursor.moveToFirst()) { // Se achou o registro if (cursor != null && cursor.moveToFirst()) { // Se achou o registro
@ -100,9 +100,9 @@ public class DatabaseHelper extends SQLiteOpenHelper { // Helper do SQLite
public List<Expense> getAllExpenses() { // Busca todas as despesas, mais recentes primeiro public List<Expense> getAllExpenses() { // Busca todas as despesas, mais recentes primeiro
List<Expense> expenseList = new ArrayList<>(); // Cria uma lista vazia List<Expense> expenseList = new ArrayList<>(); // Cria uma lista vazia
String selectQuery = "SELECT * FROM " + TABLE_EXPENSES + " ORDER BY " + COLUMN_DATE + " DESC"; // Consulta String selectQuery = "SELECT * FROM " + TABLE_EXPENSES + " ORDER BY " + COLUMN_DATE + " DESC"; // Consulta: tudo, ordenado pela data (mais recente primeiro)
SQLiteDatabase db = this.getWritableDatabase(); // Abre o banco SQLiteDatabase db = this.getWritableDatabase(); // Abre o banco
Cursor cursor = db.rawQuery(selectQuery, null); // Executa a consulta Cursor cursor = db.rawQuery(selectQuery, null); // Executa a consulta e devolve um "cursor" para navegar
if (cursor.moveToFirst()) { // Se tem pelo menos uma linha if (cursor.moveToFirst()) { // Se tem pelo menos uma linha
do { // Repete para cada linha do { // Repete para cada linha
@ -157,10 +157,10 @@ public class DatabaseHelper extends SQLiteOpenHelper { // Helper do SQLite
List<Expense> expenseList = new ArrayList<>(); // Lista vazia List<Expense> expenseList = new ArrayList<>(); // Lista vazia
SQLiteDatabase db = this.getReadableDatabase(); // Abre para leitura SQLiteDatabase db = this.getReadableDatabase(); // Abre para leitura
Cursor cursor = db.query( // Faz a consulta com filtro de categoria Cursor cursor = db.query( // Faz a consulta com filtro de categoria
TABLE_EXPENSES, TABLE_EXPENSES, // tabela "expenses"
new String[]{COLUMN_ID, COLUMN_DESCRIPTION, COLUMN_AMOUNT, COLUMN_CATEGORY, COLUMN_DATE, COLUMN_NOTES}, new String[]{COLUMN_ID, COLUMN_DESCRIPTION, COLUMN_AMOUNT, COLUMN_CATEGORY, COLUMN_DATE, COLUMN_NOTES}, // colunas
COLUMN_CATEGORY + "=?", COLUMN_CATEGORY + "=?", // condição de filtro
new String[]{category}, null, null, COLUMN_DATE + " DESC", null new String[]{category}, null, null, COLUMN_DATE + " DESC", null // valor do filtro e ordenação
); );
if (cursor.moveToFirst()) { // Se achou resultados if (cursor.moveToFirst()) { // Se achou resultados

View File

@ -89,14 +89,17 @@
android:hint="Categoria" android:hint="Categoria"
app:startIconDrawable="@drawable/ic_category" app:startIconDrawable="@drawable/ic_category"
app:boxStrokeColor="@color/primary_color" app:boxStrokeColor="@color/primary_color"
app:hintTextColor="@color/primary_color"> app:hintTextColor="@color/primary_color"
app:endIconMode="dropdown_menu">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.MaterialAutoCompleteTextView
android:id="@+id/etCategory" android:id="@+id/etCategory"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="text" android:inputType="none"
android:textColor="@color/text_primary" /> android:textColor="@color/text_primary"
android:hint="Selecione uma categoria"
android:entries="@array/expense_categories" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>

View File

@ -225,23 +225,16 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="80dp" /> android:layout_marginBottom="80dp" />
<!-- Empty state embutido (mostrado quando não há despesas) -->
<include
android:id="@+id/emptyStateContainer"
layout="@layout/empty_state"
android:visibility="gone" />
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </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> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -33,40 +33,40 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical" android:orientation="vertical"
android:padding="32dp" android:padding="32dp">
android:gravity="center">
<!-- Ícone principal --> <!-- Ícone principal -->
<ImageView <ImageView
android:layout_width="120dp" android:layout_width="120dp"
android:layout_height="120dp" android:layout_height="120dp"
android:src="@drawable/ic_wallet" android:layout_marginBottom="24dp"
android:background="@drawable/welcome_icon_background" android:background="@drawable/welcome_icon_background"
android:padding="24dp" android:padding="24dp"
android:layout_marginBottom="24dp" /> android:src="@drawable/ic_wallet" />
<!-- Título principal --> <!-- Título principal -->
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="💰 Gestão de Despesas" android:layout_marginBottom="12dp"
android:textSize="32sp"
android:textStyle="bold"
android:textColor="@color/text_primary"
android:gravity="center" android:gravity="center"
android:layout_marginBottom="12dp" /> android:text="💰 Gestão de Despesas"
android:textColor="@color/text_primary"
android:textSize="32sp"
android:textStyle="bold" />
<!-- Subtítulo --> <!-- Subtítulo -->
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Controle seus gastos de forma inteligente e organizada" android:layout_marginBottom="24dp"
android:textSize="16sp"
android:textColor="@color/text_secondary"
android:gravity="center" android:gravity="center"
android:lineSpacingExtra="4dp" android:lineSpacingExtra="4dp"
android:layout_marginBottom="24dp" /> android:text="Controle seus gastos de forma inteligente e organizada"
android:textColor="@color/text_secondary"
android:textSize="16sp" />
<!-- Botão principal --> <!-- Botão principal -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
@ -74,14 +74,14 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="56dp"
android:text="🚀 Começar Agora" android:text="🚀 Começar Agora"
android:textSize="18sp"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="18sp"
app:backgroundTint="@color/primary_color" app:backgroundTint="@color/primary_color"
app:cornerRadius="28dp" app:cornerRadius="28dp"
app:elevation="6dp"
app:icon="@drawable/ic_rocket" app:icon="@drawable/ic_rocket"
app:iconTint="@color/white"
app:iconSize="24dp" app:iconSize="24dp"
app:elevation="6dp" /> app:iconTint="@color/white" />
</LinearLayout> </LinearLayout>

View File

@ -45,4 +45,16 @@
app:icon="@drawable/ic_add_white" app:icon="@drawable/ic_add_white"
app:iconTint="@color/white" /> app:iconTint="@color/white" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnBackFromEmpty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="🔙 Voltar"
android:textColor="@color/primary_color"
app:backgroundTint="@android:color/transparent"
app:strokeColor="@color/primary_color"
app:strokeWidth="2dp"
app:cornerRadius="20dp" />
</LinearLayout> </LinearLayout>

View File

@ -1,3 +1,13 @@
<resources> <resources>
<string name="app_name">Gestão de Despesas</string> <string name="app_name">Gestão de Despesas</string>
<string-array name="expense_categories">
<item>Todas</item>
<item>Alimentação</item>
<item>Transporte</item>
<item>Entretenimento</item>
<item>Saúde</item>
<item>Compras</item>
<item>Outros</item>
</string-array>
</resources> </resources>