tudo a andar rapido vamossss

This commit is contained in:
2026-04-23 16:05:48 +01:00
parent 7a4bf84d42
commit 9a56ecd1dd
21 changed files with 990 additions and 3 deletions

View File

@@ -17,6 +17,25 @@ import com.example.lifegrid.menu.DocumentosFragment;
import com.example.lifegrid.menu.GraficosFragment; import com.example.lifegrid.menu.GraficosFragment;
import com.example.lifegrid.menu.MetasFragment; import com.example.lifegrid.menu.MetasFragment;
import com.example.lifegrid.menu.TransacoesFragment; import com.example.lifegrid.menu.TransacoesFragment;
import com.example.lifegrid.models.Transacao;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import androidx.annotation.NonNull;
import java.util.Calendar;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
/** /**
* TelaInicialActivity funciona como a janela mestre (Container) de navegação da aplicação. * TelaInicialActivity funciona como a janela mestre (Container) de navegação da aplicação.
@@ -33,6 +52,13 @@ public class TelaInicialActivity extends AppCompatActivity {
private TextView tvTransacoes3; private TextView tvTransacoes3;
private TextView tvValor4; private TextView tvValor4;
private TextView tvTransacoes4; private TextView tvTransacoes4;
private TextView tvTitulo;
private TextView tvTitulo2;
private Spinner spinnerMes;
private Spinner spinnerAno;
private String[] meses = {"Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"};
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -45,6 +71,22 @@ public class TelaInicialActivity extends AppCompatActivity {
return insets; return insets;
}); });
tvValor = findViewById(R.id.tvValor);
tvTransacoes = findViewById(R.id.tvTransacoes);
tvValor2 = findViewById(R.id.tvValor2);
tvTransacoes2 = findViewById(R.id.tvTransacoes2);
tvValor3 = findViewById(R.id.tvValor3);
tvTransacoes3 = findViewById(R.id.tvTransacoes3);
tvValor4 = findViewById(R.id.tvValor4);
tvTransacoes4 = findViewById(R.id.tvTransacoes4);
tvTitulo = findViewById(R.id.tvTitulo);
tvTitulo2 = findViewById(R.id.tvTitulo2);
spinnerMes = findViewById(R.id.spinnerMes);
spinnerAno = findViewById(R.id.spinnerAno);
setupSpinners();
Fragment transacoesFragment2 = new TransacoesFragment(); Fragment transacoesFragment2 = new TransacoesFragment();
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentContainerView, transacoesFragment2) .replace(R.id.fragmentContainerView, transacoesFragment2)
@@ -93,4 +135,108 @@ public class TelaInicialActivity extends AppCompatActivity {
.commit(); .commit();
}); });
} }
private void setupSpinners() {
ArrayAdapter<String> mesAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, meses);
mesAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerMes.setAdapter(mesAdapter);
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
List<String> anos = new ArrayList<>();
for (int i = currentYear - 5; i <= currentYear + 5; i++) {
anos.add(String.valueOf(i));
}
ArrayAdapter<String> anoAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, anos);
anoAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerAno.setAdapter(anoAdapter);
// Set current month and year
spinnerMes.setSelection(Calendar.getInstance().get(Calendar.MONTH));
spinnerAno.setSelection(anos.indexOf(String.valueOf(currentYear)));
AdapterView.OnItemSelectedListener listener = new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, android.view.View view, int position, long id) {
carregarDados();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {}
};
spinnerMes.setOnItemSelectedListener(listener);
spinnerAno.setOnItemSelectedListener(listener);
}
private void carregarDados() {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user == null) return;
int mesSelecionado = spinnerMes.getSelectedItemPosition() + 1; // 1 to 12
String anoSelecionado = spinnerAno.getSelectedItem().toString();
String mesNome = meses[spinnerMes.getSelectedItemPosition()];
tvTitulo.setText("Receitas (" + mesNome + " " + anoSelecionado + ")");
tvTitulo2.setText("Despesas (" + mesNome + " " + anoSelecionado + ")");
DatabaseReference transacoesRef = FirebaseDatabase.getInstance().getReference()
.child("users").child(user.getUid()).child("transacoes");
transacoesRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
double totalReceitas = 0;
double totalDespesas = 0;
int countReceitas = 0;
int countDespesas = 0;
for (DataSnapshot ds : snapshot.getChildren()) {
Transacao transacao = ds.getValue(Transacao.class);
if (transacao != null && transacao.getData() != null) {
String[] parts = transacao.getData().split("/");
if (parts.length == 3) {
try {
int mesTransacao = Integer.parseInt(parts[1]);
String anoTransacao = parts[2];
if (mesTransacao == mesSelecionado && anoTransacao.equals(anoSelecionado)) {
double valor = Double.parseDouble(transacao.getValor().replace(",", "."));
if ("Receita".equals(transacao.getTipo())) {
totalReceitas += valor;
countReceitas++;
} else if ("Despesa".equals(transacao.getTipo())) {
totalDespesas += valor;
countDespesas++;
}
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
}
tvValor.setText(String.format(Locale.getDefault(), "%.2f€", totalReceitas));
tvTransacoes.setText(countReceitas + " transações");
tvValor2.setText(String.format(Locale.getDefault(), "%.2f€", totalDespesas));
tvTransacoes2.setText(countDespesas + " transações");
double saldo = totalReceitas - totalDespesas;
tvValor3.setText(String.format(Locale.getDefault(), "%.2f€", saldo));
if (saldo >= 0) {
tvTransacoes3.setText("Poupança positiva");
tvTransacoes3.setTextColor(android.graphics.Color.parseColor("#8E8E8E"));
} else {
tvTransacoes3.setText("Poupança negativa");
tvTransacoes3.setTextColor(android.graphics.Color.parseColor("#FF0000"));
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(TelaInicialActivity.this, "Erro ao carregar dados", Toast.LENGTH_SHORT).show();
}
});
}
} }

View File

@@ -0,0 +1,144 @@
package com.example.lifegrid.adapters;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.lifegrid.R;
import com.example.lifegrid.models.Meta;
import com.google.android.material.button.MaterialButton;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
public class MetaAdapter extends RecyclerView.Adapter<MetaAdapter.MetaViewHolder> {
private List<Meta> metasList;
private OnItemClickListener listener;
public interface OnItemClickListener {
void onDeleteClick(Meta meta);
void onAddValueClick(Meta meta, double valorAdicional, EditText editText);
}
public MetaAdapter(List<Meta> metasList, OnItemClickListener listener) {
this.metasList = metasList;
this.listener = listener;
}
@NonNull
@Override
public MetaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_meta, parent, false);
return new MetaViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MetaViewHolder holder, int position) {
Meta meta = metasList.get(position);
holder.tvNome.setText(meta.getNome());
holder.tvCategoria.setText(meta.getCategoria());
double valorObjetivo = 0;
double valorGuardado = 0;
try {
valorObjetivo = Double.parseDouble(meta.getValor().replace(",", "."));
if (meta.getValorGuardado() != null && !meta.getValorGuardado().isEmpty()) {
valorGuardado = Double.parseDouble(meta.getValorGuardado().replace(",", "."));
}
} catch (NumberFormatException e) {
e.printStackTrace();
}
holder.tvValorStatus.setText(String.format(Locale.getDefault(), "%.2f€ / %.2f€", valorGuardado, valorObjetivo));
int progress = 0;
if (valorObjetivo > 0) {
progress = (int) ((valorGuardado / valorObjetivo) * 100);
if (progress > 100) progress = 100;
}
holder.pbProgresso.setProgress(progress);
holder.tvPercentagem.setText(progress + ".0%");
// Calculate days remaining
long daysRemaining = 0;
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault());
try {
Date goalDate = sdf.parse(meta.getData());
if (goalDate != null) {
long diff = goalDate.getTime() - new Date().getTime();
daysRemaining = diff / (1000 * 60 * 60 * 24);
}
} catch (ParseException e) {
e.printStackTrace();
}
if (daysRemaining >= 0) {
holder.tvDiasRestantes.setText("Faltam " + daysRemaining + " dias");
} else {
holder.tvDiasRestantes.setText("Atrasado " + Math.abs(daysRemaining) + " dias");
}
holder.ivDelete.setOnClickListener(v -> {
if (listener != null) {
listener.onDeleteClick(meta);
}
});
holder.btnAddValor.setOnClickListener(v -> {
if (listener != null) {
String input = holder.etAdicionarValor.getText().toString().trim();
if (!input.isEmpty()) {
try {
double valorAdicional = Double.parseDouble(input);
listener.onAddValueClick(meta, valorAdicional, holder.etAdicionarValor);
} catch (NumberFormatException e) {
Toast.makeText(v.getContext(), "Valor inválido", Toast.LENGTH_SHORT).show();
}
}
}
});
}
@Override
public int getItemCount() {
return metasList.size();
}
public static class MetaViewHolder extends RecyclerView.ViewHolder {
ImageView ivIcon, ivDelete, ivCalendar;
TextView tvNome, tvCategoria, tvLabelProgresso, tvValorStatus, tvDiasRestantes, tvPercentagem;
ProgressBar pbProgresso;
EditText etAdicionarValor;
MaterialButton btnAddValor;
public MetaViewHolder(@NonNull View itemView) {
super(itemView);
ivIcon = itemView.findViewById(R.id.ivIcon);
ivDelete = itemView.findViewById(R.id.ivDelete);
ivCalendar = itemView.findViewById(R.id.ivCalendar);
tvNome = itemView.findViewById(R.id.tvNome);
tvCategoria = itemView.findViewById(R.id.tvCategoria);
tvLabelProgresso = itemView.findViewById(R.id.tvLabelProgresso);
tvValorStatus = itemView.findViewById(R.id.tvValorStatus);
tvDiasRestantes = itemView.findViewById(R.id.tvDiasRestantes);
tvPercentagem = itemView.findViewById(R.id.tvPercentagem);
pbProgresso = itemView.findViewById(R.id.pbProgresso);
etAdicionarValor = itemView.findViewById(R.id.etAdicionarValor);
btnAddValor = itemView.findViewById(R.id.btnAddValor);
}
}
}

View File

@@ -0,0 +1,87 @@
package com.example.lifegrid.adapters;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.lifegrid.R;
import com.example.lifegrid.models.Transacao;
import java.util.List;
public class TransacaoAdapter extends RecyclerView.Adapter<TransacaoAdapter.TransacaoViewHolder> {
private List<Transacao> transacoesList;
private OnItemClickListener listener;
public interface OnItemClickListener {
void onDeleteClick(Transacao transacao);
}
public TransacaoAdapter(List<Transacao> transacoesList, OnItemClickListener listener) {
this.transacoesList = transacoesList;
this.listener = listener;
}
@NonNull
@Override
public TransacaoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transacao, parent, false);
return new TransacaoViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull TransacaoViewHolder holder, int position) {
Transacao transacao = transacoesList.get(position);
holder.tvDescricao.setText(transacao.getDescricao());
holder.tvCategoria.setText(transacao.getCategoria());
holder.tvData.setText(transacao.getData());
if ("Receita".equals(transacao.getTipo())) {
holder.ivIconBackground.setBackgroundResource(R.drawable.bg_circle_green);
holder.ivIcon.setImageResource(R.drawable.diagonalarrowrightup_110941);
holder.ivIcon.setColorFilter(Color.parseColor("#2ECC71"));
holder.tvValor.setText("+" + transacao.getValor() + "");
holder.tvValor.setTextColor(Color.parseColor("#2ECC71"));
} else {
holder.ivIconBackground.setBackgroundResource(R.drawable.bg_circle_red);
holder.ivIcon.setImageResource(R.drawable.diagonalarrowleftdownoutline_110924);
holder.ivIcon.setColorFilter(Color.parseColor("#E74C3C"));
holder.tvValor.setText("-" + transacao.getValor() + "");
holder.tvValor.setTextColor(Color.parseColor("#E74C3C"));
}
holder.ivDelete.setOnClickListener(v -> {
if (listener != null) {
listener.onDeleteClick(transacao);
}
});
}
@Override
public int getItemCount() {
return transacoesList.size();
}
public static class TransacaoViewHolder extends RecyclerView.ViewHolder {
ImageView ivIconBackground, ivIcon, ivDelete;
TextView tvDescricao, tvCategoria, tvData, tvValor;
public TransacaoViewHolder(@NonNull View itemView) {
super(itemView);
ivIconBackground = itemView.findViewById(R.id.ivIconBackground);
ivIcon = itemView.findViewById(R.id.ivIcon);
ivDelete = itemView.findViewById(R.id.ivDelete);
tvDescricao = itemView.findViewById(R.id.tvDescricao);
tvCategoria = itemView.findViewById(R.id.tvCategoria);
tvData = itemView.findViewById(R.id.tvData);
tvValor = itemView.findViewById(R.id.tvValor);
}
}
}

View File

@@ -14,16 +14,32 @@ import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import java.util.Calendar; import java.util.Calendar;
import java.util.Locale; import java.util.Locale;
import com.example.lifegrid.R; import com.example.lifegrid.R;
import com.example.lifegrid.adapters.MetaAdapter;
import com.example.lifegrid.models.Meta; import com.example.lifegrid.models.Meta;
import com.example.lifegrid.models.Transacao;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/** /**
* MetasFragment projeta objetivos futuros onde o utilizador fixa montantes e datas a alcançar * MetasFragment projeta objetivos futuros onde o utilizador fixa montantes e datas a alcançar
@@ -31,6 +47,13 @@ import com.google.firebase.database.FirebaseDatabase;
*/ */
public class MetasFragment extends Fragment { public class MetasFragment extends Fragment {
private RecyclerView rvMetas;
private MetaAdapter metaAdapter;
private List<Meta> metasList;
private TextView tvEmptyState;
private DatabaseReference databaseReference;
private String userId;
public MetasFragment() { public MetasFragment() {
// Construtor público vazio obrigatório // Construtor público vazio obrigatório
} }
@@ -49,9 +72,100 @@ public class MetasFragment extends Fragment {
Button novaTransacaoButton = root.findViewById(R.id.novaTransacaoButton); Button novaTransacaoButton = root.findViewById(R.id.novaTransacaoButton);
novaTransacaoButton.setOnClickListener(v -> showNovaMetaDialog()); novaTransacaoButton.setOnClickListener(v -> showNovaMetaDialog());
rvMetas = root.findViewById(R.id.rvMetas);
tvEmptyState = root.findViewById(R.id.textView13);
rvMetas.setLayoutManager(new LinearLayoutManager(requireContext()));
metasList = new ArrayList<>();
metaAdapter = new MetaAdapter(metasList, new MetaAdapter.OnItemClickListener() {
@Override
public void onDeleteClick(Meta meta) {
new AlertDialog.Builder(requireContext())
.setTitle("Excluir Meta")
.setMessage("Tem a certeza que deseja excluir esta meta?")
.setPositiveButton("Sim", (dialog, which) -> {
if (databaseReference != null && userId != null && meta.getId() != null) {
databaseReference.child("users").child(userId).child("metas").child(meta.getId()).removeValue();
Toast.makeText(requireContext(), "Meta excluída.", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("Não", null)
.show();
}
@Override
public void onAddValueClick(Meta meta, double valorAdicional, EditText editText) {
if (databaseReference != null && userId != null && meta.getId() != null) {
double valorAtual = 0;
if (meta.getValorGuardado() != null && !meta.getValorGuardado().isEmpty()) {
valorAtual = Double.parseDouble(meta.getValorGuardado().replace(",", "."));
}
double novoValor = valorAtual + valorAdicional;
// Update Meta
databaseReference.child("users").child(userId).child("metas").child(meta.getId())
.child("valorGuardado").setValue(String.format(Locale.US, "%.2f", novoValor));
// Create a Despesa Transaction to adjust the global balance (Receitas - Despesas)
String todayDate = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(new Date());
Transacao transacao = new Transacao("Despesa", String.format(Locale.US, "%.2f", valorAdicional), "Investimento/Meta", "Adicionado à meta: " + meta.getNome(), todayDate);
String key = databaseReference.child("users").child(userId).child("transacoes").push().getKey();
if (key != null) {
databaseReference.child("users").child(userId).child("transacoes").child(key).setValue(transacao);
}
editText.setText("");
Toast.makeText(requireContext(), "Valor adicionado e despesa registada!", Toast.LENGTH_SHORT).show();
}
}
});
rvMetas.setAdapter(metaAdapter);
carregarMetas();
return root; return root;
} }
private void carregarMetas() {
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
if (currentUser == null) return;
userId = currentUser.getUid();
databaseReference = FirebaseDatabase.getInstance().getReference();
databaseReference.child("users").child(userId).child("metas").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
metasList.clear();
for (DataSnapshot ds : snapshot.getChildren()) {
Meta m = ds.getValue(Meta.class);
if (m != null) {
m.setId(ds.getKey());
metasList.add(m);
}
}
metaAdapter.notifyDataSetChanged();
if (metasList.isEmpty()) {
tvEmptyState.setVisibility(View.VISIBLE);
rvMetas.setVisibility(View.GONE);
} else {
tvEmptyState.setVisibility(View.GONE);
rvMetas.setVisibility(View.VISIBLE);
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(requireContext(), "Erro ao carregar metas.", Toast.LENGTH_SHORT).show();
}
});
}
private void showNovaMetaDialog() { private void showNovaMetaDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext()); AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
View dialogView = getLayoutInflater().inflate(R.layout.dialog_nova_meta, null); View dialogView = getLayoutInflater().inflate(R.layout.dialog_nova_meta, null);

View File

@@ -25,12 +25,21 @@ import java.util.Calendar;
import java.util.Locale; import java.util.Locale;
import com.example.lifegrid.R; import com.example.lifegrid.R;
import com.example.lifegrid.adapters.TransacaoAdapter;
import com.example.lifegrid.models.Transacao; import com.example.lifegrid.models.Transacao;
import com.google.firebase.Firebase;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
/** /**
* TransacoesFragment controla a aba pertencente às listagens e histórico de carteira e gestão de dados base. * TransacoesFragment controla a aba pertencente às listagens e histórico de carteira e gestão de dados base.
@@ -38,7 +47,12 @@ import com.google.firebase.database.FirebaseDatabase;
*/ */
public class TransacoesFragment extends Fragment { public class TransacoesFragment extends Fragment {
private RecyclerView rvTransacoes;
private TransacaoAdapter transacaoAdapter;
private List<Transacao> transacoesList;
private TextView tvEmptyState;
private DatabaseReference databaseReference;
private String userId;
public TransacoesFragment() { public TransacoesFragment() {
// Construtor público vazio obrigatório // Construtor público vazio obrigatório
@@ -60,9 +74,68 @@ public class TransacoesFragment extends Fragment {
Button novaTransacaoButton = root.findViewById(R.id.novaTransacaoButton); Button novaTransacaoButton = root.findViewById(R.id.novaTransacaoButton);
novaTransacaoButton.setOnClickListener(v -> showNovaTransacaoDialog()); novaTransacaoButton.setOnClickListener(v -> showNovaTransacaoDialog());
rvTransacoes = root.findViewById(R.id.rvTransacoes);
tvEmptyState = root.findViewById(R.id.textView13);
rvTransacoes.setLayoutManager(new LinearLayoutManager(requireContext()));
transacoesList = new ArrayList<>();
transacaoAdapter = new TransacaoAdapter(transacoesList, transacao -> {
new AlertDialog.Builder(requireContext())
.setTitle("Excluir Transação")
.setMessage("Tem a certeza que deseja excluir esta transação?")
.setPositiveButton("Sim", (dialog, which) -> {
if (databaseReference != null && userId != null && transacao.getId() != null) {
databaseReference.child("users").child(userId).child("transacoes").child(transacao.getId()).removeValue();
Toast.makeText(requireContext(), "Transação excluída.", Toast.LENGTH_SHORT).show();
}
})
.setNegativeButton("Não", null)
.show();
});
rvTransacoes.setAdapter(transacaoAdapter);
carregarTransacoes();
return root; return root;
} }
private void carregarTransacoes() {
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
if (currentUser == null) return;
userId = currentUser.getUid();
databaseReference = FirebaseDatabase.getInstance().getReference();
databaseReference.child("users").child(userId).child("transacoes").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
transacoesList.clear();
for (DataSnapshot ds : snapshot.getChildren()) {
Transacao t = ds.getValue(Transacao.class);
if (t != null) {
t.setId(ds.getKey());
transacoesList.add(t);
}
}
transacaoAdapter.notifyDataSetChanged();
if (transacoesList.isEmpty()) {
tvEmptyState.setVisibility(View.VISIBLE);
rvTransacoes.setVisibility(View.GONE);
} else {
tvEmptyState.setVisibility(View.GONE);
rvTransacoes.setVisibility(View.VISIBLE);
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(requireContext(), "Erro ao carregar transações.", Toast.LENGTH_SHORT).show();
}
});
}
/** /**
* Cria e monta manualmente uma janela Modal (Pop-up) a fim do utilizador preencher * Cria e monta manualmente uma janela Modal (Pop-up) a fim do utilizador preencher
* os detalhes referentes a uma recém aquisição de receita ou encargo para alimentar a base de dados. * os detalhes referentes a uma recém aquisição de receita ou encargo para alimentar a base de dados.

View File

@@ -5,6 +5,8 @@ public class Meta {
private String categoria; private String categoria;
private String valor; private String valor;
private String data; private String data;
private String valorGuardado = "0.00";
private String id;
public Meta(){ public Meta(){
} }
@@ -47,4 +49,22 @@ public class Meta {
public void setData(String data) { public void setData(String data) {
this.data = data; this.data = data;
} }
public String getValorGuardado() {
return valorGuardado;
}
public void setValorGuardado(String valorGuardado) {
this.valorGuardado = valorGuardado;
}
@com.google.firebase.database.Exclude
public String getId() {
return id;
}
@com.google.firebase.database.Exclude
public void setId(String id) {
this.id = id;
}
} }

View File

@@ -6,6 +6,7 @@ public class Transacao {
private String categoria; private String categoria;
private String descricao; private String descricao;
private String data; private String data;
private String id;
public Transacao() { public Transacao() {
} }
@@ -58,4 +59,14 @@ public class Transacao {
public void setData(String data) { public void setData(String data) {
this.data = data; this.data = data;
} }
@com.google.firebase.database.Exclude
public String getId() {
return id;
}
@com.google.firebase.database.Exclude
public void setId(String id) {
this.id = id;
}
} }

View File

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

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#E8F8F0" />
</shape>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#FCE8E8" />
</shape>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background">
<shape>
<corners android:radius="4dp" />
<solid android:color="#E5E7EB" />
</shape>
</item>
<item android:id="@android:id/progress">
<clip>
<shape>
<corners android:radius="4dp" />
<solid android:color="#2563EB" />
</shape>
</clip>
</item>
</layer-list>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#1F2937"
android:pathData="M7,10l5,5l5,-5z" />
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="#8E8E8E">
<path
android:fillColor="#FF000000"
android:pathData="M19,3h-1V1h-2v2H8V1H6v2H5C3.9,3 3,3.9 3,5v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,19H5V8h14V19z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="#FF0000">
<path
android:fillColor="#FF000000"
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="#1D4ED8">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6 6,-2.69 6,-6 -2.69,-6 -6,-6zM12,16c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="#FFFFFF">
<path
android:fillColor="#FF000000"
android:pathData="M16,6l2.29,2.29 -4.88,4.88 -4,-4L2,16.59 3.41,18l6,-6 4,4 6.3,-6.29L22,12V6z"/>
</vector>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#FFFFFF" />
<corners android:radius="12dp" />
<stroke android:width="1dp" android:color="#E0E0E0" />
</shape>
</item>
<item android:gravity="end|center_vertical" android:right="12dp" android:drawable="@drawable/ic_arrow_down" />
</layer-list>

View File

@@ -32,7 +32,7 @@
android:padding="16dp" android:padding="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView11"> app:layout_constraintTop_toBottomOf="@+id/llFiltros">
<!-- TRANSAÇÕES --> <!-- TRANSAÇÕES -->
@@ -119,6 +119,40 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView10" /> app:layout_constraintTop_toBottomOf="@+id/textView10" />
<LinearLayout
android:id="@+id/llFiltros"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="20dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView11">
<Spinner
android:id="@+id/spinnerMes"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:background="@drawable/spinner_border_bg"
android:paddingStart="12dp"
android:paddingEnd="36dp"
android:spinnerMode="dropdown" />
<Spinner
android:id="@+id/spinnerAno"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:background="@drawable/spinner_border_bg"
android:paddingStart="12dp"
android:paddingEnd="36dp"
android:spinnerMode="dropdown" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/despesasCardView" android:id="@+id/despesasCardView"
android:layout_width="379dp" android:layout_width="379dp"

View File

@@ -106,6 +106,7 @@
app:layout_constraintTop_toBottomOf="@+id/textView12" /> app:layout_constraintTop_toBottomOf="@+id/textView12" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMetas"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="211dp" android:layout_height="211dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"

View File

@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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_marginBottom="12dp"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#FFFFFF">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@drawable/spinner_border_bg">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_goal"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvNome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:text="Férias"
android:textColor="#000000"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/ivIcon"
app:layout_constraintStart_toEndOf="@+id/ivIcon"
app:layout_constraintTop_toTopOf="@+id/ivIcon" />
<ImageView
android:id="@+id/ivDelete"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_delete"
app:layout_constraintBottom_toBottomOf="@+id/ivIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/ivIcon" />
<TextView
android:id="@+id/tvCategoria"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Viagem"
android:textColor="#4B5563"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@+id/tvNome"
app:layout_constraintTop_toBottomOf="@+id/tvNome" />
<TextView
android:id="@+id/tvLabelProgresso"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Progresso"
android:textColor="#374151"
android:textSize="14sp"
app:layout_constraintStart_toStartOf="@+id/tvCategoria"
app:layout_constraintTop_toBottomOf="@+id/tvCategoria" />
<TextView
android:id="@+id/tvValorStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0.00€ / 1500.00€"
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/tvLabelProgresso"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tvLabelProgresso" />
<ProgressBar
android:id="@+id/pbProgresso"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="8dp"
android:layout_marginTop="8dp"
android:progressDrawable="@drawable/custom_progressbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/tvLabelProgresso"
app:layout_constraintTop_toBottomOf="@+id/tvLabelProgresso" />
<ImageView
android:id="@+id/ivCalendar"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="12dp"
android:src="@drawable/ic_calendar"
app:layout_constraintStart_toStartOf="@+id/pbProgresso"
app:layout_constraintTop_toBottomOf="@+id/pbProgresso" />
<TextView
android:id="@+id/tvDiasRestantes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="Faltam 0 dias"
android:textColor="#4B5563"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/ivCalendar"
app:layout_constraintStart_toEndOf="@+id/ivCalendar"
app:layout_constraintTop_toTopOf="@+id/ivCalendar" />
<TextView
android:id="@+id/tvPercentagem"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="0.0%"
android:textColor="#2563EB"
android:textSize="14sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@+id/tvDiasRestantes"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/tvDiasRestantes" />
<EditText
android:id="@+id/etAdicionarValor"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:background="@drawable/spinner_border_bg"
android:hint="Adicionar valor..."
android:inputType="numberDecimal"
android:paddingHorizontal="12dp"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@+id/btnAddValor"
app:layout_constraintStart_toStartOf="@+id/tvDiasRestantes"
app:layout_constraintTop_toBottomOf="@+id/tvDiasRestantes" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAddValor"
android:layout_width="45dp"
android:layout_height="45dp"
app:icon="@drawable/ic_trend_up"
app:iconGravity="textStart"
app:iconPadding="0dp"
app:iconTint="#FFFFFF"
android:insetTop="0dp"
android:insetBottom="0dp"
app:backgroundTint="#0B0F19"
app:cornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="@+id/etAdicionarValor"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/etAdicionarValor" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,107 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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_marginBottom="12dp"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#FFFFFF">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@drawable/spinner_border_bg">
<ImageView
android:id="@+id/ivIconBackground"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_circle_green"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/ivIcon"
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/diagonalarrowrightup_110941"
app:tint="#2ECC71"
app:layout_constraintBottom_toBottomOf="@+id/ivIconBackground"
app:layout_constraintEnd_toEndOf="@+id/ivIconBackground"
app:layout_constraintStart_toStartOf="@+id/ivIconBackground"
app:layout_constraintTop_toTopOf="@+id/ivIconBackground" />
<TextView
android:id="@+id/tvDescricao"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="dinheiro"
android:textColor="#000000"
android:textSize="18sp"
app:layout_constraintStart_toEndOf="@+id/ivIconBackground"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvCategoria"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:background="@drawable/bg_badge_category"
android:paddingHorizontal="10dp"
android:paddingVertical="4dp"
android:text="Outros"
android:textColor="#000000"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@+id/tvDescricao"
app:layout_constraintStart_toEndOf="@+id/tvDescricao"
app:layout_constraintTop_toTopOf="@+id/tvDescricao" />
<ImageView
android:id="@+id/ivCalendar"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="8dp"
android:src="@drawable/ic_calendar"
app:layout_constraintStart_toStartOf="@+id/tvDescricao"
app:layout_constraintTop_toBottomOf="@+id/tvDescricao" />
<TextView
android:id="@+id/tvData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="23/04/2026"
android:textColor="#6B7280"
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="@+id/ivCalendar"
app:layout_constraintStart_toEndOf="@+id/ivCalendar"
app:layout_constraintTop_toTopOf="@+id/ivCalendar" />
<ImageView
android:id="@+id/ivDelete"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_delete"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvValor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="+25.00€"
android:textColor="#2ECC71"
android:textSize="18sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/ivDelete"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>