Correção de bugs
This commit is contained in:
8
.idea/deploymentTargetSelector.xml
generated
8
.idea/deploymentTargetSelector.xml
generated
@@ -4,6 +4,14 @@
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
<DropdownSelection timestamp="2026-04-28T16:08:13.937232Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=4LCE8PNZJREYPVNR" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
<DialogSelection />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
|
||||
@@ -32,6 +32,12 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.biometric:biometric:1.2.0-alpha05")
|
||||
implementation("com.airbnb.android:lottie:6.3.0")
|
||||
implementation("com.github.bumptech.glide:glide:4.15.1")
|
||||
annotationProcessor("com.github.bumptech.glide:compiler:4.15.1")
|
||||
implementation("com.google.ai.client.generativeai:generativeai:0.9.0")
|
||||
implementation("com.google.guava:guava:31.1-android")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("com.google.android.material:material:1.13.0")
|
||||
implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
|
||||
@@ -40,6 +46,7 @@ dependencies {
|
||||
implementation("com.google.code.gson:gson:2.10.1")
|
||||
implementation(libs.activity)
|
||||
implementation(libs.constraintlayout)
|
||||
implementation(libs.generativeai)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.ext.junit)
|
||||
androidTestImplementation(libs.espresso.core)
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
package="com.example.finzora">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
@@ -15,31 +17,49 @@
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Finzora"
|
||||
tools:targetApi="31">
|
||||
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustResize"> <intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data
|
||||
android:scheme="finzora"
|
||||
android:host="confirmado" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".MainActivity" />
|
||||
<activity android:name=".RegisterActivity" />
|
||||
<activity
|
||||
android:name=".DefinicoesActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".EditarPerfilActivity"
|
||||
android:exported="false" />
|
||||
<activity android:name=".DefinicoesActivity" android:exported="false" />
|
||||
<activity android:name=".EditarPerfilActivity" android:exported="false" />
|
||||
<activity android:name=".RecuperarPasswordActivity" android:exported="false" />
|
||||
|
||||
<activity android:name=".OnboardingActivity" android:theme="@style/Theme.AppCompat.NoActionBar"/>
|
||||
|
||||
<activity android:name=".AdicionarTransacaoActivity" />
|
||||
|
||||
<activity android:name=".ProfileActivity" />
|
||||
</application>
|
||||
|
||||
<activity android:name=".LockActivity" android:exported="false" />
|
||||
|
||||
<activity
|
||||
android:name=".NovaPasswordActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data
|
||||
android:scheme="finzora"
|
||||
android:host="recuperar" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
@@ -1,28 +1,30 @@
|
||||
package com.example.finzora;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import androidx.cardview.widget.CardView;
|
||||
|
||||
public class AdicionarTransacaoActivity extends AppCompatActivity {
|
||||
|
||||
// Declaração dos componentes do ecrã (Sem os RadioButtons!)
|
||||
private EditText editValor;
|
||||
private Spinner spinnerCategoria;
|
||||
private EditText editValor, editDescricao;
|
||||
private TextView txtCategoria;
|
||||
private Button btnGuardar;
|
||||
private ImageView btnVoltar;
|
||||
private String categoriaSelecionada = "";
|
||||
private final String[] categorias = {"Alimentação", "Contas", "Transportes", "Compras", "Lazer", "Educação", "Saúde", "Salário", "Mesada", "Prémios", "Outros"};
|
||||
|
||||
private String idTransacaoParaEditar = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -31,147 +33,160 @@ public class AdicionarTransacaoActivity extends AppCompatActivity {
|
||||
|
||||
inicializarComponentes();
|
||||
|
||||
// --- ENCHER O SPINNER COM AS CATEGORIAS ---
|
||||
String[] categorias = {"Alimentação", "Transportes", "Lazer", "Educação", "Saúde", "Salário", "Mesada", "Prémios", "Outros"};
|
||||
Intent intent = getIntent();
|
||||
if (intent.hasExtra("transacao_id")) {
|
||||
idTransacaoParaEditar = intent.getStringExtra("transacao_id");
|
||||
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(
|
||||
this,
|
||||
R.layout.item_dropdown,
|
||||
categorias
|
||||
);
|
||||
adapter.setDropDownViewResource(R.layout.item_dropdown);
|
||||
spinnerCategoria.setAdapter(adapter);
|
||||
editValor.setText(String.valueOf(intent.getDoubleExtra("valor", 0.0)));
|
||||
editDescricao.setText(intent.getStringExtra("descricao"));
|
||||
categoriaSelecionada = intent.getStringExtra("categoria");
|
||||
txtCategoria.setText(categoriaSelecionada);
|
||||
|
||||
// Botão para voltar para trás
|
||||
if (btnVoltar != null) {
|
||||
btnVoltar.setOnClickListener(v -> finish());
|
||||
btnGuardar.setText("ATUALIZAR TRANSAÇÃO");
|
||||
}
|
||||
|
||||
// AGORA O BOTÃO GUARDAR CHAMA O POP-UP!
|
||||
txtCategoria.setOnClickListener(v -> mostrarDialogCategorias());
|
||||
btnVoltar.setOnClickListener(v -> finish());
|
||||
btnGuardar.setOnClickListener(v -> perguntarTipoTransacao());
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// A MAGIA DO POP-UP
|
||||
// ====================================================================
|
||||
private void perguntarTipoTransacao() {
|
||||
String valorStr = editValor.getText().toString().trim();
|
||||
if (TextUtils.isEmpty(valorStr)) { editValor.setError("Define o valor"); return; }
|
||||
if (categoriaSelecionada.isEmpty()) { Toast.makeText(this, "Escolhe a categoria!", Toast.LENGTH_SHORT).show(); return; }
|
||||
|
||||
// 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;
|
||||
if (idTransacaoParaEditar != null) {
|
||||
int tipoOriginal = getIntent().getIntExtra("tipo", 2);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setTitle("Confirmar Alteração");
|
||||
builder.setMessage("Desejas guardar estas alterações?");
|
||||
builder.setPositiveButton("Confirmar", (dialog, which) -> salvarOuAtualizar(tipoOriginal));
|
||||
builder.setNegativeButton("Cancelar", null);
|
||||
builder.show();
|
||||
} else {
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_tipo_transacao, null);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setView(view);
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(0));
|
||||
}
|
||||
|
||||
CardView btnReceita = view.findViewById(R.id.btnTipoReceita);
|
||||
CardView btnDespesa = view.findViewById(R.id.btnTipoDespesa);
|
||||
TextView btnCancelar = view.findViewById(R.id.btnCancelarTipo);
|
||||
|
||||
btnReceita.setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
salvarOuAtualizar(1);
|
||||
});
|
||||
|
||||
btnDespesa.setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
salvarOuAtualizar(2);
|
||||
});
|
||||
|
||||
btnCancelar.setOnClickListener(v -> dialog.dismiss());
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 🏆 NOVO MENU DE CATEGORIAS (PREMIUM)
|
||||
// 🏆 NOVO MENU DE CATEGORIAS (PREMIUM)
|
||||
private void mostrarDialogCategorias() {
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_categorias, null);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setView(view);
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
if (userId == null) {
|
||||
android.widget.Toast.makeText(this, "Erro: Utilizador não identificado. Faz login novamente.", android.widget.Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(0));
|
||||
}
|
||||
|
||||
LinearLayout container = view.findViewById(R.id.containerCategorias);
|
||||
TextView btnCancelar = view.findViewById(R.id.btnCancelarCategoria);
|
||||
|
||||
// Gera a lista de categorias visualmente perfeita
|
||||
for (String cat : categorias) {
|
||||
TextView tv = new TextView(this);
|
||||
tv.setText(cat);
|
||||
tv.setTextSize(16f); // ⚠️ CORRIGIDO AQUI PARA JAVA!
|
||||
|
||||
// Rouba a cor certa ao ecrã (Claro/Escuro) para não falhar
|
||||
tv.setTextColor(txtCategoria.getCurrentTextColor());
|
||||
|
||||
// Muito espaço para ser fácil clicar com o dedo
|
||||
tv.setPadding(32, 40, 32, 40);
|
||||
|
||||
// Efeito de onda ao clicar (Ripple Effect nativo)
|
||||
android.util.TypedValue outValue = new android.util.TypedValue();
|
||||
getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true);
|
||||
tv.setBackgroundResource(outValue.resourceId);
|
||||
tv.setClickable(true);
|
||||
tv.setFocusable(true);
|
||||
|
||||
tv.setOnClickListener(v -> {
|
||||
categoriaSelecionada = cat;
|
||||
txtCategoria.setText(categoriaSelecionada);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
container.addView(tv);
|
||||
}
|
||||
btnCancelar.setOnClickListener(v -> dialog.dismiss());
|
||||
dialog.show();
|
||||
}
|
||||
private void salvarOuAtualizar(int tipoEscolhido) {
|
||||
android.content.SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
|
||||
String userId = prefs.getString("user_id", null);
|
||||
if (userId == null) 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();
|
||||
double valor = Double.parseDouble(editValor.getText().toString().replace(",", "."));
|
||||
String descricao = editDescricao.getText().toString().trim();
|
||||
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...");
|
||||
btnGuardar.setText("A GUARDAR...");
|
||||
|
||||
// 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 + "\""
|
||||
+ "}";
|
||||
|
||||
String json = "{\"user_id\":\"" + userId + "\", \"valor\":" + valor + ", \"categoria\":\"" + categoriaSelecionada + "\", \"tipo\":" + tipoEscolhido + ", \"descricao\":\"" + descricao + "\", \"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
|
||||
String url = SupabaseConfig.SUPABASE_URL + "/rest/v1/transacoes";
|
||||
String metodo = "POST";
|
||||
|
||||
if (idTransacaoParaEditar != null) {
|
||||
url += "?id=eq." + idTransacaoParaEditar;
|
||||
metodo = "PATCH";
|
||||
}
|
||||
|
||||
okhttp3.Request request = new okhttp3.Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/transacoes")
|
||||
.url(url)
|
||||
.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)
|
||||
.method(metodo, 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 onFailure(okhttp3.Call call, java.io.IOException e) {
|
||||
runOnUiThread(() -> { btnGuardar.setEnabled(true); btnGuardar.setText("GUARDAR"); });
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
});
|
||||
@Override public void onResponse(okhttp3.Call call, okhttp3.Response response) {
|
||||
runOnUiThread(() -> { if (response.isSuccessful()) finish(); });
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
android.widget.Toast.makeText(this, "ERRO LOCAL: " + e.getMessage(), android.widget.Toast.LENGTH_LONG).show();
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) { Toast.makeText(this, "Erro: " + e.getMessage(), Toast.LENGTH_SHORT).show(); }
|
||||
}
|
||||
|
||||
private void inicializarComponentes() {
|
||||
editValor = findViewById(R.id.editValor);
|
||||
spinnerCategoria = findViewById(R.id.spinnerCategoria);
|
||||
txtCategoria = findViewById(R.id.txtCategoriaTransacao);
|
||||
editDescricao = findViewById(R.id.editDescricaoTransacao);
|
||||
btnGuardar = findViewById(R.id.btnGuardar);
|
||||
btnVoltar = findViewById(R.id.btnVoltar);
|
||||
}
|
||||
|
||||
@@ -56,12 +56,13 @@ public class DBHelper extends SQLiteOpenHelper {
|
||||
|
||||
if (cursor.moveToFirst()) {
|
||||
do {
|
||||
// ⚠️ A JOGADA MÁGICA: Transformar o int antigo numa String!
|
||||
lista.add(new Transacao(
|
||||
cursor.getInt(0), // id
|
||||
cursor.getFloat(1), // valor
|
||||
cursor.getString(2),// categoria
|
||||
cursor.getInt(3), // tipo
|
||||
cursor.getString(4) // data
|
||||
String.valueOf(cursor.getInt(0)), // id convertido para texto
|
||||
cursor.getFloat(1), // valor
|
||||
cursor.getString(2), // categoria
|
||||
cursor.getInt(3), // tipo
|
||||
cursor.getString(4) // data
|
||||
));
|
||||
} while (cursor.moveToNext());
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package com.example.finzora;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
@@ -12,9 +15,20 @@ import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
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 DefinicoesActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
@@ -22,19 +36,19 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
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);
|
||||
Switch switchBiometria = findViewById(R.id.switchBiometria);
|
||||
TextView btnSuporte = findViewById(R.id.btnSuporte);
|
||||
Button btnTerminarSessao = findViewById(R.id.btnTerminarSessao);
|
||||
Button btnEliminarConta = findViewById(R.id.btnEliminarConta);
|
||||
|
||||
// --- 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));
|
||||
});
|
||||
@@ -56,7 +70,22 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
// --- 3. NOTIFICAÇÕES ---
|
||||
// --- 3. MAGIA DA BIOMETRIA (SEGURANÇA) ---
|
||||
SharedPreferences prefsUser = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
|
||||
boolean usarBiometria = prefsUser.getBoolean("usar_biometria", false);
|
||||
switchBiometria.setChecked(usarBiometria);
|
||||
|
||||
switchBiometria.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
prefsUser.edit().putBoolean("usar_biometria", isChecked).apply();
|
||||
|
||||
if (isChecked) {
|
||||
Toast.makeText(this, "A app vai pedir o teu dedo ao abrir! 🔒", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(this, "Bloqueio Desativado 🔓", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
// --- 4. NOTIFICAÇÕES ---
|
||||
switchNotificacoes.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Toast.makeText(this, "Notificações Ligadas 🔔", Toast.LENGTH_SHORT).show();
|
||||
@@ -65,11 +94,111 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
}
|
||||
});
|
||||
|
||||
// --- 4. CENTRO DE SUPORTE (POP-UP) ---
|
||||
// --- 5. CENTRO DE SUPORTE (POP-UP) ---
|
||||
btnSuporte.setOnClickListener(v -> mostrarDialogSuporte());
|
||||
|
||||
// --- 5. TERMINAR SESSÃO ---
|
||||
// --- 6. TERMINAR SESSÃO ---
|
||||
btnTerminarSessao.setOnClickListener(v -> terminarSessao());
|
||||
|
||||
// --- 7. APAGAR CONTA ---
|
||||
btnEliminarConta.setOnClickListener(v -> mostrarAvisoEliminar());
|
||||
}
|
||||
|
||||
private void mostrarAvisoEliminar() {
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("⚠️ Ação Irreversível")
|
||||
.setMessage("Tens a certeza? Todos os teus orçamentos e transações serão apagados para sempre da nossa nuvem, de acordo com as normas de proteção de dados (RGPD).")
|
||||
.setPositiveButton("Sim, Apagar Tudo", (dialog, which) -> executarLimpezaDeDados())
|
||||
.setNegativeButton("Cancelar", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
private void executarLimpezaDeDados() {
|
||||
SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
|
||||
String userId = prefs.getString("user_id", null);
|
||||
|
||||
if (userId == null) {
|
||||
Toast.makeText(this, "ERRO: Não encontrei o teu ID no telemóvel!", Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Toast.makeText(this, "A apagar transações... 🗑️", Toast.LENGTH_SHORT).show();
|
||||
|
||||
Request reqTransacoes = 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)
|
||||
.delete()
|
||||
.build();
|
||||
|
||||
client.newCall(reqTransacoes).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
runOnUiThread(() -> Toast.makeText(DefinicoesActivity.this, "Erro de net a apagar transações!", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
apagarOrcamentos(userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void apagarOrcamentos(String userId) {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
Request reqOrcamentos = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/orcamentos?user_id=eq." + userId)
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.delete()
|
||||
.build();
|
||||
|
||||
client.newCall(reqOrcamentos).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) { }
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) {
|
||||
|
||||
String jsonParams = "{\"id_alvo\":\"" + userId + "\"}";
|
||||
RequestBody body = RequestBody.create(jsonParams, MediaType.parse("application/json; charset=utf-8"));
|
||||
|
||||
Request reqApagarConta = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/rpc/apagar_conta_finzora")
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
client.newCall(reqApagarConta).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
runOnUiThread(() -> Toast.makeText(DefinicoesActivity.this, "Erro a apagar a conta final!", Toast.LENGTH_LONG).show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
final String respostaSupabase = response.body() != null ? response.body().string() : "";
|
||||
|
||||
runOnUiThread(() -> {
|
||||
if (response.isSuccessful()) {
|
||||
getSharedPreferences("DadosUtilizador", MODE_PRIVATE).edit().clear().apply();
|
||||
Toast.makeText(DefinicoesActivity.this, "Conta e dados eliminados para sempre. 🧹", Toast.LENGTH_LONG).show();
|
||||
|
||||
Intent intent = new Intent(DefinicoesActivity.this, LoginActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} else {
|
||||
Toast.makeText(DefinicoesActivity.this, "Erro: " + respostaSupabase, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void mostrarDialogSuporte() {
|
||||
@@ -78,13 +207,11 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
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.dismiss();
|
||||
mostrarDialogFAQ();
|
||||
});
|
||||
|
||||
dialog.findViewById(R.id.cardTutorial).setOnClickListener(v -> {
|
||||
@@ -92,30 +219,62 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
mostrarDialogTutorial();
|
||||
});
|
||||
|
||||
// ✉️ CONTACTAR SUPORTE: Abre a app do Gmail
|
||||
dialog.findViewById(R.id.cardMensagem).setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
mostrarDialogContactar();
|
||||
enviarEmailProfissional();
|
||||
});
|
||||
|
||||
// 📞 CONTACTOS DIRETOS: Abre o nosso Novo Design Premium!
|
||||
dialog.findViewById(R.id.cardContactos).setOnClickListener(v -> {
|
||||
Toast.makeText(this, "Email: suporte@finzora.pt\nTel: +351 800 123 456", Toast.LENGTH_LONG).show();
|
||||
dialog.dismiss();
|
||||
mostrarDialogContactosInfo();
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
// 🏆 A NOVA FUNÇÃO QUE CHAMA O DESIGN PREMIUM
|
||||
private void mostrarDialogContactosInfo() {
|
||||
Dialog dialog = new Dialog(this);
|
||||
dialog.setContentView(R.layout.dialog_contactos);
|
||||
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
|
||||
dialog.getWindow().setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
// Quando clica em Voltar, fechamos este e abrimos o menu de Suporte outra vez!
|
||||
dialog.findViewById(R.id.btnFecharContactos).setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
mostrarDialogSuporte();
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void enviarEmailProfissional() {
|
||||
Intent emailIntent = new Intent(Intent.ACTION_SENDTO);
|
||||
emailIntent.setData(Uri.parse("mailto:suporte@finzora.pt"));
|
||||
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Suporte Finzora - Ajuda");
|
||||
|
||||
try {
|
||||
startActivity(Intent.createChooser(emailIntent, "Abrir com..."));
|
||||
} catch (ActivityNotFoundException ex) {
|
||||
Toast.makeText(this, "Não tens nenhuma app de e-mail instalada no telemóvel!", Toast.LENGTH_LONG).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();
|
||||
}
|
||||
|
||||
@@ -125,39 +284,10 @@ public class DefinicoesActivity extends AppCompatActivity {
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,41 +1,66 @@
|
||||
package com.example.finzora;
|
||||
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
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 TextView tvTituloDica1, tvDescDica1, tvTituloDica2, tvDescDica2, tvTituloDica3, tvDescDica3;
|
||||
private LinearLayout layoutDistribuicao;
|
||||
|
||||
private DBHelper dbHelper;
|
||||
private View layoutConteudoDicas;
|
||||
private View layoutEstadoVazioDicas;
|
||||
|
||||
private TextView tvRespostaAI;
|
||||
private EditText editPerguntaAI;
|
||||
private ImageButton btnEnviarAI;
|
||||
private ProgressBar pbCarregandoAI;
|
||||
|
||||
private int corFundoCartao;
|
||||
private int corTextoDinamico;
|
||||
|
||||
private String contextoFinanceiroParaAI = "O utilizador ainda não tem dados financeiros registados.";
|
||||
|
||||
@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);
|
||||
@@ -45,119 +70,386 @@ public class DicasFragment extends Fragment {
|
||||
tvDescDica1 = view.findViewById(R.id.tvDescDica1);
|
||||
tvTituloDica2 = view.findViewById(R.id.tvTituloDica2);
|
||||
tvDescDica2 = view.findViewById(R.id.tvDescDica2);
|
||||
tvTituloDica3 = view.findViewById(R.id.tvTituloDica3);
|
||||
tvDescDica3 = view.findViewById(R.id.tvDescDica3);
|
||||
|
||||
layoutDistribuicao = view.findViewById(R.id.layoutDistribuicao);
|
||||
layoutConteudoDicas = view.findViewById(R.id.layoutConteudoDicas);
|
||||
layoutEstadoVazioDicas = view.findViewById(R.id.layoutEstadoVazioDicas);
|
||||
|
||||
dbHelper = new DBHelper(getActivity());
|
||||
tvRespostaAI = view.findViewById(R.id.tvRespostaAI);
|
||||
editPerguntaAI = view.findViewById(R.id.editPerguntaAI);
|
||||
btnEnviarAI = view.findViewById(R.id.btnEnviarAI);
|
||||
pbCarregandoAI = view.findViewById(R.id.pbCarregandoAI);
|
||||
|
||||
if (getContext() != null) {
|
||||
corFundoCartao = ContextCompat.getColor(getContext(), R.color.fundo_cartao);
|
||||
corTextoDinamico = ContextCompat.getColor(getContext(), R.color.texto_principal);
|
||||
}
|
||||
|
||||
btnEnviarAI.setOnClickListener(v -> perguntarAoNovoCoach());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void perguntarAoNovoCoach() {
|
||||
String pergunta = editPerguntaAI.getText().toString().trim();
|
||||
if (pergunta.isEmpty()) return;
|
||||
|
||||
pbCarregandoAI.setVisibility(View.VISIBLE);
|
||||
tvRespostaAI.setText("A analisar os dados de forma inteligente...");
|
||||
editPerguntaAI.setText("");
|
||||
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
JSONObject jsonBody = new JSONObject();
|
||||
try {
|
||||
jsonBody.put("model", "llama-3.1-8b-instant");
|
||||
JSONArray messages = new JSONArray();
|
||||
|
||||
String regrasBase = "És o Assistente de IA da Finzora, um consultor financeiro altamente profissional e analítico. " +
|
||||
"Usa ESTRITAMENTE o Português de Portugal (PT-PT). Trata o utilizador SEMPRE por 'tu'. " +
|
||||
"Sê natural, claro, focado em literacia financeira e responde com um máximo de 3 ou 4 parágrafos curtos. ";
|
||||
|
||||
JSONObject systemMsg = new JSONObject();
|
||||
systemMsg.put("role", "system");
|
||||
systemMsg.put("content", regrasBase + contextoFinanceiroParaAI);
|
||||
messages.put(systemMsg);
|
||||
|
||||
JSONObject userMsg = new JSONObject();
|
||||
userMsg.put("role", "user");
|
||||
userMsg.put("content", pergunta);
|
||||
messages.put(userMsg);
|
||||
|
||||
jsonBody.put("messages", messages);
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
|
||||
RequestBody body = RequestBody.create(jsonBody.toString(), MediaType.parse("application/json; charset=utf-8"));
|
||||
String groqApiKey = "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";
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url("https://api.groq.com/openai/v1/chat/completions")
|
||||
.addHeader("Authorization", "Bearer " + groqApiKey)
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
pbCarregandoAI.setVisibility(View.GONE);
|
||||
tvRespostaAI.setText("Erro de ligação ao serviço de Inteligência Artificial.");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
final String respBody = response.body() != null ? response.body().string() : "";
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
pbCarregandoAI.setVisibility(View.GONE);
|
||||
if (response.isSuccessful()) {
|
||||
try {
|
||||
JSONObject jsonObject = new JSONObject(respBody);
|
||||
String respostaIA = jsonObject.getJSONArray("choices")
|
||||
.getJSONObject(0).getJSONObject("message").getString("content");
|
||||
|
||||
String textoFormatado = respostaIA.replaceAll("\\*\\*(.*?)\\*\\*", "<b>$1</b>");
|
||||
textoFormatado = textoFormatado.replace("\n", "<br>");
|
||||
tvRespostaAI.setText(android.text.Html.fromHtml(textoFormatado, android.text.Html.FROM_HTML_MODE_LEGACY));
|
||||
|
||||
} catch (Exception e) {
|
||||
tvRespostaAI.setText("Erro a ler os dados da análise: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
tvRespostaAI.setText("O Assistente não está disponível neste momento.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
analisarFinancas();
|
||||
analisarFinancasDaNuvem();
|
||||
}
|
||||
|
||||
private void analisarFinancas() {
|
||||
float receitas = dbHelper.getTotalReceitas();
|
||||
float despesas = dbHelper.getTotalDespesas();
|
||||
private void analisarFinancasDaNuvem() {
|
||||
if (getActivity() == null) return;
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("DadosUtilizador", Context.MODE_PRIVATE);
|
||||
String userId = prefs.getString("user_id", null);
|
||||
if (userId == null) return;
|
||||
|
||||
// 1. Atualizar Textos Iniciais
|
||||
tvDicasReceitas.setText(String.format("€ %.2f", receitas));
|
||||
tvDicasDespesas.setText(String.format("€ %.2f", despesas));
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
// 2. Calcular Taxa de Poupança
|
||||
float taxaPoupanca = 0;
|
||||
if (receitas > 0) {
|
||||
taxaPoupanca = ((receitas - despesas) / receitas) * 100;
|
||||
}
|
||||
Request requestTransacoes = 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();
|
||||
|
||||
// Se gastou mais do que ganhou, a taxa é 0
|
||||
if (taxaPoupanca < 0) taxaPoupanca = 0;
|
||||
client.newCall(requestTransacoes).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) { }
|
||||
|
||||
tvTaxaPoupanca.setText(String.format("%.1f%%", taxaPoupanca));
|
||||
progressPoupanca.setProgress((int) taxaPoupanca);
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
if (!response.isSuccessful()) return;
|
||||
try {
|
||||
String body = response.body().string();
|
||||
JSONArray array = new JSONArray(body);
|
||||
float rec = 0, desp = 0;
|
||||
HashMap<String, Float> mapaGastos = new HashMap<>();
|
||||
|
||||
// Cores consoante a saúde financeira
|
||||
if (taxaPoupanca >= 20) {
|
||||
tvTaxaPoupanca.setTextColor(Color.parseColor("#00E676")); // Verde
|
||||
progressPoupanca.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#00E676")));
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject obj = array.getJSONObject(i);
|
||||
float v = (float) obj.getDouble("valor");
|
||||
if (obj.getInt("tipo") == 1) rec += v;
|
||||
else {
|
||||
desp += v;
|
||||
String cat = obj.getString("categoria");
|
||||
mapaGastos.put(cat, mapaGastos.getOrDefault(cat, 0f) + v);
|
||||
}
|
||||
}
|
||||
|
||||
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")));
|
||||
buscarOrcamentosECriarCerebro(userId, client, rec, desp, mapaGastos);
|
||||
|
||||
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.");
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void buscarOrcamentosECriarCerebro(String userId, OkHttpClient client, float rec, float desp, HashMap<String, Float> mapaGastos) {
|
||||
Request requestOrcamentos = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/orcamentos?user_id=eq." + userId)
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.build();
|
||||
|
||||
client.newCall(requestOrcamentos).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()) return;
|
||||
try {
|
||||
String body = response.body().string();
|
||||
JSONArray arrayOrcamentos = new JSONArray(body);
|
||||
|
||||
// 🧠 RECONSTRUIR O CÉREBRO DA IA COM TODOS OS DETALHES!
|
||||
StringBuilder cerebro = new StringBuilder();
|
||||
cerebro.append("DADOS FINANCEIROS ATUAIS DO UTILIZADOR: ");
|
||||
cerebro.append("Receitas Totais: ").append(rec).append("€. ");
|
||||
cerebro.append("Despesas Totais: ").append(desp).append("€. ");
|
||||
|
||||
// Passar as categorias onde gastaste dinheiro
|
||||
if (!mapaGastos.isEmpty()) {
|
||||
cerebro.append("Gastos por categoria: ");
|
||||
for (Map.Entry<String, Float> entry : mapaGastos.entrySet()) {
|
||||
cerebro.append(entry.getKey()).append(" (").append(entry.getValue()).append("€), ");
|
||||
}
|
||||
}
|
||||
|
||||
if (arrayOrcamentos.length() > 0) {
|
||||
cerebro.append(". ORÇAMENTOS DEFINIDOS: ");
|
||||
for (int i = 0; i < arrayOrcamentos.length(); i++) {
|
||||
JSONObject obj = arrayOrcamentos.getJSONObject(i);
|
||||
String cat = obj.getString("categoria");
|
||||
float limite = (float) obj.getDouble("valor_limite");
|
||||
float gasto = mapaGastos.containsKey(cat) ? mapaGastos.get(cat) : 0f;
|
||||
cerebro.append("[").append(cat).append(": Limite definido ").append(limite).append("€, Gasto atual ").append(gasto).append("€] ");
|
||||
}
|
||||
} else {
|
||||
cerebro.append(". O utilizador não tem orçamentos definidos de momento. ");
|
||||
}
|
||||
|
||||
// ⚠️ A JOGADA QUE FALTAVA: Guardar a memória na variável que a IA vai ler!
|
||||
contextoFinanceiroParaAI = cerebro.toString();
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> aplicarLogicaDeDicas(rec, desp, mapaGastos, arrayOrcamentos));
|
||||
}
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void aplicarLogicaDeDicas(float rec, float desp, HashMap<String, Float> mapa, JSONArray arrayOrcamentos) {
|
||||
|
||||
if (rec == 0 && desp == 0) {
|
||||
layoutConteudoDicas.setVisibility(View.GONE);
|
||||
layoutEstadoVazioDicas.setVisibility(View.VISIBLE);
|
||||
return;
|
||||
} 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!");
|
||||
layoutConteudoDicas.setVisibility(View.VISIBLE);
|
||||
layoutEstadoVazioDicas.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
// 3. Descobrir a Categoria mais gasta
|
||||
HashMap<String, Float> gastosPorCategoria = dbHelper.getDespesasPorCategoria();
|
||||
String piorCategoria = "Nenhuma";
|
||||
float maiorGasto = 0;
|
||||
// --- TOPO: RESUMO ---
|
||||
tvDicasReceitas.setText(String.format("€ %.2f", rec));
|
||||
tvDicasDespesas.setText(String.format("€ %.2f", desp));
|
||||
float taxa = (rec > 0) ? ((rec - desp) / rec) * 100 : 0;
|
||||
if (taxa < 0) taxa = 0;
|
||||
tvTaxaPoupanca.setText(String.format("%.1f%%", taxa));
|
||||
progressPoupanca.setProgress((int) taxa);
|
||||
|
||||
for (Map.Entry<String, Float> entry : gastosPorCategoria.entrySet()) {
|
||||
if (entry.getValue() > maiorGasto) {
|
||||
maiorGasto = entry.getValue();
|
||||
piorCategoria = entry.getKey();
|
||||
// --- CARTÃO 1: REGRA 50/30/20 ---
|
||||
float necessidades = 0; float desejos = 0;
|
||||
for (Map.Entry<String, Float> entry : mapa.entrySet()) {
|
||||
String cat = entry.getKey().toLowerCase();
|
||||
float val = entry.getValue();
|
||||
if (cat.contains("conta") || cat.contains("alimen") || cat.contains("saúd") || cat.contains("educa") || cat.contains("casa") || cat.contains("transp")) {
|
||||
necessidades += val;
|
||||
} else {
|
||||
desejos += val;
|
||||
}
|
||||
}
|
||||
|
||||
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!");
|
||||
if (rec > 0) {
|
||||
float percNecessidades = (necessidades / rec) * 100;
|
||||
float percDesejos = (desejos / rec) * 100;
|
||||
|
||||
if (percNecessidades <= 50 && percDesejos <= 30) {
|
||||
tvTituloDica1.setText("Balanço Perfeito ⚖️");
|
||||
tvTituloDica1.setTextColor(Color.parseColor("#00E676"));
|
||||
tvDescDica1.setText("Estás a cumprir a Regra de Ouro (50/30/20). Os teus gastos essenciais e de lazer estão equilibrados face aos teus rendimentos.");
|
||||
} else if (percDesejos > 30) {
|
||||
tvTituloDica1.setText("Atenção aos Gastos Supérfluos 🛍️");
|
||||
tvTituloDica1.setTextColor(Color.parseColor("#ECC94B"));
|
||||
tvDescDica1.setText(String.format("A alocação em despesas não essenciais representa %.0f%% do teu orçamento. Recomenda-se reduzir para a margem dos 30%%.", percDesejos));
|
||||
} else {
|
||||
tvTituloDica1.setText("Despesas Fixas Elevadas 🏠");
|
||||
tvTituloDica1.setTextColor(Color.parseColor("#F56565"));
|
||||
tvDescDica1.setText(String.format("Os teus encargos fixos representam %.0f%% do salário. O indicador ideal para manter a estabilidade financeira é de 50%%.", percNecessidades));
|
||||
}
|
||||
} else {
|
||||
tvTituloDica2.setText("Tudo Controlado ✅");
|
||||
tvTituloDica2.setTextColor(Color.parseColor("#00E676"));
|
||||
tvDescDica2.setText("Ainda não tens despesas suficientes para analisarmos. Continua o bom trabalho!");
|
||||
tvTituloDica1.setText("Regra 50/30/20 ⚖️");
|
||||
tvTituloDica1.setTextColor(corTextoDinamico);
|
||||
tvDescDica1.setText("Regista receitas para que possamos calcular a distribuição ideal do teu património.");
|
||||
}
|
||||
|
||||
// 4. Construir as barras de Distribuição de Gastos magicamente
|
||||
layoutDistribuicao.removeAllViews(); // Limpa as barras antigas
|
||||
// --- CARTÃO 2: RADAR DE ORÇAMENTOS ---
|
||||
String alertaOrcamento = "Todos os orçamentos definidos encontram-se dentro dos limites previstos.";
|
||||
int corAlerta = Color.parseColor("#00E676");
|
||||
String tituloAlerta = "Orçamentos Controlados ✅";
|
||||
|
||||
if (despesas > 0) {
|
||||
for (Map.Entry<String, Float> entry : gastosPorCategoria.entrySet()) {
|
||||
float valorCat = entry.getValue();
|
||||
if (valorCat > 0) {
|
||||
float percentagem = (valorCat / despesas) * 100;
|
||||
try {
|
||||
float maiorRisco = 0;
|
||||
String catRisco = "";
|
||||
float faltaParaLimite = 0;
|
||||
|
||||
// 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
|
||||
for (int i = 0; i < arrayOrcamentos.length(); i++) {
|
||||
JSONObject obj = arrayOrcamentos.getJSONObject(i);
|
||||
String cat = obj.getString("categoria");
|
||||
float limite = (float) obj.getDouble("valor_limite");
|
||||
float gasto = mapa.containsKey(cat) ? mapa.get(cat) : 0f;
|
||||
|
||||
// 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);
|
||||
if (limite > 0) {
|
||||
float risco = gasto / limite;
|
||||
if (risco > maiorRisco) {
|
||||
maiorRisco = risco;
|
||||
catRisco = cat;
|
||||
faltaParaLimite = limite - gasto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (maiorRisco >= 1.0) {
|
||||
tituloAlerta = "Orçamento Excedido 🚨";
|
||||
corAlerta = Color.parseColor("#FF1744");
|
||||
alertaOrcamento = "O limite definido para a categoria '" + catRisco + "' foi ultrapassado. Sugere-se o reajuste das restantes categorias.";
|
||||
} else if (maiorRisco >= 0.8) {
|
||||
tituloAlerta = "Aviso de Limite Próximo ⚠️";
|
||||
corAlerta = Color.parseColor("#ECC94B");
|
||||
alertaOrcamento = String.format("Atenção: A margem disponível para o orçamento de '%s' é de apenas %.2f€.", catRisco, faltaParaLimite);
|
||||
} else if (arrayOrcamentos.length() == 0) {
|
||||
tituloAlerta = "Planeamento Financeiro 🎯";
|
||||
corAlerta = corTextoDinamico;
|
||||
alertaOrcamento = "Acede ao separador 'Orçamentos' e estabelece limites para otimizar a tua gestão financeira.";
|
||||
}
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
|
||||
tvTituloDica2.setText(tituloAlerta);
|
||||
tvTituloDica2.setTextColor(corAlerta);
|
||||
tvDescDica2.setText(alertaOrcamento);
|
||||
|
||||
// --- CARTÃO 3: PREVISÃO E TENDÊNCIA DIÁRIA ---
|
||||
Calendar cal = Calendar.getInstance();
|
||||
int diaAtual = cal.get(Calendar.DAY_OF_MONTH);
|
||||
int diasNoMes = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||
|
||||
if (desp > 0 && diaAtual > 0) {
|
||||
float mediaDiaria = desp / diaAtual;
|
||||
float previsaoFimDoMes = mediaDiaria * diasNoMes;
|
||||
|
||||
if (previsaoFimDoMes > rec && rec > 0) {
|
||||
tvTituloDica3.setText("Projeção Mensal Elevada 📈");
|
||||
tvTituloDica3.setTextColor(Color.parseColor("#FF1744"));
|
||||
tvDescDica3.setText(String.format("A média de custos diários situa-se em %.2f€. Mantendo esta tendência, o custo final estimado será de %.2f€ (acima dos rendimentos).", mediaDiaria, previsaoFimDoMes));
|
||||
} else {
|
||||
tvTituloDica3.setText("Projeção Mensal Controlada 📉");
|
||||
tvTituloDica3.setTextColor(Color.parseColor("#00E676"));
|
||||
tvDescDica3.setText(String.format("A tua média de custos é de %.2f€ diários. A estimativa projetada para o final do mês é de %.2f€.", mediaDiaria, previsaoFimDoMes));
|
||||
}
|
||||
} else {
|
||||
TextView semDespesas = new TextView(getActivity());
|
||||
semDespesas.setText("Ainda não existem despesas registadas.");
|
||||
semDespesas.setTextColor(Color.parseColor("#B0BEC5"));
|
||||
layoutDistribuicao.addView(semDespesas);
|
||||
tvTituloDica3.setText("Projeção de Despesas 📊");
|
||||
tvTituloDica3.setTextColor(corTextoDinamico);
|
||||
tvDescDica3.setText("Regista mais movimentos ao longo do mês para que o sistema possa projetar a tua média diária de despesas.");
|
||||
}
|
||||
|
||||
// --- LISTA DE TOP DESPESAS ---
|
||||
layoutDistribuicao.removeAllViews();
|
||||
for (Map.Entry<String, Float> entry : mapa.entrySet()) {
|
||||
String categoria = entry.getKey();
|
||||
float valor = entry.getValue();
|
||||
|
||||
LinearLayout row = new LinearLayout(getContext());
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
row.setPadding(40, 30, 40, 30);
|
||||
row.setElevation(2f);
|
||||
|
||||
GradientDrawable shape = new GradientDrawable();
|
||||
shape.setCornerRadius(24f);
|
||||
shape.setColor(corFundoCartao);
|
||||
row.setBackground(shape);
|
||||
|
||||
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
|
||||
params.setMargins(0, 0, 0, 20);
|
||||
row.setLayoutParams(params);
|
||||
|
||||
String emoji = "💰";
|
||||
String catLower = categoria.toLowerCase();
|
||||
if (catLower.contains("alimen") || catLower.contains("restaurante")) emoji = "🍔";
|
||||
else if (catLower.contains("transp") || catLower.contains("carro")) emoji = "🚗";
|
||||
else if (catLower.contains("lazer") || catLower.contains("divers")) emoji = "🎮";
|
||||
else if (catLower.contains("saúd") || catLower.contains("farmácia")) emoji = "💊";
|
||||
else if (catLower.contains("educa")) emoji = "📚";
|
||||
else if (catLower.contains("casa") || catLower.contains("renda") || catLower.contains("conta")) emoji = "🏠";
|
||||
else if (catLower.contains("compras") || catLower.contains("roupa")) emoji = "🛍️";
|
||||
|
||||
TextView tvCat = new TextView(getContext());
|
||||
tvCat.setText(emoji + " " + categoria);
|
||||
tvCat.setTextColor(corTextoDinamico);
|
||||
tvCat.setTextSize(16f);
|
||||
tvCat.setTypeface(null, Typeface.BOLD);
|
||||
tvCat.setLayoutParams(new LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
|
||||
|
||||
TextView tvVal = new TextView(getContext());
|
||||
tvVal.setText(String.format("€ %.2f", valor));
|
||||
tvVal.setTextColor(Color.parseColor("#FF1744"));
|
||||
tvVal.setTextSize(16f);
|
||||
tvVal.setTypeface(null, Typeface.BOLD);
|
||||
|
||||
row.addView(tvCat);
|
||||
row.addView(tvVal);
|
||||
layoutDistribuicao.addView(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,90 @@
|
||||
package com.example.finzora;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
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.AppCompatActivity;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class EditarPerfilActivity extends AppCompatActivity {
|
||||
|
||||
private EditText editNomePerfil;
|
||||
private EditText editEmailPerfil;
|
||||
private ImageView imgFotoPerfil;
|
||||
private String caminhoFotoGuardada = null;
|
||||
|
||||
private final ActivityResultLauncher<String> seletorImagens =
|
||||
registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> {
|
||||
if (uri != null) {
|
||||
String novoCaminho = guardarImagemInternamente(uri);
|
||||
if (novoCaminho != null) {
|
||||
caminhoFotoGuardada = novoCaminho;
|
||||
|
||||
imgFotoPerfil.setPadding(0, 0, 0, 0);
|
||||
imgFotoPerfil.setImageTintList(null);
|
||||
|
||||
Glide.with(this)
|
||||
.load(new File(caminhoFotoGuardada))
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.circleCrop()
|
||||
.into(imgFotoPerfil);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@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);
|
||||
imgFotoPerfil = findViewById(R.id.imgFotoPerfil);
|
||||
|
||||
// Voltar para as definições
|
||||
btnVoltar.setOnClickListener(v -> finish());
|
||||
imgFotoPerfil.setOnClickListener(v -> seletorImagens.launch("image/*"));
|
||||
|
||||
// 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(prefs.getString("nome_usuario", "Investidor"));
|
||||
editEmailPerfil.setText(prefs.getString("email_usuario", ""));
|
||||
|
||||
editNomePerfil.setText(nomeAtual);
|
||||
editEmailPerfil.setText(emailAtual);
|
||||
caminhoFotoGuardada = prefs.getString("foto_usuario_path", null);
|
||||
if (caminhoFotoGuardada != null) {
|
||||
File arquivoFoto = new File(caminhoFotoGuardada);
|
||||
if (arquivoFoto.exists()) {
|
||||
imgFotoPerfil.setPadding(0, 0, 0, 0);
|
||||
imgFotoPerfil.setImageTintList(null);
|
||||
|
||||
Glide.with(this)
|
||||
.load(arquivoFoto)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.circleCrop()
|
||||
.into(imgFotoPerfil);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. GUARDAR OS DADOS NOVOS
|
||||
btnGuardarPerfil.setOnClickListener(v -> {
|
||||
String novoNome = editNomePerfil.getText().toString().trim();
|
||||
String novoEmail = editEmailPerfil.getText().toString().trim();
|
||||
@@ -46,14 +94,46 @@ public class EditarPerfilActivity extends AppCompatActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
// Grava na memória (SharedPreferences)
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString("nome_usuario", novoNome);
|
||||
editor.putString("email_usuario", novoEmail);
|
||||
|
||||
if (caminhoFotoGuardada != null) {
|
||||
editor.putString("foto_usuario_path", caminhoFotoGuardada);
|
||||
}
|
||||
|
||||
editor.apply();
|
||||
|
||||
// ⚠️ BACKUP TÁTICO DO NOME: Escrever no disco rígido para sobreviver ao Logout!
|
||||
try {
|
||||
FileOutputStream fos = openFileOutput("nome_perfil.txt", MODE_PRIVATE);
|
||||
fos.write(novoNome.getBytes());
|
||||
fos.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
Toast.makeText(this, "Perfil atualizado com sucesso! 🎉", Toast.LENGTH_SHORT).show();
|
||||
finish(); // Fecha o ecrã e volta atrás
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private String guardarImagemInternamente(Uri uri) {
|
||||
try {
|
||||
InputStream inputStream = getContentResolver().openInputStream(uri);
|
||||
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
|
||||
File pasta = getFilesDir();
|
||||
File arquivoFoto = new File(pasta, "foto_perfil.jpg");
|
||||
|
||||
FileOutputStream out = new FileOutputStream(arquivoFoto);
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
return arquivoFoto.getAbsolutePath();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.example.finzora;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -7,6 +9,7 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import com.github.mikephil.charting.charts.BarChart;
|
||||
import com.github.mikephil.charting.charts.PieChart;
|
||||
@@ -18,16 +21,32 @@ 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 org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class GraficosFragment extends Fragment {
|
||||
|
||||
private PieChart pieChartDespesas;
|
||||
private BarChart barChartOrcamento;
|
||||
private BarChart barChartTendencia;
|
||||
private DBHelper dbHelper;
|
||||
|
||||
// ⚠️ 1. As Vistas do Estado Vazio
|
||||
private View scrollviewGraficos;
|
||||
private View layoutEstadoVazio;
|
||||
|
||||
private int corTextoDinamica;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@@ -37,7 +56,16 @@ public class GraficosFragment extends Fragment {
|
||||
pieChartDespesas = view.findViewById(R.id.pieChartDespesas);
|
||||
barChartOrcamento = view.findViewById(R.id.barChartOrcamento);
|
||||
barChartTendencia = view.findViewById(R.id.barChartTendencia);
|
||||
dbHelper = new DBHelper(getActivity());
|
||||
|
||||
// ⚠️ 2. Ligar ao XML
|
||||
scrollviewGraficos = view.findViewById(R.id.scrollviewGraficos);
|
||||
layoutEstadoVazio = view.findViewById(R.id.layoutEstadoVazioGraficos);
|
||||
|
||||
if (getContext() != null) {
|
||||
corTextoDinamica = ContextCompat.getColor(getContext(), R.color.texto_principal);
|
||||
} else {
|
||||
corTextoDinamica = Color.BLACK;
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -45,25 +73,134 @@ public class GraficosFragment extends Fragment {
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
carregarPieChart();
|
||||
carregarBarChartOrcamento();
|
||||
carregarBarChartTendencia();
|
||||
carregarDadosDaNuvem();
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 1. GRÁFICO CIRCULAR (Despesas por Categoria)
|
||||
// ==========================================
|
||||
private void carregarPieChart() {
|
||||
private void carregarDadosDaNuvem() {
|
||||
if (getActivity() == null) return;
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("DadosUtilizador", Context.MODE_PRIVATE);
|
||||
String userId = prefs.getString("user_id", null);
|
||||
if (userId == null) return;
|
||||
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
Request reqTransacoes = 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(reqTransacoes).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()) return;
|
||||
|
||||
try {
|
||||
String jsonTransacoes = response.body().string();
|
||||
JSONArray arrTransacoes = new JSONArray(jsonTransacoes);
|
||||
|
||||
float somaReceitas = 0;
|
||||
float somaDespesas = 0;
|
||||
Map<String, Float> gastosPorCategoria = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < arrTransacoes.length(); i++) {
|
||||
JSONObject obj = arrTransacoes.getJSONObject(i);
|
||||
int tipo = obj.getInt("tipo");
|
||||
float valor = (float) obj.getDouble("valor");
|
||||
String categoria = obj.getString("categoria");
|
||||
|
||||
if (tipo == 1) {
|
||||
somaReceitas += valor;
|
||||
} else if (tipo == 2) {
|
||||
somaDespesas += valor;
|
||||
float atual = gastosPorCategoria.containsKey(categoria) ? gastosPorCategoria.get(categoria) : 0f;
|
||||
gastosPorCategoria.put(categoria, atual + valor);
|
||||
}
|
||||
}
|
||||
|
||||
final float totalReceitas = somaReceitas;
|
||||
final float totalDespesas = somaDespesas;
|
||||
final Map<String, Float> mapaGastos = gastosPorCategoria;
|
||||
|
||||
// ⚠️ 3. A Lógica de Mostrar/Esconder
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
// Se as receitas E as despesas forem 0, é porque não há transações nenhumas!
|
||||
if (totalReceitas == 0 && totalDespesas == 0) {
|
||||
scrollviewGraficos.setVisibility(View.GONE);
|
||||
layoutEstadoVazio.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
scrollviewGraficos.setVisibility(View.VISIBLE);
|
||||
layoutEstadoVazio.setVisibility(View.GONE);
|
||||
|
||||
// O utilizador tem dados! Vamos buscar os orçamentos para cruzar a informação
|
||||
carregarOrcamentosEDesenhar(userId, mapaGastos, totalReceitas, totalDespesas);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void carregarOrcamentosEDesenhar(String userId, Map<String, Float> mapaGastos, float totalReceitas, float totalDespesas) {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request reqOrcamentos = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/orcamentos?user_id=eq." + userId)
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.build();
|
||||
|
||||
client.newCall(reqOrcamentos).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response2) throws IOException {
|
||||
if (!response2.isSuccessful()) return;
|
||||
|
||||
try {
|
||||
String jsonOrcamentos = response2.body().string();
|
||||
JSONArray arrOrcamentos = new JSONArray(jsonOrcamentos);
|
||||
Map<String, Float> limitesOrcamento = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < arrOrcamentos.length(); i++) {
|
||||
JSONObject obj = arrOrcamentos.getJSONObject(i);
|
||||
limitesOrcamento.put(obj.getString("categoria"), (float) obj.getDouble("valor_limite"));
|
||||
}
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
desenharPieChart(mapaGastos);
|
||||
desenharBarChartOrcamento(limitesOrcamento, mapaGastos);
|
||||
desenharBarChartTendencia(totalReceitas, totalDespesas);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void desenharPieChart(Map<String, Float> despesas) {
|
||||
pieChartDespesas.getDescription().setEnabled(false);
|
||||
pieChartDespesas.setHoleColor(Color.parseColor("#2C5364"));
|
||||
pieChartDespesas.getLegend().setTextColor(Color.WHITE);
|
||||
pieChartDespesas.setHoleColor(Color.TRANSPARENT);
|
||||
|
||||
pieChartDespesas.getLegend().setTextColor(corTextoDinamica);
|
||||
pieChartDespesas.getLegend().setTextSize(12f);
|
||||
pieChartDespesas.getLegend().setWordWrapEnabled(true);
|
||||
|
||||
pieChartDespesas.setCenterText("Despesas");
|
||||
pieChartDespesas.setCenterTextColor(Color.WHITE);
|
||||
pieChartDespesas.setEntryLabelColor(Color.WHITE);
|
||||
pieChartDespesas.setCenterTextColor(corTextoDinamica);
|
||||
pieChartDespesas.setCenterTextSize(16f);
|
||||
|
||||
pieChartDespesas.setDrawEntryLabels(false);
|
||||
|
||||
HashMap<String, Float> despesas = dbHelper.getDespesasPorCategoria();
|
||||
ArrayList<PieEntry> entradas = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, Float> entry : despesas.entrySet()) {
|
||||
if (entry.getValue() > 0) entradas.add(new PieEntry(entry.getValue(), entry.getKey()));
|
||||
}
|
||||
@@ -72,21 +209,18 @@ public class GraficosFragment extends Fragment {
|
||||
|
||||
PieDataSet dataSet = new PieDataSet(entradas, "");
|
||||
dataSet.setColors(new int[]{Color.parseColor("#7C4DFF"), Color.parseColor("#00E5FF"), Color.parseColor("#FFD600"), Color.parseColor("#FF4081")});
|
||||
dataSet.setSliceSpace(3f);
|
||||
|
||||
PieData data = new PieData(dataSet);
|
||||
data.setValueTextSize(14f);
|
||||
data.setValueTextColor(Color.WHITE);
|
||||
data.setValueTextColor(corTextoDinamica);
|
||||
pieChartDespesas.setData(data);
|
||||
pieChartDespesas.animateY(1000);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. GRÁFICO DE BARRAS (Orçamento vs Gastos)
|
||||
// ==========================================
|
||||
private void carregarBarChartOrcamento() {
|
||||
private void desenharBarChartOrcamento(Map<String, Float> orcamentos, Map<String, Float> gastos) {
|
||||
configurarEstiloBarChart(barChartOrcamento);
|
||||
|
||||
Map<String, Float> orcamentos = dbHelper.getOrcamentosDefinidos();
|
||||
ArrayList<BarEntry> gastosEntries = new ArrayList<>();
|
||||
ArrayList<BarEntry> orcamentoEntries = new ArrayList<>();
|
||||
ArrayList<String> categorias = new ArrayList<>();
|
||||
@@ -95,7 +229,7 @@ public class GraficosFragment extends Fragment {
|
||||
for (Map.Entry<String, Float> entry : orcamentos.entrySet()) {
|
||||
String categoria = entry.getKey();
|
||||
float limite = entry.getValue();
|
||||
float gasto = dbHelper.getGastoPorCategoria(categoria);
|
||||
float gasto = gastos.containsKey(categoria) ? gastos.get(categoria) : 0f;
|
||||
|
||||
categorias.add(categoria);
|
||||
gastosEntries.add(new BarEntry(index, gasto));
|
||||
@@ -106,83 +240,96 @@ public class GraficosFragment extends Fragment {
|
||||
if (categorias.isEmpty()) { barChartOrcamento.clear(); return; }
|
||||
|
||||
BarDataSet setGastos = new BarDataSet(gastosEntries, "Gastos Reais");
|
||||
setGastos.setColor(Color.parseColor("#FF4081")); // Rosa (Figma)
|
||||
setGastos.setValueTextColor(Color.WHITE);
|
||||
setGastos.setColor(Color.parseColor("#FF4081"));
|
||||
setGastos.setValueTextColor(corTextoDinamica);
|
||||
|
||||
BarDataSet setOrcamento = new BarDataSet(orcamentoEntries, "Orçamento");
|
||||
setOrcamento.setColor(Color.parseColor("#00E5FF")); // Azul (Figma)
|
||||
setOrcamento.setValueTextColor(Color.WHITE);
|
||||
setOrcamento.setColor(Color.parseColor("#00E5FF"));
|
||||
setOrcamento.setValueTextColor(corTextoDinamica);
|
||||
|
||||
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;
|
||||
|
||||
// ⚠️ A MATEMÁTICA PERFEITA (tem de somar 1.00)
|
||||
float barWidth = 0.35f;
|
||||
float barSpace = 0.05f;
|
||||
float groupSpace = 0.20f;
|
||||
|
||||
data.setBarWidth(barWidth);
|
||||
barChartOrcamento.setData(data);
|
||||
barChartOrcamento.groupBars(-0.5f, groupSpace, barSpace);
|
||||
|
||||
// Labels no Eixo X
|
||||
// ⚠️ COMEÇA NO 0 PARA ALINHAR AO CENTRO
|
||||
barChartOrcamento.groupBars(0f, groupSpace, barSpace);
|
||||
|
||||
XAxis xAxis = barChartOrcamento.getXAxis();
|
||||
xAxis.setValueFormatter(new IndexAxisValueFormatter(categorias));
|
||||
xAxis.setAxisMinimum(-0.5f);
|
||||
xAxis.setAxisMaximum(categorias.size() - 0.5f);
|
||||
|
||||
// ⚠️ LIMITES DINÂMICOS PARA ENCAIXAR OS GRUPOS TODOS
|
||||
xAxis.setAxisMinimum(0f);
|
||||
xAxis.setAxisMaximum(barChartOrcamento.getBarData().getGroupWidth(groupSpace, barSpace) * categorias.size());
|
||||
|
||||
// Para não ficar tudo esmagado se tiveres muitas categorias, mete limite visível a 4
|
||||
barChartOrcamento.setVisibleXRangeMaximum(4);
|
||||
|
||||
barChartOrcamento.animateY(1000);
|
||||
barChartOrcamento.invalidate(); // Refresca o gráfico
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. GRÁFICO DE BARRAS (Tendência Mensal Geral)
|
||||
// ==========================================
|
||||
private void carregarBarChartTendencia() {
|
||||
private void desenharBarChartTendencia(float receitas, float despesas) {
|
||||
configurarEstiloBarChart(barChartTendencia);
|
||||
|
||||
float totalReceitas = dbHelper.getTotalReceitas();
|
||||
float totalDespesas = dbHelper.getTotalDespesas();
|
||||
|
||||
ArrayList<BarEntry> despesaEntry = new ArrayList<>();
|
||||
ArrayList<BarEntry> receitaEntry = new ArrayList<>();
|
||||
|
||||
despesaEntry.add(new BarEntry(0, totalDespesas));
|
||||
receitaEntry.add(new BarEntry(0, totalReceitas));
|
||||
despesaEntry.add(new BarEntry(0, despesas));
|
||||
receitaEntry.add(new BarEntry(0, receitas));
|
||||
|
||||
BarDataSet setDespesas = new BarDataSet(despesaEntry, "Despesas");
|
||||
setDespesas.setColor(Color.parseColor("#FF1744")); // Vermelho
|
||||
setDespesas.setValueTextColor(Color.WHITE);
|
||||
setDespesas.setColor(Color.parseColor("#FF1744"));
|
||||
setDespesas.setValueTextColor(corTextoDinamica);
|
||||
|
||||
BarDataSet setReceitas = new BarDataSet(receitaEntry, "Receitas");
|
||||
setReceitas.setColor(Color.parseColor("#00E676")); // Verde
|
||||
setReceitas.setValueTextColor(Color.WHITE);
|
||||
setReceitas.setColor(Color.parseColor("#00E676"));
|
||||
setReceitas.setValueTextColor(corTextoDinamica);
|
||||
|
||||
BarData data = new BarData(setDespesas, setReceitas);
|
||||
|
||||
float groupSpace = 0.3f; float barSpace = 0.05f; float barWidth = 0.3f;
|
||||
// ⚠️ MATEMÁTICA PERFEITA PARA A TENDÊNCIA
|
||||
float groupSpace = 0.3f;
|
||||
float barSpace = 0.05f;
|
||||
float barWidth = 0.3f;
|
||||
|
||||
data.setBarWidth(barWidth);
|
||||
barChartTendencia.setData(data);
|
||||
barChartTendencia.groupBars(-0.5f, groupSpace, barSpace);
|
||||
|
||||
// ⚠️ COMEÇA NO 0 TAMBÉM
|
||||
barChartTendencia.groupBars(0f, groupSpace, barSpace);
|
||||
|
||||
ArrayList<String> labelMes = new ArrayList<>();
|
||||
labelMes.add("Atual");
|
||||
|
||||
XAxis xAxis = barChartTendencia.getXAxis();
|
||||
xAxis.setValueFormatter(new IndexAxisValueFormatter(labelMes));
|
||||
xAxis.setAxisMinimum(-0.5f);
|
||||
xAxis.setAxisMaximum(0.5f);
|
||||
|
||||
// ⚠️ COMO É SÓ 1 GRUPO, O MÁXIMO É 1
|
||||
xAxis.setAxisMinimum(0f);
|
||||
xAxis.setAxisMaximum(1f);
|
||||
|
||||
barChartTendencia.animateY(1000);
|
||||
barChartTendencia.invalidate();
|
||||
}
|
||||
|
||||
// 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.getLegend().setTextColor(corTextoDinamica);
|
||||
chart.getAxisRight().setEnabled(false);
|
||||
|
||||
chart.getAxisLeft().setTextColor(Color.WHITE);
|
||||
chart.getAxisLeft().setTextColor(corTextoDinamica);
|
||||
chart.getAxisLeft().setDrawGridLines(true);
|
||||
chart.getAxisLeft().setGridColor(Color.parseColor("#455A64")); // Linhas de fundo subtis
|
||||
chart.getAxisLeft().setGridColor(Color.LTGRAY);
|
||||
|
||||
XAxis xAxis = chart.getXAxis();
|
||||
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
|
||||
xAxis.setTextColor(Color.WHITE);
|
||||
xAxis.setTextColor(corTextoDinamica);
|
||||
xAxis.setDrawGridLines(false);
|
||||
xAxis.setGranularity(1f);
|
||||
xAxis.setCenterAxisLabels(true);
|
||||
|
||||
84
app/src/main/java/com/example/finzora/LockActivity.java
Normal file
84
app/src/main/java/com/example/finzora/LockActivity.java
Normal file
@@ -0,0 +1,84 @@
|
||||
package com.example.finzora;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Button;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.biometric.BiometricManager;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class LockActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_lock);
|
||||
|
||||
Button btnDesbloquearApp = findViewById(R.id.btnDesbloquearApp);
|
||||
|
||||
btnDesbloquearApp.setOnClickListener(v -> solicitarBiometria());
|
||||
|
||||
// ⚠️ CORREÇÃO 1: Esperar meio segundo para o telemóvel real não entrar em pânico
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> {
|
||||
solicitarBiometria();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private void solicitarBiometria() {
|
||||
BiometricManager biometricManager = BiometricManager.from(this);
|
||||
|
||||
// ⚠️ CORREÇÃO 2: Mudamos para BIOMETRIC_WEAK para ser compatível com mais telemóveis reais
|
||||
int authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK | BiometricManager.Authenticators.DEVICE_CREDENTIAL;
|
||||
|
||||
int canAuthenticate = biometricManager.canAuthenticate(authenticators);
|
||||
|
||||
if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
Executor executor = ContextCompat.getMainExecutor(this);
|
||||
BiometricPrompt biometricPrompt = new BiometricPrompt(LockActivity.this,
|
||||
executor, new BiometricPrompt.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||
super.onAuthenticationError(errorCode, errString);
|
||||
// Se der erro 10 (User Canceled), não fazemos nada, ele clica no botão se quiser tentar de novo
|
||||
if (errorCode != 10) {
|
||||
Toast.makeText(getApplicationContext(), "Erro: " + errString, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
startActivity(new Intent(LockActivity.this, MainActivity.class));
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed();
|
||||
Toast.makeText(getApplicationContext(), "Biometria não reconhecida.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle("Finzora: Escudo de Privacidade")
|
||||
.setSubtitle("Usa a impressão digital ou o PIN do telemóvel")
|
||||
.setAllowedAuthenticators(authenticators) // ⚠️ CORREÇÃO 3: Usar os mesmos flags aqui
|
||||
.build();
|
||||
|
||||
biometricPrompt.authenticate(promptInfo);
|
||||
} else {
|
||||
// Se cair aqui, o Toast vai dizer o código do erro para investigarmos
|
||||
Toast.makeText(this, "Escudo Inativo (Erro: " + canAuthenticate + "). A aceder...", Toast.LENGTH_LONG).show();
|
||||
startActivity(new Intent(LockActivity.this, MainActivity.class));
|
||||
finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,9 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.Call;
|
||||
@@ -36,28 +39,36 @@ public class LoginActivity extends AppCompatActivity {
|
||||
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
|
||||
}
|
||||
boolean usarBiometria = prefs.getBoolean("usar_biometria", false);
|
||||
|
||||
if (usarBiometria) {
|
||||
startActivity(new Intent(LoginActivity.this, LockActivity.class));
|
||||
} else {
|
||||
startActivity(new Intent(LoginActivity.this, MainActivity.class));
|
||||
}
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_login);
|
||||
|
||||
Intent intentDeepLink = getIntent();
|
||||
if (intentDeepLink != null && Intent.ACTION_VIEW.equals(intentDeepLink.getAction())) {
|
||||
android.net.Uri uri = intentDeepLink.getData();
|
||||
if (uri != null && "finzora".equals(uri.getScheme()) && "confirmado".equals(uri.getHost())) {
|
||||
Toast.makeText(this, "✅ Conta confirmada com sucesso! Já podes entrar.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
inicializarComponentes();
|
||||
|
||||
// Clique no botão "Entrar" -> Agora faz Login de verdade!
|
||||
btnEntrar.setOnClickListener(v -> validarDados());
|
||||
|
||||
// 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));
|
||||
});
|
||||
@@ -74,7 +85,6 @@ public class LoginActivity extends AppCompatActivity {
|
||||
editPassword.setError("Introduza a sua palavra-passe");
|
||||
editPassword.requestFocus();
|
||||
} else {
|
||||
// Desativa o botão enquanto pensa
|
||||
btnEntrar.setEnabled(false);
|
||||
btnEntrar.setText("A VERIFICAR DADOS...");
|
||||
fazerLoginNoSupabase(email, password);
|
||||
@@ -87,7 +97,6 @@ public class LoginActivity extends AppCompatActivity {
|
||||
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)
|
||||
@@ -114,7 +123,6 @@ public class LoginActivity extends AppCompatActivity {
|
||||
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");
|
||||
@@ -122,30 +130,65 @@ public class LoginActivity extends AppCompatActivity {
|
||||
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.putBoolean("is_logged_in", true);
|
||||
editor.putString("user_id", userId);
|
||||
editor.putString("email_usuario", email);
|
||||
|
||||
// 🏆 A MAGIA ACONTECE AQUI: Recuperar Nome e Foto!
|
||||
|
||||
// 1. Recuperar Nome do Backup Físico
|
||||
String nomeRecuperado = "Investidor";
|
||||
try {
|
||||
FileInputStream fis = openFileInput("nome_perfil.txt");
|
||||
byte[] bytes = new byte[fis.available()];
|
||||
fis.read(bytes);
|
||||
fis.close();
|
||||
nomeRecuperado = new String(bytes);
|
||||
} catch (Exception e) {
|
||||
// Se não tiver backup, tenta ver se a Base de Dados devolveu algum nome
|
||||
try {
|
||||
JSONObject meta = jsonResponse.getJSONObject("user").optJSONObject("user_metadata");
|
||||
if (meta != null && meta.has("nome")) nomeRecuperado = meta.getString("nome");
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
editor.putString("nome_usuario", nomeRecuperado);
|
||||
|
||||
// 2. Recuperar Caminho da Foto (Ela está sempre guardada na pasta principal)
|
||||
File arquivoFoto = new File(getFilesDir(), "foto_perfil.jpg");
|
||||
if (arquivoFoto.exists()) {
|
||||
editor.putString("foto_usuario_path", arquivoFoto.getAbsolutePath());
|
||||
}
|
||||
|
||||
editor.apply();
|
||||
|
||||
Toast.makeText(LoginActivity.this, "Bem-vindo de volta!", Toast.LENGTH_SHORT).show();
|
||||
irParaDashboard();
|
||||
|
||||
irParaSeguranca();
|
||||
|
||||
} 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
|
||||
editPassword.setText("");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void irParaDashboard() {
|
||||
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
|
||||
private void irParaSeguranca() {
|
||||
SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
|
||||
boolean usarBiometria = prefs.getBoolean("usar_biometria", false);
|
||||
|
||||
Intent intent;
|
||||
if (usarBiometria) {
|
||||
intent = new Intent(LoginActivity.this, LockActivity.class);
|
||||
} else {
|
||||
intent = new Intent(LoginActivity.this, MainActivity.class);
|
||||
}
|
||||
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
package com.example.finzora;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.pdf.PdfDocument;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.viewpager2.widget.ViewPager2;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.android.material.tabs.TabLayout;
|
||||
import com.google.android.material.tabs.TabLayoutMediator;
|
||||
@@ -18,7 +33,12 @@ import com.google.android.material.tabs.TabLayoutMediator;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.DateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
@@ -35,6 +55,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
private Button btnSair;
|
||||
|
||||
private TextView tvSaldoGeral, tvReceitasGeral, tvDespesasGeral;
|
||||
private JSONArray listaTransacoesGlobal;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -65,60 +86,480 @@ public class MainActivity extends AppCompatActivity {
|
||||
String nome = prefs.getString("nome_usuario", "Investidor");
|
||||
tvNomeUsuario.setText("Olá, " + nome);
|
||||
|
||||
btnSair.setOnClickListener(v -> {
|
||||
prefs.edit().clear().apply();
|
||||
startActivity(new Intent(this, LoginActivity.class));
|
||||
finish();
|
||||
});
|
||||
btnSair.setOnClickListener(v -> finishAffinity());
|
||||
|
||||
fabAdicionar.setOnClickListener(v -> {
|
||||
startActivity(new Intent(this, AdicionarTransacaoActivity.class));
|
||||
});
|
||||
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));
|
||||
btnAbrirDefinicoes.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, DefinicoesActivity.class)));
|
||||
}
|
||||
|
||||
ImageView imgLogoPerfil = findViewById(R.id.imgLogo);
|
||||
if (imgLogoPerfil != null) {
|
||||
imgLogoPerfil.setOnClickListener(v -> startActivity(new Intent(MainActivity.this, EditarPerfilActivity.class)));
|
||||
}
|
||||
|
||||
ImageView btnExportarPDF = findViewById(R.id.btnExportarPDF);
|
||||
if (btnExportarPDF != null) {
|
||||
btnExportarPDF.setOnClickListener(v -> {
|
||||
if (listaTransacoesGlobal != null && listaTransacoesGlobal.length() > 0) {
|
||||
mostrarDialogoExportacao();
|
||||
} else {
|
||||
Toast.makeText(this, "Ainda não tens dados para exportar!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
configurarAbas();
|
||||
atualizarCartoes(); // Chama a nova função ligada à net!
|
||||
atualizarCartoes();
|
||||
carregarFotoPerfil();
|
||||
|
||||
// 🛡️ TÁTICA DE VISIBILIDADE DO BOTÃO +
|
||||
viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
super.onPageSelected(position);
|
||||
// Posições: 0 = Transações, 1 = Orçamentos, 2 = Gráficos, 3 = Objetivos, 4 = Dicas
|
||||
if (position == 0 || position == 1) {
|
||||
fabAdicionar.show(); // Só mostra nas Transações e Orçamentos
|
||||
} else {
|
||||
fabAdicionar.hide(); // Esconde nos Gráficos, Objetivos e Dicas
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void mostrarDialogoExportacao() {
|
||||
View view = getLayoutInflater().inflate(R.layout.dialog_exportar, null);
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setView(view);
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(0));
|
||||
}
|
||||
|
||||
CardView btnPdf = view.findViewById(R.id.btnOpcaoPDF);
|
||||
CardView btnExcel = view.findViewById(R.id.btnOpcaoExcel);
|
||||
TextView btnCancelar = view.findViewById(R.id.btnCancelarExportacao);
|
||||
|
||||
btnPdf.setOnClickListener(v -> {
|
||||
gerarRelatorioPDF(listaTransacoesGlobal);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
btnExcel.setOnClickListener(v -> {
|
||||
gerarRelatorioCSV(listaTransacoesGlobal);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
btnCancelar.setOnClickListener(v -> dialog.dismiss());
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void gerarRelatorioCSV(JSONArray transacoes) {
|
||||
StringBuilder csvData = new StringBuilder();
|
||||
csvData.append("Data,Descricao,Categoria,Tipo,Valor(Euros)\n");
|
||||
|
||||
try {
|
||||
for (int i = 0; i < transacoes.length(); i++) {
|
||||
JSONObject obj = transacoes.getJSONObject(i);
|
||||
String data = obj.optString("data", "---");
|
||||
String desc = obj.optString("descricao", "---").replace(",", " ");
|
||||
String cat = obj.optString("categoria", "---");
|
||||
int tipo = obj.optInt("tipo");
|
||||
double valor = obj.optDouble("valor");
|
||||
|
||||
String tipoStr = (tipo == 1) ? "Receita" : "Despesa";
|
||||
csvData.append(data).append(",").append(desc).append(",").append(cat).append(",").append(tipoStr).append(",").append(valor).append("\n");
|
||||
}
|
||||
|
||||
String nomeFicheiro = "Finzora_Export_" + System.currentTimeMillis() + ".csv";
|
||||
File arquivoCsv = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), nomeFicheiro);
|
||||
|
||||
FileOutputStream fos = new FileOutputStream(arquivoCsv);
|
||||
fos.write(csvData.toString().getBytes());
|
||||
fos.close();
|
||||
|
||||
Toast.makeText(this, "Ficheiro Excel guardado com sucesso! 📊", Toast.LENGTH_LONG).show();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Toast.makeText(this, "Erro ao gerar o ficheiro Excel.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// 🏆 A JOGADA DE MESTRE: PDF COM DIAGNÓSTICO INTELIGENTE!
|
||||
private void gerarRelatorioPDF(JSONArray transacoes) {
|
||||
PdfDocument pdf = new PdfDocument();
|
||||
int numeroPagina = 1;
|
||||
PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(595, 842, numeroPagina).create();
|
||||
PdfDocument.Page pagina = pdf.startPage(pageInfo);
|
||||
Canvas canvas = pagina.getCanvas();
|
||||
Paint paint = new Paint();
|
||||
|
||||
int totalTransacoes = transacoes.length();
|
||||
double maiorDespesa = 0, maiorReceita = 0, somaDespesas = 0, somaReceitas = 0;
|
||||
double necessidades = 0, desejos = 0;
|
||||
String descMaiorDespesa = "-", descMaiorReceita = "-";
|
||||
int countDespesas = 0;
|
||||
|
||||
try {
|
||||
for (int i = 0; i < totalTransacoes; i++) {
|
||||
JSONObject obj = transacoes.getJSONObject(i);
|
||||
double v = obj.getDouble("valor");
|
||||
int t = obj.getInt("tipo");
|
||||
String d = obj.getString("descricao");
|
||||
String cat = obj.optString("categoria", "").toLowerCase();
|
||||
|
||||
if (t == 1) {
|
||||
somaReceitas += v;
|
||||
if (v > maiorReceita) { maiorReceita = v; descMaiorReceita = d; }
|
||||
} else {
|
||||
somaDespesas += v; countDespesas++;
|
||||
if (v > maiorDespesa) { maiorDespesa = v; descMaiorDespesa = d; }
|
||||
|
||||
if (cat.contains("conta") || cat.contains("alimen") || cat.contains("saúd") || cat.contains("educa") || cat.contains("casa") || cat.contains("transp")) {
|
||||
necessidades += v;
|
||||
} else {
|
||||
desejos += v;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
|
||||
double mediaDespesa = countDespesas > 0 ? somaDespesas / countDespesas : 0;
|
||||
|
||||
// CABEÇALHO DO RELATÓRIO
|
||||
paint.setColor(Color.parseColor("#00E676"));
|
||||
canvas.drawRect(0, 0, 595, 120, paint);
|
||||
|
||||
paint.setColor(Color.parseColor("#1A202C"));
|
||||
paint.setTextSize(28f);
|
||||
paint.setFakeBoldText(true);
|
||||
canvas.drawText("FINZORA", 40, 65, paint);
|
||||
|
||||
paint.setTextSize(12f);
|
||||
paint.setFakeBoldText(false);
|
||||
canvas.drawText("RELATÓRIO FINANCEIRO E DIAGNÓSTICO", 40, 90, paint);
|
||||
|
||||
SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
|
||||
String nome = prefs.getString("nome_usuario", "Investidor");
|
||||
String email = prefs.getString("email_usuario", "---");
|
||||
|
||||
paint.setTextAlign(Paint.Align.RIGHT);
|
||||
canvas.drawText("Utilizador: " + nome, 555, 60, paint);
|
||||
canvas.drawText("Email: " + email, 555, 80, paint);
|
||||
paint.setTextAlign(Paint.Align.LEFT);
|
||||
|
||||
paint.setColor(Color.parseColor("#F7FAFC"));
|
||||
canvas.drawRoundRect(40, 140, 555, 210, 10, 10, paint);
|
||||
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setTextSize(12f);
|
||||
paint.setFakeBoldText(true);
|
||||
canvas.drawText("SALDOS ATUAIS", 60, 165, paint);
|
||||
|
||||
paint.setFakeBoldText(false);
|
||||
paint.setTextSize(11f);
|
||||
canvas.drawText("SALDO: " + tvSaldoGeral.getText().toString(), 60, 190, paint);
|
||||
canvas.drawText("RECEITAS: " + tvReceitasGeral.getText().toString(), 250, 190, paint);
|
||||
canvas.drawText("DESPESAS: " + tvDespesasGeral.getText().toString(), 420, 190, paint);
|
||||
|
||||
paint.setColor(Color.parseColor("#F1F5F9"));
|
||||
canvas.drawRoundRect(40, 225, 555, 305, 10, 10, paint);
|
||||
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setTextSize(12f);
|
||||
paint.setFakeBoldText(true);
|
||||
canvas.drawText("ANÁLISE DE PERFORMANCE", 60, 250, paint);
|
||||
|
||||
paint.setFakeBoldText(false);
|
||||
paint.setTextSize(11f);
|
||||
canvas.drawText("Maior Despesa: " + descMaiorDespesa + " (" + String.format("%.2f €", maiorDespesa) + ")", 60, 275, paint);
|
||||
canvas.drawText("Maior Receita: " + descMaiorReceita + " (" + String.format("%.2f €", maiorReceita) + ")", 60, 295, paint);
|
||||
|
||||
canvas.drawText("Média por Despesa: " + String.format("%.2f €", mediaDespesa), 350, 275, paint);
|
||||
canvas.drawText("Total de Movimentos: " + totalTransacoes, 350, 295, paint);
|
||||
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setTextSize(12f);
|
||||
paint.setFakeBoldText(true);
|
||||
canvas.drawText("BALANÇO VISUAL", 40, 345, paint);
|
||||
|
||||
double totalDinheiro = somaReceitas + somaDespesas;
|
||||
|
||||
if (totalDinheiro > 0) {
|
||||
RectF areaDoGrafico = new RectF(60, 365, 200, 505);
|
||||
float anguloReceitas = (float) ((somaReceitas / totalDinheiro) * 360f);
|
||||
float anguloDespesas = (float) ((somaDespesas / totalDinheiro) * 360f);
|
||||
|
||||
paint.setColor(Color.parseColor("#00E676"));
|
||||
canvas.drawArc(areaDoGrafico, 0, anguloReceitas, true, paint);
|
||||
|
||||
paint.setColor(Color.parseColor("#FF1744"));
|
||||
canvas.drawArc(areaDoGrafico, anguloReceitas, anguloDespesas, true, paint);
|
||||
|
||||
paint.setTextSize(11f);
|
||||
paint.setFakeBoldText(false);
|
||||
paint.setColor(Color.parseColor("#00E676"));
|
||||
canvas.drawRect(230, 410, 245, 425, paint);
|
||||
paint.setColor(Color.BLACK);
|
||||
canvas.drawText("Receitas (" + String.format("%.0f", (somaReceitas/totalDinheiro)*100) + "%)", 255, 422, paint);
|
||||
|
||||
paint.setColor(Color.parseColor("#FF1744"));
|
||||
canvas.drawRect(230, 440, 245, 455, paint);
|
||||
paint.setColor(Color.BLACK);
|
||||
canvas.drawText("Despesas (" + String.format("%.0f", (somaDespesas/totalDinheiro)*100) + "%)", 255, 452, paint);
|
||||
}
|
||||
|
||||
paint.setFakeBoldText(true);
|
||||
paint.setTextSize(14f);
|
||||
canvas.drawText("HISTÓRICO DETALHADO", 40, 540, paint);
|
||||
|
||||
paint.setColor(Color.parseColor("#EDF2F7"));
|
||||
canvas.drawRect(40, 555, 555, 580, paint);
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setTextSize(12f);
|
||||
canvas.drawText("DESCRIÇÃO", 50, 572, paint);
|
||||
canvas.drawText("VALOR", 350, 572, paint);
|
||||
canvas.drawText("DATA", 470, 572, paint);
|
||||
|
||||
paint.setFakeBoldText(false);
|
||||
int y = 605;
|
||||
|
||||
try {
|
||||
for (int i = 0; i < transacoes.length(); i++) {
|
||||
if (y > 760) {
|
||||
desenharRodape(canvas, paint, numeroPagina);
|
||||
pdf.finishPage(pagina);
|
||||
|
||||
numeroPagina++;
|
||||
pageInfo = new PdfDocument.PageInfo.Builder(595, 842, numeroPagina).create();
|
||||
pagina = pdf.startPage(pageInfo);
|
||||
canvas = pagina.getCanvas();
|
||||
|
||||
paint.setColor(Color.parseColor("#00E676"));
|
||||
canvas.drawRect(0, 0, 595, 40, paint);
|
||||
paint.setColor(Color.parseColor("#1A202C"));
|
||||
paint.setTextSize(14f);
|
||||
paint.setFakeBoldText(true);
|
||||
canvas.drawText("FINZORA - Continuação do Histórico", 40, 25, paint);
|
||||
|
||||
paint.setColor(Color.parseColor("#EDF2F7"));
|
||||
canvas.drawRect(40, 60, 555, 85, paint);
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setTextSize(12f);
|
||||
canvas.drawText("DESCRIÇÃO", 50, 77, paint);
|
||||
canvas.drawText("VALOR", 350, 77, paint);
|
||||
canvas.drawText("DATA", 470, 77, paint);
|
||||
|
||||
y = 110;
|
||||
paint.setFakeBoldText(false);
|
||||
}
|
||||
|
||||
JSONObject obj = transacoes.getJSONObject(i);
|
||||
String desc = obj.getString("descricao");
|
||||
double valor = obj.getDouble("valor");
|
||||
String data = obj.optString("data", "---");
|
||||
int tipo = obj.getInt("tipo");
|
||||
|
||||
if (i % 2 == 0) {
|
||||
paint.setColor(Color.parseColor("#F8FAFC"));
|
||||
canvas.drawRect(40, y - 20, 555, y + 10, paint);
|
||||
}
|
||||
|
||||
paint.setColor(Color.BLACK);
|
||||
canvas.drawText(desc, 50, y, paint);
|
||||
|
||||
if (tipo == 1) {
|
||||
paint.setColor(Color.parseColor("#2F855A"));
|
||||
canvas.drawText("+ " + String.format("%.2f €", valor), 350, y, paint);
|
||||
} else {
|
||||
paint.setColor(Color.parseColor("#C53030"));
|
||||
canvas.drawText("- " + String.format("%.2f €", valor), 350, y, paint);
|
||||
}
|
||||
|
||||
paint.setColor(Color.BLACK);
|
||||
canvas.drawText(data, 470, y, paint);
|
||||
y += 35;
|
||||
}
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
|
||||
// 🧠 ----------------- NOVO BLOCO: DIAGNÓSTICO FINANCEIRO COM TEXTO QUEBRADO -----------------
|
||||
|
||||
y += 20;
|
||||
|
||||
// Aumentei o espaço de controlo para a nova caixa que é mais alta (160px)
|
||||
if (y + 160 > 800) {
|
||||
desenharRodape(canvas, paint, numeroPagina);
|
||||
pdf.finishPage(pagina);
|
||||
numeroPagina++;
|
||||
pageInfo = new PdfDocument.PageInfo.Builder(595, 842, numeroPagina).create();
|
||||
pagina = pdf.startPage(pageInfo);
|
||||
canvas = pagina.getCanvas();
|
||||
y = 60;
|
||||
}
|
||||
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setFakeBoldText(true);
|
||||
paint.setTextSize(14f);
|
||||
canvas.drawText("💡 DIAGNÓSTICO E RECOMENDAÇÕES", 40, y, paint);
|
||||
|
||||
y += 15;
|
||||
// Desenha a caixa azul mais alta
|
||||
paint.setColor(Color.parseColor("#EBF8FF"));
|
||||
canvas.drawRoundRect(40, y, 555, y + 150, 10, 10, paint);
|
||||
|
||||
y += 25;
|
||||
paint.setTextSize(11f);
|
||||
int colunaTitulos = 50;
|
||||
int colunaTexto = 200; // Puxei o texto um bocadinho mais para a esquerda para caber à vontade
|
||||
|
||||
// 1. Regra 50/30/20
|
||||
double percNecessidades = somaReceitas > 0 ? (necessidades / somaReceitas) * 100 : 0;
|
||||
double percDesejos = somaReceitas > 0 ? (desejos / somaReceitas) * 100 : 0;
|
||||
|
||||
paint.setColor(Color.BLACK);
|
||||
paint.setFakeBoldText(true);
|
||||
canvas.drawText("Balanço (Regra 50/30/20):", colunaTitulos, y, paint);
|
||||
paint.setFakeBoldText(false);
|
||||
|
||||
if (somaReceitas > 0) {
|
||||
if (percNecessidades <= 50 && percDesejos <= 30) {
|
||||
canvas.drawText("Excelente! Gastos essenciais e de lazer", colunaTexto, y, paint);
|
||||
canvas.drawText("encontram-se equilibrados.", colunaTexto, y + 15, paint);
|
||||
} else if (percDesejos > 30) {
|
||||
canvas.drawText("Alerta: Gastos em Lazer elevados (" + String.format("%.0f", percDesejos) + "%).", colunaTexto, y, paint);
|
||||
canvas.drawText("Aconselha-se redução para 30%.", colunaTexto, y + 15, paint);
|
||||
} else {
|
||||
canvas.drawText("Aviso: Despesas Fixas elevadas", colunaTexto, y, paint);
|
||||
canvas.drawText("(" + String.format("%.0f", percNecessidades) + "% do rendimento total).", colunaTexto, y + 15, paint);
|
||||
}
|
||||
} else {
|
||||
canvas.drawText("Registe receitas para cálculo fiável.", colunaTexto, y, paint);
|
||||
}
|
||||
|
||||
// 2. Taxa de Poupança
|
||||
y += 35; // Espaço duplo por causa das duas linhas de cima
|
||||
double taxaPoupanca = somaReceitas > 0 ? ((somaReceitas - somaDespesas) / somaReceitas) * 100 : 0;
|
||||
paint.setFakeBoldText(true);
|
||||
canvas.drawText("Taxa de Poupança Global:", colunaTitulos, y, paint);
|
||||
paint.setFakeBoldText(false);
|
||||
canvas.drawText(String.format("%.1f%%", Math.max(taxaPoupanca, 0)) + " do rendimento foi retido.", colunaTexto, y, paint);
|
||||
|
||||
// 3. Projeção Mensal
|
||||
y += 25;
|
||||
Calendar cal = Calendar.getInstance();
|
||||
int diaAtual = cal.get(Calendar.DAY_OF_MONTH);
|
||||
int diasNoMes = cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
||||
double mediaDiaria = diaAtual > 0 ? somaDespesas / diaAtual : 0;
|
||||
double previsao = mediaDiaria * diasNoMes;
|
||||
|
||||
paint.setFakeBoldText(true);
|
||||
canvas.drawText("Projeção (Fim do Mês):", colunaTitulos, y, paint);
|
||||
paint.setFakeBoldText(false);
|
||||
canvas.drawText(String.format("Média de %.2f€ diários. Gasto", mediaDiaria), colunaTexto, y, paint);
|
||||
canvas.drawText(String.format("final estimado: %.2f€", previsao), colunaTexto, y + 15, paint);
|
||||
|
||||
// 4. Parecer Final
|
||||
y += 35;
|
||||
paint.setFakeBoldText(true);
|
||||
canvas.drawText("Parecer Final:", colunaTitulos, y, paint);
|
||||
paint.setFakeBoldText(false);
|
||||
|
||||
if (previsao > somaReceitas && somaReceitas > 0) {
|
||||
paint.setColor(Color.parseColor("#C53030")); // Vermelho
|
||||
canvas.drawText("ALTO RISCO: Mantendo esta tendência,", colunaTexto, y, paint);
|
||||
canvas.drawText("irá fechar o mês com saldo negativo.", colunaTexto, y + 15, paint);
|
||||
} else if (somaReceitas == 0) {
|
||||
paint.setColor(Color.DKGRAY);
|
||||
canvas.drawText("A aguardar entrada de receitas para", colunaTexto, y, paint);
|
||||
canvas.drawText("efetuar um diagnóstico final.", colunaTexto, y + 15, paint);
|
||||
} else {
|
||||
paint.setColor(Color.parseColor("#2F855A")); // Verde
|
||||
canvas.drawText("ESTÁVEL: A sua projeção indica que", colunaTexto, y, paint);
|
||||
canvas.drawText("fechará o mês com lucro.", colunaTexto, y + 15, paint);
|
||||
}
|
||||
// -------------------------------------------------------------------------------
|
||||
|
||||
desenharRodape(canvas, paint, numeroPagina);
|
||||
pdf.finishPage(pagina);
|
||||
|
||||
String nomeFicheiro = "Relatorio_Consultoria_Finzora_" + System.currentTimeMillis() + ".pdf";
|
||||
File arquivoPdf = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), nomeFicheiro);
|
||||
|
||||
try {
|
||||
pdf.writeTo(new FileOutputStream(arquivoPdf));
|
||||
Toast.makeText(this, "Relatório de Consultoria gerado com sucesso! 📄📈", Toast.LENGTH_LONG).show();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
pdf.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void desenharRodape(Canvas canvas, Paint paint, int paginaActual) {
|
||||
paint.setColor(Color.GRAY);
|
||||
paint.setTextSize(10f);
|
||||
paint.setFakeBoldText(false);
|
||||
canvas.drawLine(40, 800, 555, 800, paint);
|
||||
|
||||
String dataGeracao = java.text.DateFormat.getDateTimeInstance().format(new java.util.Date());
|
||||
canvas.drawText("Emitido por Finzora Consultoria Financeira em: " + dataGeracao, 40, 815, paint);
|
||||
canvas.drawText("Página " + paginaActual, 510, 815, paint);
|
||||
}
|
||||
|
||||
private void carregarFotoPerfil() {
|
||||
ImageView imgLogoPerfil = findViewById(R.id.imgLogo);
|
||||
if (imgLogoPerfil == null) return;
|
||||
|
||||
SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
|
||||
String caminhoFoto = prefs.getString("foto_usuario_path", null);
|
||||
|
||||
if (caminhoFoto != null) {
|
||||
File arquivoFoto = new File(caminhoFoto);
|
||||
if (arquivoFoto.exists()) {
|
||||
imgLogoPerfil.setPadding(0, 0, 0, 0);
|
||||
imgLogoPerfil.setImageTintList(null);
|
||||
|
||||
Glide.with(this)
|
||||
.load(arquivoFoto)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.circleCrop()
|
||||
.into(imgLogoPerfil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
atualizarCartoes();
|
||||
|
||||
carregarFotoPerfil();
|
||||
SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
|
||||
String nome = prefs.getString("nome_usuario", "Investidor");
|
||||
if (tvNomeUsuario != null) {
|
||||
tvNomeUsuario.setText("Olá, " + nome);
|
||||
}
|
||||
if (tvNomeUsuario != null) tvNomeUsuario.setText("Olá, " + nome);
|
||||
}
|
||||
|
||||
private void configurarAbas() {
|
||||
ViewPagerAdapter adapter = new ViewPagerAdapter(this);
|
||||
viewPager.setAdapter(adapter);
|
||||
|
||||
new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> {
|
||||
switch (position) {
|
||||
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;
|
||||
case 3: tab.setText("Objetivos"); break;
|
||||
case 4: 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();
|
||||
@@ -138,44 +579,32 @@ public class MainActivity extends AppCompatActivity {
|
||||
try {
|
||||
String jsonResposta = response.body().string();
|
||||
JSONArray jsonArray = new JSONArray(jsonResposta);
|
||||
listaTransacoesGlobal = jsonArray;
|
||||
|
||||
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;
|
||||
}
|
||||
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));
|
||||
}
|
||||
tvSaldoGeral.setTextColor(saldo < 0 ? Color.parseColor("#FF1744") : getResources().getColor(R.color.texto_principal));
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
116
app/src/main/java/com/example/finzora/NovaPasswordActivity.java
Normal file
116
app/src/main/java/com/example/finzora/NovaPasswordActivity.java
Normal file
@@ -0,0 +1,116 @@
|
||||
package com.example.finzora;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.widget.Button;
|
||||
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 NovaPasswordActivity extends AppCompatActivity {
|
||||
|
||||
private TextInputEditText editNovaPass, editConfirmaNovaPass;
|
||||
private Button btnGuardar;
|
||||
private String accessTokenParaRecuperar = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_nova_password);
|
||||
|
||||
editNovaPass = findViewById(R.id.editNovaPass);
|
||||
editConfirmaNovaPass = findViewById(R.id.editConfirmaNovaPass);
|
||||
btnGuardar = findViewById(R.id.btnGuardarNovaPass);
|
||||
|
||||
// ⚠️ O RADAR EM AÇÃO: Tentar ler o link mágico com que a app foi aberta
|
||||
Uri uri = getIntent().getData();
|
||||
if (uri != null && uri.getFragment() != null) {
|
||||
// O Supabase manda o token no "fragment" do link (depois do #)
|
||||
String fragmento = uri.getFragment();
|
||||
String[] partes = fragmento.split("&");
|
||||
for (String parte : partes) {
|
||||
if (parte.startsWith("access_token=")) {
|
||||
accessTokenParaRecuperar = parte.substring("access_token=".length());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (accessTokenParaRecuperar == null) {
|
||||
Toast.makeText(this, "Erro: Link mágico inválido ou expirado.", Toast.LENGTH_LONG).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
btnGuardar.setOnClickListener(v -> validarEAtualizarPassword());
|
||||
}
|
||||
|
||||
private void validarEAtualizarPassword() {
|
||||
String pass1 = editNovaPass.getText().toString().trim();
|
||||
String pass2 = editConfirmaNovaPass.getText().toString().trim();
|
||||
|
||||
if (TextUtils.isEmpty(pass1) || pass1.length() < 6) {
|
||||
editNovaPass.setError("Mínimo de 6 caracteres.");
|
||||
return;
|
||||
}
|
||||
if (!pass1.equals(pass2)) {
|
||||
editConfirmaNovaPass.setError("As senhas não coincidem.");
|
||||
return;
|
||||
}
|
||||
|
||||
btnGuardar.setEnabled(false);
|
||||
btnGuardar.setText("A GUARDAR...");
|
||||
|
||||
// ☁️ Enviar a nova password para a Nuvem
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
String json = "{\"password\":\"" + pass1 + "\"}";
|
||||
RequestBody body = RequestBody.create(json, MediaType.parse("application/json; charset=utf-8"));
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/auth/v1/user")
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
// Usamos o token mágico que apanhamos do email para provar quem somos!
|
||||
.addHeader("Authorization", "Bearer " + accessTokenParaRecuperar)
|
||||
.put(body) // Para atualizar os dados do utilizador usa-se PUT
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
runOnUiThread(() -> {
|
||||
Toast.makeText(NovaPasswordActivity.this, "Erro de rede!", Toast.LENGTH_SHORT).show();
|
||||
btnGuardar.setEnabled(true);
|
||||
btnGuardar.setText("GUARDAR PALAVRA-PASSE");
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
runOnUiThread(() -> {
|
||||
if (response.isSuccessful()) {
|
||||
Toast.makeText(NovaPasswordActivity.this, "Password atualizada com sucesso! 🎉", Toast.LENGTH_LONG).show();
|
||||
// Volta para o Ecrã de Login para entrar com a nova pass
|
||||
startActivity(new Intent(NovaPasswordActivity.this, LoginActivity.class));
|
||||
finish();
|
||||
} else {
|
||||
Toast.makeText(NovaPasswordActivity.this, "Erro ao atualizar. Tenta pedir novo link.", Toast.LENGTH_LONG).show();
|
||||
btnGuardar.setEnabled(true);
|
||||
btnGuardar.setText("GUARDAR PALAVRA-PASSE");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
17
app/src/main/java/com/example/finzora/Objetivo.java
Normal file
17
app/src/main/java/com/example/finzora/Objetivo.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package com.example.finzora;
|
||||
|
||||
public class Objetivo {
|
||||
private String id;
|
||||
private String nome;
|
||||
private float valorAlvo;
|
||||
|
||||
public Objetivo(String id, String nome, float valorAlvo) {
|
||||
this.id = id;
|
||||
this.nome = nome;
|
||||
this.valorAlvo = valorAlvo;
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
public String getNome() { return nome; }
|
||||
public float getValorAlvo() { return valorAlvo; }
|
||||
}
|
||||
119
app/src/main/java/com/example/finzora/ObjetivosAdapter.java
Normal file
119
app/src/main/java/com/example/finzora/ObjetivosAdapter.java
Normal file
@@ -0,0 +1,119 @@
|
||||
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.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.cardview.widget.CardView;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import java.util.List;
|
||||
|
||||
public class ObjetivosAdapter extends RecyclerView.Adapter<ObjetivosAdapter.ObjetivosViewHolder> {
|
||||
|
||||
private List<Objetivo> listaObjetivos;
|
||||
private ObjetivosFragment fragment;
|
||||
private float saldoAtual;
|
||||
|
||||
public ObjetivosAdapter(List<Objetivo> lista, float saldoAtual, ObjetivosFragment fragment) {
|
||||
this.listaObjetivos = lista;
|
||||
this.saldoAtual = saldoAtual;
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ObjetivosViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_objetivo, parent, false);
|
||||
return new ObjetivosViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull ObjetivosViewHolder holder, int position) {
|
||||
Objetivo item = listaObjetivos.get(position);
|
||||
|
||||
int percentagem = 0;
|
||||
float valorGuardado = 0;
|
||||
|
||||
if (saldoAtual > 0) {
|
||||
valorGuardado = saldoAtual;
|
||||
if (valorGuardado >= item.getValorAlvo()) {
|
||||
valorGuardado = item.getValorAlvo();
|
||||
percentagem = 100;
|
||||
} else {
|
||||
percentagem = (int) ((valorGuardado / item.getValorAlvo()) * 100);
|
||||
}
|
||||
}
|
||||
|
||||
holder.tvNome.setText(item.getNome());
|
||||
holder.progress.setProgress(percentagem);
|
||||
holder.tvPercentagem.setText(percentagem + "%");
|
||||
|
||||
// 🏆 A MAGIA DA CELEBRAÇÃO QUANDO CHEGAS AOS 100%
|
||||
if (percentagem == 100) {
|
||||
holder.tvPercentagem.setTextColor(Color.parseColor("#00E676"));
|
||||
holder.progress.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#00E676")));
|
||||
holder.imgIcone.setColorFilter(Color.parseColor("#00E676"));
|
||||
|
||||
holder.cardObjetivo.setCardBackgroundColor(Color.parseColor("#1A00E676"));
|
||||
|
||||
holder.tvValores.setText("🎉 Parabéns! Já podes comprar a tua conquista.");
|
||||
holder.tvValores.setTextColor(Color.parseColor("#00E676"));
|
||||
|
||||
holder.btnEditar.setVisibility(View.GONE);
|
||||
|
||||
} else {
|
||||
holder.tvPercentagem.setTextColor(Color.parseColor("#00B8D4"));
|
||||
holder.progress.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#00B8D4")));
|
||||
holder.imgIcone.setColorFilter(Color.parseColor("#00B8D4"));
|
||||
|
||||
holder.cardObjetivo.setCardBackgroundColor(Color.TRANSPARENT);
|
||||
holder.tvValores.setText(String.format("Guardado: € %.2f / Alvo: € %.2f", valorGuardado, item.getValorAlvo()));
|
||||
holder.tvValores.setTextColor(Color.GRAY);
|
||||
|
||||
holder.btnEditar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
// ✏️ Ação do NOVO Botão Editar AGORA A FUNCIONAR
|
||||
holder.btnEditar.setOnClickListener(v -> {
|
||||
if (fragment != null) {
|
||||
fragment.editarObjetivo(item); // CHAMA A JANELA DE EDIÇÃO!
|
||||
}
|
||||
});
|
||||
|
||||
// 🗑️ Botão Eliminar
|
||||
holder.btnEliminar.setOnClickListener(v -> {
|
||||
if (fragment != null) {
|
||||
fragment.confirmarExclusaoObjetivo(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return listaObjetivos.size();
|
||||
}
|
||||
|
||||
static class ObjetivosViewHolder extends RecyclerView.ViewHolder {
|
||||
CardView cardObjetivo;
|
||||
TextView tvNome, tvValores, tvPercentagem;
|
||||
ProgressBar progress;
|
||||
ImageView btnEliminar, btnEditar, imgIcone;
|
||||
|
||||
public ObjetivosViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
cardObjetivo = itemView.findViewById(R.id.cardObjetivo);
|
||||
tvNome = itemView.findViewById(R.id.tvNomeObjetivo);
|
||||
tvValores = itemView.findViewById(R.id.tvValoresObjetivo);
|
||||
tvPercentagem = itemView.findViewById(R.id.tvPercentagemObjetivo);
|
||||
progress = itemView.findViewById(R.id.progressObjetivo);
|
||||
btnEliminar = itemView.findViewById(R.id.btnEliminarObjetivo);
|
||||
btnEditar = itemView.findViewById(R.id.btnEditarObjetivo);
|
||||
imgIcone = itemView.findViewById(R.id.imgIconeObjetivo);
|
||||
}
|
||||
}
|
||||
}
|
||||
326
app/src/main/java/com/example/finzora/ObjetivosFragment.java
Normal file
326
app/src/main/java/com/example/finzora/ObjetivosFragment.java
Normal file
@@ -0,0 +1,326 @@
|
||||
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.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
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 com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
|
||||
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.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class ObjetivosFragment extends Fragment {
|
||||
|
||||
private RecyclerView recyclerObjetivos;
|
||||
private ObjetivosAdapter adapter;
|
||||
private View layoutVazio;
|
||||
private float saldoAtualCalculado = 0f;
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_objetivos, container, false);
|
||||
|
||||
recyclerObjetivos = view.findViewById(R.id.recyclerObjetivos);
|
||||
recyclerObjetivos.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
layoutVazio = view.findViewById(R.id.layoutObjetivosVazios);
|
||||
|
||||
FloatingActionButton fabAdicionar = view.findViewById(R.id.fabAdicionarObjetivo);
|
||||
fabAdicionar.setOnClickListener(v -> mostrarDialogoAdicionar());
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
calcularSaldoECarregarObjetivos();
|
||||
}
|
||||
|
||||
// 🧠 PRIMEIRO: Calculamos o Saldo Total (Receitas - Despesas)
|
||||
private void calcularSaldoECarregarObjetivos() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("DadosUtilizador", Context.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 {
|
||||
JSONArray jsonArray = new JSONArray(response.body().string());
|
||||
float receitas = 0, despesas = 0;
|
||||
|
||||
for (int i = 0; i < jsonArray.length(); i++) {
|
||||
JSONObject obj = jsonArray.getJSONObject(i);
|
||||
if (obj.getInt("tipo") == 1) receitas += (float) obj.getDouble("valor");
|
||||
else despesas += (float) obj.getDouble("valor");
|
||||
}
|
||||
|
||||
saldoAtualCalculado = receitas - despesas;
|
||||
if(saldoAtualCalculado < 0) saldoAtualCalculado = 0;
|
||||
|
||||
carregarObjetivosDoSupabase();
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ☁️ SEGUNDO: Buscar os dados à tabela nova
|
||||
private void carregarObjetivosDoSupabase() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("DadosUtilizador", Context.MODE_PRIVATE);
|
||||
String userId = prefs.getString("user_id", null);
|
||||
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/objetivos?user_id=eq." + userId + "&order=created_at.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) {}
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
if (response.isSuccessful()) {
|
||||
try {
|
||||
List<Objetivo> lista = new ArrayList<>();
|
||||
JSONArray array = new JSONArray(response.body().string());
|
||||
|
||||
for (int i = 0; i < array.length(); i++) {
|
||||
JSONObject obj = array.getJSONObject(i);
|
||||
lista.add(new Objetivo(
|
||||
obj.getString("id"),
|
||||
obj.getString("nome_objetivo"),
|
||||
(float) obj.getDouble("valor_alvo")
|
||||
));
|
||||
}
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
adapter = new ObjetivosAdapter(lista, saldoAtualCalculado, ObjetivosFragment.this);
|
||||
recyclerObjetivos.setAdapter(adapter);
|
||||
|
||||
if (lista.isEmpty()) {
|
||||
recyclerObjetivos.setVisibility(View.GONE);
|
||||
layoutVazio.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
recyclerObjetivos.setVisibility(View.VISIBLE);
|
||||
layoutVazio.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ➕ TERCEIRO: O Pop-up (AGORA COM DESIGN PREMIUM!)
|
||||
private void mostrarDialogoAdicionar() {
|
||||
View view = LayoutInflater.from(getActivity()).inflate(R.layout.dialog_novo_objetivo, null);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setView(view);
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(0));
|
||||
}
|
||||
|
||||
EditText editNome = view.findViewById(R.id.editNomeObjetivo);
|
||||
EditText editValor = view.findViewById(R.id.editValorObjetivo);
|
||||
TextView btnCancelar = view.findViewById(R.id.btnCancelarObjetivo);
|
||||
Button btnGuardar = view.findViewById(R.id.btnGuardarObjetivo);
|
||||
|
||||
btnCancelar.setOnClickListener(v -> dialog.dismiss());
|
||||
|
||||
btnGuardar.setOnClickListener(v -> {
|
||||
String nome = editNome.getText().toString().trim();
|
||||
String valorStr = editValor.getText().toString().trim();
|
||||
|
||||
if (!nome.isEmpty() && !valorStr.isEmpty()) {
|
||||
guardarNoSupabase(nome, Float.parseFloat(valorStr));
|
||||
dialog.dismiss();
|
||||
} else {
|
||||
Toast.makeText(getActivity(), "Preenche todos os campos!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void guardarNoSupabase(String nome, float valorAlvo) {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("DadosUtilizador", Context.MODE_PRIVATE);
|
||||
String userId = prefs.getString("user_id", null);
|
||||
|
||||
try {
|
||||
JSONObject json = new JSONObject();
|
||||
json.put("user_id", userId);
|
||||
json.put("nome_objetivo", nome);
|
||||
json.put("valor_alvo", valorAlvo);
|
||||
|
||||
RequestBody body = RequestBody.create(json.toString(), MediaType.parse("application/json; charset=utf-8"));
|
||||
Request request = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/objetivos")
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("Prefer", "return=minimal")
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
new OkHttpClient().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) {
|
||||
if (response.isSuccessful() && getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> carregarObjetivosDoSupabase());
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
public void confirmarExclusaoObjetivo(Objetivo obj) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle("Eliminar Objetivo")
|
||||
.setMessage("Queres apagar o objetivo '" + obj.getNome() + "'?")
|
||||
.setPositiveButton("Sim", (dialog, which) -> {
|
||||
Request request = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/objetivos?id=eq." + obj.getId())
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.delete()
|
||||
.build();
|
||||
|
||||
new OkHttpClient().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) {
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> carregarObjetivosDoSupabase());
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.setNegativeButton("Não", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
// 🏆 A NOVA JOGADA: FUNÇÃO PARA EDITAR! (Corrigida sem o tvTituloDialogObjetivo)
|
||||
public void editarObjetivo(Objetivo objetivo) {
|
||||
if (getActivity() == null) return;
|
||||
|
||||
// Reutilizamos o design bonito do dialog de adicionar
|
||||
View view = LayoutInflater.from(getActivity()).inflate(R.layout.dialog_novo_objetivo, null);
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setView(view);
|
||||
AlertDialog dialog = builder.create();
|
||||
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(0));
|
||||
}
|
||||
|
||||
EditText editNome = view.findViewById(R.id.editNomeObjetivo);
|
||||
EditText editValor = view.findViewById(R.id.editValorObjetivo);
|
||||
TextView btnCancelar = view.findViewById(R.id.btnCancelarObjetivo);
|
||||
Button btnGuardar = view.findViewById(R.id.btnGuardarObjetivo);
|
||||
|
||||
// Preencher com os dados atuais
|
||||
editNome.setText(objetivo.getNome());
|
||||
editValor.setText(String.valueOf(objetivo.getValorAlvo()));
|
||||
btnGuardar.setText("Atualizar");
|
||||
|
||||
btnCancelar.setOnClickListener(v -> dialog.dismiss());
|
||||
|
||||
btnGuardar.setOnClickListener(v -> {
|
||||
String novoNome = editNome.getText().toString().trim();
|
||||
String valorStr = editValor.getText().toString().trim();
|
||||
|
||||
if (!novoNome.isEmpty() && !valorStr.isEmpty()) {
|
||||
double novoValor = Double.parseDouble(valorStr);
|
||||
atualizarObjetivoNoSupabase(objetivo.getId(), novoNome, novoValor);
|
||||
dialog.dismiss();
|
||||
} else {
|
||||
Toast.makeText(getActivity(), "Preenche todos os campos!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
// 🚀 ENVIA O UPDATE PARA O SUPABASE
|
||||
private void atualizarObjetivoNoSupabase(String idObjetivo, String novoNome, double novoValor) {
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
String jsonUpdate = "{\"nome_objetivo\":\"" + novoNome + "\", \"valor_alvo\":" + novoValor + "}";
|
||||
RequestBody body = RequestBody.create(jsonUpdate, MediaType.parse("application/json; charset=utf-8"));
|
||||
|
||||
Request request = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/objetivos?id=eq." + idObjetivo)
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.patch(body) // Usamos PATCH para atualizar
|
||||
.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 net ao atualizar.", Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (response.isSuccessful()) {
|
||||
Toast.makeText(getActivity(), "Objetivo atualizado! 🚀", Toast.LENGTH_SHORT).show();
|
||||
// Recarregar os objetivos na lista!
|
||||
carregarObjetivosDoSupabase();
|
||||
} else {
|
||||
Toast.makeText(getActivity(), "Erro a guardar.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ 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 TextView btnSaltar;
|
||||
private TabLayout tabLayoutIndicator;
|
||||
|
||||
@Override
|
||||
@@ -32,21 +32,28 @@ public class OnboardingActivity extends AppCompatActivity {
|
||||
// --- 2. CRIAR OS SLIDES ---
|
||||
List<OnboardingItem> lista = new ArrayList<>();
|
||||
|
||||
// SLIDE 1: Usamos a variável 'nomeRecuperado' aqui
|
||||
// SLIDE 1: Boas-vindas
|
||||
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
|
||||
R.drawable.ic_wallet
|
||||
));
|
||||
|
||||
// SLIDE 2
|
||||
// SLIDE 2: Transações
|
||||
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
|
||||
// ⚠️ SLIDE 3: A NOSSA NOVA CONTRATAÇÃO (OBJETIVOS)
|
||||
lista.add(new OnboardingItem(
|
||||
"Atinge os teus Objetivos \uD83C\uDFAF",
|
||||
"Tem um alvo a atingir? Cria cofres de poupança e vê a magia acontecer com o cálculo automático de progresso!",
|
||||
R.drawable.ic_lazer // Se tiveres um ícone mais adequado como ic_cofre podes mudar aqui
|
||||
));
|
||||
|
||||
// SLIDE 4: Inteligência Artificial
|
||||
lista.add(new OnboardingItem(
|
||||
"Inteligência Artificial",
|
||||
"Recebe dicas automáticas baseadas nos teus gastos para poupares mais todos os meses.",
|
||||
@@ -94,7 +101,6 @@ public class OnboardingActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@@ -5,22 +5,33 @@ import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
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<OrcamentoAdapter.OrcamentoViewHolder> {
|
||||
|
||||
// Lista de pares: Chave=Categoria, Valor=Limite
|
||||
private List<Map.Entry<String, Float>> listaOrcamentos;
|
||||
private DBHelper dbHelper;
|
||||
public static class OrcamentoItem {
|
||||
String categoria;
|
||||
float limite;
|
||||
float gasto;
|
||||
|
||||
public OrcamentoAdapter(List<Map.Entry<String, Float>> lista, DBHelper db) {
|
||||
public OrcamentoItem(String categoria, float limite, float gasto) {
|
||||
this.categoria = categoria;
|
||||
this.limite = limite;
|
||||
this.gasto = gasto;
|
||||
}
|
||||
}
|
||||
|
||||
private List<OrcamentoItem> listaOrcamentos;
|
||||
private OrcamentoFragment fragment;
|
||||
|
||||
public OrcamentoAdapter(List<OrcamentoItem> lista, OrcamentoFragment fragment) {
|
||||
this.listaOrcamentos = lista;
|
||||
this.dbHelper = db;
|
||||
this.fragment = fragment;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -32,30 +43,42 @@ public class OrcamentoAdapter extends RecyclerView.Adapter<OrcamentoAdapter.Orca
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull OrcamentoViewHolder holder, int position) {
|
||||
Map.Entry<String, Float> entry = listaOrcamentos.get(position);
|
||||
String categoria = entry.getKey();
|
||||
float limite = entry.getValue();
|
||||
OrcamentoItem item = listaOrcamentos.get(position);
|
||||
|
||||
// Calcular quanto já gastou
|
||||
float gasto = dbHelper.getGastoPorCategoria(categoria);
|
||||
float restante = limite - gasto;
|
||||
int percentagem = (int) ((gasto / limite) * 100);
|
||||
int percentagem = (int) ((item.gasto / item.limite) * 100);
|
||||
if (percentagem > 100) percentagem = 100;
|
||||
|
||||
// Atualizar Textos
|
||||
holder.tvCategoria.setText(categoria);
|
||||
holder.tvValores.setText(String.format("€ %.2f / € %.2f", gasto, limite));
|
||||
holder.tvCategoria.setText(item.categoria);
|
||||
holder.tvValores.setText(String.format("Gasto: € %.2f / Limite: € %.2f", item.gasto, item.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
|
||||
// ⚠️ A TÁTICA NOVA: Mudar a imagem conforme o texto!
|
||||
holder.imgIcone.setImageResource(obterIconeCategoria(item.categoria));
|
||||
|
||||
// Dica do Mister: Se quiseres que os ícones fiquem todos verdes para combinar com o design,
|
||||
// podes descomentar a linha abaixo tirando as duas barras (//):
|
||||
// holder.imgIcone.setColorFilter(Color.parseColor("#00E676"));
|
||||
|
||||
// A TÁTICA DO SEMÁFORO
|
||||
if (percentagem < 80) {
|
||||
holder.tvPercentagem.setText(percentagem + "%");
|
||||
holder.tvPercentagem.setTextColor(Color.parseColor("#00E676"));
|
||||
holder.progress.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#00E676")));
|
||||
} else if (percentagem >= 80 && percentagem < 100) {
|
||||
holder.tvPercentagem.setText(percentagem + "%");
|
||||
holder.tvPercentagem.setTextColor(Color.parseColor("#ECC94B"));
|
||||
holder.progress.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#ECC94B")));
|
||||
} 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
|
||||
holder.tvPercentagem.setText("100%+");
|
||||
holder.tvPercentagem.setTextColor(Color.parseColor("#FF1744"));
|
||||
holder.progress.setProgressTintList(ColorStateList.valueOf(Color.parseColor("#FF1744")));
|
||||
}
|
||||
|
||||
holder.btnEliminar.setOnClickListener(v -> {
|
||||
if (fragment != null) {
|
||||
fragment.confirmarExclusaoOrcamento(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,16 +86,39 @@ public class OrcamentoAdapter extends RecyclerView.Adapter<OrcamentoAdapter.Orca
|
||||
return listaOrcamentos.size();
|
||||
}
|
||||
|
||||
// ⚠️ O NOSSO RADAR DE CATEGORIAS COMPLETO!
|
||||
private int obterIconeCategoria(String cat) {
|
||||
if (cat == null) return R.drawable.ic_outros;
|
||||
|
||||
switch (cat.toLowerCase()) {
|
||||
case "alimentação": return R.drawable.ic_alimentacao;
|
||||
case "contas": return R.drawable.ic_contas;
|
||||
case "transportes": return R.drawable.ic_transportes;
|
||||
case "compras": return R.drawable.ic_compras;
|
||||
case "lazer": return R.drawable.ic_lazer;
|
||||
case "educação": return R.drawable.ic_educacao;
|
||||
case "saúde": return R.drawable.ic_saude;
|
||||
default: return R.drawable.ic_outros;
|
||||
}
|
||||
}
|
||||
|
||||
static class OrcamentoViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvCategoria, tvValores, tvRestante;
|
||||
TextView tvCategoria, tvValores, tvPercentagem;
|
||||
ProgressBar progress;
|
||||
ImageView btnEliminar;
|
||||
ImageView imgIcone;
|
||||
|
||||
public OrcamentoViewHolder(@NonNull View itemView) {
|
||||
super(itemView);
|
||||
tvCategoria = itemView.findViewById(R.id.tvCatOrcamento);
|
||||
tvCategoria = itemView.findViewById(R.id.tvCategoriaOrcamento);
|
||||
tvValores = itemView.findViewById(R.id.tvValoresOrcamento);
|
||||
tvRestante = itemView.findViewById(R.id.tvRestante);
|
||||
tvPercentagem = itemView.findViewById(R.id.tvPercentagemOrcamento);
|
||||
progress = itemView.findViewById(R.id.progressOrcamento);
|
||||
btnEliminar = itemView.findViewById(R.id.btnEliminarOrcamento);
|
||||
|
||||
// ⚠️ ATENÇÃO CAPITÃO: Verifica se "imgIconeOrcamento" é mesmo o ID da imagem
|
||||
// no teu ficheiro R.layout.item_orcamento! Se der erro, é só corrigir o nome aqui.
|
||||
imgIcone = itemView.findViewById(R.id.imgIconeOrcamento);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +1,69 @@
|
||||
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.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
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.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
public class OrcamentoFragment extends Fragment {
|
||||
|
||||
private Spinner spinnerCategoria;
|
||||
private TextView txtCategoria;
|
||||
private String categoriaSelecionada = "";
|
||||
// ⚠️ COMPRAS ADICIONADAS AO PLANTEL!
|
||||
private final String[] categorias = {"Alimentação", "Contas", "Transportes", "Compras", "Lazer", "Educação", "Saúde", "Salário", "Mesada", "Prémios", "Outros"};
|
||||
|
||||
private EditText editLimite;
|
||||
private Button btnSalvar;
|
||||
private RecyclerView recyclerOrcamentos;
|
||||
private DBHelper dbHelper;
|
||||
private View layoutEstadoVazio;
|
||||
|
||||
@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);
|
||||
txtCategoria = view.findViewById(R.id.txtCategoriaOrcamento);
|
||||
editLimite = view.findViewById(R.id.editLimiteOrcamento);
|
||||
btnSalvar = view.findViewById(R.id.btnDefinirOrcamento);
|
||||
recyclerOrcamentos = view.findViewById(R.id.recyclerOrcamentos);
|
||||
layoutEstadoVazio = view.findViewById(R.id.layoutEstadoVazioOrcamento);
|
||||
|
||||
// Configura a lista
|
||||
recyclerOrcamentos.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
|
||||
// Inicia a base de dados
|
||||
dbHelper = new DBHelper(getActivity());
|
||||
txtCategoria.setOnClickListener(v -> mostrarDialogCategorias());
|
||||
|
||||
// Configurações iniciais
|
||||
configurarSpinner();
|
||||
btnSalvar.setOnClickListener(v -> salvarOrcamentoNaNuvem());
|
||||
|
||||
// Ação do botão
|
||||
btnSalvar.setOnClickListener(v -> salvarOrcamento());
|
||||
|
||||
// Mostrar dados
|
||||
carregarOrcamentos();
|
||||
carregarOrcamentosDaNuvem();
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -60,39 +71,242 @@ public class OrcamentoFragment extends Fragment {
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
carregarOrcamentos();
|
||||
carregarOrcamentosDaNuvem();
|
||||
}
|
||||
|
||||
// Método corrigido (havia um duplicado antes)
|
||||
private void configurarSpinner() {
|
||||
String[] categorias = {"Alimentação", "Transporte", "Salário", "Lazer", "Contas", "Saúde", "Outros"};
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(), R.layout.item_dropdown, categorias);
|
||||
spinnerCategoria.setAdapter(adapter);
|
||||
private void mostrarDialogCategorias() {
|
||||
// 1. Criar um Dialog "em branco"
|
||||
android.app.Dialog dialog = new android.app.Dialog(getActivity());
|
||||
|
||||
// 2. MAGIA: Dizer-lhe para usar o TEU design premium!
|
||||
dialog.setContentView(R.layout.dialog_categorias);
|
||||
|
||||
// 3. Fazer o fundo padrão ficar transparente para o teu CardView arredondado brilhar
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(android.graphics.Color.TRANSPARENT));
|
||||
dialog.getWindow().setLayout(android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
// 4. Ligar o botão de Cancelar do teu XML
|
||||
TextView btnCancelar = dialog.findViewById(R.id.btnCancelarCategoria);
|
||||
btnCancelar.setOnClickListener(v -> dialog.dismiss());
|
||||
|
||||
// 5. Injetar as categorias no teu LinearLayout vazio (dentro do ScrollView)
|
||||
android.widget.LinearLayout container = dialog.findViewById(R.id.containerCategorias);
|
||||
|
||||
// 🛑 A CORREÇÃO DA TINTA: Usar a nossa própria cor texto_principal!
|
||||
int corTexto = androidx.core.content.ContextCompat.getColor(getActivity(), R.color.texto_principal);
|
||||
|
||||
android.util.TypedValue clickEffect = new android.util.TypedValue();
|
||||
getActivity().getTheme().resolveAttribute(android.R.attr.selectableItemBackground, clickEffect, true);
|
||||
|
||||
for (String categoria : categorias) {
|
||||
TextView tvCat = new TextView(getActivity());
|
||||
tvCat.setText(categoria);
|
||||
tvCat.setTextSize(18f);
|
||||
tvCat.setPadding(32, 32, 32, 32); // Espaçamento elegante
|
||||
tvCat.setTextColor(corTexto);
|
||||
|
||||
// Efeito visual seguro sem fechar a app!
|
||||
tvCat.setBackgroundResource(clickEffect.resourceId);
|
||||
tvCat.setClickable(true);
|
||||
|
||||
// O que acontece quando clicas numa categoria
|
||||
tvCat.setOnClickListener(v -> {
|
||||
categoriaSelecionada = categoria;
|
||||
txtCategoria.setText(categoriaSelecionada);
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
container.addView(tvCat);
|
||||
}
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void salvarOrcamento() {
|
||||
String limiteStr = editLimite.getText().toString();
|
||||
private void salvarOrcamentoNaNuvem() {
|
||||
String limiteStr = editLimite.getText().toString().replace(",", ".");
|
||||
|
||||
if (categoriaSelecionada.isEmpty()) {
|
||||
Toast.makeText(getActivity(), "Por favor, escolhe uma categoria!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (limiteStr.isEmpty()) {
|
||||
editLimite.setError("Define um valor");
|
||||
return;
|
||||
}
|
||||
|
||||
String categoria = spinnerCategoria.getSelectedItem().toString();
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("DadosUtilizador", Context.MODE_PRIVATE);
|
||||
String userId = prefs.getString("user_id", null);
|
||||
if (userId == null) return;
|
||||
|
||||
float limite = Float.parseFloat(limiteStr);
|
||||
|
||||
dbHelper.salvarOrcamento(categoria, limite);
|
||||
btnSalvar.setEnabled(false);
|
||||
btnSalvar.setText("A GRAVAR...");
|
||||
|
||||
Toast.makeText(getActivity(), "Orçamento definido!", Toast.LENGTH_SHORT).show();
|
||||
editLimite.setText(""); // Limpar campo
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
String json = "{\"user_id\":\"" + userId + "\", \"categoria\":\"" + categoriaSelecionada + "\", \"valor_limite\":" + limite + "}";
|
||||
RequestBody body = RequestBody.create(json, MediaType.parse("application/json; charset=utf-8"));
|
||||
|
||||
carregarOrcamentos(); // Atualizar lista
|
||||
Request request = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/orcamentos?on_conflict=user_id,categoria")
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Content-Type", "application/json")
|
||||
.addHeader("Prefer", "resolution=merge-duplicates")
|
||||
.post(body)
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
if(getActivity() != null) getActivity().runOnUiThread(() -> {
|
||||
btnSalvar.setEnabled(true); btnSalvar.setText("Definir Orçamento");
|
||||
Toast.makeText(getActivity(), "Erro de internet!", Toast.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
if(getActivity() != null) getActivity().runOnUiThread(() -> {
|
||||
btnSalvar.setEnabled(true); btnSalvar.setText("Definir Orçamento");
|
||||
if (response.isSuccessful()) {
|
||||
Toast.makeText(getActivity(), "Orçamento guardado nas nuvens! ☁️", Toast.LENGTH_SHORT).show();
|
||||
editLimite.setText("");
|
||||
categoriaSelecionada = "";
|
||||
txtCategoria.setText("Selecionar Categoria...");
|
||||
carregarOrcamentosDaNuvem();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void carregarOrcamentos() {
|
||||
Map<String, Float> orcamentosMap = dbHelper.getOrcamentosDefinidos();
|
||||
List<Map.Entry<String, Float>> lista = new ArrayList<>(orcamentosMap.entrySet());
|
||||
private void carregarOrcamentosDaNuvem() {
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("DadosUtilizador", Context.MODE_PRIVATE);
|
||||
String userId = prefs.getString("user_id", null);
|
||||
if (userId == null) return;
|
||||
|
||||
OrcamentoAdapter adapter = new OrcamentoAdapter(lista, dbHelper);
|
||||
recyclerOrcamentos.setAdapter(adapter);
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
|
||||
Request reqOrcamentos = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/orcamentos?user_id=eq." + userId)
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.build();
|
||||
|
||||
client.newCall(reqOrcamentos).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()) return;
|
||||
|
||||
try {
|
||||
String jsonOrcamentos = response.body().string();
|
||||
JSONArray arrOrcamentos = new JSONArray(jsonOrcamentos);
|
||||
Map<String, Float> mapaLimites = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < arrOrcamentos.length(); i++) {
|
||||
JSONObject obj = arrOrcamentos.getJSONObject(i);
|
||||
mapaLimites.put(obj.getString("categoria"), (float) obj.getDouble("valor_limite"));
|
||||
}
|
||||
|
||||
Request reqDespesas = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/transacoes?user_id=eq." + userId + "&tipo=eq.2")
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.build();
|
||||
|
||||
client.newCall(reqDespesas).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {}
|
||||
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response2) throws IOException {
|
||||
if (!response2.isSuccessful()) return;
|
||||
|
||||
try {
|
||||
String jsonDespesas = response2.body().string();
|
||||
JSONArray arrDespesas = new JSONArray(jsonDespesas);
|
||||
Map<String, Float> mapaGastos = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < arrDespesas.length(); i++) {
|
||||
JSONObject obj = arrDespesas.getJSONObject(i);
|
||||
String cat = obj.getString("categoria");
|
||||
float valor = (float) obj.getDouble("valor");
|
||||
float atual = mapaGastos.containsKey(cat) ? mapaGastos.get(cat) : 0f;
|
||||
mapaGastos.put(cat, atual + valor);
|
||||
}
|
||||
|
||||
List<OrcamentoAdapter.OrcamentoItem> listaFinal = new ArrayList<>();
|
||||
for (Map.Entry<String, Float> entry : mapaLimites.entrySet()) {
|
||||
String categoria = entry.getKey();
|
||||
float limite = entry.getValue();
|
||||
float gasto = mapaGastos.containsKey(categoria) ? mapaGastos.get(categoria) : 0f;
|
||||
listaFinal.add(new OrcamentoAdapter.OrcamentoItem(categoria, limite, gasto));
|
||||
}
|
||||
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
OrcamentoAdapter adapter = new OrcamentoAdapter(listaFinal, OrcamentoFragment.this);
|
||||
recyclerOrcamentos.setAdapter(adapter);
|
||||
|
||||
if (listaFinal.isEmpty()) {
|
||||
recyclerOrcamentos.setVisibility(View.GONE);
|
||||
layoutEstadoVazio.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
recyclerOrcamentos.setVisibility(View.VISIBLE);
|
||||
layoutEstadoVazio.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void confirmarExclusaoOrcamento(OrcamentoAdapter.OrcamentoItem item) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle("Eliminar Orçamento")
|
||||
.setMessage("Queres apagar o limite de orçamento para " + item.categoria + "?")
|
||||
.setPositiveButton("Sim", (dialog, which) -> {
|
||||
|
||||
SharedPreferences prefs = getActivity().getSharedPreferences("DadosUtilizador", Context.MODE_PRIVATE);
|
||||
String userId = prefs.getString("user_id", "");
|
||||
|
||||
OkHttpClient client = new OkHttpClient();
|
||||
Request request = new Request.Builder()
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/orcamentos?user_id=eq." + userId + "&categoria=eq." + item.categoria)
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.delete()
|
||||
.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(), "Orçamento apagado!", Toast.LENGTH_SHORT).show();
|
||||
carregarOrcamentosDaNuvem();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
.setNegativeButton("Não", null)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
@@ -35,10 +35,7 @@ public class RegisterActivity extends AppCompatActivity {
|
||||
|
||||
inicializarComponentes();
|
||||
|
||||
// Voltar para o Login
|
||||
txtLogin.setOnClickListener(v -> finish());
|
||||
|
||||
// Clicar em Criar Conta
|
||||
btnCriarConta.setOnClickListener(v -> validarDados());
|
||||
}
|
||||
|
||||
@@ -78,7 +75,6 @@ public class RegisterActivity extends AppCompatActivity {
|
||||
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"));
|
||||
|
||||
@@ -93,7 +89,7 @@ public class RegisterActivity extends AppCompatActivity {
|
||||
@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();
|
||||
Toast.makeText(RegisterActivity.this, "Erro de rede!", Toast.LENGTH_SHORT).show();
|
||||
resetBotao();
|
||||
});
|
||||
}
|
||||
@@ -105,34 +101,49 @@ public class RegisterActivity extends AppCompatActivity {
|
||||
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
|
||||
// ⚠️ JOGADA DE MESTRE: Tentar ler o ID de forma mais flexível
|
||||
String userId = "";
|
||||
if (jsonResponse.has("user")) {
|
||||
userId = jsonResponse.getJSONObject("user").getString("id");
|
||||
} else if (jsonResponse.has("id")) {
|
||||
userId = jsonResponse.getString("id");
|
||||
}
|
||||
|
||||
// Guardar dados e marcar como logado
|
||||
SharedPreferences prefs = getSharedPreferences("DadosUtilizador", MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
editor.putString("nome_usuario", nome);
|
||||
editor.putString("user_id", userId);
|
||||
editor.putString("email_usuario", email);
|
||||
editor.putBoolean("is_logged_in", true);
|
||||
editor.apply();
|
||||
|
||||
Toast.makeText(RegisterActivity.this, "Sucesso! Bem-vindo à nuvem.", Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(RegisterActivity.this, "Bem-vindo à Finzora! Verifica o teu email! 📩", Toast.LENGTH_LONG).show();
|
||||
|
||||
// Avançar para o Onboarding
|
||||
// Avançar sem olhar para trás
|
||||
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();
|
||||
// Se chegámos aqui, a conta foi criada (isSuccessful), mas o JSON era estranho
|
||||
// Avançamos na mesma para não prender o utilizador
|
||||
startActivity(new Intent(RegisterActivity.this, OnboardingActivity.class));
|
||||
finish();
|
||||
}
|
||||
} 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();
|
||||
String msg = erroObj.optString("msg", "Erro no registo");
|
||||
|
||||
if (msg.contains("already registered")) {
|
||||
Toast.makeText(RegisterActivity.this, "Este email já tem conta!", Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(RegisterActivity.this, "Erro: " + msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Toast.makeText(RegisterActivity.this, "Erro desconhecido: " + responseData, Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(RegisterActivity.this, "Erro na nuvem", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
resetBotao();
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package com.example.finzora;
|
||||
|
||||
public class Transacao {
|
||||
private int id;
|
||||
private String id;
|
||||
private float valor;
|
||||
private String categoria;
|
||||
private int tipo; // 1 = Receita, 2 = Despesa
|
||||
private String data;
|
||||
// ⚠️ NOVA JOGADA: Adicionada a descrição!
|
||||
private String descricao;
|
||||
|
||||
// --- CONSTRUTOR 1: Para ler da Base de Dados (TEM ID) ---
|
||||
public Transacao(int id, float valor, String categoria, int tipo, String data) {
|
||||
public Transacao(String id, float valor, String categoria, int tipo, String data) {
|
||||
this.id = id;
|
||||
this.valor = valor;
|
||||
this.categoria = categoria;
|
||||
@@ -24,15 +26,19 @@ public class Transacao {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
// --- GETTERS (Para os outros lerem) ---
|
||||
public int getId() { return id; }
|
||||
// --- GETTERS ---
|
||||
public String getId() { return id; }
|
||||
public float getValor() { return valor; }
|
||||
public String getCategoria() { return categoria; }
|
||||
public int getTipo() { return tipo; }
|
||||
public String getData() { return data; }
|
||||
public String getDescricao() { return descricao; } // Getter da descrição
|
||||
|
||||
// --- SETTERS (Opcional, mas evita erros se algum código antigo os chamar) ---
|
||||
public void setId(int id) { this.id = id; }
|
||||
// --- SETTERS ---
|
||||
public void setId(String id) { this.id = id; }
|
||||
public void setValor(float valor) { this.valor = valor; }
|
||||
public void setCategoria(String categoria) { this.categoria = categoria; }
|
||||
public void setTipo(int tipo) { this.tipo = tipo; }
|
||||
public void setData(String data) { this.data = data; }
|
||||
public void setDescricao(String descricao) { this.descricao = descricao; } // Setter da descrição
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.example.finzora;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
@@ -9,71 +8,165 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class TransacoesAdapter extends RecyclerView.Adapter<TransacoesAdapter.MyViewHolder> {
|
||||
public class TransacoesAdapter extends RecyclerView.Adapter<TransacoesAdapter.TransacaoViewHolder> {
|
||||
|
||||
private List<Transacao> listaTransacoes;
|
||||
private Context context;
|
||||
private TransacoesFragment fragment;
|
||||
private List<Transacao> listaOriginal;
|
||||
private List<Transacao> listaVisivel;
|
||||
private OnTransacaoClickListener listener;
|
||||
|
||||
// O teu construtor mantém-se igual!
|
||||
public TransacoesAdapter(List<Transacao> lista, Context context, TransacoesFragment fragment) {
|
||||
this.listaTransacoes = lista;
|
||||
this.context = context;
|
||||
this.fragment = fragment;
|
||||
public interface OnTransacaoClickListener {
|
||||
void onTransacaoClick(Transacao t);
|
||||
}
|
||||
|
||||
public TransacoesAdapter(List<Transacao> lista, OnTransacaoClickListener listener) {
|
||||
this.listaOriginal = new ArrayList<>(lista);
|
||||
this.listaVisivel = lista;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@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);
|
||||
public TransacaoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_transacao, parent, false);
|
||||
return new TransacaoViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
|
||||
Transacao transacao = listaTransacoes.get(position);
|
||||
public void onBindViewHolder(@NonNull TransacaoViewHolder holder, int position) {
|
||||
Transacao t = listaVisivel.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);
|
||||
String desc = t.getDescricao();
|
||||
if (desc != null && !desc.trim().isEmpty() && !desc.equalsIgnoreCase("Sem descrição")) {
|
||||
holder.tvDescricao.setText(desc);
|
||||
} else {
|
||||
holder.tvDescricao.setText(t.getCategoria());
|
||||
}
|
||||
|
||||
// O botão de apagar agora chama-se btnEliminar no XML
|
||||
holder.btnEliminar.setOnClickListener(v -> {
|
||||
if (fragment != null) fragment.confirmarExclusao(transacao);
|
||||
});
|
||||
holder.tvData.setText(t.getData());
|
||||
|
||||
if (t.getTipo() == 1) { // Receita
|
||||
holder.tvValor.setText(String.format("+ %.2f €", t.getValor()));
|
||||
holder.tvValor.setTextColor(Color.parseColor("#00E676"));
|
||||
holder.imgIcone.setImageResource(R.drawable.ic_wallet);
|
||||
} else { // Despesa
|
||||
holder.tvValor.setText(String.format("- %.2f €", t.getValor()));
|
||||
holder.tvValor.setTextColor(Color.parseColor("#FF1744"));
|
||||
holder.imgIcone.setImageResource(obterIconeInteligente(t.getCategoria(), t.getDescricao()));
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener(v -> listener.onTransacaoClick(t));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return listaTransacoes.size();
|
||||
return listaVisivel.size();
|
||||
}
|
||||
|
||||
public class MyViewHolder extends RecyclerView.ViewHolder {
|
||||
// --- AQUI ESTÃO OS NOVOS NOMES DO XML ---
|
||||
TextView tvDescricao, tvValor, tvData;
|
||||
ImageView imgIcone, btnEliminar;
|
||||
// 🧠 FUNÇÃO INTELIGENTE PARA REMOVER ACENTOS (ex: transformar "água" em "agua")
|
||||
private String removerAcentos(String str) {
|
||||
if (str == null) return "";
|
||||
CharSequence unaccented = java.text.Normalizer.normalize(str, java.text.Normalizer.Form.NFD);
|
||||
return unaccented.toString().replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
|
||||
}
|
||||
|
||||
public MyViewHolder(@NonNull View itemView) {
|
||||
// 🚀 O NOVO MOTOR DE BUSCA TECNOLÓGICO
|
||||
public void filtrarLista(String textoProcura) {
|
||||
listaVisivel.clear();
|
||||
|
||||
if (textoProcura == null || textoProcura.trim().isEmpty()) {
|
||||
listaVisivel.addAll(listaOriginal);
|
||||
} else {
|
||||
// Limpamos o texto: sem espaços extra, em minúsculas e sem acentos!
|
||||
String queryLimpa = removerAcentos(textoProcura.toLowerCase().trim());
|
||||
|
||||
for (Transacao t : listaOriginal) {
|
||||
String descricao = removerAcentos(t.getDescricao() != null ? t.getDescricao().toLowerCase() : "");
|
||||
String categoria = removerAcentos(t.getCategoria() != null ? t.getCategoria().toLowerCase() : "");
|
||||
|
||||
// Transforma o valor e a data em texto para também podermos pesquisar por eles!
|
||||
String valorStr = String.valueOf(t.getValor());
|
||||
String dataStr = t.getData() != null ? t.getData() : "";
|
||||
|
||||
// 1. Pesquisa por Palavras (Ignora letras no meio das palavras)
|
||||
boolean matchDescricao = descricao.startsWith(queryLimpa) || descricao.contains(" " + queryLimpa);
|
||||
boolean matchCategoria = categoria.startsWith(queryLimpa) || categoria.contains(" " + queryLimpa);
|
||||
|
||||
// 2. Pesquisa de Matemática e Calendário
|
||||
boolean matchValor = valorStr.contains(queryLimpa);
|
||||
boolean matchData = dataStr.contains(queryLimpa);
|
||||
|
||||
// Se bater certo com a Descrição, Categoria, Valor OU Data... a transação é mostrada!
|
||||
if (matchDescricao || matchCategoria || matchValor || matchData) {
|
||||
listaVisivel.add(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public int obterIconeInteligente(String cat, String desc) {
|
||||
String frase = (cat + " " + (desc != null ? desc : "")).toLowerCase();
|
||||
|
||||
// --- EXCEÇÕES PREMIUM (Ícones Específicos que criaste) ---
|
||||
if (frase.matches(".*(café|snack).*")) return R.drawable.ic_cafe;
|
||||
if (frase.matches(".*(supermercado|pingo doce|continente|lidl).*")) return R.drawable.ic_carrinho;
|
||||
if (frase.matches(".*(renda|casa|condomínio).*")) return R.drawable.ic_casa;
|
||||
if (frase.matches(".*(internet|meo|nos|vodafone|telemóvel).*")) return R.drawable.ic_telemovel;
|
||||
if (frase.matches(".*(ginásio|fitness|yoga).*")) return R.drawable.ic_ginasio;
|
||||
|
||||
if (frase.matches(".*(compras|roupa|sapatilhas|zara|shein|amazon|aliexpress|worten|fnac|shopping|loja|perfume).*")) {
|
||||
return R.drawable.ic_compras;
|
||||
}
|
||||
|
||||
// --- GRUPOS GERAIS DA TUA LISTA ---
|
||||
if (frase.matches(".*(restaurante|almoço|jantar|pizza|burger|sushi).*")) {
|
||||
return R.drawable.ic_alimentacao;
|
||||
}
|
||||
if (frase.matches(".*(luz|edp|água|gás|seguro).*")) {
|
||||
return R.drawable.ic_contas;
|
||||
}
|
||||
if (frase.matches(".*(uber|bolt|gota|gasolina|diesel|repsol|galp|autocarro|comboio|cp|metro|oficina|estacionamento).*")) {
|
||||
return R.drawable.ic_transportes;
|
||||
}
|
||||
if (frase.matches(".*(jogo|steam|ps5|xbox|netflix|disney|spotify|cinema|concerto|bar|festa|estádio|futebol).*")) {
|
||||
return R.drawable.ic_lazer;
|
||||
}
|
||||
if (frase.matches(".*(farmácia|médico|hospital|dentista|exames).*")) {
|
||||
return R.drawable.ic_saude;
|
||||
}
|
||||
if (frase.matches(".*(escola|faculdade|curso|livro|udemy|propina|papelaria).*")) {
|
||||
return R.drawable.ic_educacao;
|
||||
}
|
||||
|
||||
switch (cat.toLowerCase()) {
|
||||
case "alimentação": return R.drawable.ic_alimentacao;
|
||||
case "contas": return R.drawable.ic_contas;
|
||||
case "transportes": return R.drawable.ic_transportes;
|
||||
case "compras": return R.drawable.ic_compras;
|
||||
case "lazer": return R.drawable.ic_lazer;
|
||||
case "educação": return R.drawable.ic_educacao;
|
||||
case "saúde": return R.drawable.ic_saude;
|
||||
case "salário":
|
||||
case "mesada":
|
||||
case "prémios": return R.drawable.ic_wallet;
|
||||
default: return R.drawable.ic_outros;
|
||||
}
|
||||
}
|
||||
|
||||
static class TransacaoViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView tvDescricao, tvData, tvValor;
|
||||
ImageView imgIcone;
|
||||
|
||||
public TransacaoViewHolder(@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,18 @@ package com.example.finzora;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -14,6 +21,8 @@ import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialog;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
@@ -31,13 +40,30 @@ public class TransacoesFragment extends Fragment {
|
||||
|
||||
private RecyclerView recyclerTransacoes;
|
||||
private TransacoesAdapter adapter;
|
||||
private View layoutEstadoVazio;
|
||||
private EditText editPesquisar;
|
||||
|
||||
@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()));
|
||||
layoutEstadoVazio = view.findViewById(R.id.layoutEstadoVazio);
|
||||
editPesquisar = view.findViewById(R.id.editPesquisar);
|
||||
|
||||
editPesquisar.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
if (adapter != null) { adapter.filtrarLista(s.toString()); }
|
||||
}
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -47,20 +73,15 @@ public class TransacoesFragment extends Fragment {
|
||||
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
|
||||
if (userId == null) return;
|
||||
|
||||
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")
|
||||
.url(SupabaseConfig.SUPABASE_URL + "/rest/v1/transacoes?user_id=eq." + userId + "&order=created_at.desc")
|
||||
.addHeader("apikey", SupabaseConfig.SUPABASE_KEY)
|
||||
.addHeader("Authorization", "Bearer " + SupabaseConfig.SUPABASE_KEY)
|
||||
.build();
|
||||
@@ -69,90 +90,158 @@ public class TransacoesFragment extends Fragment {
|
||||
@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()
|
||||
);
|
||||
getActivity().runOnUiThread(() -> Toast.makeText(getActivity(), "Erro de internet.", 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<Transacao> listaNuvem = new ArrayList<>();
|
||||
|
||||
try {
|
||||
String jsonResposta = response.body().string();
|
||||
List<Transacao> listaNuvem = new ArrayList<>();
|
||||
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");
|
||||
String id = obj.optString("id", "");
|
||||
float valor = (float) obj.optDouble("valor", 0.0);
|
||||
String categoria = obj.isNull("categoria") ? "Desconhecido" : obj.optString("categoria");
|
||||
int tipo = obj.optInt("tipo", 1);
|
||||
String data = obj.isNull("data") ? "" : obj.optString("data");
|
||||
String descricao = obj.isNull("descricao") ? "Sem descrição" : obj.optString("descricao");
|
||||
|
||||
Transacao t = new Transacao(valor, categoria, tipo, data);
|
||||
t.setId(id); // Guarda o ID verdadeiro da nuvem para podermos apagar depois!
|
||||
t.setId(id);
|
||||
t.setDescricao(descricao);
|
||||
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);
|
||||
adapter = new TransacoesAdapter(listaNuvem, transacao -> abrirCartaoDeslizante(transacao));
|
||||
recyclerTransacoes.setAdapter(adapter);
|
||||
|
||||
if (listaNuvem.isEmpty()) {
|
||||
recyclerTransacoes.setVisibility(View.GONE);
|
||||
layoutEstadoVazio.setVisibility(View.VISIBLE);
|
||||
editPesquisar.setVisibility(View.GONE);
|
||||
} else {
|
||||
recyclerTransacoes.setVisibility(View.VISIBLE);
|
||||
layoutEstadoVazio.setVisibility(View.GONE);
|
||||
editPesquisar.setVisibility(View.VISIBLE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) { e.printStackTrace(); }
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// APAGAR TRANSAÇÃO NA NUVEM
|
||||
// ====================================================================
|
||||
private void abrirCartaoDeslizante(Transacao t) {
|
||||
if (getActivity() == null) return;
|
||||
|
||||
BottomSheetDialog dialog = new BottomSheetDialog(getActivity());
|
||||
View view = getLayoutInflater().inflate(R.layout.bottom_sheet_detalhe, null);
|
||||
|
||||
ImageView imgIcone = view.findViewById(R.id.imgDetalheIcone);
|
||||
TextView tvTitulo = view.findViewById(R.id.tvDetalheTitulo);
|
||||
TextView tvValor = view.findViewById(R.id.tvDetalheValor);
|
||||
TextView tvDataHora = view.findViewById(R.id.tvDetalheDataHora);
|
||||
TextView tvCategoria = view.findViewById(R.id.tvDetalheCategoria);
|
||||
TextView tvDescricao = view.findViewById(R.id.tvDetalheDescricao);
|
||||
|
||||
imgIcone.setImageResource(adapter.obterIconeInteligente(t.getCategoria(), t.getDescricao()));
|
||||
|
||||
String desc = t.getDescricao();
|
||||
if (desc != null && !desc.trim().isEmpty() && !desc.equalsIgnoreCase("Sem descrição") && !desc.equals("null")) {
|
||||
tvTitulo.setText(desc);
|
||||
} else {
|
||||
tvTitulo.setText(t.getCategoria());
|
||||
}
|
||||
|
||||
tvDataHora.setText(t.getData());
|
||||
tvCategoria.setText(t.getCategoria());
|
||||
tvDescricao.setText(desc != null && !desc.trim().isEmpty() && !desc.equals("null") ? desc : "Sem descrição");
|
||||
|
||||
if (t.getTipo() == 1) {
|
||||
tvValor.setText(String.format("+ %.2f €", t.getValor()));
|
||||
tvValor.setTextColor(android.graphics.Color.parseColor("#00E676"));
|
||||
imgIcone.setColorFilter(android.graphics.Color.parseColor("#00E676"));
|
||||
} else {
|
||||
tvValor.setText(String.format("- %.2f €", t.getValor()));
|
||||
tvValor.setTextColor(android.graphics.Color.parseColor("#FF1744"));
|
||||
imgIcone.setColorFilter(android.graphics.Color.parseColor("#FF1744"));
|
||||
}
|
||||
|
||||
view.findViewById(R.id.btnFecharDetalhe).setOnClickListener(v -> dialog.dismiss());
|
||||
|
||||
view.findViewById(R.id.btnEditarTransacao).setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
|
||||
Intent intent = new Intent(getActivity(), AdicionarTransacaoActivity.class);
|
||||
intent.putExtra("transacao_id", t.getId());
|
||||
intent.putExtra("valor", (double) t.getValor());
|
||||
intent.putExtra("descricao", t.getDescricao());
|
||||
intent.putExtra("categoria", t.getCategoria());
|
||||
intent.putExtra("tipo", t.getTipo());
|
||||
startActivity(intent);
|
||||
});
|
||||
|
||||
view.findViewById(R.id.btnApagarTransacao).setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
confirmarExclusao(t);
|
||||
});
|
||||
|
||||
dialog.setContentView(view);
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
// 🏆 NOVA JOGADA: O Pop-up Premium para Eliminar Transações!
|
||||
public void confirmarExclusao(Transacao transacao) {
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setTitle("Eliminar Transação")
|
||||
.setMessage("Apagar " + transacao.getCategoria() + "?")
|
||||
.setPositiveButton("Sim", (dialog, which) -> {
|
||||
if (getActivity() == null) return;
|
||||
|
||||
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();
|
||||
android.app.Dialog dialog = new android.app.Dialog(getActivity());
|
||||
dialog.setContentView(R.layout.dialog_eliminar);
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {}
|
||||
if (dialog.getWindow() != null) {
|
||||
dialog.getWindow().setBackgroundDrawable(new android.graphics.drawable.ColorDrawable(android.graphics.Color.TRANSPARENT));
|
||||
dialog.getWindow().setLayout(android.view.ViewGroup.LayoutParams.MATCH_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
|
||||
@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
|
||||
// Se clicar em cancelar, fecha o pop-up
|
||||
dialog.findViewById(R.id.btnCancelarEliminar).setOnClickListener(v -> dialog.dismiss());
|
||||
|
||||
// Atualiza os cartões na MainActivity
|
||||
if (getActivity() instanceof MainActivity) {
|
||||
((MainActivity) getActivity()).atualizarCartoes();
|
||||
}
|
||||
});
|
||||
// Se clicar no botão vermelho "ELIMINAR", faz a magia no Supabase!
|
||||
dialog.findViewById(R.id.btnConfirmarEliminar).setOnClickListener(v -> {
|
||||
dialog.dismiss();
|
||||
|
||||
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()
|
||||
.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 com sucesso!", Toast.LENGTH_SHORT).show();
|
||||
carregarDadosDoSupabase();
|
||||
editPesquisar.setText("");
|
||||
if (getActivity() instanceof MainActivity) {
|
||||
((MainActivity) getActivity()).atualizarCartoes();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
})
|
||||
.setNegativeButton("Não", null)
|
||||
.show();
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,14 @@ public class ViewPagerAdapter extends FragmentStateAdapter {
|
||||
case 0: return new TransacoesFragment();
|
||||
case 1: return new OrcamentoFragment();
|
||||
case 2: return new GraficosFragment();
|
||||
case 3: return new DicasFragment();
|
||||
case 3: return new ObjetivosFragment(); // ⚠️ O NOSSO NOVO JOGADOR ESTÁ AQUI
|
||||
case 4: return new DicasFragment(); // ⚠️ Dicas passou para a posição 4
|
||||
default: return new TransacoesFragment();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return 4; // Total de 4 abas
|
||||
return 5; // ⚠️ Total atualizado para 5 abas!
|
||||
}
|
||||
}
|
||||
8
app/src/main/res/drawable/bg_bottom_sheet.xml
Normal file
8
app/src/main/res/drawable/bg_bottom_sheet.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/bg_dinamico" />
|
||||
|
||||
<corners
|
||||
android:topLeftRadius="24dp"
|
||||
android:topRightRadius="24dp" />
|
||||
</shape>
|
||||
10
app/src/main/res/drawable/bg_coach_input_rect.xml
Normal file
10
app/src/main/res/drawable/bg_coach_input_rect.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Usamos a mesma tática do fundo adaptável para o claro/escuro -->
|
||||
<solid android:color="#1A888888" />
|
||||
<corners android:radius="8dp" />
|
||||
<!-- Borda fina adaptável -->
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="?android:attr/textColorTertiary" />
|
||||
</shape>
|
||||
3
app/src/main/res/drawable/bg_coach_rect.xml
Normal file
3
app/src/main/res/drawable/bg_coach_rect.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="#2D3748" /> <corners android:radius="12dp" /> </shape>
|
||||
8
app/src/main/res/drawable/bg_dialog_arredondado.xml
Normal file
8
app/src/main/res/drawable/bg_dialog_arredondado.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
|
||||
<solid android:color="@color/fundo_cartao" />
|
||||
|
||||
<corners android:radius="20dp" />
|
||||
|
||||
</shape>
|
||||
11
app/src/main/res/drawable/bg_search_bar.xml
Normal file
11
app/src/main/res/drawable/bg_search_bar.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Usamos uma cor adaptável com ligeira transparência em vez de preto fixo -->
|
||||
<solid android:color="#1A888888" />
|
||||
<corners android:radius="12dp" />
|
||||
<padding
|
||||
android:bottom="8dp"
|
||||
android:left="12dp"
|
||||
android:right="12dp"
|
||||
android:top="8dp" />
|
||||
</shape>
|
||||
17
app/src/main/res/drawable/bg_tech_input.xml
Normal file
17
app/src/main/res/drawable/bg_tech_input.xml
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_focused="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#0D1117" />
|
||||
<stroke android:width="2dp" android:color="@color/tech_accent_cyan" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#0D1117" />
|
||||
<stroke android:width="1dp" android:color="#30363D" />
|
||||
<corners android:radius="12dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
20
app/src/main/res/drawable/custom_progress_bar.xml
Normal file
20
app/src/main/res/drawable/custom_progress_bar.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:id="@android:id/background">
|
||||
<shape>
|
||||
<corners android:radius="8dp"/>
|
||||
<solid android:color="#33A0AEC0"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
<item android:id="@android:id/progress">
|
||||
<clip>
|
||||
<shape>
|
||||
<corners android:radius="8dp"/>
|
||||
<solid android:color="@color/tech_accent_cyan"/>
|
||||
</shape>
|
||||
</clip>
|
||||
</item>
|
||||
|
||||
</layer-list>
|
||||
3
app/src/main/res/drawable/ic_alimentacao.xml
Normal file
3
app/src/main/res/drawable/ic_alimentacao.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M11,9H9V2H7v7H5V2H3v7c0,2.12 1.66,3.84 3.75,3.97V22h2.5v-9.03C11.34,12.84 13,11.12 13,9V2h-2V9zM16,6v8h2.5v8H21V2c-2.76,0 -5,2.24 -5,4z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_cafe.xml
Normal file
3
app/src/main/res/drawable/ic_cafe.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M20,3H4v10c0,2.21 1.79,4 4,4h6c2.21,0 4,-1.79 4,-4v-3h2c1.11,0 2,-0.9 2,-2V5C22,3.9 21.11,3 20,3zM20,8h-2V5h2V8zM4,19h16v2H4V19z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_carrinho.xml
Normal file
3
app/src/main/res/drawable/ic_carrinho.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M7,18c-1.1,0 -1.99,0.9 -1.99,2S5.9,22 7,22s2,-0.9 2,-2S8.1,18 7,18zM17,18c-1.1,0 -1.99,0.9 -1.99,2s0.89,2 1.99,2s2,-0.9 2,-2S18.1,18 17,18zM8.1,13h7.45c0.75,0 1.41,-0.41 1.75,-1.03l3.58,-6.49c0.22,-0.4 0.22,-0.88 -0.01,-1.28C20.64,3.8 20.21,3.56 19.74,3.56H5.21l-0.94,-2H1v2h2l3.6,7.59l-1.35,2.44C4.52,15.37 5.48,17 7,17h12v-2H7l1.1,-2z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_casa.xml
Normal file
3
app/src/main/res/drawable/ic_casa.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M10,20v-6h4v6h5v-8h3L12,3L2,12h3v8H10z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_compras.xml
Normal file
3
app/src/main/res/drawable/ic_compras.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M18,6h-2c0,-2.21 -1.79,-4 -4,-4S8,3.79 8,6H6C4.9,6 4,6.9 4,8v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2V8C20,6.9 19.1,6 18,6zM12,4c1.1,0 2,0.9 2,2h-4C10,4.9 10.9,4 12,4zM18,20H6V8h12V20z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_contas.xml
Normal file
3
app/src/main/res/drawable/ic_contas.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M18,17H6v-2h12V17zM18,13H6v-2h12V13zM18,9H6V7h12V9zM3,22l1.5,-1.5L6,22l1.5,-1.5L9,22l1.5,-1.5L12,22l1.5,-1.5L15,22l1.5,-1.5L18,22l1.5,-1.5L21,22V2l-1.5,1.5L18,2l-1.5,1.5L15,2l-1.5,1.5L12,2l-1.5,1.5L9,2L7.5,3.5L6,2L4.5,3.5L3,2V22z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_educacao.xml
Normal file
3
app/src/main/res/drawable/ic_educacao.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M12,3L1,9l4,2.18v6L12,21l7,-3.82v-6l2.12,-1.15V17h2V9L12,3zM12,18.72l-5,-2.73v-5.09l5,2.73l5,-2.73v5.09L12,18.72zM12,10.28L5.34,6.64L12,3l6.66,3.64L12,10.28z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_ginasio.xml
Normal file
3
app/src/main/res/drawable/ic_ginasio.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M20.57,14.86L22,13.43L20.57,12L17,15.57L8.43,7L12,3.43L10.57,2L9.14,3.43L7.71,2L5.57,4.14L4.14,2.71L2.71,4.14L4.14,5.57L2,7.71L3.43,9.14L2,10.57L3.43,12L7,8.43L15.57,17L12,20.57L13.43,22L14.86,20.57L16.29,22L18.43,19.86L19.86,21.29L21.29,19.86L19.86,18.43L22,16.29L20.57,14.86z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_lazer.xml
Normal file
3
app/src/main/res/drawable/ic_lazer.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M21.58,16.09l-1.09,-7.66C20.18,6.27 18.4,5 16.25,5H7.75C5.6,5 3.82,6.27 3.51,8.43l-1.09,7.66C2.2,17.63 3.39,19 4.94,19c0.68,0 1.32,-0.27 1.8,-0.75L9,16h6l2.25,2.25c0.48,0.48 1.13,0.75 1.8,0.75C20.61,19 21.8,17.63 21.58,16.09zM19.59,16.16l-2.02,-2.02C17.18,13.76 16.65,13.5 16.1,13.5H7.9c-0.55,0 -1.08,0.26 -1.47,0.64L4.41,16.16C4.16,16.41 3.75,16.28 3.84,15.93l1.09,-7.66C5.09,7.18 5.98,6.5 7.06,6.5h9.88c1.08,0 1.97,0.68 2.14,1.77l1.09,7.66C20.25,16.28 19.84,16.41 19.59,16.16zM9,11.5c0.83,0 1.5,-0.67 1.5,-1.5S9.83,8.5 9,8.5S7.5,9.17 7.5,10S8.17,11.5 9,11.5zM15,11.5c0.83,0 1.5,-0.67 1.5,-1.5S15.83,8.5 15,8.5S13.5,9.17 13.5,10S14.17,11.5 15,11.5z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_outros.xml
Normal file
3
app/src/main/res/drawable/ic_outros.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M12,2l-5.5,9h11L12,2zM12,5.84L13.93,9h-3.87L12,5.84zM3,13.5h6v6H3V13.5zM5,15.5v2h2v-2H5zM17.5,13c-1.93,0 -3.5,1.57 -3.5,3.5s1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S19.43,13 17.5,13zM17.5,18c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5s1.5,0.67 1.5,1.5S18.33,18 17.5,18z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_salario.xml
Normal file
3
app/src/main/res/drawable/ic_salario.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_saude.xml
Normal file
3
app/src/main/res/drawable/ic_saude.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M19,3H5C3.9,3 3.01,3.9 3.01,5L3,19c0,1.1 0.89,2 1.99,2H19c1.1,0 2,-0.9 2,-2V5C21,3.9 20.1,3 19,3zM19,19H5V5h14V19zM10.5,17h3v-3.5H17v-3h-3.5V7h-3v3.5H7v3h3.5V17z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_telemovel.xml
Normal file
3
app/src/main/res/drawable/ic_telemovel.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M17,1.01L7,1C5.9,1 5,1.9 5,3v18c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3C19,1.9 18.1,1.01 17,1.01zM17,19H7V5h10V19z"/>
|
||||
</vector>
|
||||
3
app/src/main/res/drawable/ic_transportes.xml
Normal file
3
app/src/main/res/drawable/ic_transportes.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5h-11C5.84,5 5.28,5.42 5.08,6.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8L18.92,6.01zM6.85,7h10.29l1.08,3.11H5.77L6.85,7zM19,17H5v-5h14V17zM7.5,16c0.83,0 1.5,-0.67 1.5,-1.5S8.33,13 7.5,13S6,13.67 6,14.5S6.67,16 7.5,16zM16.5,16c0.83,0 1.5,-0.67 1.5,-1.5s-0.67,-1.5 -1.5,-1.5s-1.5,0.67 -1.5,1.5S15.67,16 16.5,16z"/>
|
||||
</vector>
|
||||
@@ -4,7 +4,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/bg_tech_gradient"
|
||||
android:background="@color/bg_dinamico"
|
||||
android:padding="24dp">
|
||||
|
||||
<ImageView
|
||||
@@ -12,7 +12,7 @@
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
app:tint="@color/white"
|
||||
app:tint="@color/texto_dinamico"
|
||||
android:layout_marginBottom="24dp" />
|
||||
|
||||
<TextView
|
||||
@@ -21,7 +21,7 @@
|
||||
android:text="Nova Transação"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:layout_marginBottom="32dp"/>
|
||||
|
||||
<EditText
|
||||
@@ -30,7 +30,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Valor (€)"
|
||||
android:textColorHint="#B0BEC5"
|
||||
android:textColor="@color/white"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:inputType="numberDecimal"
|
||||
android:backgroundTint="@color/tech_accent_cyan"
|
||||
android:textSize="20sp"
|
||||
@@ -40,22 +40,51 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Categoria"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerCategoria"
|
||||
<TextView
|
||||
android:id="@+id/txtCategoriaTransacao"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:text="Selecionar Categoria..."
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:textSize="16sp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:background="@drawable/bg_card_tech"
|
||||
android:layout_marginBottom="40dp"/> <Button
|
||||
android:id="@+id/btnGuardar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Guardar Transação"
|
||||
android:textColor="@color/black"
|
||||
android:padding="12dp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/tech_accent_cyan"/>
|
||||
android:layout_marginBottom="24dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Descrição (Opcional)"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editDescricaoTransacao"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_card_tech"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:padding="16dp"
|
||||
android:hint="Ex: Jantar, Uber, Conta da luz..."
|
||||
android:textColorHint="?android:attr/textColorSecondary"
|
||||
android:inputType="textCapSentences"
|
||||
android:layout_marginBottom="40dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnGuardar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Guardar Transação"
|
||||
android:textColor="@color/black"
|
||||
android:padding="12dp"
|
||||
android:textStyle="bold"
|
||||
app:backgroundTint="@color/tech_accent_cyan"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -5,32 +5,34 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp"
|
||||
android:background="@color/fundo_app"> <LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="32dp">
|
||||
android:background="@color/fundo_app">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnVoltarDefinicoes"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="←"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingEnd="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="32dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Definições"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/btnVoltarDefinicoes"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="←"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingEnd="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Definições"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnEditarPerfil"
|
||||
@@ -55,6 +57,17 @@
|
||||
|
||||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/linha_separadora" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/switchBiometria"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Bloqueio por Biometria"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="18sp"
|
||||
android:paddingVertical="16dp" />
|
||||
|
||||
<View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/linha_separadora" />
|
||||
|
||||
<Switch
|
||||
android:id="@+id/switchNotificacoes"
|
||||
android:layout_width="match_parent"
|
||||
@@ -87,4 +100,18 @@
|
||||
android:textStyle="bold"
|
||||
android:padding="16dp"
|
||||
app:cornerRadius="8dp"
|
||||
android:backgroundTint="#FF1744" /> </LinearLayout>
|
||||
android:backgroundTint="#424242" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnEliminarConta"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Apagar Conta e Dados"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textStyle="bold"
|
||||
android:padding="16dp"
|
||||
android:layout_marginTop="12dp"
|
||||
app:cornerRadius="8dp"
|
||||
android:backgroundTint="#FF1744" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -5,48 +5,50 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp"
|
||||
android:background="#1A202C"> <LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="40dp">
|
||||
android:background="@color/bg_dinamico">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnVoltarEditarPerfil"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="←"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingEnd="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="40dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Editar Perfil"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:id="@+id/btnVoltarEditarPerfil"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="←"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingEnd="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Editar Perfil"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:textSize="28sp"
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:id="@+id/imgFotoPerfil"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@drawable/bg_circle_icon"
|
||||
android:src="@android:drawable/ic_menu_camera"
|
||||
app:tint="#FFFFFF"
|
||||
android:padding="24dp"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_marginBottom="32dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Nome"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
@@ -65,7 +67,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Email"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
@@ -90,4 +92,5 @@
|
||||
android:textStyle="bold"
|
||||
android:padding="16dp"
|
||||
app:cornerRadius="8dp"
|
||||
android:backgroundTint="#00E676" /> </LinearLayout>
|
||||
android:backgroundTint="#00E676" />
|
||||
</LinearLayout>
|
||||
43
app/src/main/res/layout/activity_lock.xml
Normal file
43
app/src/main/res/layout/activity_lock.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:background="@color/bg_dinamico"
|
||||
android:padding="32dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
android:src="@drawable/ic_carteira_tech"
|
||||
android:layout_marginBottom="24dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Finzora"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="32sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="O teu gestor financeiro seguro."
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginBottom="48dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDesbloquearApp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:text="Desbloquear"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#1A202C"
|
||||
android:backgroundTint="#00E676"
|
||||
app:cornerRadius="12dp"/>
|
||||
</LinearLayout>
|
||||
@@ -18,14 +18,15 @@
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgLogo"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_width="45dp"
|
||||
android:layout_height="45dp"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="@drawable/bg_circle_icon"
|
||||
android:padding="8dp"
|
||||
android:src="@android:drawable/ic_menu_gallery"
|
||||
app:tint="@color/white" />
|
||||
app:tint="@color/white"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
@@ -52,6 +53,30 @@
|
||||
android:textSize="14sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnAbrirDefinicoes"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_toStartOf="@id/btnExportarPDF"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@drawable/ic_settings_pap"
|
||||
app:tint="@color/tech_accent_cyan"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Definições" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnExportarPDF"
|
||||
android:layout_width="35dp"
|
||||
android:layout_height="35dp"
|
||||
android:layout_toStartOf="@id/btnSair"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="15dp"
|
||||
android:src="@android:drawable/ic_menu_save"
|
||||
app:tint="@color/tech_accent_cyan"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="Exportar Relatório" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnSair"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -110,16 +135,4 @@
|
||||
app:tint="@color/black"
|
||||
app:elevation="6dp"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnAbrirDefinicoes"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="bottom|start"
|
||||
android:layout_margin="24dp"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_settings_pap"
|
||||
app:tint="@color/texto_principal"
|
||||
android:elevation="6dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
113
app/src/main/res/layout/activity_nova_password.xml
Normal file
113
app/src/main/res/layout/activity_nova_password.xml
Normal file
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/fundo_app"
|
||||
android:padding="24dp"
|
||||
tools:context=".NovaPasswordActivity">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgTechLock"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:padding="8dp"
|
||||
android:src="@android:drawable/ic_secure"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:tint="@color/tech_accent_cyan" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTechTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="NOVA PALAVRA-PASSE"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/imgTechLock" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTechSubtitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Verificação de segurança ativa.\nDefine a tua nova senha de acesso."
|
||||
android:textAlignment="center"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTechTitle" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayoutInputs"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvTechSubtitle">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:hintEnabled="false">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editNovaPass"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:background="@drawable/bg_tech_input"
|
||||
android:hint="Nova palavra-passe"
|
||||
android:textColorHint="#546E7A"
|
||||
android:inputType="textPassword"
|
||||
android:padding="16dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:hintEnabled="false">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editConfirmaNovaPass"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:background="@drawable/bg_tech_input"
|
||||
android:hint="Confirmar palavra-passe"
|
||||
android:textColorHint="#546E7A"
|
||||
android:inputType="textPassword"
|
||||
android:padding="16dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnGuardarNovaPass"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:backgroundTint="@color/tech_accent_cyan"
|
||||
android:text="GUARDAR PALAVRA-PASSE"
|
||||
android:textColor="@color/black"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
app:cornerRadius="12dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/linearLayoutInputs" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
136
app/src/main/res/layout/bottom_sheet_detalhe.xml
Normal file
136
app/src/main/res/layout/bottom_sheet_detalhe.xml
Normal file
@@ -0,0 +1,136 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="@color/bg_dinamico"
|
||||
android:padding="24dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnFecharDetalhe"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||
app:tint="@color/texto_dinamico"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgDetalheIcone"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:background="@drawable/bg_circle_icon"
|
||||
android:src="@android:drawable/ic_menu_agenda"
|
||||
app:tint="#FFFFFF"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDetalheTitulo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="Título da Transação"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDetalheValor"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="- 13,38 €"
|
||||
android:textColor="@color/texto_dinamico"
|
||||
android:textSize="36sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvDetalheDataHora"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="12/04, 10:14"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="32dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:background="#2D3748"
|
||||
android:padding="16dp"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Categoria"
|
||||
android:textColor="#A0AEC0"
|
||||
android:textStyle="bold"/>
|
||||
<TextView
|
||||
android:id="@+id/tvDetalheCategoria"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Entretenimento"
|
||||
android:textColor="#00E676"
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Descrição"
|
||||
android:textColor="#A0AEC0"
|
||||
android:textStyle="bold"/>
|
||||
<TextView
|
||||
android:id="@+id/tvDetalheDescricao"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Subscrição Mensal"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textAlignment="viewEnd"
|
||||
android:maxWidth="200dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnEditarTransacao"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Editar Transação"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#00B8D4"
|
||||
android:textStyle="bold"
|
||||
android:padding="12dp"
|
||||
app:cornerRadius="8dp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnApagarTransacao"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Eliminar Transação"
|
||||
android:textColor="#FFFFFF"
|
||||
android:backgroundTint="#F56565"
|
||||
android:textStyle="bold"
|
||||
android:padding="12dp"
|
||||
app:cornerRadius="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
57
app/src/main/res/layout/dialog_categorias.xml
Normal file
57
app/src/main/res/layout/dialog_categorias.xml
Normal file
@@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:cardBackgroundColor="@color/fundo_cartao"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="Selecionar Categoria"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="350dp"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/containerCategorias"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="16dp" />
|
||||
</ScrollView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnCancelarCategoria"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="end"
|
||||
android:padding="12dp"
|
||||
android:text="CANCELAR"
|
||||
android:textColor="#F56565"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -24,9 +24,14 @@
|
||||
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Contactar Suporte" android:textSize="18sp" android:textStyle="bold" android:textColor="#1A202C"/>
|
||||
</LinearLayout>
|
||||
|
||||
<EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="O seu email" android:background="#F7FAFC" android:padding="16dp" android:layout_marginBottom="12dp"/>
|
||||
<EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Assunto" android:background="#F7FAFC" android:padding="16dp" android:layout_marginBottom="12dp"/>
|
||||
<EditText android:layout_width="match_parent" android:layout_height="120dp" android:hint="Descreva o seu problema..." android:background="#F7FAFC" android:padding="16dp" android:gravity="top|start" android:layout_marginBottom="24dp"/>
|
||||
<EditText android:id="@+id/editEmailSuporte"
|
||||
android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="O seu email" android:background="#F7FAFC" android:padding="16dp" android:layout_marginBottom="12dp"/>
|
||||
|
||||
<EditText android:id="@+id/editAssunto"
|
||||
android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Assunto" android:background="#F7FAFC" android:padding="16dp" android:layout_marginBottom="12dp"/>
|
||||
|
||||
<EditText android:id="@+id/editProblema"
|
||||
android:layout_width="match_parent" android:layout_height="120dp" android:hint="Descreva o seu problema..." android:background="#F7FAFC" android:padding="16dp" android:gravity="top|start" android:layout_marginBottom="24dp"/>
|
||||
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:gravity="end">
|
||||
<Button android:id="@+id/btnCancelarContacto" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cancelar" android:backgroundTint="#FFFFFF" android:textColor="#2D3748" android:layout_marginEnd="8dp"/>
|
||||
|
||||
141
app/src/main/res/layout/dialog_contactos.xml
Normal file
141
app/src/main/res/layout/dialog_contactos.xml
Normal file
@@ -0,0 +1,141 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="24dp"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Contactos Finzora"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="Precisas de ajuda urgente? Fala connosco diretamente."
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<!-- 📧 Zona do Email -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📧"
|
||||
android:textSize="28sp"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Email de Suporte"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="suporte@finzora.pt"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#00E676"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 📞 Zona do Telefone -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="📞"
|
||||
android:textSize="28sp"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Linha de Apoio"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="+351 800 123 456"
|
||||
android:textSize="15sp"
|
||||
android:textColor="#00E676"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 🕒 Zona do Horário (com fundo suave) -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:background="#1A888888"
|
||||
android:padding="12dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🕒"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Dias úteis: 09:00 - 18:00"
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Botão Fechar -->
|
||||
<TextView
|
||||
android:id="@+id/btnFecharContactos"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="VOLTAR"
|
||||
android:gravity="end"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
78
app/src/main/res/layout/dialog_eliminar.xml
Normal file
78
app/src/main/res/layout/dialog_eliminar.xml
Normal file
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="24dp"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp"
|
||||
android:gravity="center">
|
||||
|
||||
<!-- Ícone de Lixo gigante no topo -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="🗑️"
|
||||
android:textSize="40sp"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Eliminar Transação"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:gravity="center"
|
||||
android:text="Tens a certeza que queres apagar esta transação? Esta ação não pode ser desfeita."
|
||||
android:textSize="14sp"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnCancelarEliminar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="CANCELAR"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:layout_marginEnd="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnConfirmarEliminar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="ELIMINAR"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="#FF1744"
|
||||
android:padding="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
137
app/src/main/res/layout/dialog_exportar.xml
Normal file
137
app/src/main/res/layout/dialog_exportar.xml
Normal file
@@ -0,0 +1,137 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="📥 Exportar Dados"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/btnOpcaoPDF"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="#1A888888"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="📄"
|
||||
android:textSize="28sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Relatório PDF"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ideal para ler e imprimir"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="13sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/btnOpcaoExcel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="#1A888888"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="📊"
|
||||
android:textSize="28sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ficheiro Excel (.CSV)"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ideal para tabelas e gráficos"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="13sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnCancelarExportacao"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="CANCELAR"
|
||||
android:textColor="#F56565"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -42,6 +42,12 @@
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Aceda ao separador 'Gráficos' para ver representações visuais dos seus gastos organizados por categoria e período." android:textColor="#718096" android:textSize="13sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ⚠️ A NOSSA NOVA FAQ SOBRE OBJETIVOS -->
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@drawable/bg_tech_gradient" android:backgroundTint="#F7FAFC" android:padding="16dp" android:layout_marginBottom="12dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Como funcionam os Objetivos de Poupança?" android:textStyle="bold" android:textColor="#2D3748" android:layout_marginBottom="8dp"/>
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="No separador 'Objetivos', clique no botão azul para criar um alvo (ex: Comprar PS5). A barra de progresso preenche-se automaticamente com base no seu Saldo Total disponível (Receitas - Despesas)." android:textColor="#718096" android:textSize="13sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@drawable/bg_tech_gradient" android:backgroundTint="#F7FAFC" android:padding="16dp" android:layout_marginBottom="12dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="O que são as dicas financeiras?" android:textStyle="bold" android:textColor="#2D3748" android:layout_marginBottom="8dp"/>
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="No separador 'Dicas', encontrará recomendações personalizadas baseadas nos seus padrões de gastos para melhorar a sua saúde financeira." android:textColor="#718096" android:textSize="13sp"/>
|
||||
|
||||
73
app/src/main/res/layout/dialog_novo_objetivo.xml
Normal file
73
app/src/main/res/layout/dialog_novo_objetivo.xml
Normal file
@@ -0,0 +1,73 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
android:text="🎯 Novo Objetivo"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editNomeObjetivo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:hint="Ex: Comprar PS5"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textColorHint="?android:attr/textColorSecondary" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editValorObjetivo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:hint="Valor Alvo (Ex: 500.00)"
|
||||
android:inputType="numberDecimal"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textColorHint="?android:attr/textColorSecondary" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="end"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnCancelarObjetivo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="12dp"
|
||||
android:text="CANCELAR"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnGuardarObjetivo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:backgroundTint="#00B8D4"
|
||||
android:text="GUARDAR"
|
||||
android:textColor="#FFFFFF"
|
||||
app:cornerRadius="8dp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
161
app/src/main/res/layout/dialog_tipo_transacao.xml
Normal file
161
app/src/main/res/layout/dialog_tipo_transacao.xml
Normal file
@@ -0,0 +1,161 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="Tipo de Transação"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- CARTÃO RECEITA -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/btnTipoReceita"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="#1A888888"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Ícone Verde Redondo -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:cardBackgroundColor="#2F855A"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="0dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="+"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="24sp"
|
||||
android:textStyle="bold" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Receita"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Entrada de dinheiro (Ex: Salário)"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="13sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<!-- CARTÃO DESPESA -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/btnTipoDespesa"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="#1A888888"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<!-- Ícone Vermelho Redondo -->
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:cardBackgroundColor="#C53030"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="0dp">
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:text="-"
|
||||
android:textColor="#FFFFFF"
|
||||
android:textSize="26sp"
|
||||
android:textStyle="bold" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Despesa"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Saída de dinheiro (Ex: Compras)"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="13sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnCancelarTipo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:padding="12dp"
|
||||
android:text="CANCELAR"
|
||||
android:textColor="#F56565"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -42,8 +42,14 @@
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Use os gráficos para visualizar para onde vai o seu dinheiro." android:textColor="#718096" android:textSize="13sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- ⚠️ A NOSSA NOVA ABA AQUI COMO PASSO 4 -->
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@drawable/bg_tech_gradient" android:backgroundTint="#F7FAFC" android:padding="16dp" android:layout_marginBottom="12dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="4. Siga as dicas personalizadas" android:textStyle="bold" android:textColor="#2D3748" android:layout_marginBottom="8dp"/>
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="4. Crie Objetivos de Poupança" android:textStyle="bold" android:textColor="#2D3748" android:layout_marginBottom="8dp"/>
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Tem um alvo a atingir? Registe-o no separador 'Objetivos'. A Finzora calcula automaticamente o seu progresso com base no seu Saldo Total!" android:textColor="#718096" android:textSize="13sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="@drawable/bg_tech_gradient" android:backgroundTint="#F7FAFC" android:padding="16dp" android:layout_marginBottom="12dp">
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="5. Siga as dicas personalizadas" android:textStyle="bold" android:textColor="#2D3748" android:layout_marginBottom="8dp"/>
|
||||
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="A Finzora analisa os seus padrões e fornece recomendações úteis." android:textColor="#718096" android:textSize="13sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
@@ -1,395 +1,313 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bg_tech_gradient"
|
||||
android:fillViewport="true">
|
||||
android:background="?android:attr/windowBackground">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutEstadoVazioDicas"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Resumo da Sua Saúde Financeira"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
android:text="📊"
|
||||
android:textSize="60sp" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Sem dados para analisar"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="32dp"
|
||||
android:text="Adiciona transações para receberes conselhos da nossa Inteligência Artificial."
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/layoutConteudoDicas"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:padding="16dp"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardBackgroundColor="#2C5364"
|
||||
app:cardElevation="4dp">
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="?android:attr/colorBackgroundFloating"
|
||||
app:cardCornerRadius="20dp"
|
||||
app:cardElevation="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Taxa de Poupança"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"/>
|
||||
<TextView
|
||||
android:id="@+id/tvTaxaPoupanca"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="0.0%"
|
||||
android:textColor="#00E676"
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
android:text="🤖 Finzora AI Coach"
|
||||
android:textColor="@color/tech_accent_cyan"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressPoupanca"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:progressTint="#00E676"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:id="@+id/tvRespostaAI"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Olá! Já analisei os teus dados. Pergunta-me qualquer coisa sobre como poupar ou investir."
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:background="@drawable/bg_search_bar"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp"
|
||||
android:paddingVertical="4dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editPerguntaAI"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="@null"
|
||||
android:hint="Pede uma dica tática..."
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textColorHint="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/btnEnviarAI"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_menu_send"
|
||||
app:tint="@color/tech_accent_cyan" />
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pbCarregandoAI"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="8dp"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="?android:attr/colorBackgroundFloating"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:background="#1AFFFFFF"
|
||||
android:padding="12dp"
|
||||
android:layout_marginEnd="8dp">
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="Taxa de Poupança"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/tvTaxaPoupanca"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Receitas"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textSize="12sp"/>
|
||||
android:text="0%"
|
||||
android:textColor="#00E676"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressPoupanca"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:progressTint="#00E676" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/tvDicasReceitas"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="€ 0.00"
|
||||
android:textColor="#00E676"
|
||||
android:textStyle="bold"
|
||||
android:textSize="16sp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:background="#1AFFFFFF"
|
||||
android:padding="12dp"
|
||||
android:layout_marginStart="8dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Despesas"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textSize="12sp"/>
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/tvDicasDespesas"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="€ 0.00"
|
||||
android:textColor="#FF1744"
|
||||
android:textStyle="bold"
|
||||
android:textSize="16sp"/>
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Dicas Personalizadas"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardDica1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardBackgroundColor="#2C5364"
|
||||
app:cardElevation="2dp">
|
||||
<LinearLayout
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:id="@+id/tvTituloDica1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="A calcular..."
|
||||
android:textColor="#00E676"
|
||||
android:textStyle="bold"
|
||||
android:textSize="16sp"/>
|
||||
<TextView
|
||||
android:id="@+id/tvDescDica1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="..."
|
||||
android:textColor="#B0BEC5"
|
||||
android:layout_marginTop="4dp"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardBackgroundColor="?android:attr/colorBackgroundFloating"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:id="@+id/cardDica2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardBackgroundColor="#2C5364"
|
||||
app:cardElevation="2dp">
|
||||
<LinearLayout
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTituloDica1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="A Regra 50/30/20 ⚖️"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/tvDescDica1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="A calcular a tua distribuição de riqueza..."
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
<TextView
|
||||
android:id="@+id/tvTituloDica2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="A analisar gastos..."
|
||||
android:textColor="#FF1744"
|
||||
android:textStyle="bold"
|
||||
android:textSize="16sp"/>
|
||||
<TextView
|
||||
android:id="@+id/tvDescDica2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="..."
|
||||
android:textColor="#B0BEC5"
|
||||
android:layout_marginTop="4dp"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardBackgroundColor="?android:attr/colorBackgroundFloating"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Distribuição de Gastos"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardBackgroundColor="#2C5364"
|
||||
app:cardElevation="2dp">
|
||||
<TextView
|
||||
android:id="@+id/tvTituloDica2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Radar de Orçamentos 🎯"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/tvDescDica2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="A verificar limites..."
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:cardBackgroundColor="?android:attr/colorBackgroundFloating"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvTituloDica3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ritmo de Gastos 🏃♂️"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
<TextView
|
||||
android:id="@+id/tvDescDica3"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:text="A calcular a tua velocidade de gastos..."
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:text="Top Despesas"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutDistribuicao"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
android:orientation="vertical" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="12dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@drawable/ic_idea"
|
||||
app:tint="@color/white"
|
||||
android:layout_marginEnd="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Dicas Rápidas de Economia"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="32dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardBackgroundColor="#2C5364"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/checkbox_on_background"
|
||||
app:tint="#00E676"
|
||||
android:layout_marginTop="2dp"/>
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="12dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Prepare refeições em casa"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pode poupar até €200/mês reduzindo refeições fora"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="2dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/checkbox_on_background"
|
||||
app:tint="#00E676"
|
||||
android:layout_marginTop="2dp"/>
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="12dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Compare preços antes de comprar"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Use apps de comparação para encontrar melhores ofertas"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="2dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="16dp">
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/checkbox_on_background"
|
||||
app:tint="#00E676"
|
||||
android:layout_marginTop="2dp"/>
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="12dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Cancele subscrições não utilizadas"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Reveja streamings, ginásios e apps que paga mas não usa"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="2dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/checkbox_on_background"
|
||||
app:tint="#00E676"
|
||||
android:layout_marginTop="2dp"/>
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="12dp">
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Use transportes públicos"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Economize em combustível, estacionamento e manutenção"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="2dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</ScrollView>
|
||||
</FrameLayout>
|
||||
@@ -1,88 +1,147 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/bg_tech_gradient"
|
||||
android:fillViewport="true">
|
||||
android:background="@color/bg_dinamico">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollviewGraficos"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardBackgroundColor="@color/fundo_cartao"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Distribuição de Despesas"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<com.github.mikephil.charting.charts.PieChart
|
||||
android:id="@+id/pieChartDespesas"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardBackgroundColor="@color/fundo_cartao"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Orçamento vs Gasto"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<com.github.mikephil.charting.charts.BarChart
|
||||
android:id="@+id/barChartOrcamento"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardBackgroundColor="@color/fundo_cartao"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tendência de Gastos"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<com.github.mikephil.charting.charts.BarChart
|
||||
android:id="@+id/barChartTendencia"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"/>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutEstadoVazioGraficos"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:padding="32dp">
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="250dp"
|
||||
app:lottie_rawRes="@raw/anim_grafico_vazio"
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_loop="true"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Despesas por Categoria"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:text="À espera de dados! 📊"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardBackgroundColor="#2C5364"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<com.github.mikephil.charting.charts.PieChart
|
||||
android:id="@+id/pieChartDespesas"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="12dp"/>
|
||||
</androidx.cardview.widget.CardView>
|
||||
android:textColor="@color/texto_principal"
|
||||
android:layout_marginTop="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Orçamento vs Gastos Reais"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardBackgroundColor="#2C5364"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<com.github.mikephil.charting.charts.BarChart
|
||||
android:id="@+id/barChartOrcamento"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="12dp"/>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Tendência Mensal (Geral)"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="300dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardBackgroundColor="#2C5364"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<com.github.mikephil.charting.charts.BarChart
|
||||
android:id="@+id/barChartTendencia"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="12dp"/>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
android:text="Regista as tuas primeiras transações para veres a magia dos gráficos acontecer."
|
||||
android:textSize="15sp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_marginTop="8dp"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
</FrameLayout>
|
||||
56
app/src/main/res/layout/fragment_objetivos.xml
Normal file
56
app/src/main/res/layout/fragment_objetivos.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/bg_dinamico">
|
||||
|
||||
<!-- Lista onde vão aparecer os Cofres -->
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerObjetivos"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="80dp"/>
|
||||
|
||||
<!-- Mensagem quando não há objetivos -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutObjetivosVazios"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:visibility="gone">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:src="@drawable/ic_lazer"
|
||||
app:tint="#A0AEC0"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sem objetivos definidos."
|
||||
android:textColor="#A0AEC0"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"/>
|
||||
</LinearLayout>
|
||||
|
||||
<!-- Botão de Adicionar Objetivo -->
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabAdicionarObjetivo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_margin="24dp"
|
||||
android:src="@android:drawable/ic_input_add"
|
||||
app:tint="#FFFFFF"
|
||||
app:backgroundTint="#00B8D4"
|
||||
app:elevation="6dp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -1,29 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/bg_tech_gradient"
|
||||
android:padding="16dp"
|
||||
tools:ignore="HardcodedText">
|
||||
android:background="?android:attr/windowBackground"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Planeamento de Orçamento"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="18sp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardBackgroundColor="@color/white">
|
||||
app:cardBackgroundColor="?android:attr/colorBackgroundFloating"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -31,48 +30,79 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Categoria"
|
||||
android:textColor="#333333"
|
||||
android:textStyle="bold"
|
||||
android:textSize="12sp"/>
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinnerOrcamento"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="48dp"
|
||||
android:background="#F5F5F5"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
android:orientation="horizontal"
|
||||
android:baselineAligned="false">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Limite Mensal (€)"
|
||||
android:textColor="#333333"
|
||||
android:textStyle="bold"
|
||||
android:textSize="12sp"/>
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginEnd="8dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editLimite"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="#F5F5F5"
|
||||
android:padding="8dp"
|
||||
android:inputType="numberDecimal"
|
||||
android:hint="0.00"
|
||||
android:textColor="#000000"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Categoria"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtCategoriaOrcamento"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="45dp"
|
||||
android:text="Selecionar..."
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="14sp"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:background="@drawable/bg_coach_input_rect" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Limite (€)"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editLimiteOrcamento"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="45dp"
|
||||
android:hint="0.00"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textColorHint="?android:attr/textColorSecondary"
|
||||
android:inputType="numberDecimal"
|
||||
android:backgroundTint="@color/tech_accent_cyan"
|
||||
android:paddingStart="4dp"
|
||||
android:textSize="16sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnDefinirOrcamento"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="45dp"
|
||||
android:text="Definir Orçamento"
|
||||
android:backgroundTint="#0F2027"
|
||||
android:textColor="@color/white"/>
|
||||
android:textColor="#1A202C"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cornerRadius="8dp"
|
||||
app:backgroundTint="@color/tech_accent_cyan" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
@@ -80,14 +110,55 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Orçamentos Ativos"
|
||||
android:textColor="@color/white"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerOrcamentos"
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerOrcamentos"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutEstadoVazioOrcamento"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:padding="16dp">
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="150dp"
|
||||
app:lottie_rawRes="@raw/anim_vazio"
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_loop="true"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Sem limites definidos!"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:layout_marginTop="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Usa o cartão acima para definir objetivos."
|
||||
android:textSize="14sp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_marginTop="4dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,14 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/bg_tech_gradient"
|
||||
android:padding="8dp">
|
||||
android:background="@color/bg_dinamico">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerTransacoes"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="80dp"/> </LinearLayout>
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/bg_search_bar"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/ic_menu_search"
|
||||
app:tint="#A0AEC0" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editPesquisar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="Pesquisar transação..."
|
||||
android:textColorHint="#718096"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="14sp"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"/>
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerTransacoes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="20dp"
|
||||
android:clipToPadding="false"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutEstadoVazio"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center"
|
||||
android:visibility="gone"
|
||||
android:padding="32dp">
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:layout_width="200dp"
|
||||
android:layout_height="200dp"
|
||||
app:lottie_rawRes="@raw/anim_vazio"
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_loop="true"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Ainda não tens transações!"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:layout_marginTop="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Clica no botão + para começares a registar os teus movimentos."
|
||||
android:textSize="14sp"
|
||||
android:textAlignment="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:layout_marginTop="8dp"/>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -3,10 +3,7 @@
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="16dp"
|
||||
android:paddingVertical="12dp"
|
||||
android:padding="16dp"
|
||||
android:textSize="16sp"
|
||||
android:textColor="#FFFFFF"
|
||||
android:background="#1A202C"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"/>
|
||||
android:textColor="@color/texto_principal"
|
||||
android:background="@color/bg_dinamico" />
|
||||
111
app/src/main/res/layout/item_objetivo.xml
Normal file
111
app/src/main/res/layout/item_objetivo.xml
Normal file
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/cardObjetivo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="2dp"
|
||||
app:cardBackgroundColor="?android:attr/colorBackgroundFloating">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgIconeObjetivo"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:src="@drawable/ic_lazer"
|
||||
app:tint="#00B8D4" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvNomeObjetivo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_toEndOf="@id/imgIconeObjetivo"
|
||||
android:layout_toStartOf="@id/layoutBotoesAcao"
|
||||
android:text="Nome do Objetivo"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- Zona dos Botões de Ação -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutBotoesAcao"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<!-- ✏️ Novo Botão Editar -->
|
||||
<ImageView
|
||||
android:id="@+id/btnEditarObjetivo"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_menu_edit"
|
||||
app:tint="#ECC94B" />
|
||||
|
||||
<!-- 🗑️ Botão Eliminar -->
|
||||
<ImageView
|
||||
android:id="@+id/btnEliminarObjetivo"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_menu_delete"
|
||||
app:tint="#F56565" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvValoresObjetivo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:text="Guardado: € 0.00 / Alvo: € 0.00"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressObjetivo"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="8dp"
|
||||
android:layout_weight="1"
|
||||
android:max="100"
|
||||
android:progress="0"
|
||||
android:progressTint="#00B8D4" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvPercentagemObjetivo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="0%"
|
||||
android:textColor="#00B8D4"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -3,10 +3,11 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardBackgroundColor="#2C5364"
|
||||
app:cardElevation="2dp">
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
app:cardBackgroundColor="?android:attr/colorBackgroundFloating"
|
||||
app:cardCornerRadius="16dp"
|
||||
app:cardElevation="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -18,25 +19,42 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgIconeOrcamento"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:src="@drawable/ic_alimentacao"
|
||||
app:tint="@color/tech_accent_cyan" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvCatOrcamento"
|
||||
android:id="@+id/tvCategoriaOrcamento"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginStart="12dp"
|
||||
android:text="Alimentação"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"/>
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/btnEliminarOrcamento"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:src="@android:drawable/ic_menu_delete"
|
||||
app:tint="#F56565" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvValoresOrcamento"
|
||||
android:id="@+id/tvPercentagemOrcamento"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="€85.50 / €300.00"
|
||||
android:textColor="#B0BEC5"
|
||||
android:textSize="14sp"/>
|
||||
android:text="75%"
|
||||
android:textColor="@color/tech_accent_cyan"
|
||||
android:textStyle="bold" />
|
||||
</LinearLayout>
|
||||
|
||||
<ProgressBar
|
||||
@@ -44,18 +62,18 @@
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="8dp"
|
||||
android:progressDrawable="@drawable/progress_savings"
|
||||
android:max="100"
|
||||
android:progress="50"/>
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:progress="75"
|
||||
android:progressDrawable="@drawable/custom_progress_bar" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvRestante"
|
||||
android:layout_width="wrap_content"
|
||||
android:id="@+id/tvValoresOrcamento"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Restam €214.50 (72%)"
|
||||
android:textColor="#90A4AE"
|
||||
android:text="Gasto: € 150.00 / Limite: € 200.00"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="6dp"/>
|
||||
|
||||
android:textAlignment="viewEnd" />
|
||||
</LinearLayout>
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -5,10 +5,9 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="8dp"
|
||||
app:cardBackgroundColor="#FFFFFF"
|
||||
app:cardBackgroundColor="@color/fundo_cartao"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardElevation="2dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -36,7 +35,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Descrição"
|
||||
android:textColor="#1A202C"
|
||||
android:textColor="@color/texto_principal"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
@@ -45,7 +44,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Data"
|
||||
android:textColor="#718096"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginTop="4dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
1
app/src/main/res/raw/anim_dicas_vazio.json
Normal file
1
app/src/main/res/raw/anim_dicas_vazio.json
Normal file
File diff suppressed because one or more lines are too long
1
app/src/main/res/raw/anim_grafico_vazio.json
Normal file
1
app/src/main/res/raw/anim_grafico_vazio.json
Normal file
File diff suppressed because one or more lines are too long
1
app/src/main/res/raw/anim_vazio.json
Normal file
1
app/src/main/res/raw/anim_vazio.json
Normal file
File diff suppressed because one or more lines are too long
@@ -4,4 +4,5 @@
|
||||
<color name="texto_principal">#FFFFFF</color>
|
||||
<color name="fundo_cartao">#2D3748</color>
|
||||
<color name="linha_separadora">#2D3748</color>
|
||||
</resources>
|
||||
|
||||
<color name="bg_dinamico">#1A202C</color> <color name="texto_dinamico">#FFFFFF</color> </resources>
|
||||
@@ -3,14 +3,16 @@
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="tech_accent_cyan">#00E676</color>
|
||||
<color name="tech_card_bg">#2D3748</color>
|
||||
<color name="text_secondary">#A0AEC0</color>
|
||||
<color name="tech_accent_green">#48BB78</color>
|
||||
<color name="tech_accent_yellow">#ECC94B</color> <color name="tech_accent_red">#F56565</color>
|
||||
<color name="tech_bg_dark">#1A202C</color>
|
||||
<color name="tech_accent_yellow">#ECC94B</color>
|
||||
<color name="tech_accent_red">#F56565</color>
|
||||
|
||||
<color name="fundo_app">#FFFFFF</color>
|
||||
<color name="texto_principal">#1A202C</color>
|
||||
<color name="fundo_cartao">#F7FAFC</color>
|
||||
<color name="fundo_app">#F3F4F6</color>
|
||||
<color name="fundo_cartao">#FFFFFF</color> <color name="tech_card_bg">#FFFFFF</color>
|
||||
<color name="tech_bg_dark">#F8F9FA</color>
|
||||
|
||||
<color name="texto_principal">#1A202C</color> <color name="text_secondary">#718096</color>
|
||||
<color name="linha_separadora">#E2E8F0</color>
|
||||
<color name="bg_dinamico">#F3F4F6</color>
|
||||
<color name="texto_dinamico">#1A202C</color>
|
||||
</resources>
|
||||
@@ -1,3 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
@@ -7,6 +7,7 @@ appcompat = "1.7.1"
|
||||
material = "1.13.0"
|
||||
activity = "1.12.2"
|
||||
constraintlayout = "2.2.1"
|
||||
generativeai = "0.9.0"
|
||||
|
||||
[libraries]
|
||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||
@@ -16,6 +17,7 @@ appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "a
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
|
||||
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
|
||||
generativeai = { group = "com.google.ai.client.generativeai", name = "generativeai", version.ref = "generativeai" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
||||
Reference in New Issue
Block a user