diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e078032..522f5c7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -48,6 +48,8 @@ + + diff --git a/app/src/main/java/com/example/finzora/AlterarPasswordActivity.java b/app/src/main/java/com/example/finzora/AlterarPasswordActivity.java new file mode 100644 index 0000000..d3222bc --- /dev/null +++ b/app/src/main/java/com/example/finzora/AlterarPasswordActivity.java @@ -0,0 +1,57 @@ +package com.example.finzora; + +import android.os.Bundle; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.Toast; + +import androidx.appcompat.app.AppCompatActivity; + +public class AlterarPasswordActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_alterar_password); + + ImageView btnVoltar = findViewById(R.id.btnVoltarPassword); + EditText editPasswordAtual = findViewById(R.id.editPasswordAtual); + EditText editPasswordNova = findViewById(R.id.editPasswordNova); + EditText editPasswordConfirmar = findViewById(R.id.editPasswordConfirmar); + Button btnGuardar = findViewById(R.id.btnGuardarPassword); + + // Voltar atrás + btnVoltar.setOnClickListener(v -> finish()); + + // Guardar Nova Palavra-passe + btnGuardar.setOnClickListener(v -> { + String passAtual = editPasswordAtual.getText().toString(); + String passNova = editPasswordNova.getText().toString(); + String passConfirma = editPasswordConfirmar.getText().toString(); + + // 1. Validar se os campos estão preenchidos + if (passAtual.isEmpty() || passNova.isEmpty() || passConfirma.isEmpty()) { + Toast.makeText(this, "Preenche todos os campos!", Toast.LENGTH_SHORT).show(); + return; + } + + // 2. Validar o tamanho + if (passNova.length() < 6) { + editPasswordNova.setError("Tem de ter pelo menos 6 caracteres"); + return; + } + + // 3. Validar se as palavras-passe novas coincidem + if (!passNova.equals(passConfirma)) { + editPasswordConfirmar.setError("As palavras-passe não coincidem!"); + return; + } + + // AQUI DEVE ENTRAR O TEU CÓDIGO DO SUPABASE PARA MUDAR A PASS! + // Por agora, damos a sensação de sucesso: + Toast.makeText(this, "Palavra-passe atualizada com segurança! 🔒", Toast.LENGTH_LONG).show(); + finish(); + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/finzora/DefinicoesActivity.java b/app/src/main/java/com/example/finzora/DefinicoesActivity.java index 5d94f09..dfab991 100644 --- a/app/src/main/java/com/example/finzora/DefinicoesActivity.java +++ b/app/src/main/java/com/example/finzora/DefinicoesActivity.java @@ -11,13 +11,15 @@ import android.net.Uri; import android.os.Bundle; import android.view.ViewGroup; import android.widget.Button; -import android.widget.Switch; -import android.widget.TextView; +import android.widget.LinearLayout; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.widget.Toolbar; + +import com.google.android.material.switchmaterial.SwitchMaterial; import java.io.IOException; @@ -36,20 +38,21 @@ public class DefinicoesActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_definicoes); - TextView btnVoltarDefinicoes = findViewById(R.id.btnVoltarDefinicoes); - TextView btnEditarPerfil = findViewById(R.id.btnEditarPerfil); - Switch switchModoEscuro = findViewById(R.id.switchModoEscuro); - Switch switchNotificacoes = findViewById(R.id.switchNotificacoes); - Switch switchBiometria = findViewById(R.id.switchBiometria); - TextView btnSuporte = findViewById(R.id.btnSuporte); + // --- VINCULAR OS NOVOS JOGADORES DO DESIGN PREMIUM --- + Toolbar toolbar = findViewById(R.id.toolbarDefinicoes); + LinearLayout itemEditarPerfil = findViewById(R.id.itemEditarPerfil); + SwitchMaterial switchModoEscuro = findViewById(R.id.switchModoEscuro); + SwitchMaterial switchNotificacoes = findViewById(R.id.switchNotificacoes); + SwitchMaterial switchBiometria = findViewById(R.id.switchBiometria); + LinearLayout itemCentroSuporte = findViewById(R.id.itemCentroSuporte); Button btnTerminarSessao = findViewById(R.id.btnTerminarSessao); - Button btnEliminarConta = findViewById(R.id.btnEliminarConta); + Button btnApagarConta = findViewById(R.id.btnApagarConta); - // --- 0. BOTÃO DE VOLTAR --- - btnVoltarDefinicoes.setOnClickListener(v -> finish()); + // --- 0. BOTÃO DE VOLTAR (Na nova Toolbar) --- + toolbar.setNavigationOnClickListener(v -> finish()); - // --- 1. EDITAR PERFIL --- - btnEditarPerfil.setOnClickListener(v -> { + // --- 1. EDITAR PERFIL (Agora é um item clicável) --- + itemEditarPerfil.setOnClickListener(v -> { startActivity(new Intent(DefinicoesActivity.this, EditarPerfilActivity.class)); }); @@ -95,13 +98,13 @@ public class DefinicoesActivity extends AppCompatActivity { }); // --- 5. CENTRO DE SUPORTE (POP-UP) --- - btnSuporte.setOnClickListener(v -> mostrarDialogSuporte()); + itemCentroSuporte.setOnClickListener(v -> mostrarDialogSuporte()); // --- 6. TERMINAR SESSÃO --- btnTerminarSessao.setOnClickListener(v -> terminarSessao()); // --- 7. APAGAR CONTA --- - btnEliminarConta.setOnClickListener(v -> mostrarAvisoEliminar()); + btnApagarConta.setOnClickListener(v -> mostrarAvisoEliminar()); } private void mostrarAvisoEliminar() { @@ -225,7 +228,7 @@ public class DefinicoesActivity extends AppCompatActivity { enviarEmailProfissional(); }); - // 📞 CONTACTOS DIRETOS: Abre o nosso Novo Design Premium! + // 📞 CONTACTOS DIRETOS dialog.findViewById(R.id.cardContactos).setOnClickListener(v -> { dialog.dismiss(); mostrarDialogContactosInfo(); @@ -234,7 +237,6 @@ public class DefinicoesActivity extends AppCompatActivity { dialog.show(); } - // 🏆 A NOVA FUNÇÃO QUE CHAMA O DESIGN PREMIUM private void mostrarDialogContactosInfo() { Dialog dialog = new Dialog(this); dialog.setContentView(R.layout.dialog_contactos); @@ -244,7 +246,6 @@ public class DefinicoesActivity extends AppCompatActivity { dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } - // Quando clica em Voltar, fechamos este e abrimos o menu de Suporte outra vez! dialog.findViewById(R.id.btnFecharContactos).setOnClickListener(v -> { dialog.dismiss(); mostrarDialogSuporte(); diff --git a/app/src/main/java/com/example/finzora/DicasFragment.java b/app/src/main/java/com/example/finzora/DicasFragment.java index c3b2edf..d02c56b 100644 --- a/app/src/main/java/com/example/finzora/DicasFragment.java +++ b/app/src/main/java/com/example/finzora/DicasFragment.java @@ -9,11 +9,13 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; import android.widget.EditText; -import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -38,19 +40,20 @@ import okhttp3.Response; public class DicasFragment extends Fragment { - private TextView tvTaxaPoupanca, tvDicasReceitas, tvDicasDespesas; - private ProgressBar progressPoupanca; - private TextView tvTituloDica1, tvDescDica1, tvTituloDica2, tvDescDica2, tvTituloDica3, tvDescDica3; - private LinearLayout layoutDistribuicao; - - private View layoutConteudoDicas; - private View layoutEstadoVazioDicas; - - private TextView tvRespostaAI; + // IA private EditText editPerguntaAI; - private ImageButton btnEnviarAI; - private ProgressBar pbCarregandoAI; + private ImageView btnEnviarAI; + private TextView tvRespostaAI; // 🚀 Adicionámos o jogador responsável por mostrar o texto da IA! + // Diagnóstico + private ProgressBar progressPoupanca; + private TextView tvTaxaPoupancaValor, tvReceitasResumo, tvDespesasResumo; + private TextView tvTituloDica1, tvDescDica1; + private TextView tvTituloDica2, tvDescDica2; + private TextView tvTituloDica3, tvDescDica3; + + // Top Despesas + private LinearLayout layoutDistribuicao; private int corFundoCartao; private int corTextoDinamico; @@ -61,10 +64,19 @@ public class DicasFragment extends Fragment { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_dicas, container, false); - tvTaxaPoupanca = view.findViewById(R.id.tvTaxaPoupanca); - tvDicasReceitas = view.findViewById(R.id.tvDicasReceitas); - tvDicasDespesas = view.findViewById(R.id.tvDicasDespesas); + if (getContext() != null) { + corFundoCartao = ContextCompat.getColor(getContext(), R.color.fundo_cartao); + corTextoDinamico = ContextCompat.getColor(getContext(), R.color.texto_principal); + } + + editPerguntaAI = view.findViewById(R.id.editPerguntaAI); + btnEnviarAI = view.findViewById(R.id.btnEnviarAI); + tvRespostaAI = view.findViewById(R.id.tvRespostaAI); // Ligar ao XML + progressPoupanca = view.findViewById(R.id.progressPoupanca); + tvTaxaPoupancaValor = view.findViewById(R.id.tvTaxaPoupancaValor); + tvReceitasResumo = view.findViewById(R.id.tvReceitasResumo); + tvDespesasResumo = view.findViewById(R.id.tvDespesasResumo); tvTituloDica1 = view.findViewById(R.id.tvTituloDica1); tvDescDica1 = view.findViewById(R.id.tvDescDica1); @@ -74,30 +86,29 @@ public class DicasFragment extends Fragment { tvDescDica3 = view.findViewById(R.id.tvDescDica3); layoutDistribuicao = view.findViewById(R.id.layoutDistribuicao); - layoutConteudoDicas = view.findViewById(R.id.layoutConteudoDicas); - layoutEstadoVazioDicas = view.findViewById(R.id.layoutEstadoVazioDicas); - tvRespostaAI = view.findViewById(R.id.tvRespostaAI); - editPerguntaAI = view.findViewById(R.id.editPerguntaAI); - btnEnviarAI = view.findViewById(R.id.btnEnviarAI); - pbCarregandoAI = view.findViewById(R.id.pbCarregandoAI); - - if (getContext() != null) { - corFundoCartao = ContextCompat.getColor(getContext(), R.color.fundo_cartao); - corTextoDinamico = ContextCompat.getColor(getContext(), R.color.texto_principal); + if (btnEnviarAI != null) { + btnEnviarAI.setOnClickListener(v -> perguntarAoNovoCoach()); } - btnEnviarAI.setOnClickListener(v -> perguntarAoNovoCoach()); - return view; } private void perguntarAoNovoCoach() { + if (editPerguntaAI == null || tvRespostaAI == null) return; + String pergunta = editPerguntaAI.getText().toString().trim(); if (pergunta.isEmpty()) return; - pbCarregandoAI.setVisibility(View.VISIBLE); - tvRespostaAI.setText("A analisar os dados de forma inteligente..."); + // Tática: Esconder o teclado para não tapar a resposta + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(editPerguntaAI.getWindowToken(), 0); + } + + // Escrever logo no cartão que a máquina está a pensar! + tvRespostaAI.setText("A processar análise... 🧠"); + tvRespostaAI.setTextColor(ContextCompat.getColor(getContext(), R.color.tech_accent_cyan)); editPerguntaAI.setText(""); OkHttpClient client = new OkHttpClient(); @@ -138,8 +149,8 @@ public class DicasFragment extends Fragment { public void onFailure(@NonNull Call call, @NonNull IOException e) { if (getActivity() != null) { getActivity().runOnUiThread(() -> { - pbCarregandoAI.setVisibility(View.GONE); - tvRespostaAI.setText("Erro de ligação ao serviço de Inteligência Artificial."); + tvRespostaAI.setText("Falha na ligação. A minha antena está sem sinal, tenta outra vez! 📡"); + tvRespostaAI.setTextColor(Color.parseColor("#F56565")); // Vermelho suave }); } } @@ -147,25 +158,26 @@ public class DicasFragment extends Fragment { @Override public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { final String respBody = response.body() != null ? response.body().string() : ""; - if (getActivity() != null) { getActivity().runOnUiThread(() -> { - pbCarregandoAI.setVisibility(View.GONE); if (response.isSuccessful()) { try { JSONObject jsonObject = new JSONObject(respBody); String respostaIA = jsonObject.getJSONArray("choices") .getJSONObject(0).getJSONObject("message").getString("content"); - String textoFormatado = respostaIA.replaceAll("\\*\\*(.*?)\\*\\*", "$1"); - textoFormatado = textoFormatado.replace("\n", "
"); - tvRespostaAI.setText(android.text.Html.fromHtml(textoFormatado, android.text.Html.FROM_HTML_MODE_LEGACY)); + // Limpar asteriscos e colocar a resposta diretamente no cartão! + String textoLimpo = respostaIA.replaceAll("\\*\\*", ""); + tvRespostaAI.setText(textoLimpo); + tvRespostaAI.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary)); // Volta à cor normal } catch (Exception e) { - tvRespostaAI.setText("Erro a ler os dados da análise: " + e.getMessage()); + tvRespostaAI.setText("Erro ao processar a resposta dos servidores."); + tvRespostaAI.setTextColor(Color.parseColor("#F56565")); } } else { - tvRespostaAI.setText("O Assistente não está disponível neste momento."); + tvRespostaAI.setText("Os servidores da IA estão ocupados neste momento. ⏱️"); + tvRespostaAI.setTextColor(Color.parseColor("#ECC94B")); // Amarelo de aviso } }); } @@ -216,9 +228,7 @@ public class DicasFragment extends Fragment { mapaGastos.put(cat, mapaGastos.getOrDefault(cat, 0f) + v); } } - buscarOrcamentosECriarCerebro(userId, client, rec, desp, mapaGastos); - } catch (Exception e) { e.printStackTrace(); } } }); @@ -242,13 +252,11 @@ public class DicasFragment extends Fragment { String body = response.body().string(); JSONArray arrayOrcamentos = new JSONArray(body); - // 🧠 RECONSTRUIR O CÉREBRO DA IA COM TODOS OS DETALHES! StringBuilder cerebro = new StringBuilder(); - cerebro.append("DADOS FINANCEIROS ATUAIS DO UTILIZADOR: "); - cerebro.append("Receitas Totais: ").append(rec).append("€. "); - cerebro.append("Despesas Totais: ").append(desp).append("€. "); + cerebro.append("DADOS FINANCEIROS ATUAIS: "); + cerebro.append("Receitas: ").append(rec).append("€. "); + cerebro.append("Despesas: ").append(desp).append("€. "); - // Passar as categorias onde gastaste dinheiro if (!mapaGastos.isEmpty()) { cerebro.append("Gastos por categoria: "); for (Map.Entry entry : mapaGastos.entrySet()) { @@ -257,19 +265,16 @@ public class DicasFragment extends Fragment { } if (arrayOrcamentos.length() > 0) { - cerebro.append(". ORÇAMENTOS DEFINIDOS: "); + cerebro.append(". ORÇAMENTOS: "); for (int i = 0; i < arrayOrcamentos.length(); i++) { JSONObject obj = arrayOrcamentos.getJSONObject(i); String cat = obj.getString("categoria"); float limite = (float) obj.getDouble("valor_limite"); float gasto = mapaGastos.containsKey(cat) ? mapaGastos.get(cat) : 0f; - cerebro.append("[").append(cat).append(": Limite definido ").append(limite).append("€, Gasto atual ").append(gasto).append("€] "); + cerebro.append("[").append(cat).append(": Limite ").append(limite).append("€, Gasto ").append(gasto).append("€] "); } - } else { - cerebro.append(". O utilizador não tem orçamentos definidos de momento. "); } - // ⚠️ A JOGADA QUE FALTAVA: Guardar a memória na variável que a IA vai ler! contextoFinanceiroParaAI = cerebro.toString(); if (getActivity() != null) { @@ -282,24 +287,14 @@ public class DicasFragment extends Fragment { private void aplicarLogicaDeDicas(float rec, float desp, HashMap mapa, JSONArray arrayOrcamentos) { - if (rec == 0 && desp == 0) { - layoutConteudoDicas.setVisibility(View.GONE); - layoutEstadoVazioDicas.setVisibility(View.VISIBLE); - return; - } else { - layoutConteudoDicas.setVisibility(View.VISIBLE); - layoutEstadoVazioDicas.setVisibility(View.GONE); - } - - // --- TOPO: RESUMO --- - tvDicasReceitas.setText(String.format("€ %.2f", rec)); - tvDicasDespesas.setText(String.format("€ %.2f", desp)); float taxa = (rec > 0) ? ((rec - desp) / rec) * 100 : 0; if (taxa < 0) taxa = 0; - tvTaxaPoupanca.setText(String.format("%.1f%%", taxa)); - progressPoupanca.setProgress((int) taxa); - // --- CARTÃO 1: REGRA 50/30/20 --- + if (progressPoupanca != null) progressPoupanca.setProgress((int) taxa); + if (tvTaxaPoupancaValor != null) tvTaxaPoupancaValor.setText(String.format("%.1f%%", taxa)); + if (tvReceitasResumo != null) tvReceitasResumo.setText(String.format("€ %.2f", rec)); + if (tvDespesasResumo != null) tvDespesasResumo.setText(String.format("€ %.2f", desp)); + float necessidades = 0; float desejos = 0; for (Map.Entry entry : mapa.entrySet()) { String cat = entry.getKey().toLowerCase(); @@ -316,25 +311,20 @@ public class DicasFragment extends Fragment { float percDesejos = (desejos / rec) * 100; if (percNecessidades <= 50 && percDesejos <= 30) { - tvTituloDica1.setText("Balanço Perfeito ⚖️"); - tvTituloDica1.setTextColor(Color.parseColor("#00E676")); - tvDescDica1.setText("Estás a cumprir a Regra de Ouro (50/30/20). Os teus gastos essenciais e de lazer estão equilibrados face aos teus rendimentos."); + if(tvTituloDica1 != null) { tvTituloDica1.setText("Balanço Perfeito ⚖️"); tvTituloDica1.setTextColor(Color.parseColor("#00E676")); } + if(tvDescDica1 != null) tvDescDica1.setText("Estás a cumprir a Regra de Ouro (50/30/20). Os teus gastos essenciais e de lazer estão equilibrados face aos teus rendimentos."); } else if (percDesejos > 30) { - tvTituloDica1.setText("Atenção aos Gastos Supérfluos 🛍️"); - tvTituloDica1.setTextColor(Color.parseColor("#ECC94B")); - tvDescDica1.setText(String.format("A alocação em despesas não essenciais representa %.0f%% do teu orçamento. Recomenda-se reduzir para a margem dos 30%%.", percDesejos)); + if(tvTituloDica1 != null) { tvTituloDica1.setText("Atenção aos Gastos Supérfluos 🛍️"); tvTituloDica1.setTextColor(Color.parseColor("#ECC94B")); } + if(tvDescDica1 != null) tvDescDica1.setText(String.format("A alocação em despesas não essenciais representa %.0f%% do teu orçamento. Recomenda-se reduzir para a margem dos 30%%.", percDesejos)); } else { - tvTituloDica1.setText("Despesas Fixas Elevadas 🏠"); - tvTituloDica1.setTextColor(Color.parseColor("#F56565")); - tvDescDica1.setText(String.format("Os teus encargos fixos representam %.0f%% do salário. O indicador ideal para manter a estabilidade financeira é de 50%%.", percNecessidades)); + if(tvTituloDica1 != null) { tvTituloDica1.setText("Despesas Fixas Elevadas 🏠"); tvTituloDica1.setTextColor(Color.parseColor("#F56565")); } + if(tvDescDica1 != null) tvDescDica1.setText(String.format("Os teus encargos fixos representam %.0f%% do salário. O indicador ideal para manter a estabilidade financeira é de 50%%.", percNecessidades)); } } else { - tvTituloDica1.setText("Regra 50/30/20 ⚖️"); - tvTituloDica1.setTextColor(corTextoDinamico); - tvDescDica1.setText("Regista receitas para que possamos calcular a distribuição ideal do teu património."); + if(tvTituloDica1 != null) { tvTituloDica1.setText("Regra 50/30/20 ⚖️"); tvTituloDica1.setTextColor(corTextoDinamico); } + if(tvDescDica1 != null) tvDescDica1.setText("Regista receitas para que possamos calcular a distribuição ideal do teu património."); } - // --- CARTÃO 2: RADAR DE ORÇAMENTOS --- String alertaOrcamento = "Todos os orçamentos definidos encontram-se dentro dos limites previstos."; int corAlerta = Color.parseColor("#00E676"); String tituloAlerta = "Orçamentos Controlados ✅"; @@ -363,7 +353,7 @@ public class DicasFragment extends Fragment { if (maiorRisco >= 1.0) { tituloAlerta = "Orçamento Excedido 🚨"; corAlerta = Color.parseColor("#FF1744"); - alertaOrcamento = "O limite definido para a categoria '" + catRisco + "' foi ultrapassado. Sugere-se o reajuste das restantes categorias."; + alertaOrcamento = "O limite definido para a categoria '" + catRisco + "' foi ultrapassado. Sugere-se o reajuste."; } else if (maiorRisco >= 0.8) { tituloAlerta = "Aviso de Limite Próximo ⚠️"; corAlerta = Color.parseColor("#ECC94B"); @@ -375,11 +365,9 @@ public class DicasFragment extends Fragment { } } catch (Exception e) { e.printStackTrace(); } - tvTituloDica2.setText(tituloAlerta); - tvTituloDica2.setTextColor(corAlerta); - tvDescDica2.setText(alertaOrcamento); + if(tvTituloDica2 != null) { tvTituloDica2.setText(tituloAlerta); tvTituloDica2.setTextColor(corAlerta); } + if(tvDescDica2 != null) tvDescDica2.setText(alertaOrcamento); - // --- CARTÃO 3: PREVISÃO E TENDÊNCIA DIÁRIA --- Calendar cal = Calendar.getInstance(); int diaAtual = cal.get(Calendar.DAY_OF_MONTH); int diasNoMes = cal.getActualMaximum(Calendar.DAY_OF_MONTH); @@ -389,67 +377,65 @@ public class DicasFragment extends Fragment { float previsaoFimDoMes = mediaDiaria * diasNoMes; if (previsaoFimDoMes > rec && rec > 0) { - tvTituloDica3.setText("Projeção Mensal Elevada 📈"); - tvTituloDica3.setTextColor(Color.parseColor("#FF1744")); - tvDescDica3.setText(String.format("A média de custos diários situa-se em %.2f€. Mantendo esta tendência, o custo final estimado será de %.2f€ (acima dos rendimentos).", mediaDiaria, previsaoFimDoMes)); + if(tvTituloDica3 != null) { tvTituloDica3.setText("Projeção Mensal Elevada 📈"); tvTituloDica3.setTextColor(Color.parseColor("#FF1744")); } + if(tvDescDica3 != null) tvDescDica3.setText(String.format("A média de custos diários situa-se em %.2f€. Mantendo esta tendência, o custo final estimado será de %.2f€ (acima dos rendimentos).", mediaDiaria, previsaoFimDoMes)); } else { - tvTituloDica3.setText("Projeção Mensal Controlada 📉"); - tvTituloDica3.setTextColor(Color.parseColor("#00E676")); - tvDescDica3.setText(String.format("A tua média de custos é de %.2f€ diários. A estimativa projetada para o final do mês é de %.2f€.", mediaDiaria, previsaoFimDoMes)); + if(tvTituloDica3 != null) { tvTituloDica3.setText("Projeção Mensal Controlada 📉"); tvTituloDica3.setTextColor(Color.parseColor("#00E676")); } + if(tvDescDica3 != null) tvDescDica3.setText(String.format("A tua média de custos é de %.2f€ diários. A estimativa projetada para o final do mês é de %.2f€.", mediaDiaria, previsaoFimDoMes)); } } else { - tvTituloDica3.setText("Projeção de Despesas 📊"); - tvTituloDica3.setTextColor(corTextoDinamico); - tvDescDica3.setText("Regista mais movimentos ao longo do mês para que o sistema possa projetar a tua média diária de despesas."); + if(tvTituloDica3 != null) { tvTituloDica3.setText("Projeção de Despesas 📊"); tvTituloDica3.setTextColor(corTextoDinamico); } + if(tvDescDica3 != null) tvDescDica3.setText("Regista mais movimentos ao longo do mês para projetarmos a tua média diária."); } - // --- LISTA DE TOP DESPESAS --- - layoutDistribuicao.removeAllViews(); - for (Map.Entry entry : mapa.entrySet()) { - String categoria = entry.getKey(); - float valor = entry.getValue(); + if (layoutDistribuicao != null) { + layoutDistribuicao.removeAllViews(); + for (Map.Entry entry : mapa.entrySet()) { + String categoria = entry.getKey(); + float valor = entry.getValue(); - LinearLayout row = new LinearLayout(getContext()); - row.setOrientation(LinearLayout.HORIZONTAL); - row.setPadding(40, 30, 40, 30); - row.setElevation(2f); + LinearLayout row = new LinearLayout(getContext()); + row.setOrientation(LinearLayout.HORIZONTAL); + row.setPadding(40, 40, 40, 40); + row.setElevation(0f); - GradientDrawable shape = new GradientDrawable(); - shape.setCornerRadius(24f); - shape.setColor(corFundoCartao); - row.setBackground(shape); + GradientDrawable shape = new GradientDrawable(); + shape.setCornerRadius(32f); + shape.setColor(corFundoCartao); + row.setBackground(shape); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); - params.setMargins(0, 0, 0, 20); - row.setLayoutParams(params); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + params.setMargins(0, 0, 0, 24); + row.setLayoutParams(params); - String emoji = "💰"; - String catLower = categoria.toLowerCase(); - if (catLower.contains("alimen") || catLower.contains("restaurante")) emoji = "🍔"; - else if (catLower.contains("transp") || catLower.contains("carro")) emoji = "🚗"; - else if (catLower.contains("lazer") || catLower.contains("divers")) emoji = "🎮"; - else if (catLower.contains("saúd") || catLower.contains("farmácia")) emoji = "💊"; - else if (catLower.contains("educa")) emoji = "📚"; - else if (catLower.contains("casa") || catLower.contains("renda") || catLower.contains("conta")) emoji = "🏠"; - else if (catLower.contains("compras") || catLower.contains("roupa")) emoji = "🛍️"; + String emoji = "💰"; + String catLower = categoria.toLowerCase(); + if (catLower.contains("alimen") || catLower.contains("restaurante")) emoji = "🍔"; + else if (catLower.contains("transp") || catLower.contains("carro")) emoji = "🚗"; + else if (catLower.contains("lazer") || catLower.contains("divers")) emoji = "🎮"; + else if (catLower.contains("saúd") || catLower.contains("farmácia")) emoji = "💊"; + else if (catLower.contains("educa")) emoji = "📚"; + else if (catLower.contains("casa") || catLower.contains("renda") || catLower.contains("conta")) emoji = "🏠"; + else if (catLower.contains("compras") || catLower.contains("roupa")) emoji = "🛍️"; - TextView tvCat = new TextView(getContext()); - tvCat.setText(emoji + " " + categoria); - tvCat.setTextColor(corTextoDinamico); - tvCat.setTextSize(16f); - tvCat.setTypeface(null, Typeface.BOLD); - tvCat.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); + TextView tvCat = new TextView(getContext()); + tvCat.setText(emoji + " " + categoria); + tvCat.setTextColor(corTextoDinamico); + tvCat.setTextSize(16f); + tvCat.setTypeface(null, Typeface.BOLD); + tvCat.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)); - TextView tvVal = new TextView(getContext()); - tvVal.setText(String.format("€ %.2f", valor)); - tvVal.setTextColor(Color.parseColor("#FF1744")); - tvVal.setTextSize(16f); - tvVal.setTypeface(null, Typeface.BOLD); + TextView tvVal = new TextView(getContext()); + tvVal.setText(String.format("€ %.2f", valor)); + tvVal.setTextColor(Color.parseColor("#FF1744")); + tvVal.setTextSize(16f); + tvVal.setTypeface(null, Typeface.BOLD); - row.addView(tvCat); - row.addView(tvVal); - layoutDistribuicao.addView(row); + row.addView(tvCat); + row.addView(tvVal); + layoutDistribuicao.addView(row); + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/example/finzora/EditarPerfilActivity.java b/app/src/main/java/com/example/finzora/EditarPerfilActivity.java index 7579100..f4a4488 100644 --- a/app/src/main/java/com/example/finzora/EditarPerfilActivity.java +++ b/app/src/main/java/com/example/finzora/EditarPerfilActivity.java @@ -28,6 +28,7 @@ public class EditarPerfilActivity extends AppCompatActivity { private EditText editNomePerfil; private EditText editEmailPerfil; + private EditText editTelemovelPerfil; // NOVO JOGADOR INSCRITO! private ImageView imgFotoPerfil; private String caminhoFotoGuardada = null; @@ -56,18 +57,23 @@ public class EditarPerfilActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.activity_editar_perfil); - TextView btnVoltar = findViewById(R.id.btnVoltarEditarPerfil); + // A CORREÇÃO DE MESTRE: Agora a seta de voltar é lida como ImageView (Imagem) + ImageView btnVoltar = findViewById(R.id.btnVoltarEditarPerfil); editNomePerfil = findViewById(R.id.editNomePerfil); editEmailPerfil = findViewById(R.id.editEmailPerfil); + editTelemovelPerfil = findViewById(R.id.editTelemovelPerfil); // Ligar ao XML + Button btnAlterarPassword = findViewById(R.id.btnAlterarPassword); // Ligar ao XML Button btnGuardarPerfil = findViewById(R.id.btnGuardarPerfil); imgFotoPerfil = findViewById(R.id.imgFotoPerfil); btnVoltar.setOnClickListener(v -> finish()); imgFotoPerfil.setOnClickListener(v -> seletorImagens.launch("image/*")); + // Carregar os dados que já estavam guardados na app SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE); editNomePerfil.setText(prefs.getString("nome_usuario", "Investidor")); editEmailPerfil.setText(prefs.getString("email_usuario", "")); + editTelemovelPerfil.setText(prefs.getString("telemovel_usuario", "")); // Carrega o telemóvel caminhoFotoGuardada = prefs.getString("foto_usuario_path", null); if (caminhoFotoGuardada != null) { @@ -85,9 +91,16 @@ public class EditarPerfilActivity extends AppCompatActivity { } } + // 🚀 PASSE DE RUTURA: Abrir o novo ecrã profissional de segurança + btnAlterarPassword.setOnClickListener(v -> { + startActivity(new Intent(EditarPerfilActivity.this, AlterarPasswordActivity.class)); + }); + + // Quando o utilizador clica em Guardar btnGuardarPerfil.setOnClickListener(v -> { String novoNome = editNomePerfil.getText().toString().trim(); String novoEmail = editEmailPerfil.getText().toString().trim(); + String novoTelemovel = editTelemovelPerfil.getText().toString().trim(); // Lê o que foi escrito if (novoNome.isEmpty()) { editNomePerfil.setError("O nome não pode estar vazio!"); @@ -97,6 +110,7 @@ public class EditarPerfilActivity extends AppCompatActivity { SharedPreferences.Editor editor = prefs.edit(); editor.putString("nome_usuario", novoNome); editor.putString("email_usuario", novoEmail); + editor.putString("telemovel_usuario", novoTelemovel); // Guarda o telemóvel if (caminhoFotoGuardada != null) { editor.putString("foto_usuario_path", caminhoFotoGuardada); diff --git a/app/src/main/java/com/example/finzora/GraficosFragment.java b/app/src/main/java/com/example/finzora/GraficosFragment.java index bd18614..8041af7 100644 --- a/app/src/main/java/com/example/finzora/GraficosFragment.java +++ b/app/src/main/java/com/example/finzora/GraficosFragment.java @@ -42,11 +42,11 @@ public class GraficosFragment extends Fragment { private BarChart barChartOrcamento; private BarChart barChartTendencia; - // ⚠️ 1. As Vistas do Estado Vazio private View scrollviewGraficos; private View layoutEstadoVazio; private int corTextoDinamica; + private int corGridSubtil; @Nullable @Override @@ -57,14 +57,15 @@ public class GraficosFragment extends Fragment { barChartOrcamento = view.findViewById(R.id.barChartOrcamento); barChartTendencia = view.findViewById(R.id.barChartTendencia); - // ⚠️ 2. Ligar ao XML scrollviewGraficos = view.findViewById(R.id.scrollviewGraficos); layoutEstadoVazio = view.findViewById(R.id.layoutEstadoVazioGraficos); if (getContext() != null) { corTextoDinamica = ContextCompat.getColor(getContext(), R.color.texto_principal); + corGridSubtil = Color.parseColor("#1A888888"); // Uma grelha muito subtil e quase invisível } else { corTextoDinamica = Color.BLACK; + corGridSubtil = Color.LTGRAY; } return view; @@ -125,18 +126,14 @@ public class GraficosFragment extends Fragment { final float totalDespesas = somaDespesas; final Map mapaGastos = gastosPorCategoria; - // ⚠️ 3. A Lógica de Mostrar/Esconder if (getActivity() != null) { getActivity().runOnUiThread(() -> { - // Se as receitas E as despesas forem 0, é porque não há transações nenhumas! if (totalReceitas == 0 && totalDespesas == 0) { scrollviewGraficos.setVisibility(View.GONE); layoutEstadoVazio.setVisibility(View.VISIBLE); } else { scrollviewGraficos.setVisibility(View.VISIBLE); layoutEstadoVazio.setVisibility(View.GONE); - - // O utilizador tem dados! Vamos buscar os orçamentos para cruzar a informação carregarOrcamentosEDesenhar(userId, mapaGastos, totalReceitas, totalDespesas); } }); @@ -193,6 +190,7 @@ public class GraficosFragment extends Fragment { pieChartDespesas.getLegend().setTextColor(corTextoDinamica); pieChartDespesas.getLegend().setTextSize(12f); pieChartDespesas.getLegend().setWordWrapEnabled(true); + pieChartDespesas.getLegend().setForm(com.github.mikephil.charting.components.Legend.LegendForm.CIRCLE); // Fica mais tecnológico! pieChartDespesas.setCenterText("Despesas"); pieChartDespesas.setCenterTextColor(corTextoDinamica); @@ -208,14 +206,24 @@ public class GraficosFragment extends Fragment { if (entradas.isEmpty()) { pieChartDespesas.clear(); return; } PieDataSet dataSet = new PieDataSet(entradas, ""); - dataSet.setColors(new int[]{Color.parseColor("#7C4DFF"), Color.parseColor("#00E5FF"), Color.parseColor("#FFD600"), Color.parseColor("#FF4081")}); - dataSet.setSliceSpace(3f); + + // 🚀 PALETA CYBERPUNK PREMIUM: Tons Ciano, Azul Escuro, Roxo Neon, etc. + dataSet.setColors(new int[]{ + Color.parseColor("#00E5FF"), // Cyan Tech + Color.parseColor("#2979FF"), // Blue Deep + Color.parseColor("#D500F9"), // Neon Purple + Color.parseColor("#00E676"), // Emerald + Color.parseColor("#FFEA00"), // Yellow Tech + Color.parseColor("#FF1744") // Danger Red + }); + + dataSet.setSliceSpace(4f); // Espaço entre as fatias dá o efeito premium PieData data = new PieData(dataSet); - data.setValueTextSize(14f); - data.setValueTextColor(corTextoDinamica); + data.setValueTextSize(12f); + data.setValueTextColor(Color.WHITE); // Branco destaca bem em cima destas cores fortes pieChartDespesas.setData(data); - pieChartDespesas.animateY(1000); + pieChartDespesas.animateY(1200); } private void desenharBarChartOrcamento(Map orcamentos, Map gastos) { @@ -239,17 +247,16 @@ public class GraficosFragment extends Fragment { if (categorias.isEmpty()) { barChartOrcamento.clear(); return; } - BarDataSet setGastos = new BarDataSet(gastosEntries, "Gastos Reais"); - setGastos.setColor(Color.parseColor("#FF4081")); + BarDataSet setGastos = new BarDataSet(gastosEntries, "Gasto Real"); + setGastos.setColor(Color.parseColor("#FF1744")); // Vermelho de Gasto setGastos.setValueTextColor(corTextoDinamica); - BarDataSet setOrcamento = new BarDataSet(orcamentoEntries, "Orçamento"); - setOrcamento.setColor(Color.parseColor("#00E5FF")); + BarDataSet setOrcamento = new BarDataSet(orcamentoEntries, "Limite (Orçamento)"); + setOrcamento.setColor(Color.parseColor("#00B8D4")); // Ciano Tech do Limite setOrcamento.setValueTextColor(corTextoDinamica); BarData data = new BarData(setGastos, setOrcamento); - // ⚠️ A MATEMÁTICA PERFEITA (tem de somar 1.00) float barWidth = 0.35f; float barSpace = 0.05f; float groupSpace = 0.20f; @@ -257,21 +264,16 @@ public class GraficosFragment extends Fragment { data.setBarWidth(barWidth); barChartOrcamento.setData(data); - // ⚠️ COMEÇA NO 0 PARA ALINHAR AO CENTRO barChartOrcamento.groupBars(0f, groupSpace, barSpace); XAxis xAxis = barChartOrcamento.getXAxis(); xAxis.setValueFormatter(new IndexAxisValueFormatter(categorias)); - - // ⚠️ LIMITES DINÂMICOS PARA ENCAIXAR OS GRUPOS TODOS xAxis.setAxisMinimum(0f); xAxis.setAxisMaximum(barChartOrcamento.getBarData().getGroupWidth(groupSpace, barSpace) * categorias.size()); - // Para não ficar tudo esmagado se tiveres muitas categorias, mete limite visível a 4 barChartOrcamento.setVisibleXRangeMaximum(4); - - barChartOrcamento.animateY(1000); - barChartOrcamento.invalidate(); // Refresca o gráfico + barChartOrcamento.animateY(1200); + barChartOrcamento.invalidate(); } private void desenharBarChartTendencia(float receitas, float despesas) { @@ -284,16 +286,15 @@ public class GraficosFragment extends Fragment { receitaEntry.add(new BarEntry(0, receitas)); BarDataSet setDespesas = new BarDataSet(despesaEntry, "Despesas"); - setDespesas.setColor(Color.parseColor("#FF1744")); + setDespesas.setColor(Color.parseColor("#FF1744")); // Red Tech setDespesas.setValueTextColor(corTextoDinamica); BarDataSet setReceitas = new BarDataSet(receitaEntry, "Receitas"); - setReceitas.setColor(Color.parseColor("#00E676")); + setReceitas.setColor(Color.parseColor("#00E676")); // Green Emerald setReceitas.setValueTextColor(corTextoDinamica); BarData data = new BarData(setDespesas, setReceitas); - // ⚠️ MATEMÁTICA PERFEITA PARA A TENDÊNCIA float groupSpace = 0.3f; float barSpace = 0.05f; float barWidth = 0.3f; @@ -301,36 +302,39 @@ public class GraficosFragment extends Fragment { data.setBarWidth(barWidth); barChartTendencia.setData(data); - // ⚠️ COMEÇA NO 0 TAMBÉM barChartTendencia.groupBars(0f, groupSpace, barSpace); ArrayList labelMes = new ArrayList<>(); - labelMes.add("Atual"); + labelMes.add("Mês Atual"); XAxis xAxis = barChartTendencia.getXAxis(); xAxis.setValueFormatter(new IndexAxisValueFormatter(labelMes)); - // ⚠️ COMO É SÓ 1 GRUPO, O MÁXIMO É 1 xAxis.setAxisMinimum(0f); xAxis.setAxisMaximum(1f); - barChartTendencia.animateY(1000); + barChartTendencia.animateY(1200); barChartTendencia.invalidate(); } private void configurarEstiloBarChart(BarChart chart) { chart.getDescription().setEnabled(false); + chart.getLegend().setTextColor(corTextoDinamica); - chart.getAxisRight().setEnabled(false); + chart.getLegend().setForm(com.github.mikephil.charting.components.Legend.LegendForm.CIRCLE); // Ícones redondos na legenda + + chart.getAxisRight().setEnabled(false); // Desliga eixo direito chart.getAxisLeft().setTextColor(corTextoDinamica); chart.getAxisLeft().setDrawGridLines(true); - chart.getAxisLeft().setGridColor(Color.LTGRAY); + chart.getAxisLeft().setGridColor(corGridSubtil); // Grelha muito subtil e tecnológica + chart.getAxisLeft().setAxisLineColor(Color.TRANSPARENT); // Remove linha forte do eixo Y XAxis xAxis = chart.getXAxis(); xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); xAxis.setTextColor(corTextoDinamica); - xAxis.setDrawGridLines(false); + xAxis.setDrawGridLines(false); // Remove grelha feia vertical + xAxis.setAxisLineColor(corGridSubtil); xAxis.setGranularity(1f); xAxis.setCenterAxisLabels(true); } diff --git a/app/src/main/java/com/example/finzora/MainActivity.java b/app/src/main/java/com/example/finzora/MainActivity.java index 9e3e313..90cca70 100644 --- a/app/src/main/java/com/example/finzora/MainActivity.java +++ b/app/src/main/java/com/example/finzora/MainActivity.java @@ -13,7 +13,6 @@ import android.os.Bundle; import android.os.Environment; import android.view.LayoutInflater; import android.view.View; -import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -52,7 +51,6 @@ public class MainActivity extends AppCompatActivity { private ViewPager2 viewPager; private FloatingActionButton fabAdicionar; private TextView tvNomeUsuario; - private Button btnSair; private TextView tvSaldoGeral, tvReceitasGeral, tvDespesasGeral; private JSONArray listaTransacoesGlobal; @@ -76,7 +74,6 @@ public class MainActivity extends AppCompatActivity { viewPager = findViewById(R.id.viewPager); fabAdicionar = findViewById(R.id.fabAdicionar); tvNomeUsuario = findViewById(R.id.tvNomeUsuario); - btnSair = findViewById(R.id.btnSair); tvSaldoGeral = findViewById(R.id.tvSaldoGeral); tvReceitasGeral = findViewById(R.id.tvReceitasGeral); @@ -86,8 +83,6 @@ public class MainActivity extends AppCompatActivity { String nome = prefs.getString("nome_usuario", "Investidor"); tvNomeUsuario.setText("Olá, " + nome); - btnSair.setOnClickListener(v -> finishAffinity()); - fabAdicionar.setOnClickListener(v -> startActivity(new Intent(this, AdicionarTransacaoActivity.class))); ImageView btnAbrirDefinicoes = findViewById(R.id.btnAbrirDefinicoes); diff --git a/app/src/main/res/drawable/ic_bell.xml b/app/src/main/res/drawable/ic_bell.xml new file mode 100644 index 0000000..a7d48df --- /dev/null +++ b/app/src/main/res/drawable/ic_bell.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_chevron_right.xml b/app/src/main/res/drawable/ic_chevron_right.xml new file mode 100644 index 0000000..faa95a6 --- /dev/null +++ b/app/src/main/res/drawable/ic_chevron_right.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_delete_forever.xml b/app/src/main/res/drawable/ic_delete_forever.xml new file mode 100644 index 0000000..f666063 --- /dev/null +++ b/app/src/main/res/drawable/ic_delete_forever.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_edit_profile.xml b/app/src/main/res/drawable/ic_edit_profile.xml new file mode 100644 index 0000000..7e73f3e --- /dev/null +++ b/app/src/main/res/drawable/ic_edit_profile.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_fingerprint.xml b/app/src/main/res/drawable/ic_fingerprint.xml new file mode 100644 index 0000000..cecdb83 --- /dev/null +++ b/app/src/main/res/drawable/ic_fingerprint.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..3ac42d8 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml new file mode 100644 index 0000000..23d0cb0 --- /dev/null +++ b/app/src/main/res/drawable/ic_logout.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_moon.xml b/app/src/main/res/drawable/ic_moon.xml new file mode 100644 index 0000000..9615267 --- /dev/null +++ b/app/src/main/res/drawable/ic_moon.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_support.xml b/app/src/main/res/drawable/ic_support.xml new file mode 100644 index 0000000..95b3824 --- /dev/null +++ b/app/src/main/res/drawable/ic_support.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_alterar_password.xml b/app/src/main/res/layout/activity_alterar_password.xml new file mode 100644 index 0000000..6da97e3 --- /dev/null +++ b/app/src/main/res/layout/activity_alterar_password.xml @@ -0,0 +1,153 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_definicoes.xml b/app/src/main/res/layout/activity_definicoes.xml index 91b774a..2fb748b 100644 --- a/app/src/main/res/layout/activity_definicoes.xml +++ b/app/src/main/res/layout/activity_definicoes.xml @@ -1,117 +1,352 @@ - + android:background="@color/fundo_app" + tools:context=".DefinicoesActivity"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:background="@color/fundo_app" + android:orientation="vertical" + android:paddingHorizontal="20dp" + android:paddingTop="8dp" + android:paddingBottom="16dp" + app:layout_constraintBottom_toBottomOf="parent"> - + + + app:cornerRadius="16dp" + app:icon="@drawable/ic_logout" + app:iconGravity="textStart" + app:iconTint="@color/texto_principal" + app:strokeColor="@color/linha_separadora" + app:strokeWidth="1dp" /> - + - - - - - - - - - - - - - - - - - - - - -