diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml
new file mode 100644
index 0000000..91f9558
--- /dev/null
+++ b/.idea/deviceManager.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a3dcf35..b621ebf 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -4,9 +4,7 @@ plugins {
android {
namespace = "com.example.finzora"
- compileSdk {
- version = release(36)
- }
+ compileSdk = 36
defaultConfig {
applicationId = "com.example.finzora"
@@ -34,8 +32,12 @@ android {
}
dependencies {
- implementation(libs.appcompat)
- implementation(libs.material)
+ implementation("androidx.appcompat:appcompat:1.6.1")
+ implementation("com.google.android.material:material:1.13.0")
+ implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
+ implementation("androidx.viewpager2:viewpager2:1.1.0")
+ implementation("com.squareup.okhttp3:okhttp:4.12.0")
+ implementation("com.google.code.gson:gson:2.10.1")
implementation(libs.activity)
implementation(libs.constraintlayout)
testImplementation(libs.junit)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 55134a1..be00765 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,6 +1,9 @@
+ xmlns:tools="http://schemas.android.com/tools"
+ package="com.example.finzora">
+
+
+ android:theme="@style/Theme.Finzora"
+ tools:targetApi="31">
-
-
-
-
+ android:exported="true"
+ android:windowSoftInputMode="adjustResize">
+
+
+
+
+
+
+
+
-
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/AdicionarTransacaoActivity.java b/app/src/main/java/com/example/finzora/AdicionarTransacaoActivity.java
new file mode 100644
index 0000000..11a7e21
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/AdicionarTransacaoActivity.java
@@ -0,0 +1,178 @@
+package com.example.finzora;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.Spinner;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+public class AdicionarTransacaoActivity extends AppCompatActivity {
+
+ // Declaração dos componentes do ecrã (Sem os RadioButtons!)
+ private EditText editValor;
+ private Spinner spinnerCategoria;
+ private Button btnGuardar;
+ private ImageView btnVoltar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_adicionar_transacao);
+
+ inicializarComponentes();
+
+ // --- ENCHER O SPINNER COM AS CATEGORIAS ---
+ String[] categorias = {"Alimentação", "Transportes", "Lazer", "Educação", "Saúde", "Salário", "Mesada", "Prémios", "Outros"};
+
+ ArrayAdapter adapter = new ArrayAdapter<>(
+ this,
+ R.layout.item_dropdown,
+ categorias
+ );
+ adapter.setDropDownViewResource(R.layout.item_dropdown);
+ spinnerCategoria.setAdapter(adapter);
+
+ // Botão para voltar para trás
+ if (btnVoltar != null) {
+ btnVoltar.setOnClickListener(v -> finish());
+ }
+
+ // AGORA O BOTÃO GUARDAR CHAMA O POP-UP!
+ btnGuardar.setOnClickListener(v -> perguntarTipoTransacao());
+ }
+
+ // ====================================================================
+ // A MAGIA DO POP-UP
+ // ====================================================================
+ private void perguntarTipoTransacao() {
+ String valorStr = editValor.getText().toString().trim();
+
+ // Primeiro, verifica se ele preencheu o valor antes de perguntar o tipo
+ if (TextUtils.isEmpty(valorStr)) {
+ editValor.setError("Preenche o valor primeiro!");
+ editValor.requestFocus();
+ return;
+ }
+
+ // Criar o Pop-up de escolha
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Tipo de Transação");
+ builder.setMessage("Esta transação é uma Receita (entrada) ou uma Despesa (saída)?");
+
+ // Botão de Receita (Passa o tipo 1 para a base de dados)
+ builder.setPositiveButton("Receita 📈", (dialog, which) -> {
+ salvarTransacaoNaBaseDeDados(1);
+ });
+
+ // Botão de Despesa (Passa o tipo 2 para a base de dados)
+ builder.setNegativeButton("Despesa 📉", (dialog, which) -> {
+ salvarTransacaoNaBaseDeDados(2);
+ });
+
+ // Botão Cancelar (Caso o utilizador queira fechar e alterar algo)
+ builder.setNeutralButton("Cancelar", (dialog, which) -> {
+ dialog.dismiss();
+ });
+
+ // Mostrar o Pop-up no ecrã
+ AlertDialog dialog = builder.create();
+ dialog.show();
+ }
+
+ // ====================================================================
+ // GUARDAR NA BASE DE DADOS APÓS A ESCOLHA
+ // ====================================================================
+ private void salvarTransacaoNaBaseDeDados(int tipoEscolhido) {
+ // Primeiro, vamos buscar o "Carimbo" (ID) de quem está a usar a app
+ android.content.SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ String userId = prefs.getString("user_id", null);
+
+ if (userId == null) {
+ android.widget.Toast.makeText(this, "Erro: Utilizador não identificado. Faz login novamente.", android.widget.Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ try {
+ String valorStr = editValor.getText().toString().trim();
+ valorStr = valorStr.replace(",", "."); // Evitar crashes com vírgulas
+ double valor = Double.parseDouble(valorStr);
+ String categoria = spinnerCategoria.getSelectedItem().toString();
+ String dataStr = new java.text.SimpleDateFormat("dd/MM/yyyy", java.util.Locale.getDefault()).format(new java.util.Date());
+
+ // Mudar o texto do botão para o utilizador perceber que está a gravar
+ btnGuardar.setEnabled(false);
+ btnGuardar.setText("A GRAVAR NAS NUVENS...");
+
+ // Preparar o cliente de Internet
+ okhttp3.OkHttpClient client = new okhttp3.OkHttpClient();
+
+ // Construir o JSON que vai viajar até ao Supabase
+ // AQUI ESTÁ A CORREÇÃO: a coluna chama-se "data" para bater certo com o teu SQL!
+ String json = "{"
+ + "\"user_id\":\"" + userId + "\", "
+ + "\"valor\":" + valor + ", "
+ + "\"categoria\":\"" + categoria + "\", "
+ + "\"tipo\":" + tipoEscolhido + ", "
+ + "\"data\":\"" + dataStr + "\""
+ + "}";
+
+ okhttp3.RequestBody body = okhttp3.RequestBody.create(json, okhttp3.MediaType.parse("application/json; charset=utf-8"));
+
+ // Fazer o pedido POST para a tabela "transacoes" do Supabase
+ okhttp3.Request request = new okhttp3.Request.Builder()
+ .url(SupabaseConfig.SUPABASE_URL + "/rest/v1/transacoes")
+ .addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
+ .addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
+ .addHeader("Content-Type", "application/json")
+ .addHeader("Prefer", "return=minimal") // Diz ao Supabase para não devolver os dados de volta
+ .post(body)
+ .build();
+
+ // Executar o envio em segundo plano para não bloquear o ecrã
+ client.newCall(request).enqueue(new okhttp3.Callback() {
+ @Override
+ public void onFailure(@androidx.annotation.NonNull okhttp3.Call call, @androidx.annotation.NonNull java.io.IOException e) {
+ runOnUiThread(() -> {
+ btnGuardar.setEnabled(true);
+ btnGuardar.setText("Guardar Transação");
+ android.widget.Toast.makeText(AdicionarTransacaoActivity.this, "Erro de net! A transação não foi guardada.", android.widget.Toast.LENGTH_SHORT).show();
+ });
+ }
+
+ @Override
+ public void onResponse(@androidx.annotation.NonNull okhttp3.Call call, @androidx.annotation.NonNull okhttp3.Response response) throws java.io.IOException {
+ runOnUiThread(() -> {
+ if (response.isSuccessful()) {
+ android.widget.Toast.makeText(AdicionarTransacaoActivity.this, "Transação guardada com sucesso! 🎉", android.widget.Toast.LENGTH_SHORT).show();
+ finish(); // Volta ao ecrã principal
+ } else {
+ btnGuardar.setEnabled(true);
+ btnGuardar.setText("Guardar Transação");
+ android.widget.Toast.makeText(AdicionarTransacaoActivity.this, "Erro no Supabase. Tenta novamente.", android.widget.Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ });
+
+ } catch (Exception e) {
+ android.widget.Toast.makeText(this, "ERRO LOCAL: " + e.getMessage(), android.widget.Toast.LENGTH_LONG).show();
+ e.printStackTrace();
+ }
+ }
+ private void inicializarComponentes() {
+ editValor = findViewById(R.id.editValor);
+ spinnerCategoria = findViewById(R.id.spinnerCategoria);
+ btnGuardar = findViewById(R.id.btnGuardar);
+ btnVoltar = findViewById(R.id.btnVoltar);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/DBHelper.java b/app/src/main/java/com/example/finzora/DBHelper.java
new file mode 100644
index 0000000..e1160d0
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/DBHelper.java
@@ -0,0 +1,154 @@
+package com.example.finzora;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class DBHelper extends SQLiteOpenHelper {
+
+ private static final String DATABASE_NAME = "finzora.db";
+ private static final int DATABASE_VERSION = 2; // Versão 2 para incluir Orçamentos
+
+ public DBHelper(Context context) {
+ super(context, DATABASE_NAME, null, DATABASE_VERSION);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ // Criação da Tabela de Transações
+ db.execSQL("CREATE TABLE transacoes (id INTEGER PRIMARY KEY AUTOINCREMENT, valor REAL, categoria TEXT, tipo INTEGER, data TEXT)");
+
+ // Criação da Tabela de Orçamentos
+ db.execSQL("CREATE TABLE orcamentos (categoria TEXT PRIMARY KEY, valor_limite REAL)");
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ db.execSQL("DROP TABLE IF EXISTS transacoes");
+ db.execSQL("DROP TABLE IF EXISTS orcamentos");
+ onCreate(db);
+ }
+
+ // ==========================================
+ // MÉTODOS PARA AS TRANSAÇÕES
+ // ==========================================
+
+ public void adicionarTransacao(Transacao transacao) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put("valor", transacao.getValor());
+ values.put("categoria", transacao.getCategoria());
+ values.put("tipo", transacao.getTipo());
+ values.put("data", transacao.getData());
+ db.insert("transacoes", null, values);
+ }
+
+ public List listarTransacoes() {
+ List lista = new ArrayList<>();
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor = db.rawQuery("SELECT * FROM transacoes ORDER BY id DESC", null);
+
+ if (cursor.moveToFirst()) {
+ do {
+ lista.add(new Transacao(
+ cursor.getInt(0), // id
+ cursor.getFloat(1), // valor
+ cursor.getString(2),// categoria
+ cursor.getInt(3), // tipo
+ cursor.getString(4) // data
+ ));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ return lista;
+ }
+
+ public void eliminarTransacao(int id) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ db.delete("transacoes", "id=?", new String[]{String.valueOf(id)});
+ }
+
+ // ==========================================
+ // MÉTODOS PARA OS ORÇAMENTOS
+ // ==========================================
+
+ public void salvarOrcamento(String categoria, float limite) {
+ SQLiteDatabase db = this.getWritableDatabase();
+ ContentValues values = new ContentValues();
+ values.put("categoria", categoria);
+ values.put("valor_limite", limite);
+ // CONFLICT_REPLACE garante que se a categoria já existir, ele só atualiza o limite
+ db.insertWithOnConflict("orcamentos", null, values, SQLiteDatabase.CONFLICT_REPLACE);
+ }
+
+ public Map getOrcamentosDefinidos() {
+ Map orcamentos = new HashMap<>();
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor = db.rawQuery("SELECT * FROM orcamentos", null);
+
+ if (cursor.moveToFirst()) {
+ do {
+ orcamentos.put(cursor.getString(0), cursor.getFloat(1));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ return orcamentos;
+ }
+
+ public float getGastoPorCategoria(String categoria) {
+ SQLiteDatabase db = this.getReadableDatabase();
+ // Soma todas as transações que sejam Despesa (tipo=2) na categoria escolhida
+ Cursor cursor = db.rawQuery("SELECT SUM(valor) FROM transacoes WHERE tipo=2 AND categoria=?", new String[]{categoria});
+ float total = 0;
+ if (cursor.moveToFirst()) {
+ total = cursor.getFloat(0);
+ }
+ cursor.close();
+ return total;
+ }
+
+ // ==========================================
+ // MATEMÁTICA PARA OS CARTÕES E GRÁFICOS
+ // ==========================================
+
+ // Calcula todo o dinheiro que entrou (Receitas, tipo = 1)
+ public float getTotalReceitas() {
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor = db.rawQuery("SELECT SUM(valor) FROM transacoes WHERE tipo=1", null);
+ float total = 0;
+ if (cursor.moveToFirst()) total = cursor.getFloat(0);
+ cursor.close();
+ return total;
+ }
+
+ // Calcula todo o dinheiro que saiu (Despesas, tipo = 2)
+ public float getTotalDespesas() {
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor = db.rawQuery("SELECT SUM(valor) FROM transacoes WHERE tipo=2", null);
+ float total = 0;
+ if (cursor.moveToFirst()) total = cursor.getFloat(0);
+ cursor.close();
+ return total;
+ }
+
+ // Prepara os dados para a aba dos Gráficos (Soma os gastos de cada categoria)
+ public HashMap getDespesasPorCategoria() {
+ HashMap mapa = new HashMap<>();
+ SQLiteDatabase db = this.getReadableDatabase();
+ Cursor cursor = db.rawQuery("SELECT categoria, SUM(valor) FROM transacoes WHERE tipo=2 GROUP BY categoria", null);
+
+ if (cursor.moveToFirst()) {
+ do {
+ mapa.put(cursor.getString(0), cursor.getFloat(1));
+ } while (cursor.moveToNext());
+ }
+ cursor.close();
+ return mapa;
+ }
+}
\ 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
new file mode 100644
index 0000000..f7a040a
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/DefinicoesActivity.java
@@ -0,0 +1,175 @@
+package com.example.finzora;
+
+import android.app.Dialog;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.Switch;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+
+public class DefinicoesActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_definicoes);
+
+ // Ligar todos os botões do ecrã ao nosso código Java
+ TextView btnVoltarDefinicoes = findViewById(R.id.btnVoltarDefinicoes);
+ TextView btnEditarPerfil = findViewById(R.id.btnEditarPerfil);
+ Switch switchModoEscuro = findViewById(R.id.switchModoEscuro);
+ Switch switchNotificacoes = findViewById(R.id.switchNotificacoes);
+ TextView btnSuporte = findViewById(R.id.btnSuporte);
+ Button btnTerminarSessao = findViewById(R.id.btnTerminarSessao);
+
+ // --- 0. BOTÃO DE VOLTAR ---
+ btnVoltarDefinicoes.setOnClickListener(v -> finish());
+
+ // --- 1. EDITAR PERFIL ---
+ // Agora já abre o novo ecrã de Edição de Perfil!
+ btnEditarPerfil.setOnClickListener(v -> {
+ startActivity(new Intent(DefinicoesActivity.this, EditarPerfilActivity.class));
+ });
+
+ // --- 2. MAGIA DO MODO ESCURO / CLARO ---
+ SharedPreferences prefsTema = getSharedPreferences("TemaApp", MODE_PRIVATE);
+ boolean isModoEscuro = prefsTema.getBoolean("modo_escuro", true);
+ switchModoEscuro.setChecked(isModoEscuro);
+
+ switchModoEscuro.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ prefsTema.edit().putBoolean("modo_escuro", isChecked).apply();
+
+ if (isChecked) {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+ Toast.makeText(this, "Modo Escuro Ativado 🌙", Toast.LENGTH_SHORT).show();
+ } else {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+ Toast.makeText(this, "Modo Claro Ativado ☀️", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ // --- 3. NOTIFICAÇÕES ---
+ switchNotificacoes.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ if (isChecked) {
+ Toast.makeText(this, "Notificações Ligadas 🔔", Toast.LENGTH_SHORT).show();
+ } else {
+ Toast.makeText(this, "Notificações Desligadas 🔕", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ // --- 4. CENTRO DE SUPORTE (POP-UP) ---
+ btnSuporte.setOnClickListener(v -> mostrarDialogSuporte());
+
+ // --- 5. TERMINAR SESSÃO ---
+ btnTerminarSessao.setOnClickListener(v -> terminarSessao());
+ }
+
+ private void mostrarDialogSuporte() {
+ Dialog dialog = new Dialog(this);
+ dialog.setContentView(R.layout.dialog_suporte);
+ dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ // O Botão de Fechar o Pop-up principal
+ dialog.findViewById(R.id.btnFecharSuporte).setOnClickListener(v -> dialog.dismiss());
+
+ // Ligar os cliques nos Cartões
+ dialog.findViewById(R.id.cardFAQ).setOnClickListener(v -> {
+ dialog.dismiss(); // Fecha este menu
+ mostrarDialogFAQ(); // Abre o FAQ
+ });
+
+ dialog.findViewById(R.id.cardTutorial).setOnClickListener(v -> {
+ dialog.dismiss();
+ mostrarDialogTutorial();
+ });
+
+ dialog.findViewById(R.id.cardMensagem).setOnClickListener(v -> {
+ dialog.dismiss();
+ mostrarDialogContactar();
+ });
+
+ dialog.findViewById(R.id.cardContactos).setOnClickListener(v -> {
+ Toast.makeText(this, "Email: suporte@finzora.pt\nTel: +351 800 123 456", Toast.LENGTH_LONG).show();
+ });
+
+ dialog.show();
+ }
+
+ private void mostrarDialogFAQ() {
+ Dialog dialog = new Dialog(this);
+ dialog.setContentView(R.layout.dialog_faq);
+ dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ // Setinha de voltar (Fecha o FAQ e volta a abrir o menu principal)
+ dialog.findViewById(R.id.btnVoltarFAQ).setOnClickListener(v -> {
+ dialog.dismiss();
+ mostrarDialogSuporte();
+ });
+
+ dialog.show();
+ }
+
+ private void mostrarDialogTutorial() {
+ Dialog dialog = new Dialog(this);
+ dialog.setContentView(R.layout.dialog_tutorial);
+ dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ // Setinha de voltar
+ dialog.findViewById(R.id.btnVoltarTutorial).setOnClickListener(v -> {
+ dialog.dismiss();
+ mostrarDialogSuporte();
+ });
+
+ dialog.show();
+ }
+
+ private void mostrarDialogContactar() {
+ Dialog dialog = new Dialog(this);
+ dialog.setContentView(R.layout.dialog_contactar);
+ dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ // Setinha de voltar
+ dialog.findViewById(R.id.btnVoltarContactar).setOnClickListener(v -> {
+ dialog.dismiss();
+ mostrarDialogSuporte();
+ });
+
+ // Botões do formulário
+ dialog.findViewById(R.id.btnCancelarContacto).setOnClickListener(v -> {
+ dialog.dismiss();
+ mostrarDialogSuporte();
+ });
+
+ dialog.findViewById(R.id.btnEnviarMensagem).setOnClickListener(v -> {
+ Toast.makeText(this, "Mensagem enviada com sucesso!", Toast.LENGTH_SHORT).show();
+ dialog.dismiss();
+ mostrarDialogSuporte();
+ });
+
+ dialog.show();
+ }
+
+ private void terminarSessao() {
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ prefs.edit().clear().apply();
+
+ Toast.makeText(this, "Sessão terminada com sucesso!", Toast.LENGTH_SHORT).show();
+
+ Intent intent = new Intent(this, LoginActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ finish();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/DicasFragment.java b/app/src/main/java/com/example/finzora/DicasFragment.java
index f05b067..4b87698 100644
--- a/app/src/main/java/com/example/finzora/DicasFragment.java
+++ b/app/src/main/java/com/example/finzora/DicasFragment.java
@@ -1,6 +1,163 @@
package com.example.finzora;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import java.util.HashMap;
+import java.util.Map;
public class DicasFragment extends Fragment {
-}
+
+ // Componentes de Saúde Financeira
+ private TextView tvTaxaPoupanca, tvDicasReceitas, tvDicasDespesas;
+ private ProgressBar progressPoupanca;
+
+ // Componentes das Dicas
+ private TextView tvTituloDica1, tvDescDica1;
+ private TextView tvTituloDica2, tvDescDica2;
+
+ // Distribuição de Gastos
+ private LinearLayout layoutDistribuicao;
+
+ private DBHelper dbHelper;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_dicas, container, false);
+
+ // Ligar os componentes
+ tvTaxaPoupanca = view.findViewById(R.id.tvTaxaPoupanca);
+ tvDicasReceitas = view.findViewById(R.id.tvDicasReceitas);
+ tvDicasDespesas = view.findViewById(R.id.tvDicasDespesas);
+ progressPoupanca = view.findViewById(R.id.progressPoupanca);
+
+ tvTituloDica1 = view.findViewById(R.id.tvTituloDica1);
+ tvDescDica1 = view.findViewById(R.id.tvDescDica1);
+ tvTituloDica2 = view.findViewById(R.id.tvTituloDica2);
+ tvDescDica2 = view.findViewById(R.id.tvDescDica2);
+
+ layoutDistribuicao = view.findViewById(R.id.layoutDistribuicao);
+
+ dbHelper = new DBHelper(getActivity());
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ analisarFinancas();
+ }
+
+ private void analisarFinancas() {
+ float receitas = dbHelper.getTotalReceitas();
+ float despesas = dbHelper.getTotalDespesas();
+
+ // 1. Atualizar Textos Iniciais
+ tvDicasReceitas.setText(String.format("€ %.2f", receitas));
+ tvDicasDespesas.setText(String.format("€ %.2f", despesas));
+
+ // 2. Calcular Taxa de Poupança
+ float taxaPoupanca = 0;
+ if (receitas > 0) {
+ taxaPoupanca = ((receitas - despesas) / receitas) * 100;
+ }
+
+ // Se gastou mais do que ganhou, a taxa é 0
+ if (taxaPoupanca < 0) taxaPoupanca = 0;
+
+ tvTaxaPoupanca.setText(String.format("%.1f%%", taxaPoupanca));
+ progressPoupanca.setProgress((int) taxaPoupanca);
+
+ // Cores consoante a saúde financeira
+ if (taxaPoupanca >= 20) {
+ tvTaxaPoupanca.setTextColor(Color.parseColor("#00E676")); // Verde
+ progressPoupanca.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#00E676")));
+
+ tvTituloDica1.setText("Excelente Taxa de Poupança! \uD83C\uDF1F");
+ tvTituloDica1.setTextColor(Color.parseColor("#00E676"));
+ tvDescDica1.setText("Estás a poupar " + String.format("%.1f", taxaPoupanca) + "% dos teus rendimentos. Continua com este ótimo hábito financeiro!");
+ } else if (taxaPoupanca > 0) {
+ tvTaxaPoupanca.setTextColor(Color.parseColor("#FFD600")); // Amarelo
+ progressPoupanca.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#FFD600")));
+
+ tvTituloDica1.setText("Atenção à Poupança \uD83D\uDD0D");
+ tvTituloDica1.setTextColor(Color.parseColor("#FFD600"));
+ tvDescDica1.setText("Estás a poupar muito pouco. A meta recomendada é guardar pelo menos 20% do que ganhas.");
+ } else {
+ tvTaxaPoupanca.setTextColor(Color.parseColor("#FF1744")); // Vermelho
+ progressPoupanca.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#FF1744")));
+
+ tvTituloDica1.setText("Alerta Vermelho! \uD83D\uDEA8");
+ tvTituloDica1.setTextColor(Color.parseColor("#FF1744"));
+ tvDescDica1.setText("Os teus gastos superam ou igualam os teus ganhos. Verifica urgentemente para onde está a ir o teu dinheiro!");
+ }
+
+ // 3. Descobrir a Categoria mais gasta
+ HashMap gastosPorCategoria = dbHelper.getDespesasPorCategoria();
+ String piorCategoria = "Nenhuma";
+ float maiorGasto = 0;
+
+ for (Map.Entry entry : gastosPorCategoria.entrySet()) {
+ if (entry.getValue() > maiorGasto) {
+ maiorGasto = entry.getValue();
+ piorCategoria = entry.getKey();
+ }
+ }
+
+ if (maiorGasto > 0) {
+ float percPiorCategoria = (maiorGasto / despesas) * 100;
+ tvTituloDica2.setText("Gastos Elevados em " + piorCategoria);
+ tvTituloDica2.setTextColor(Color.parseColor("#FF1744"));
+ tvDescDica2.setText(String.format("%.1f%%", percPiorCategoria) + " das tuas despesas são em " + piorCategoria + " (€ " + String.format("%.2f", maiorGasto) + "). Tenta reduzir aqui!");
+ } else {
+ tvTituloDica2.setText("Tudo Controlado ✅");
+ tvTituloDica2.setTextColor(Color.parseColor("#00E676"));
+ tvDescDica2.setText("Ainda não tens despesas suficientes para analisarmos. Continua o bom trabalho!");
+ }
+
+ // 4. Construir as barras de Distribuição de Gastos magicamente
+ layoutDistribuicao.removeAllViews(); // Limpa as barras antigas
+
+ if (despesas > 0) {
+ for (Map.Entry entry : gastosPorCategoria.entrySet()) {
+ float valorCat = entry.getValue();
+ if (valorCat > 0) {
+ float percentagem = (valorCat / despesas) * 100;
+
+ // Criar o título da categoria (Ex: Alimentação - €50.00 (20%))
+ TextView tvCat = new TextView(getActivity());
+ tvCat.setText(entry.getKey() + " — € " + String.format("%.2f", valorCat) + " (" + (int) percentagem + "%)");
+ tvCat.setTextColor(Color.WHITE);
+ tvCat.setTextSize(14f);
+ tvCat.setPadding(0, 16, 0, 8); // Margens
+
+ // Criar a barra de progresso horizontal
+ ProgressBar pb = new ProgressBar(getActivity(), null, android.R.attr.progressBarStyleHorizontal);
+ pb.setMax(100);
+ pb.setProgress((int) percentagem);
+ pb.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#00E5FF"))); // Azul Tech
+
+ // Adicionar ao ecrã
+ layoutDistribuicao.addView(tvCat);
+ layoutDistribuicao.addView(pb);
+ }
+ }
+ } else {
+ TextView semDespesas = new TextView(getActivity());
+ semDespesas.setText("Ainda não existem despesas registadas.");
+ semDespesas.setTextColor(Color.parseColor("#B0BEC5"));
+ layoutDistribuicao.addView(semDespesas);
+ }
+ }
+}
\ 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
new file mode 100644
index 0000000..c30b3b8
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/EditarPerfilActivity.java
@@ -0,0 +1,59 @@
+package com.example.finzora;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class EditarPerfilActivity extends AppCompatActivity {
+
+ private EditText editNomePerfil;
+ private EditText editEmailPerfil;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_editar_perfil);
+
+ // Ligar ao XML
+ TextView btnVoltar = findViewById(R.id.btnVoltarEditarPerfil);
+ editNomePerfil = findViewById(R.id.editNomePerfil);
+ editEmailPerfil = findViewById(R.id.editEmailPerfil);
+ Button btnGuardarPerfil = findViewById(R.id.btnGuardarPerfil);
+
+ // Voltar para as definições
+ btnVoltar.setOnClickListener(v -> finish());
+
+ // 1. CARREGAR OS DADOS ATUAIS DA MEMÓRIA
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ String nomeAtual = prefs.getString("nome_usuario", "Investidor");
+ String emailAtual = prefs.getString("email_usuario", ""); // Pode estar vazio se não guardaste no login
+
+ editNomePerfil.setText(nomeAtual);
+ editEmailPerfil.setText(emailAtual);
+
+ // 2. GUARDAR OS DADOS NOVOS
+ btnGuardarPerfil.setOnClickListener(v -> {
+ String novoNome = editNomePerfil.getText().toString().trim();
+ String novoEmail = editEmailPerfil.getText().toString().trim();
+
+ if (novoNome.isEmpty()) {
+ editNomePerfil.setError("O nome não pode estar vazio!");
+ return;
+ }
+
+ // Grava na memória (SharedPreferences)
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString("nome_usuario", novoNome);
+ editor.putString("email_usuario", novoEmail);
+ editor.apply();
+
+ Toast.makeText(this, "Perfil atualizado com sucesso! 🎉", Toast.LENGTH_SHORT).show();
+ finish(); // Fecha o ecrã e volta atrás
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/GraficosFragment.java b/app/src/main/java/com/example/finzora/GraficosFragment.java
index a7e3774..4f3bd4c 100644
--- a/app/src/main/java/com/example/finzora/GraficosFragment.java
+++ b/app/src/main/java/com/example/finzora/GraficosFragment.java
@@ -1,6 +1,190 @@
package com.example.finzora;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import com.github.mikephil.charting.charts.BarChart;
+import com.github.mikephil.charting.charts.PieChart;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+import com.github.mikephil.charting.data.PieData;
+import com.github.mikephil.charting.data.PieDataSet;
+import com.github.mikephil.charting.data.PieEntry;
+import com.github.mikephil.charting.formatter.IndexAxisValueFormatter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
public class GraficosFragment extends Fragment {
-}
+
+ private PieChart pieChartDespesas;
+ private BarChart barChartOrcamento;
+ private BarChart barChartTendencia;
+ private DBHelper dbHelper;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_graficos, container, false);
+
+ pieChartDespesas = view.findViewById(R.id.pieChartDespesas);
+ barChartOrcamento = view.findViewById(R.id.barChartOrcamento);
+ barChartTendencia = view.findViewById(R.id.barChartTendencia);
+ dbHelper = new DBHelper(getActivity());
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ carregarPieChart();
+ carregarBarChartOrcamento();
+ carregarBarChartTendencia();
+ }
+
+ // ==========================================
+ // 1. GRÁFICO CIRCULAR (Despesas por Categoria)
+ // ==========================================
+ private void carregarPieChart() {
+ pieChartDespesas.getDescription().setEnabled(false);
+ pieChartDespesas.setHoleColor(Color.parseColor("#2C5364"));
+ pieChartDespesas.getLegend().setTextColor(Color.WHITE);
+ pieChartDespesas.setCenterText("Despesas");
+ pieChartDespesas.setCenterTextColor(Color.WHITE);
+ pieChartDespesas.setEntryLabelColor(Color.WHITE);
+
+ HashMap despesas = dbHelper.getDespesasPorCategoria();
+ ArrayList entradas = new ArrayList<>();
+
+ for (Map.Entry entry : despesas.entrySet()) {
+ if (entry.getValue() > 0) entradas.add(new PieEntry(entry.getValue(), entry.getKey()));
+ }
+
+ 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")});
+
+ PieData data = new PieData(dataSet);
+ data.setValueTextSize(14f);
+ data.setValueTextColor(Color.WHITE);
+ pieChartDespesas.setData(data);
+ pieChartDespesas.animateY(1000);
+ }
+
+ // ==========================================
+ // 2. GRÁFICO DE BARRAS (Orçamento vs Gastos)
+ // ==========================================
+ private void carregarBarChartOrcamento() {
+ configurarEstiloBarChart(barChartOrcamento);
+
+ Map orcamentos = dbHelper.getOrcamentosDefinidos();
+ ArrayList gastosEntries = new ArrayList<>();
+ ArrayList orcamentoEntries = new ArrayList<>();
+ ArrayList categorias = new ArrayList<>();
+
+ int index = 0;
+ for (Map.Entry entry : orcamentos.entrySet()) {
+ String categoria = entry.getKey();
+ float limite = entry.getValue();
+ float gasto = dbHelper.getGastoPorCategoria(categoria);
+
+ categorias.add(categoria);
+ gastosEntries.add(new BarEntry(index, gasto));
+ orcamentoEntries.add(new BarEntry(index, limite));
+ index++;
+ }
+
+ if (categorias.isEmpty()) { barChartOrcamento.clear(); return; }
+
+ BarDataSet setGastos = new BarDataSet(gastosEntries, "Gastos Reais");
+ setGastos.setColor(Color.parseColor("#FF4081")); // Rosa (Figma)
+ setGastos.setValueTextColor(Color.WHITE);
+
+ BarDataSet setOrcamento = new BarDataSet(orcamentoEntries, "Orçamento");
+ setOrcamento.setColor(Color.parseColor("#00E5FF")); // Azul (Figma)
+ setOrcamento.setValueTextColor(Color.WHITE);
+
+ BarData data = new BarData(setGastos, setOrcamento);
+ // Lógica de agrupamento (Grouped Bar Chart)
+ float groupSpace = 0.2f; float barSpace = 0.05f; float barWidth = 0.35f;
+ data.setBarWidth(barWidth);
+ barChartOrcamento.setData(data);
+ barChartOrcamento.groupBars(-0.5f, groupSpace, barSpace);
+
+ // Labels no Eixo X
+ XAxis xAxis = barChartOrcamento.getXAxis();
+ xAxis.setValueFormatter(new IndexAxisValueFormatter(categorias));
+ xAxis.setAxisMinimum(-0.5f);
+ xAxis.setAxisMaximum(categorias.size() - 0.5f);
+
+ barChartOrcamento.animateY(1000);
+ }
+
+ // ==========================================
+ // 3. GRÁFICO DE BARRAS (Tendência Mensal Geral)
+ // ==========================================
+ private void carregarBarChartTendencia() {
+ configurarEstiloBarChart(barChartTendencia);
+
+ float totalReceitas = dbHelper.getTotalReceitas();
+ float totalDespesas = dbHelper.getTotalDespesas();
+
+ ArrayList despesaEntry = new ArrayList<>();
+ ArrayList receitaEntry = new ArrayList<>();
+
+ despesaEntry.add(new BarEntry(0, totalDespesas));
+ receitaEntry.add(new BarEntry(0, totalReceitas));
+
+ BarDataSet setDespesas = new BarDataSet(despesaEntry, "Despesas");
+ setDespesas.setColor(Color.parseColor("#FF1744")); // Vermelho
+ setDespesas.setValueTextColor(Color.WHITE);
+
+ BarDataSet setReceitas = new BarDataSet(receitaEntry, "Receitas");
+ setReceitas.setColor(Color.parseColor("#00E676")); // Verde
+ setReceitas.setValueTextColor(Color.WHITE);
+
+ BarData data = new BarData(setDespesas, setReceitas);
+
+ float groupSpace = 0.3f; float barSpace = 0.05f; float barWidth = 0.3f;
+ data.setBarWidth(barWidth);
+ barChartTendencia.setData(data);
+ barChartTendencia.groupBars(-0.5f, groupSpace, barSpace);
+
+ ArrayList labelMes = new ArrayList<>();
+ labelMes.add("Atual");
+
+ XAxis xAxis = barChartTendencia.getXAxis();
+ xAxis.setValueFormatter(new IndexAxisValueFormatter(labelMes));
+ xAxis.setAxisMinimum(-0.5f);
+ xAxis.setAxisMaximum(0.5f);
+
+ barChartTendencia.animateY(1000);
+ }
+
+ // Função de limpeza de design comum aos dois gráficos de barras
+ private void configurarEstiloBarChart(BarChart chart) {
+ chart.getDescription().setEnabled(false);
+ chart.getLegend().setTextColor(Color.WHITE);
+ chart.getAxisRight().setEnabled(false); // Esconde números à direita
+
+ chart.getAxisLeft().setTextColor(Color.WHITE);
+ chart.getAxisLeft().setDrawGridLines(true);
+ chart.getAxisLeft().setGridColor(Color.parseColor("#455A64")); // Linhas de fundo subtis
+
+ XAxis xAxis = chart.getXAxis();
+ xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
+ xAxis.setTextColor(Color.WHITE);
+ xAxis.setDrawGridLines(false);
+ xAxis.setGranularity(1f);
+ xAxis.setCenterAxisLabels(true);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/LoginActivity.java b/app/src/main/java/com/example/finzora/LoginActivity.java
index 3689b84..d7c387d 100644
--- a/app/src/main/java/com/example/finzora/LoginActivity.java
+++ b/app/src/main/java/com/example/finzora/LoginActivity.java
@@ -1,36 +1,66 @@
package com.example.finzora;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
+import org.json.JSONObject;
+import java.io.IOException;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
public class LoginActivity extends AppCompatActivity {
private TextInputEditText editEmail, editPassword;
private Button btnEntrar;
- private TextView txtRegistrar;
+ private TextView txtRegistrar, txtEsqueciPassword;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ boolean jaDeuLogin = prefs.getBoolean("is_logged_in", false);
+
+ if (jaDeuLogin) {
+ // Já tem o carimbo! Vai direto para o ecrã principal.
+ Intent intent = new Intent(LoginActivity.this, MainActivity.class);
+ startActivity(intent);
+ finish();
+ return; // Para o código aqui para não desenhar o ecrã de login
+ }
+
+
setContentView(R.layout.activity_login);
- // 1. Inicializar os componentes da interface
inicializarComponentes();
- // 2. Configurar o clique no botão "Entrar"
+ // Clique no botão "Entrar" -> Agora faz Login de verdade!
btnEntrar.setOnClickListener(v -> validarDados());
- // 3. Configurar o clique para ir para a tela de Registo
+ // Clique para ir para Registo
txtRegistrar.setOnClickListener(v -> {
Intent intent = new Intent(LoginActivity.this, RegisterActivity.class);
startActivity(intent);
});
+
+ // Clique no "Esqueci-me da palavra-passe" -> Agora abre o novo ecrã!
+ txtEsqueciPassword.setOnClickListener(v -> {
+ startActivity(new Intent(LoginActivity.this, RecuperarPasswordActivity.class));
+ });
}
private void validarDados() {
@@ -44,20 +74,81 @@ public class LoginActivity extends AppCompatActivity {
editPassword.setError("Introduza a sua palavra-passe");
editPassword.requestFocus();
} else {
- // Se tudo estiver OK, por enquanto vamos apenas simular o login
- // e saltar para o Dashboard (que será a nossa próxima fase)
- Toast.makeText(this, "Login efetuado com sucesso!", Toast.LENGTH_SHORT).show();
-
- // Aqui é onde irás validar com a Base de Dados ou Firebase no futuro
- irParaDashboard();
+ // Desativa o botão enquanto pensa
+ btnEntrar.setEnabled(false);
+ btnEntrar.setText("A VERIFICAR DADOS...");
+ fazerLoginNoSupabase(email, password);
}
}
+ private void fazerLoginNoSupabase(String email, String password) {
+ OkHttpClient client = new OkHttpClient();
+
+ String json = "{\"email\":\"" + email + "\", \"password\":\"" + password + "\"}";
+ RequestBody body = RequestBody.create(json, MediaType.parse("application/json; charset=utf-8"));
+
+ // URL para fazer login (grant_type=password)
+ Request request = new Request.Builder()
+ .url(SupabaseConfig.SUPABASE_URL + "/auth/v1/token?grant_type=password")
+ .addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
+ .addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
+ .post(body)
+ .build();
+
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ runOnUiThread(() -> {
+ Toast.makeText(LoginActivity.this, "Erro de ligação à internet!", Toast.LENGTH_SHORT).show();
+ btnEntrar.setEnabled(true);
+ btnEntrar.setText("INICIAR SESSÃO");
+ });
+ }
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ final String responseData = response.body().string();
+
+ runOnUiThread(() -> {
+ btnEntrar.setEnabled(true);
+ btnEntrar.setText("INICIAR SESSÃO");
+
+ if (response.isSuccessful()) {
+ // SUCESSO! A palavra-passe estava certa!
+ try {
+ JSONObject jsonResponse = new JSONObject(responseData);
+ String userId = jsonResponse.getJSONObject("user").getString("id");
+
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+
+ editor.putBoolean("is_logged_in", true); // O nosso carimbo!
+ editor.putString("user_id", userId); // O ID do Supabase
+ editor.putString("email_usuario", email);// Guardamos o email para o Perfil
+ editor.apply();
+
+ Toast.makeText(LoginActivity.this, "Bem-vindo de volta!", Toast.LENGTH_SHORT).show();
+ irParaDashboard();
+
+ } catch (Exception e) {
+ Toast.makeText(LoginActivity.this, "Erro a processar os dados.", Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ // ERRO! Palavra-passe errada ou email não existe!
+ Toast.makeText(LoginActivity.this, "Credenciais incorretas. Tenta novamente!", Toast.LENGTH_LONG).show();
+ editPassword.setError("Palavra-passe errada");
+ editPassword.setText(""); // Limpa a password para ele tentar de novo
+ }
+ });
+ }
+ });
+ }
+
private void irParaDashboard() {
- // Altera 'MainActivity' para o nome da tua classe do Dashboard
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
- finish(); // Fecha o login para o utilizador não voltar atrás ao carregar no botão 'back'
+ finish();
}
private void inicializarComponentes() {
@@ -65,5 +156,6 @@ public class LoginActivity extends AppCompatActivity {
editPassword = findViewById(R.id.editPassword);
btnEntrar = findViewById(R.id.btnEntrar);
txtRegistrar = findViewById(R.id.txtRegistrar);
+ txtEsqueciPassword = findViewById(R.id.txtEsqueciPassword);
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/MainActivity.java b/app/src/main/java/com/example/finzora/MainActivity.java
index 9e47a25..6740c69 100644
--- a/app/src/main/java/com/example/finzora/MainActivity.java
+++ b/app/src/main/java/com/example/finzora/MainActivity.java
@@ -1,59 +1,101 @@
package com.example.finzora;
import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Color;
import android.os.Bundle;
import android.widget.Button;
+import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
import androidx.viewpager2.widget.ViewPager2;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.IOException;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
+
public class MainActivity extends AppCompatActivity {
- private Button btnSair;
private TabLayout tabLayout;
private ViewPager2 viewPager;
+ private FloatingActionButton fabAdicionar;
private TextView tvNomeUsuario;
+ private Button btnSair;
+
+ private TextView tvSaldoGeral, tvReceitasGeral, tvDespesasGeral;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- // 1. PRIMEIRO PASSO OBRIGATÓRIO: Ligar as variáveis ao XML
- inicializarComponentes();
+ SharedPreferences prefsTema = getSharedPreferences("TemaApp", MODE_PRIVATE);
+ boolean isModoEscuro = prefsTema.getBoolean("modo_escuro", true);
- // 2. Agora sim, podemos tentar mudar o nome com segurança
- try {
- String nomeRecebido = getIntent().getStringExtra("CHAVE_NOME");
-
- // Só altera o texto se realmente tivermos recebido um nome (ex: vindo do Registo)
- if (nomeRecebido != null) {
- tvNomeUsuario.setText("Olá, " + nomeRecebido + "!");
- }
- } catch (Exception e) {
- e.printStackTrace(); // Se der erro, ignora e mantém o texto original
+ if (isModoEscuro) {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+ } else {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
}
- // 3. Configurar os botões e abas
- configurarBotaoSair();
- configurarAbas();
- }
+ setContentView(R.layout.activity_main);
- private void inicializarComponentes() {
- btnSair = findViewById(R.id.btnSair);
tabLayout = findViewById(R.id.tabLayoutDashboard);
viewPager = findViewById(R.id.viewPager);
- tvNomeUsuario = findViewById(R.id.tvNomeUsuario); // Certifica-te que este ID existe no XML
- }
+ 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);
+ tvDespesasGeral = findViewById(R.id.tvDespesasGeral);
+
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ String nome = prefs.getString("nome_usuario", "Investidor");
+ tvNomeUsuario.setText("Olá, " + nome);
- private void configurarBotaoSair() {
btnSair.setOnClickListener(v -> {
- Intent intent = new Intent(MainActivity.this, LoginActivity.class);
- startActivity(intent);
+ prefs.edit().clear().apply();
+ startActivity(new Intent(this, LoginActivity.class));
finish();
});
+
+ fabAdicionar.setOnClickListener(v -> {
+ startActivity(new Intent(this, AdicionarTransacaoActivity.class));
+ });
+
+ ImageView btnAbrirDefinicoes = findViewById(R.id.btnAbrirDefinicoes);
+ if (btnAbrirDefinicoes != null) {
+ btnAbrirDefinicoes.setOnClickListener(v -> {
+ startActivity(new Intent(MainActivity.this, DefinicoesActivity.class));
+ });
+ }
+
+ configurarAbas();
+ atualizarCartoes(); // Chama a nova função ligada à net!
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ atualizarCartoes();
+
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ String nome = prefs.getString("nome_usuario", "Investidor");
+ if (tvNomeUsuario != null) {
+ tvNomeUsuario.setText("Olá, " + nome);
+ }
}
private void configurarAbas() {
@@ -62,16 +104,80 @@ public class MainActivity extends AppCompatActivity {
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
switch (position) {
- case 0:
- tab.setText("Transações");
- break;
- case 1:
- tab.setText("Gráficos");
- break;
- case 2:
- tab.setText("Dicas");
- break;
+ case 0: tab.setText("Transações"); break;
+ case 1: tab.setText("Orçamentos"); break;
+ case 2: tab.setText("Gráficos"); break;
+ case 3: tab.setText("Dicas"); break;
}
}).attach();
}
+
+ // ==========================================================
+ // --- CALCULAR O SALDO DIRETAMENTE DO SUPABASE ---
+ // ==========================================================
+ public void atualizarCartoes() {
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ String userId = prefs.getString("user_id", null);
+
+ if (userId == null) return;
+
+ OkHttpClient client = new OkHttpClient();
+ Request request = new Request.Builder()
+ .url(SupabaseConfig.SUPABASE_URL + "/rest/v1/transacoes?user_id=eq." + userId)
+ .addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
+ .addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
+ .build();
+
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {}
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ if (response.isSuccessful()) {
+ try {
+ String jsonResposta = response.body().string();
+ JSONArray jsonArray = new JSONArray(jsonResposta);
+
+ float receitas = 0;
+ float despesas = 0;
+
+ // Percorrer todas as transações da nuvem e somar!
+ for (int i = 0; i < jsonArray.length(); i++) {
+ JSONObject obj = jsonArray.getJSONObject(i);
+ float valor = (float) obj.getDouble("valor");
+ int tipo = obj.getInt("tipo");
+
+ if (tipo == 1) {
+ receitas += valor;
+ } else if (tipo == 2) {
+ despesas += valor;
+ }
+ }
+
+ final float totalReceitas = receitas;
+ final float totalDespesas = despesas;
+ final float saldo = receitas - despesas;
+
+ // Atualizar o design do ecrã
+ runOnUiThread(() -> {
+ if (tvReceitasGeral != null) tvReceitasGeral.setText(String.format("€ %.2f", totalReceitas));
+ if (tvDespesasGeral != null) tvDespesasGeral.setText(String.format("€ %.2f", totalDespesas));
+ if (tvSaldoGeral != null) {
+ tvSaldoGeral.setText(String.format("€ %.2f", saldo));
+ if (saldo < 0) {
+ tvSaldoGeral.setTextColor(Color.parseColor("#FF1744"));
+ } else {
+ tvSaldoGeral.setTextColor(getResources().getColor(R.color.texto_principal));
+ }
+ }
+ });
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/OnboardingActivity.java b/app/src/main/java/com/example/finzora/OnboardingActivity.java
new file mode 100644
index 0000000..d3942b2
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/OnboardingActivity.java
@@ -0,0 +1,102 @@
+package com.example.finzora;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.viewpager2.widget.ViewPager2;
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+import java.util.ArrayList;
+import java.util.List;
+
+public class OnboardingActivity extends AppCompatActivity {
+
+ private OnboardingAdapter onboardingAdapter;
+ private Button btnProximo;
+ private TextView btnSaltar; // Atenção: no teu XML o Saltar era um TextView
+ private TabLayout tabLayoutIndicator;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_onboarding);
+
+ // --- 1. RECUPERAR O NOME DA MEMÓRIA ---
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ // Tenta ler "nome_usuario". Se não existir, mete "Visitante"
+ String nomeRecuperado = prefs.getString("nome_usuario", "Visitante");
+
+ // --- 2. CRIAR OS SLIDES ---
+ List lista = new ArrayList<>();
+
+ // SLIDE 1: Usamos a variável 'nomeRecuperado' aqui
+ lista.add(new OnboardingItem(
+ "Olá, " + nomeRecuperado + "! \uD83D\uDC4B",
+ "Bem-vindo ao Finzora. A tua gestão financeira pessoal, agora com tecnologia de ponta.",
+ R.drawable.ic_wallet // Confirma se tens este ícone, ou usa ic_launcher_foreground
+ ));
+
+ // SLIDE 2
+ lista.add(new OnboardingItem(
+ "Controlo Total",
+ "Regista receitas e despesas num piscar de olhos e mantém o teu saldo sempre atualizado.",
+ R.drawable.ic_chart
+ ));
+
+ // SLIDE 3
+ lista.add(new OnboardingItem(
+ "Inteligência Artificial",
+ "Recebe dicas automáticas baseadas nos teus gastos para poupares mais todos os meses.",
+ R.drawable.ic_idea
+ ));
+
+ // --- 3. CONFIGURAR VIEWPAGER E BOTÕES ---
+ ViewPager2 viewPager = findViewById(R.id.viewPagerOnboarding);
+ tabLayoutIndicator = findViewById(R.id.tabLayoutIndicator);
+ btnProximo = findViewById(R.id.btnProximo);
+ btnSaltar = findViewById(R.id.btnSaltar);
+
+ onboardingAdapter = new OnboardingAdapter(lista);
+ viewPager.setAdapter(onboardingAdapter);
+
+ // Ligar as barras de progresso (TabLayout)
+ new TabLayoutMediator(tabLayoutIndicator, viewPager, (tab, position) -> {
+ // Deixar vazio (só queremos as barras, sem texto)
+ }).attach();
+
+ // Lógica do Botão Próximo
+ btnProximo.setOnClickListener(v -> {
+ if (viewPager.getCurrentItem() + 1 < onboardingAdapter.getItemCount()) {
+ viewPager.setCurrentItem(viewPager.getCurrentItem() + 1);
+ } else {
+ finalizarOnboarding();
+ }
+ });
+
+ // Lógica do Botão Saltar
+ btnSaltar.setOnClickListener(v -> finalizarOnboarding());
+
+ // Mudar texto "Próximo" para "Começar" na última página
+ viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageSelected(int position) {
+ super.onPageSelected(position);
+ if (position == onboardingAdapter.getItemCount() - 1) {
+ btnProximo.setText("Começar");
+ } else {
+ btnProximo.setText("Próximo");
+ }
+ }
+ });
+ }
+
+ private void finalizarOnboarding() {
+ // Tem de ir para ProfileActivity.class, e NÃO para MainActivity.class
+ Intent intent = new Intent(OnboardingActivity.this, ProfileActivity.class);
+ startActivity(intent);
+ finish();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/OnboardingAdapter.java b/app/src/main/java/com/example/finzora/OnboardingAdapter.java
new file mode 100644
index 0000000..d35b59c
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/OnboardingAdapter.java
@@ -0,0 +1,55 @@
+package com.example.finzora;
+
+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 java.util.List;
+
+public class OnboardingAdapter extends RecyclerView.Adapter {
+
+ private List onboardingItems;
+
+ public OnboardingAdapter(List onboardingItems) {
+ this.onboardingItems = onboardingItems;
+ }
+
+ @NonNull
+ @Override
+ public OnboardingViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_onboarding, parent, false);
+ return new OnboardingViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull OnboardingViewHolder holder, int position) {
+ holder.setOnboardingData(onboardingItems.get(position));
+ }
+
+ @Override
+ public int getItemCount() {
+ return onboardingItems.size();
+ }
+
+ class OnboardingViewHolder extends RecyclerView.ViewHolder {
+ private TextView textTitulo;
+ private TextView textDescricao;
+ private ImageView imageOnboarding;
+
+ OnboardingViewHolder(@NonNull View itemView) {
+ super(itemView);
+ textTitulo = itemView.findViewById(R.id.textTitulo);
+ textDescricao = itemView.findViewById(R.id.textDescricao);
+ imageOnboarding = itemView.findViewById(R.id.imgOnboarding);
+ }
+
+ void setOnboardingData(OnboardingItem item) {
+ textTitulo.setText(item.getTitulo());
+ textDescricao.setText(item.getDescricao());
+ imageOnboarding.setImageResource(item.getImagem());
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/finzora/OnboardingItem.java b/app/src/main/java/com/example/finzora/OnboardingItem.java
new file mode 100644
index 0000000..458b311
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/OnboardingItem.java
@@ -0,0 +1,16 @@
+package com.example.finzora;
+
+public class OnboardingItem {
+ private String titulo, descricao;
+ private int imagem;
+
+ public OnboardingItem(String titulo, String descricao, int imagem) {
+ this.titulo = titulo;
+ this.descricao = descricao;
+ this.imagem = imagem;
+ }
+
+ public String getTitulo() { return titulo; }
+ public String getDescricao() { return descricao; }
+ public int getImagem() { return imagem; }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/OrcamentoAdapter.java b/app/src/main/java/com/example/finzora/OrcamentoAdapter.java
new file mode 100644
index 0000000..6577704
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/OrcamentoAdapter.java
@@ -0,0 +1,78 @@
+package com.example.finzora;
+
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import java.util.List;
+import java.util.Map;
+
+public class OrcamentoAdapter extends RecyclerView.Adapter {
+
+ // Lista de pares: Chave=Categoria, Valor=Limite
+ private List> listaOrcamentos;
+ private DBHelper dbHelper;
+
+ public OrcamentoAdapter(List> lista, DBHelper db) {
+ this.listaOrcamentos = lista;
+ this.dbHelper = db;
+ }
+
+ @NonNull
+ @Override
+ public OrcamentoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_orcamento, parent, false);
+ return new OrcamentoViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull OrcamentoViewHolder holder, int position) {
+ Map.Entry entry = listaOrcamentos.get(position);
+ String categoria = entry.getKey();
+ float limite = entry.getValue();
+
+ // Calcular quanto já gastou
+ float gasto = dbHelper.getGastoPorCategoria(categoria);
+ float restante = limite - gasto;
+ int percentagem = (int) ((gasto / limite) * 100);
+ if (percentagem > 100) percentagem = 100;
+
+ // Atualizar Textos
+ holder.tvCategoria.setText(categoria);
+ holder.tvValores.setText(String.format("€ %.2f / € %.2f", gasto, limite));
+ holder.progress.setProgress(percentagem);
+
+ if (restante >= 0) {
+ holder.tvRestante.setText(String.format("Restam € %.2f (%d%%)", restante, 100 - percentagem));
+ holder.tvRestante.setTextColor(Color.parseColor("#90A4AE")); // Cinzento
+ holder.progress.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#00E676"))); // Verde
+ } else {
+ holder.tvRestante.setText(String.format("Ultrapassado por € %.2f!", Math.abs(restante)));
+ holder.tvRestante.setTextColor(Color.parseColor("#FF1744")); // Vermelho
+ holder.progress.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#FF1744"))); // Vermelho
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return listaOrcamentos.size();
+ }
+
+ static class OrcamentoViewHolder extends RecyclerView.ViewHolder {
+ TextView tvCategoria, tvValores, tvRestante;
+ ProgressBar progress;
+
+ public OrcamentoViewHolder(@NonNull View itemView) {
+ super(itemView);
+ tvCategoria = itemView.findViewById(R.id.tvCatOrcamento);
+ tvValores = itemView.findViewById(R.id.tvValoresOrcamento);
+ tvRestante = itemView.findViewById(R.id.tvRestante);
+ progress = itemView.findViewById(R.id.progressOrcamento);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/OrcamentoFragment.java b/app/src/main/java/com/example/finzora/OrcamentoFragment.java
new file mode 100644
index 0000000..fa8b1c8
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/OrcamentoFragment.java
@@ -0,0 +1,98 @@
+package com.example.finzora;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Spinner;
+import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class OrcamentoFragment extends Fragment {
+
+ private Spinner spinnerCategoria;
+ private EditText editLimite;
+ private Button btnSalvar;
+ private RecyclerView recyclerOrcamentos;
+ private DBHelper dbHelper;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ // Carrega o layout XML correto
+ View view = inflater.inflate(R.layout.fragment_orcamento, container, false);
+
+ // Liga os componentes do Java aos IDs do XML
+ // SE AQUI FICAR VERMELHO, É PORQUE O XML NÃO FOI GUARDADO
+ spinnerCategoria = view.findViewById(R.id.spinnerOrcamento);
+ editLimite = view.findViewById(R.id.editLimite);
+ btnSalvar = view.findViewById(R.id.btnDefinirOrcamento);
+ recyclerOrcamentos = view.findViewById(R.id.recyclerOrcamentos);
+
+ // Configura a lista
+ recyclerOrcamentos.setLayoutManager(new LinearLayoutManager(getActivity()));
+
+ // Inicia a base de dados
+ dbHelper = new DBHelper(getActivity());
+
+ // Configurações iniciais
+ configurarSpinner();
+
+ // Ação do botão
+ btnSalvar.setOnClickListener(v -> salvarOrcamento());
+
+ // Mostrar dados
+ carregarOrcamentos();
+
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ carregarOrcamentos();
+ }
+
+ // Método corrigido (havia um duplicado antes)
+ private void configurarSpinner() {
+ String[] categorias = {"Alimentação", "Transporte", "Salário", "Lazer", "Contas", "Saúde", "Outros"};
+ ArrayAdapter adapter = new ArrayAdapter<>(getActivity(), R.layout.item_dropdown, categorias);
+ spinnerCategoria.setAdapter(adapter);
+ }
+
+ private void salvarOrcamento() {
+ String limiteStr = editLimite.getText().toString();
+ if (limiteStr.isEmpty()) {
+ editLimite.setError("Define um valor");
+ return;
+ }
+
+ String categoria = spinnerCategoria.getSelectedItem().toString();
+ float limite = Float.parseFloat(limiteStr);
+
+ dbHelper.salvarOrcamento(categoria, limite);
+
+ Toast.makeText(getActivity(), "Orçamento definido!", Toast.LENGTH_SHORT).show();
+ editLimite.setText(""); // Limpar campo
+
+ carregarOrcamentos(); // Atualizar lista
+ }
+
+ private void carregarOrcamentos() {
+ Map orcamentosMap = dbHelper.getOrcamentosDefinidos();
+ List> lista = new ArrayList<>(orcamentosMap.entrySet());
+
+ OrcamentoAdapter adapter = new OrcamentoAdapter(lista, dbHelper);
+ recyclerOrcamentos.setAdapter(adapter);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/ProfileActivity.java b/app/src/main/java/com/example/finzora/ProfileActivity.java
new file mode 100644
index 0000000..8a232ad
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/ProfileActivity.java
@@ -0,0 +1,151 @@
+package com.example.finzora;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import com.google.android.material.textfield.TextInputEditText;
+import java.util.Random;
+
+public class ProfileActivity extends AppCompatActivity {
+
+ private ImageView imgPerfil;
+ private TextInputEditText editNomePerfil, editBio;
+ private Button btnEscolherFoto, btnGuardarPerfil;
+ private TextView btnSaltarPerfil;
+ private Uri imagemUriSelecionada = null;
+
+ // Variável de controlo
+ private boolean vindoDoMenu = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_profile);
+
+ // Verifica de onde viemos
+ vindoDoMenu = getIntent().getBooleanExtra("origem_menu", false);
+
+ inicializarComponentes();
+ adaptarInterface(); // Ajusta os textos dos botões
+ carregarDadosAtuais();
+
+ // 1. Escolher Foto
+ ActivityResultLauncher galleryLauncher = registerForActivityResult(
+ new ActivityResultContracts.GetContent(),
+ uri -> {
+ if (uri != null) {
+ imagemUriSelecionada = uri;
+ imgPerfil.setImageURI(uri);
+ imgPerfil.setColorFilter(null);
+ }
+ }
+ );
+
+ btnEscolherFoto.setOnClickListener(v -> galleryLauncher.launch("image/*"));
+
+ // 2. Guardar
+ btnGuardarPerfil.setOnClickListener(v -> salvarPerfil());
+
+ // 3. Botão Saltar / Cancelar
+ btnSaltarPerfil.setOnClickListener(v -> {
+ if (vindoDoMenu) {
+ // Se veio do menu, "Saltar" funciona como "Cancelar"
+ finish();
+ } else {
+ // Se veio do Tutorial, salta mas mostra as boas-vindas
+ mostrarMensagemBoasVindas("Investidor");
+ }
+ });
+ }
+
+ private void inicializarComponentes() {
+ imgPerfil = findViewById(R.id.imgPerfil);
+ editNomePerfil = findViewById(R.id.editNomePerfil);
+ editBio = findViewById(R.id.editBio);
+ btnEscolherFoto = findViewById(R.id.btnEscolherFoto);
+ btnGuardarPerfil = findViewById(R.id.btnGuardarPerfil);
+ btnSaltarPerfil = findViewById(R.id.btnSaltarPerfil);
+ }
+
+ private void adaptarInterface() {
+ if (vindoDoMenu) {
+ // Se já estamos na app
+ btnSaltarPerfil.setText("Cancelar");
+ btnGuardarPerfil.setText("Atualizar Perfil");
+ } else {
+ // Se é o tutorial inicial
+ btnSaltarPerfil.setText("Saltar por agora");
+ btnGuardarPerfil.setText("Guardar e Entrar");
+ }
+ }
+
+ private void carregarDadosAtuais() {
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ editNomePerfil.setText(prefs.getString("nome_usuario", ""));
+ editBio.setText(prefs.getString("bio_usuario", ""));
+
+ String fotoStr = prefs.getString("foto_usuario", null);
+ if (fotoStr != null) {
+ imgPerfil.setImageURI(Uri.parse(fotoStr));
+ imgPerfil.setColorFilter(null);
+ }
+ }
+
+ private void salvarPerfil() {
+ String novoNome = editNomePerfil.getText().toString().trim();
+ String novaBio = editBio.getText().toString().trim();
+
+ if (novoNome.isEmpty()) {
+ editNomePerfil.setError("O nome não pode estar vazio");
+ return;
+ }
+
+ SharedPreferences.Editor editor = getSharedPreferences("DadosUtilizador", MODE_PRIVATE).edit();
+ editor.putString("nome_usuario", novoNome);
+ editor.putString("bio_usuario", novaBio);
+ if (imagemUriSelecionada != null) {
+ editor.putString("foto_usuario", imagemUriSelecionada.toString());
+ }
+ editor.apply();
+
+ // --- LÓGICA DE SAÍDA ---
+ if (vindoDoMenu) {
+ // Edição simples: avisa e volta para trás
+ Toast.makeText(this, "Perfil atualizado!", Toast.LENGTH_SHORT).show();
+ finish();
+ } else {
+ // Primeira vez: Mostra a mensagem inspiradora
+ mostrarMensagemBoasVindas(novoNome);
+ }
+ }
+
+ private void mostrarMensagemBoasVindas(String nome) {
+ String[] frases = {
+ "\"Não poupes o que resta depois de gastar, mas gasta o que resta depois de poupar.\" – Warren Buffett",
+ "\"O segredo para ficar à frente é começar.\" – Mark Twain",
+ "\"A tua liberdade financeira começa hoje!\" 🚀"
+ };
+ String fraseDoDia = frases[new Random().nextInt(frases.length)];
+
+ new AlertDialog.Builder(this)
+ .setTitle("Bem-vindo(a), " + nome + "!")
+ .setMessage(fraseDoDia + "\n\nO teu perfil está pronto. Vamos dominar as tuas finanças?")
+ .setPositiveButton("VAMOS LÁ!", (dialog, which) -> {
+ Intent intent = new Intent(ProfileActivity.this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ finish();
+ })
+ .setCancelable(false)
+ .show();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/RecuperarPasswordActivity.java b/app/src/main/java/com/example/finzora/RecuperarPasswordActivity.java
new file mode 100644
index 0000000..ec5775a
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/RecuperarPasswordActivity.java
@@ -0,0 +1,90 @@
+package com.example.finzora;
+
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import com.google.android.material.textfield.TextInputEditText;
+
+import java.io.IOException;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
+public class RecuperarPasswordActivity extends AppCompatActivity {
+
+ private TextInputEditText editEmailRecuperar;
+ private Button btnEnviarEmail;
+ private ImageView btnVoltar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_recuperar_password);
+
+ editEmailRecuperar = findViewById(R.id.editEmailRecuperar);
+ btnEnviarEmail = findViewById(R.id.btnEnviarEmail);
+ btnVoltar = findViewById(R.id.btnVoltarRecuperar);
+
+ // Voltar para trás
+ btnVoltar.setOnClickListener(v -> finish());
+
+ btnEnviarEmail.setOnClickListener(v -> {
+ String email = editEmailRecuperar.getText().toString().trim();
+ if (TextUtils.isEmpty(email)) {
+ editEmailRecuperar.setError("Introduz o teu email");
+ editEmailRecuperar.requestFocus();
+ } else {
+ btnEnviarEmail.setEnabled(false);
+ btnEnviarEmail.setText("A ENVIAR...");
+ pedirRecuperacaoPassword(email);
+ }
+ });
+ }
+
+ private void pedirRecuperacaoPassword(String email) {
+ OkHttpClient client = new OkHttpClient();
+ String json = "{\"email\":\"" + email + "\"}";
+ RequestBody body = RequestBody.create(json, MediaType.parse("application/json; charset=utf-8"));
+
+ Request request = new Request.Builder()
+ .url(SupabaseConfig.SUPABASE_URL + "/auth/v1/recover")
+ .addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
+ .addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
+ .post(body)
+ .build();
+
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ runOnUiThread(() -> {
+ Toast.makeText(RecuperarPasswordActivity.this, "Erro de ligação à internet!", Toast.LENGTH_SHORT).show();
+ btnEnviarEmail.setEnabled(true);
+ btnEnviarEmail.setText("ENVIAR LINK MÁGICO");
+ });
+ }
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ runOnUiThread(() -> {
+ btnEnviarEmail.setEnabled(true);
+ btnEnviarEmail.setText("ENVIAR LINK MÁGICO");
+ if (response.isSuccessful()) {
+ Toast.makeText(RecuperarPasswordActivity.this, "Email enviado! Verifica a caixa de entrada.", Toast.LENGTH_LONG).show();
+ finish(); // Fecha esta tela e volta ao Login
+ } else {
+ Toast.makeText(RecuperarPasswordActivity.this, "Erro: Verifica se o email está correto ou se a conta existe.", Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/RegisterActivity.java b/app/src/main/java/com/example/finzora/RegisterActivity.java
index ebe8c97..961ba20 100644
--- a/app/src/main/java/com/example/finzora/RegisterActivity.java
+++ b/app/src/main/java/com/example/finzora/RegisterActivity.java
@@ -1,17 +1,30 @@
package com.example.finzora;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.textfield.TextInputEditText;
+import org.json.JSONObject;
+import java.io.IOException;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.MediaType;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+
public class RegisterActivity extends AppCompatActivity {
- private TextInputEditText editNome, editEmail, editPassword, editConfirmPassword;
+ private TextInputEditText editNome, editEmailRegister, editPassRegister, editConfirmPass;
private Button btnCriarConta;
private TextView txtLogin;
@@ -22,45 +35,123 @@ public class RegisterActivity extends AppCompatActivity {
inicializarComponentes();
- // Volta para a tela de Login
+ // Voltar para o Login
txtLogin.setOnClickListener(v -> finish());
- // Valida e cria a conta
+ // Clicar em Criar Conta
btnCriarConta.setOnClickListener(v -> validarDados());
}
private void validarDados() {
String nome = editNome.getText().toString().trim();
- String email = editEmail.getText().toString().trim();
- String password = editPassword.getText().toString().trim();
- String confirmPass = editConfirmPassword.getText().toString().trim();
+ String email = editEmailRegister.getText().toString().trim();
+ String password = editPassRegister.getText().toString().trim();
+ String confirmPass = editConfirmPass.getText().toString().trim();
if (TextUtils.isEmpty(nome)) {
editNome.setError("Introduza o seu nome");
- } else if (TextUtils.isEmpty(email)) {
- editEmail.setError("Introduza um email válido");
- } else if (TextUtils.isEmpty(password)) {
- editPassword.setError("Defina uma palavra-passe");
- } else if (password.length() < 6) {
- editPassword.setError("A senha deve ter pelo menos 6 caracteres");
- } else if (!password.equals(confirmPass)) {
- editConfirmPassword.setError("As palavras-passe não coincidem");
- } else {
-
- Toast.makeText(this, "Conta criada com sucesso!", Toast.LENGTH_SHORT).show();
- Intent intent = new Intent(RegisterActivity.this, MainActivity.class);
- intent.putExtra("CHAVE_NOME", nome);
- startActivity(intent);
- finish();
+ return;
}
+ if (TextUtils.isEmpty(email)) {
+ editEmailRegister.setError("Introduza um email válido");
+ return;
+ }
+ if (TextUtils.isEmpty(password)) {
+ editPassRegister.setError("Defina uma palavra-passe");
+ return;
+ }
+ if (password.length() < 6) {
+ editPassRegister.setError("Mínimo 6 caracteres");
+ return;
+ }
+ if (!password.equals(confirmPass)) {
+ editConfirmPass.setError("As senhas não coincidem");
+ return;
+ }
+
+ btnCriarConta.setEnabled(false);
+ btnCriarConta.setText("A criar conta na nuvem...");
+
+ registarNoSupabase(nome, email, password);
+ }
+
+ private void registarNoSupabase(String nome, String email, String password) {
+ OkHttpClient client = new OkHttpClient();
+
+ // JSON com os dados para o Supabase Auth
+ String json = "{\"email\":\"" + email + "\", \"password\":\"" + password + "\", \"data\": {\"nome\": \"" + nome + "\"}}";
+ RequestBody body = RequestBody.create(json, MediaType.parse("application/json; charset=utf-8"));
+
+ Request request = new Request.Builder()
+ .url(SupabaseConfig.SUPABASE_URL + "/auth/v1/signup")
+ .addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
+ .addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
+ .post(body)
+ .build();
+
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ runOnUiThread(() -> {
+ Toast.makeText(RegisterActivity.this, "Erro de rede! Verifica a internet.", Toast.LENGTH_SHORT).show();
+ resetBotao();
+ });
+ }
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ final String responseData = response.body().string();
+
+ runOnUiThread(() -> {
+ if (response.isSuccessful()) {
+ try {
+ // Extrair o ID do utilizador da resposta JSON
+ JSONObject jsonResponse = new JSONObject(responseData);
+ String userId = jsonResponse.getString("id");
+
+ // Guardar Nome e ID localmente para usar nas transações
+ SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString("nome_usuario", nome);
+ editor.putString("user_id", userId);
+ editor.apply();
+
+ Toast.makeText(RegisterActivity.this, "Sucesso! Bem-vindo à nuvem.", Toast.LENGTH_SHORT).show();
+
+ // Avançar para o Onboarding
+ startActivity(new Intent(RegisterActivity.this, OnboardingActivity.class));
+ finish();
+
+ } catch (Exception e) {
+ Toast.makeText(RegisterActivity.this, "Erro ao ler dados da nuvem", Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ // LER O ERRO REAL DO SUPABASE
+ try {
+ JSONObject erroObj = new JSONObject(responseData);
+ String mensagemErroReal = erroObj.getString("msg");
+ Toast.makeText(RegisterActivity.this, "ERRO: " + mensagemErroReal, Toast.LENGTH_LONG).show();
+ } catch (Exception e) {
+ Toast.makeText(RegisterActivity.this, "Erro desconhecido: " + responseData, Toast.LENGTH_LONG).show();
+ }
+ resetBotao();
+ }
+ });
+ }
+ });
+ }
+
+ private void resetBotao() {
+ btnCriarConta.setEnabled(true);
+ btnCriarConta.setText("CRIAR CONTA");
}
private void inicializarComponentes() {
editNome = findViewById(R.id.editNome);
- editEmail = findViewById(R.id.editEmailRegister);
- editPassword = findViewById(R.id.editPassRegister);
- editConfirmPassword = findViewById(R.id.editConfirmPass);
+ editEmailRegister = findViewById(R.id.editEmailRegister);
+ editPassRegister = findViewById(R.id.editPassRegister);
+ editConfirmPass = findViewById(R.id.editConfirmPass);
btnCriarConta = findViewById(R.id.btnCriarConta);
txtLogin = findViewById(R.id.txtLogin);
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/SupabaseConfig.java b/app/src/main/java/com/example/finzora/SupabaseConfig.java
new file mode 100644
index 0000000..8e65037
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/SupabaseConfig.java
@@ -0,0 +1,8 @@
+package com.example.finzora;
+
+public class SupabaseConfig {
+
+ public static final String SUPABASE_URL = "https://cnxbsherdagpdpjhbtae.supabase.co";
+ public static final String SUPABASE_KEY = "sb_secret_bJg45XiAMz-KTyXw2FmMMA_HF97o_aV";
+
+}
diff --git a/app/src/main/java/com/example/finzora/Transacao.java b/app/src/main/java/com/example/finzora/Transacao.java
new file mode 100644
index 0000000..884a331
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/Transacao.java
@@ -0,0 +1,38 @@
+package com.example.finzora;
+
+public class Transacao {
+ private int id;
+ private float valor;
+ private String categoria;
+ private int tipo; // 1 = Receita, 2 = Despesa
+ private String data;
+
+ // --- CONSTRUTOR 1: Para ler da Base de Dados (TEM ID) ---
+ public Transacao(int id, float valor, String categoria, int tipo, String data) {
+ this.id = id;
+ this.valor = valor;
+ this.categoria = categoria;
+ this.tipo = tipo;
+ this.data = data;
+ }
+
+ // --- CONSTRUTOR 2: Para criar nova transação (NÃO TEM ID) ---
+ public Transacao(float valor, String categoria, int tipo, String data) {
+ this.valor = valor;
+ this.categoria = categoria;
+ this.tipo = tipo;
+ this.data = data;
+ }
+
+ // --- GETTERS (Para os outros lerem) ---
+ public int getId() { return id; }
+ public float getValor() { return valor; }
+ public String getCategoria() { return categoria; }
+ public int getTipo() { return tipo; }
+ public String getData() { return data; }
+
+ // --- SETTERS (Opcional, mas evita erros se algum código antigo os chamar) ---
+ public void setId(int id) { this.id = id; }
+ public void setValor(float valor) { this.valor = valor; }
+ public void setCategoria(String categoria) { this.categoria = categoria; }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/TransacoesAdapter.java b/app/src/main/java/com/example/finzora/TransacoesAdapter.java
new file mode 100644
index 0000000..fedf1a0
--- /dev/null
+++ b/app/src/main/java/com/example/finzora/TransacoesAdapter.java
@@ -0,0 +1,79 @@
+package com.example.finzora;
+
+import android.content.Context;
+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 java.util.List;
+
+public class TransacoesAdapter extends RecyclerView.Adapter {
+
+ private List listaTransacoes;
+ private Context context;
+ private TransacoesFragment fragment;
+
+ // O teu construtor mantém-se igual!
+ public TransacoesAdapter(List lista, Context context, TransacoesFragment fragment) {
+ this.listaTransacoes = lista;
+ this.context = context;
+ this.fragment = fragment;
+ }
+
+ @NonNull
+ @Override
+ public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View itemLista = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transacao, parent, false);
+ return new MyViewHolder(itemLista);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
+ Transacao transacao = listaTransacoes.get(position);
+
+ // --- ATUALIZADO PARA OS NOVOS IDs DO XML ---
+ holder.tvDescricao.setText(transacao.getCategoria());
+ holder.tvData.setText(transacao.getData());
+
+ // Cores e Ícones
+ if (transacao.getTipo() == 1) { // Receita
+ holder.tvValor.setTextColor(Color.parseColor("#388E3C")); // Verde
+ holder.tvValor.setText("+ € " + String.format("%.2f", transacao.getValor()));
+ if(holder.imgIcone != null) holder.imgIcone.setImageResource(android.R.drawable.arrow_up_float);
+ } else { // Despesa
+ holder.tvValor.setTextColor(Color.parseColor("#D32F2F")); // Vermelho
+ holder.tvValor.setText("- € " + String.format("%.2f", transacao.getValor()));
+ if(holder.imgIcone != null) holder.imgIcone.setImageResource(android.R.drawable.arrow_down_float);
+ }
+
+ // O botão de apagar agora chama-se btnEliminar no XML
+ holder.btnEliminar.setOnClickListener(v -> {
+ if (fragment != null) fragment.confirmarExclusao(transacao);
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return listaTransacoes.size();
+ }
+
+ public class MyViewHolder extends RecyclerView.ViewHolder {
+ // --- AQUI ESTÃO OS NOVOS NOMES DO XML ---
+ TextView tvDescricao, tvValor, tvData;
+ ImageView imgIcone, btnEliminar;
+
+ public MyViewHolder(@NonNull View itemView) {
+ super(itemView);
+ // Agora liga perfeitamente ao layout tech!
+ tvDescricao = itemView.findViewById(R.id.tvDescricao);
+ tvData = itemView.findViewById(R.id.tvData);
+ tvValor = itemView.findViewById(R.id.tvValor);
+ imgIcone = itemView.findViewById(R.id.imgIcone);
+ btnEliminar = itemView.findViewById(R.id.btnEliminar);
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/TransacoesFragment.java b/app/src/main/java/com/example/finzora/TransacoesFragment.java
index f9a0849..01afa5d 100644
--- a/app/src/main/java/com/example/finzora/TransacoesFragment.java
+++ b/app/src/main/java/com/example/finzora/TransacoesFragment.java
@@ -1,6 +1,158 @@
package com.example.finzora;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import okhttp3.Call;
+import okhttp3.Callback;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.Response;
public class TransacoesFragment extends Fragment {
-}
+
+ private RecyclerView recyclerTransacoes;
+ private TransacoesAdapter adapter;
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_transacoes, container, false);
+ recyclerTransacoes = view.findViewById(R.id.recyclerTransacoes);
+ recyclerTransacoes.setLayoutManager(new LinearLayoutManager(getActivity()));
+ return view;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ carregarDadosDoSupabase();
+ }
+
+ // ====================================================================
+ // BUSCAR AS TRANSAÇÕES À NUVEM (SUPABASE)
+ // ====================================================================
+ public void carregarDadosDoSupabase() {
+ SharedPreferences prefs = getActivity().getSharedPreferences("DadosUtilizador", Context.MODE_PRIVATE);
+ String userId = prefs.getString("user_id", null);
+
+ if (userId == null) return; // Se não houver utilizador, não faz nada
+
+ OkHttpClient client = new OkHttpClient();
+
+ // O URL pede ao Supabase: "Dá-me as transações onde o user_id seja igual ao meu!"
+ Request request = new Request.Builder()
+ .url(SupabaseConfig.SUPABASE_URL + "/rest/v1/transacoes?user_id=eq." + userId + "&order=id.desc")
+ .addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
+ .addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
+ .build();
+
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(() ->
+ Toast.makeText(getActivity(), "Erro de internet ao carregar transações.", Toast.LENGTH_SHORT).show()
+ );
+ }
+ }
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ if (response.isSuccessful()) {
+ String jsonResposta = response.body().string();
+ List listaNuvem = new ArrayList<>();
+
+ try {
+ JSONArray jsonArray = new JSONArray(jsonResposta);
+ for (int i = 0; i < jsonArray.length(); i++) {
+ JSONObject obj = jsonArray.getJSONObject(i);
+
+ // Traduzir do JSON do Supabase para o teu Java
+ int id = obj.getInt("id");
+ float valor = (float) obj.getDouble("valor");
+ String categoria = obj.getString("categoria");
+ int tipo = obj.getInt("tipo");
+ String data = obj.getString("data");
+
+ Transacao t = new Transacao(valor, categoria, tipo, data);
+ t.setId(id); // Guarda o ID verdadeiro da nuvem para podermos apagar depois!
+ listaNuvem.add(t);
+ }
+
+ // Atualizar o ecrã tem de ser sempre na Thread Principal (runOnUiThread)
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(() -> {
+ adapter = new TransacoesAdapter(listaNuvem, getActivity(), TransacoesFragment.this);
+ recyclerTransacoes.setAdapter(adapter);
+ });
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+ }
+
+ // ====================================================================
+ // APAGAR TRANSAÇÃO NA NUVEM
+ // ====================================================================
+ public void confirmarExclusao(Transacao transacao) {
+ new AlertDialog.Builder(getActivity())
+ .setTitle("Eliminar Transação")
+ .setMessage("Apagar " + transacao.getCategoria() + "?")
+ .setPositiveButton("Sim", (dialog, which) -> {
+
+ OkHttpClient client = new OkHttpClient();
+ Request request = new Request.Builder()
+ .url(SupabaseConfig.SUPABASE_URL + "/rest/v1/transacoes?id=eq." + transacao.getId())
+ .addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
+ .addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
+ .delete() // O comando mágico para apagar!
+ .build();
+
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {}
+
+ @Override
+ public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
+ if (getActivity() != null) {
+ getActivity().runOnUiThread(() -> {
+ Toast.makeText(getActivity(), "Eliminado das nuvens!", Toast.LENGTH_SHORT).show();
+ carregarDadosDoSupabase(); // Recarrega a lista
+
+ // Atualiza os cartões na MainActivity
+ if (getActivity() instanceof MainActivity) {
+ ((MainActivity) getActivity()).atualizarCartoes();
+ }
+ });
+ }
+ }
+ });
+
+ })
+ .setNegativeButton("Não", null)
+ .show();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/finzora/ViewPagerAdapter.java b/app/src/main/java/com/example/finzora/ViewPagerAdapter.java
index bcdee36..f579958 100644
--- a/app/src/main/java/com/example/finzora/ViewPagerAdapter.java
+++ b/app/src/main/java/com/example/finzora/ViewPagerAdapter.java
@@ -14,17 +14,17 @@ public class ViewPagerAdapter extends FragmentStateAdapter {
@NonNull
@Override
public Fragment createFragment(int position) {
- // Aqui definimos a ordem das abas
switch (position) {
- case 0: return new TransacoesFragment(); // Aba 1
- case 1: return new GraficosFragment(); // Aba 2
- case 2: return new DicasFragment(); // Aba 3
+ case 0: return new TransacoesFragment();
+ case 1: return new OrcamentoFragment();
+ case 2: return new GraficosFragment();
+ case 3: return new DicasFragment();
default: return new TransacoesFragment();
}
}
@Override
public int getItemCount() {
- return 3; // Temos 3 abas no total
+ return 4; // Total de 4 abas
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_card_tech.xml b/app/src/main/res/drawable/bg_card_tech.xml
new file mode 100644
index 0000000..8492834
--- /dev/null
+++ b/app/src/main/res/drawable/bg_card_tech.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_circle_icon.xml b/app/src/main/res/drawable/bg_circle_icon.xml
new file mode 100644
index 0000000..2374ebe
--- /dev/null
+++ b/app/src/main/res/drawable/bg_circle_icon.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_icone_transacao.xml b/app/src/main/res/drawable/bg_icone_transacao.xml
new file mode 100644
index 0000000..ac3ad52
--- /dev/null
+++ b/app/src/main/res/drawable/bg_icone_transacao.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_tech_gradient.xml b/app/src/main/res/drawable/bg_tech_gradient.xml
new file mode 100644
index 0000000..3ac40a9
--- /dev/null
+++ b/app/src/main/res/drawable/bg_tech_gradient.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/eliminar.png b/app/src/main/res/drawable/eliminar.png
new file mode 100644
index 0000000..2993cff
Binary files /dev/null and b/app/src/main/res/drawable/eliminar.png differ
diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml
new file mode 100644
index 0000000..88561cb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,9 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_delete_24.xml b/app/src/main/res/drawable/ic_baseline_delete_24.xml
new file mode 100644
index 0000000..a8b409b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_delete_24.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_carteira_tech.xml b/app/src/main/res/drawable/ic_carteira_tech.xml
new file mode 100644
index 0000000..65cae36
--- /dev/null
+++ b/app/src/main/res/drawable/ic_carteira_tech.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_chart.xml b/app/src/main/res/drawable/ic_chart.xml
new file mode 100644
index 0000000..134828b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chart.xml
@@ -0,0 +1,10 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_idea.xml b/app/src/main/res/drawable/ic_idea.xml
new file mode 100644
index 0000000..7eb050a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_idea.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_pulse.xml b/app/src/main/res/drawable/ic_pulse.xml
new file mode 100644
index 0000000..0b3c421
--- /dev/null
+++ b/app/src/main/res/drawable/ic_pulse.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_settings_pap.xml b/app/src/main/res/drawable/ic_settings_pap.xml
new file mode 100644
index 0000000..ebb9aa4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings_pap.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_wallet.xml b/app/src/main/res/drawable/ic_wallet.xml
index d7707dd..76f73d1 100644
--- a/app/src/main/res/drawable/ic_wallet.xml
+++ b/app/src/main/res/drawable/ic_wallet.xml
@@ -1,5 +1,9 @@
-
-
-
-
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/progress_neon.xml b/app/src/main/res/drawable/progress_neon.xml
new file mode 100644
index 0000000..d15ef06
--- /dev/null
+++ b/app/src/main/res/drawable/progress_neon.xml
@@ -0,0 +1,18 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/progress_savings.xml b/app/src/main/res/drawable/progress_savings.xml
new file mode 100644
index 0000000..aea0751
--- /dev/null
+++ b/app/src/main/res/drawable/progress_savings.xml
@@ -0,0 +1,17 @@
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/wallet.png b/app/src/main/res/drawable/wallet.png
new file mode 100644
index 0000000..dd3dcdb
Binary files /dev/null and b/app/src/main/res/drawable/wallet.png differ
diff --git a/app/src/main/res/layout/activity_adicionar_transacao.xml b/app/src/main/res/layout/activity_adicionar_transacao.xml
new file mode 100644
index 0000000..5eb3d1a
--- /dev/null
+++ b/app/src/main/res/layout/activity_adicionar_transacao.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 0000000..97551cd
--- /dev/null
+++ b/app/src/main/res/layout/activity_definicoes.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_editar_perfil.xml b/app/src/main/res/layout/activity_editar_perfil.xml
new file mode 100644
index 0000000..066e6be
--- /dev/null
+++ b/app/src/main/res/layout/activity_editar_perfil.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index c82bf90..5cf57cf 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -1,95 +1,145 @@
-
+
-
+ android:orientation="vertical"
+ android:gravity="center"
+ android:layout_marginTop="60dp"
+ app:layout_constraintTop_toTopOf="parent">
-
+
+ android:id="@+id/imgLogoLogin"
+ android:layout_width="100dp" android:layout_height="100dp"
+ android:src="@drawable/ic_carteira_tech"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginBottom="24dp"
+ android:background="@android:color/transparent"/>
+
-
+
-
+
+
-
+
+
+
+
+ android:inputType="textEmailAddress"
+ android:textColor="@color/white"/>
+
-
-
+
-
+ android:hint="Password"
+ android:inputType="textPassword"
+ android:textColor="@color/white"/>
+
-
-
+
-
+
-
+
-
-
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index e725e7e..40d8d1f 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,104 +1,125 @@
-
+ android:background="@color/fundo_app">
-
+ android:layout_height="match_parent"
+ android:orientation="vertical">
-
+
-
+
+
+
+
+
+
+
+
+
-
+ android:backgroundTint="#FF1744"
+ style="@style/Widget.MaterialComponents.Button.UnelevatedButton"
+ app:cornerRadius="8dp"/>
+
-
-
-
+
+ android:orientation="horizontal">
+
+
+
+
+
-
-
-
-
-
+ android:layout_marginTop="16dp"
+ android:background="@android:color/transparent"
+ app:tabTextColor="@color/texto_principal"
+ app:tabSelectedTextColor="@color/tech_accent_cyan"
+ app:tabIndicatorColor="@color/tech_accent_cyan"
+ app:tabMode="scrollable"/>
-
+
+
+
+ android:layout_gravity="bottom|end"
+ android:layout_margin="24dp"
+ android:src="@android:drawable/ic_input_add"
+ app:backgroundTint="@color/tech_accent_cyan"
+ app:tint="@color/black"
+ app:elevation="6dp"/>
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml
new file mode 100644
index 0000000..b92af37
--- /dev/null
+++ b/app/src/main/res/layout/activity_onboarding.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_profile.xml b/app/src/main/res/layout/activity_profile.xml
new file mode 100644
index 0000000..6f5dd37
--- /dev/null
+++ b/app/src/main/res/layout/activity_profile.xml
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_recuperar_password.xml b/app/src/main/res/layout/activity_recuperar_password.xml
new file mode 100644
index 0000000..9655795
--- /dev/null
+++ b/app/src/main/res/layout/activity_recuperar_password.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml
index 6fb22cf..84405f1 100644
--- a/app/src/main/res/layout/activity_register.xml
+++ b/app/src/main/res/layout/activity_register.xml
@@ -1,131 +1,162 @@
+
-
-
+
-
+
+
+
+
+ android:layout_height="wrap_content"
+ style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
+ android:textColorHint="#90A4AE"
+ app:boxStrokeColor="@color/tech_accent_green"
+ app:boxBackgroundColor="#10FFFFFF"
+ app:boxCornerRadiusTopStart="12dp"
+ app:boxCornerRadiusTopEnd="12dp"
+ app:boxCornerRadiusBottomStart="12dp"
+ app:boxCornerRadiusBottomEnd="12dp"
+ app:hintTextColor="@color/tech_accent_green">
-
-
-
-
-
-
-
+ android:hint="Nome Completo"
+ android:inputType="textPersonName"
+ android:textColor="@color/white"/>
+
-
-
+
-
+ android:hint="Email"
+ android:inputType="textEmailAddress"
+ android:textColor="@color/white"/>
+
-
-
+
-
+ android:hint="Password"
+ android:inputType="textPassword"
+ android:textColor="@color/white"/>
+
-
-
+
-
+ android:hint="Confirmar Password"
+ android:inputType="textPassword"
+ android:textColor="@color/white"/>
+
-
-
+
-
+
-
-
-
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/card_despesas.xml b/app/src/main/res/layout/card_despesas.xml
index 99c7481..f46d897 100644
--- a/app/src/main/res/layout/card_despesas.xml
+++ b/app/src/main/res/layout/card_despesas.xml
@@ -1,41 +1,49 @@
-
+
+ app:cardElevation="4dp"
+ app:cardBackgroundColor="@color/fundo_cartao">
+ android:padding="20dp">
+
+ android:textColor="@color/texto_principal"
+ android:textSize="14sp"
+ android:textStyle="bold" />
+
+ android:src="@android:drawable/arrow_down_float"
+ app:tint="@color/tech_accent_red" />
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/card_receitas.xml b/app/src/main/res/layout/card_receitas.xml
index f93f1a6..1d0d7a5 100644
--- a/app/src/main/res/layout/card_receitas.xml
+++ b/app/src/main/res/layout/card_receitas.xml
@@ -1,41 +1,49 @@
-
+
+ app:cardElevation="4dp"
+ app:cardBackgroundColor="@color/fundo_cartao">
+ android:padding="20dp">
+
+ android:textColor="@color/texto_principal"
+ android:textSize="14sp"
+ android:textStyle="bold" />
+
+ android:src="@android:drawable/arrow_up_float"
+ app:tint="@color/tech_accent_green" />
+
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/card_saldo_total.xml b/app/src/main/res/layout/card_saldo_total.xml
index 10bc68b..13897cf 100644
--- a/app/src/main/res/layout/card_saldo_total.xml
+++ b/app/src/main/res/layout/card_saldo_total.xml
@@ -1,32 +1,45 @@
-
+
+ app:cardElevation="4dp"
+ app:cardBackgroundColor="@color/fundo_cartao">
-
+ android:layout_height="wrap_content">
+ android:textColor="@color/texto_principal"
+ android:textSize="14sp"
+ android:textStyle="bold" />
+
-
-
-
\ No newline at end of file
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_contactar.xml b/app/src/main/res/layout/dialog_contactar.xml
new file mode 100644
index 0000000..6ab745d
--- /dev/null
+++ b/app/src/main/res/layout/dialog_contactar.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_faq.xml b/app/src/main/res/layout/dialog_faq.xml
new file mode 100644
index 0000000..c3acd21
--- /dev/null
+++ b/app/src/main/res/layout/dialog_faq.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_suporte.xml b/app/src/main/res/layout/dialog_suporte.xml
new file mode 100644
index 0000000..95b76ee
--- /dev/null
+++ b/app/src/main/res/layout/dialog_suporte.xml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_tutorial.xml b/app/src/main/res/layout/dialog_tutorial.xml
new file mode 100644
index 0000000..456f6e3
--- /dev/null
+++ b/app/src/main/res/layout/dialog_tutorial.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_dicas.xml b/app/src/main/res/layout/fragment_dicas.xml
new file mode 100644
index 0000000..ba65bc4
--- /dev/null
+++ b/app/src/main/res/layout/fragment_dicas.xml
@@ -0,0 +1,395 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_graficos.xml b/app/src/main/res/layout/fragment_graficos.xml
new file mode 100644
index 0000000..5b7de45
--- /dev/null
+++ b/app/src/main/res/layout/fragment_graficos.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_orcamento.xml b/app/src/main/res/layout/fragment_orcamento.xml
new file mode 100644
index 0000000..9c16cc7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_orcamento.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_transacoes.xml b/app/src/main/res/layout/fragment_transacoes.xml
new file mode 100644
index 0000000..badfb98
--- /dev/null
+++ b/app/src/main/res/layout/fragment_transacoes.xml
@@ -0,0 +1,14 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_dropdown.xml b/app/src/main/res/layout/item_dropdown.xml
new file mode 100644
index 0000000..7b804b7
--- /dev/null
+++ b/app/src/main/res/layout/item_dropdown.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_onboarding.xml b/app/src/main/res/layout/item_onboarding.xml
new file mode 100644
index 0000000..fe8034e
--- /dev/null
+++ b/app/src/main/res/layout/item_onboarding.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_orcamento.xml b/app/src/main/res/layout/item_orcamento.xml
new file mode 100644
index 0000000..bc5ad3c
--- /dev/null
+++ b/app/src/main/res/layout/item_orcamento.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_transacao.xml b/app/src/main/res/layout/item_transacao.xml
new file mode 100644
index 0000000..7fbf9ff
--- /dev/null
+++ b/app/src/main/res/layout/item_transacao.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml
new file mode 100644
index 0000000..d52d5ed
--- /dev/null
+++ b/app/src/main/res/values-night/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #1A202C
+ #FFFFFF
+ #2D3748
+ #2D3748
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index c8524cd..2062299 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -2,4 +2,15 @@
#FF000000
#FFFFFFFF
+ #00E676
+ #2D3748
+ #A0AEC0
+ #48BB78
+ #ECC94B #F56565
+ #1A202C
+
+ #FFFFFF
+ #1A202C
+ #F7FAFC
+ #E2E8F0
\ No newline at end of file
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
index a56512a..a759014 100644
--- a/app/src/main/res/values/ids.xml
+++ b/app/src/main/res/values/ids.xml
@@ -1,4 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 1b6dcf9..5f10b84 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,8 +1,29 @@
diff --git a/gradle.properties b/gradle.properties
index 4387edc..d7d8e66 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -18,4 +18,14 @@ android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
-android.nonTransitiveRClass=true
\ No newline at end of file
+android.nonTransitiveRClass=true
+android.defaults.buildfeatures.resvalues=true
+android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
+android.enableAppCompileTimeRClass=false
+android.usesSdkInManifest.disallowed=false
+android.uniquePackageNames=false
+android.dependency.useConstraints=true
+android.r8.strictFullModeForKeepRules=false
+android.r8.optimizedResourceShrinking=false
+android.builtInKotlin=false
+android.newDsl=false
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index ceb0910..273bbb9 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,5 @@
[versions]
-agp = "8.13.2"
+agp = "9.0.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 512d540..1223269 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
#Tue Jan 20 14:33:41 WET 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle.kts b/settings.gradle.kts
index d73da22..cac4f3b 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -16,9 +16,9 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
+ maven { url = uri("https://jitpack.io") }
}
}
rootProject.name = "Finzora"
include(":app")
-
\ No newline at end of file