From 8c9bdc551a238699e09c17ac5fb3958492ca73f3 Mon Sep 17 00:00:00 2001
From: 230415 <230415@epvc.pt>
Date: Tue, 5 May 2026 16:18:47 +0100
Subject: [PATCH] ia
---
app/build.gradle.kts | 2 +
app/src/main/AndroidManifest.xml | 3 +
.../example/lifegrid/DefinicoesActivity.java | 8 ++
.../example/lifegrid/DocumentosActivity.java | 91 ++++++++++++++++++
.../lifegrid/InvoiceScannerHelper.java | 92 +++++++++++++++++++
.../example/lifegrid/TelaInicialActivity.java | 65 ++++++++++++-
.../lifegrid/menu/TransacoesFragment.java | 38 +++++++-
.../main/res/layout/activity_definicoes.xml | 16 ++++
.../main/res/layout/activity_documentos.xml | 58 ++++++++++++
.../main/res/layout/activity_tela_inicial.xml | 12 ++-
app/src/main/res/layout/fragment_ativos.xml | 7 +-
app/src/main/res/layout/fragment_metas.xml | 2 +-
.../main/res/layout/fragment_transacoes.xml | 4 +-
13 files changed, 389 insertions(+), 9 deletions(-)
create mode 100644 app/src/main/java/com/example/lifegrid/DocumentosActivity.java
create mode 100644 app/src/main/java/com/example/lifegrid/InvoiceScannerHelper.java
create mode 100644 app/src/main/res/layout/activity_documentos.xml
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index e3cbbcf..d9f2581 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -58,6 +58,8 @@ dependencies {
implementation(libs.ui.tooling.preview)
implementation(libs.material3)
implementation(libs.material3.adaptive.navigation.suite)
+ implementation("com.google.ai.client.generativeai:generativeai:0.7.0")
+ implementation("com.google.guava:guava:31.1-android")
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index acb3ccd..45d35d5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -13,6 +13,9 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.LifeGrid">
+
diff --git a/app/src/main/java/com/example/lifegrid/DefinicoesActivity.java b/app/src/main/java/com/example/lifegrid/DefinicoesActivity.java
index 2860e2b..6497fc9 100644
--- a/app/src/main/java/com/example/lifegrid/DefinicoesActivity.java
+++ b/app/src/main/java/com/example/lifegrid/DefinicoesActivity.java
@@ -108,6 +108,14 @@ public class DefinicoesActivity extends AppCompatActivity {
}
}
+ TextView tvDocuments = findViewById(R.id.tvDocuments);
+ if (tvDocuments != null) {
+ tvDocuments.setOnClickListener(v -> {
+ Intent intent = new Intent(DefinicoesActivity.this, DocumentosActivity.class);
+ startActivity(intent);
+ });
+ }
+
btnBack.setOnClickListener(v -> {
finish();
});
diff --git a/app/src/main/java/com/example/lifegrid/DocumentosActivity.java b/app/src/main/java/com/example/lifegrid/DocumentosActivity.java
new file mode 100644
index 0000000..3a231da
--- /dev/null
+++ b/app/src/main/java/com/example/lifegrid/DocumentosActivity.java
@@ -0,0 +1,91 @@
+package com.example.lifegrid;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+public class DocumentosActivity extends AppCompatActivity {
+
+ private ImageView btnBack;
+ private LinearLayout llDocumentsList;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ setContentView(R.layout.activity_documentos);
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+
+ btnBack = findViewById(R.id.btnBack);
+ llDocumentsList = findViewById(R.id.llDocumentsList);
+
+ btnBack.setOnClickListener(v -> finish());
+
+ loadDocuments();
+ }
+
+ private void loadDocuments() {
+ SharedPreferences prefs = getSharedPreferences("LifeGridDocs", Context.MODE_PRIVATE);
+ int count = prefs.getInt("doc_count", 0);
+
+ if (count == 0) {
+ TextView emptyText = new TextView(this);
+ emptyText.setText("Nenhum documento guardado.");
+ emptyText.setPadding(32, 32, 32, 32);
+ llDocumentsList.addView(emptyText);
+ return;
+ }
+
+ for (int i = 0; i < count; i++) {
+ String uriString = prefs.getString("doc_uri_" + i, "");
+ String desc = prefs.getString("doc_desc_" + i, "Sem descrição");
+ String data = prefs.getString("doc_data_" + i, "Sem data");
+
+ LinearLayout itemLayout = new LinearLayout(this);
+ itemLayout.setOrientation(LinearLayout.HORIZONTAL);
+ itemLayout.setPadding(16, 16, 16, 16);
+
+ ImageView ivDoc = new ImageView(this);
+ ivDoc.setLayoutParams(new LinearLayout.LayoutParams(200, 200));
+ ivDoc.setScaleType(ImageView.ScaleType.CENTER_CROP);
+ try {
+ ivDoc.setImageURI(Uri.parse(uriString));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ LinearLayout textLayout = new LinearLayout(this);
+ textLayout.setOrientation(LinearLayout.VERTICAL);
+ textLayout.setPadding(16, 0, 0, 0);
+
+ TextView tvDesc = new TextView(this);
+ tvDesc.setText(desc);
+ tvDesc.setTextSize(16);
+ tvDesc.setTypeface(null, android.graphics.Typeface.BOLD);
+
+ TextView tvData = new TextView(this);
+ tvData.setText(data);
+
+ textLayout.addView(tvDesc);
+ textLayout.addView(tvData);
+
+ itemLayout.addView(ivDoc);
+ itemLayout.addView(textLayout);
+
+ llDocumentsList.addView(itemLayout);
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/lifegrid/InvoiceScannerHelper.java b/app/src/main/java/com/example/lifegrid/InvoiceScannerHelper.java
new file mode 100644
index 0000000..c49c21c
--- /dev/null
+++ b/app/src/main/java/com/example/lifegrid/InvoiceScannerHelper.java
@@ -0,0 +1,92 @@
+package com.example.lifegrid;
+
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.util.Log;
+
+import com.google.ai.client.generativeai.GenerativeModel;
+import com.google.ai.client.generativeai.java.GenerativeModelFutures;
+import com.google.ai.client.generativeai.type.Content;
+import com.google.ai.client.generativeai.type.GenerateContentResponse;
+import com.google.common.util.concurrent.FutureCallback;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.InputStream;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+public class InvoiceScannerHelper {
+
+ private static final String API_KEY = "YOUR_GEMINI_API_KEY_HERE"; // Substitua pela sua chave API
+ private static final String PROMPT = "Analisa esta fatura. Devolve APENAS um objeto JSON com os campos: 'valor' (Double, apenas números e ponto decimal), 'descricao' (String curta, nome do estabelecimento ou produto), 'categoria' (String, ex: Alimentação, Transporte, Lazer, Saúde, Casa, Educação, Outros), e 'data' (String formato DD/MM/AAAA). Se não conseguires ler algum, coloca valor padrão ou string vazia.";
+
+ public interface ScanCallback {
+ void onSuccess(double valor, String descricao, String categoria, String data);
+ void onError(String error);
+ }
+
+ public static void scanInvoice(Context context, Uri imageUri, ScanCallback callback) {
+ if (API_KEY.equals("YOUR_GEMINI_API_KEY_HERE")) {
+ callback.onError("Chave API do Gemini não configurada.");
+ return;
+ }
+
+ try {
+ InputStream imageStream = context.getContentResolver().openInputStream(imageUri);
+ Bitmap bitmap = BitmapFactory.decodeStream(imageStream);
+
+ GenerativeModel gm = new GenerativeModel(
+ "gemini-1.5-flash",
+ API_KEY
+ );
+ GenerativeModelFutures model = GenerativeModelFutures.from(gm);
+
+ Content content = new Content.Builder()
+ .addText(PROMPT)
+ .addImage(bitmap)
+ .build();
+
+ Executor executor = Executors.newSingleThreadExecutor();
+ ListenableFuture response = model.generateContent(content);
+
+ Futures.addCallback(response, new FutureCallback() {
+ @Override
+ public void onSuccess(GenerateContentResponse result) {
+ try {
+ String textResponse = result.getText();
+ if (textResponse != null) {
+ textResponse = textResponse.replace("```json", "").replace("```", "").trim();
+ JSONObject json = new JSONObject(textResponse);
+ double valor = json.optDouble("valor", 0.0);
+ String descricao = json.optString("descricao", "");
+ String categoria = json.optString("categoria", "Outros");
+ String data = json.optString("data", "");
+ callback.onSuccess(valor, descricao, categoria, data);
+ } else {
+ callback.onError("Resposta vazia da IA.");
+ }
+ } catch (JSONException e) {
+ Log.e("InvoiceScanner", "Erro ao fazer parse do JSON: " + result.getText(), e);
+ callback.onError("Erro ao ler dados da fatura.");
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ Log.e("InvoiceScanner", "Falha na API do Gemini", t);
+ callback.onError("Falha ao comunicar com a IA: " + t.getMessage());
+ }
+ }, executor);
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ callback.onError("Erro ao processar imagem: " + e.getMessage());
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/lifegrid/TelaInicialActivity.java b/app/src/main/java/com/example/lifegrid/TelaInicialActivity.java
index 51f4f0b..18d45ad 100644
--- a/app/src/main/java/com/example/lifegrid/TelaInicialActivity.java
+++ b/app/src/main/java/com/example/lifegrid/TelaInicialActivity.java
@@ -59,6 +59,9 @@ public class TelaInicialActivity extends AppCompatActivity {
private String[] meses = {"Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"};
+ private androidx.activity.result.ActivityResultLauncher takePictureLauncher;
+ private android.net.Uri currentPhotoUri;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -70,7 +73,18 @@ public class TelaInicialActivity extends AppCompatActivity {
return insets;
});
-
+ takePictureLauncher = registerForActivityResult(
+ new androidx.activity.result.contract.ActivityResultContracts.TakePicture(),
+ success -> {
+ if (success) {
+ processInvoiceImage(currentPhotoUri);
+ }
+ }
+ );
+
+ com.google.android.material.floatingactionbutton.FloatingActionButton fabScanInvoice = findViewById(R.id.fabScanInvoice);
+ fabScanInvoice.setOnClickListener(v -> startInvoiceScan());
+
ivHeaderProfilePicture = findViewById(R.id.ivHeaderProfilePicture);
tvHeaderUsername = findViewById(R.id.tvHeaderUsername);
@@ -141,6 +155,55 @@ public class TelaInicialActivity extends AppCompatActivity {
startActivity(intent);
});
}
+
+ public void startInvoiceScan() {
+ java.io.File photoFile = new java.io.File(getFilesDir(), "invoice_" + System.currentTimeMillis() + ".jpg");
+ currentPhotoUri = androidx.core.content.FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile);
+ takePictureLauncher.launch(currentPhotoUri);
+ }
+
+ private void processInvoiceImage(android.net.Uri imageUri) {
+ Toast.makeText(this, "A processar fatura com IA...", Toast.LENGTH_LONG).show();
+ InvoiceScannerHelper.scanInvoice(this, imageUri, new InvoiceScannerHelper.ScanCallback() {
+ @Override
+ public void onSuccess(double valor, String descricao, String categoria, String data) {
+ runOnUiThread(() -> {
+ Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragmentContainerView);
+ TransacoesFragment transFragment;
+ if (currentFragment instanceof TransacoesFragment) {
+ transFragment = (TransacoesFragment) currentFragment;
+ } else {
+ updateNavSelection(findViewById(R.id.carteiraImageView));
+ transFragment = new TransacoesFragment();
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.fragmentContainerView, transFragment)
+ .commit();
+ getSupportFragmentManager().executePendingTransactions();
+ }
+ transFragment.showNovaTransacaoDialog(valor, descricao, categoria, data);
+
+ // Save document to shared preferences or database
+ saveDocument(imageUri.toString(), descricao, data);
+ });
+ }
+
+ @Override
+ public void onError(String error) {
+ runOnUiThread(() -> Toast.makeText(TelaInicialActivity.this, error, Toast.LENGTH_LONG).show());
+ }
+ });
+ }
+
+ private void saveDocument(String uriString, String descricao, String data) {
+ SharedPreferences prefs = getSharedPreferences("LifeGridDocs", Context.MODE_PRIVATE);
+ int count = prefs.getInt("doc_count", 0);
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.putString("doc_uri_" + count, uriString);
+ editor.putString("doc_desc_" + count, descricao);
+ editor.putString("doc_data_" + count, data);
+ editor.putInt("doc_count", count + 1);
+ editor.apply();
+ }
@Override
protected void onResume() {
diff --git a/app/src/main/java/com/example/lifegrid/menu/TransacoesFragment.java b/app/src/main/java/com/example/lifegrid/menu/TransacoesFragment.java
index 93a288d..3ceb86a 100644
--- a/app/src/main/java/com/example/lifegrid/menu/TransacoesFragment.java
+++ b/app/src/main/java/com/example/lifegrid/menu/TransacoesFragment.java
@@ -74,6 +74,15 @@ public class TransacoesFragment extends Fragment {
Button novaTransacaoButton = root.findViewById(R.id.novaTransacaoButton);
novaTransacaoButton.setOnClickListener(v -> showNovaTransacaoDialog());
+ Button escanearTransacaoButton = root.findViewById(R.id.escanearTransacaoButton);
+ if (escanearTransacaoButton != null) {
+ escanearTransacaoButton.setOnClickListener(v -> {
+ if (getActivity() instanceof com.example.lifegrid.TelaInicialActivity) {
+ ((com.example.lifegrid.TelaInicialActivity) getActivity()).startInvoiceScan();
+ }
+ });
+ }
+
rvTransacoes = root.findViewById(R.id.rvTransacoes);
tvEmptyState = root.findViewById(R.id.textView13);
@@ -136,11 +145,15 @@ public class TransacoesFragment extends Fragment {
});
}
+ public void showNovaTransacaoDialog() {
+ showNovaTransacaoDialog(0.0, "", "", "");
+ }
+
/**
* Cria e monta manualmente uma janela Modal (Pop-up) a fim do utilizador preencher
* os detalhes referentes a uma recém aquisição de receita ou encargo para alimentar a base de dados.
*/
- private void showNovaTransacaoDialog() {
+ public void showNovaTransacaoDialog(double defaultValor, String defaultDescricao, String defaultCategoria, String defaultData) {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
View dialogView = getLayoutInflater().inflate(R.layout.dialog_nova_transacao, null);
builder.setView(dialogView);
@@ -154,6 +167,9 @@ public class TransacoesFragment extends Fragment {
btnFechar.setOnClickListener(v -> dialog.dismiss());
EditText etData = dialogView.findViewById(R.id.etData);
+ if (!defaultData.isEmpty()) {
+ etData.setText(defaultData);
+ }
etData.setOnClickListener(v -> {
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
@@ -170,7 +186,13 @@ public class TransacoesFragment extends Fragment {
Button btnAdicionarTransacao = dialogView.findViewById(R.id.btnAdicionarTransacao);
EditText etValor = dialogView.findViewById(R.id.etValor);
+ if (defaultValor > 0) {
+ etValor.setText(String.valueOf(defaultValor));
+ }
EditText etDescricao = dialogView.findViewById(R.id.etDescricao);
+ if (!defaultDescricao.isEmpty()) {
+ etDescricao.setText(defaultDescricao);
+ }
Spinner spinnerCategoria = dialogView.findViewById(R.id.spinnerCategoria);
Spinner spinnerTipo = dialogView.findViewById(R.id.spinnerTipo);
@@ -182,12 +204,26 @@ public class TransacoesFragment extends Fragment {
arrayResId, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerCategoria.setAdapter(adapter);
+
+ if (!defaultCategoria.isEmpty()) {
+ for (int i = 0; i < adapter.getCount(); i++) {
+ if (adapter.getItem(i).toString().equalsIgnoreCase(defaultCategoria)) {
+ spinnerCategoria.setSelection(i);
+ break;
+ }
+ }
+ }
}
@Override
public void onNothingSelected(AdapterView> parent) {
}
});
+
+ // Se vier da IA (geralmente despesa)
+ if (!defaultDescricao.isEmpty()) {
+ spinnerTipo.setSelection(1); // Despesa default para faturas
+ }
btnAdicionarTransacao.setOnClickListener(v -> {
String valor = etValor.getText().toString().trim();
diff --git a/app/src/main/res/layout/activity_definicoes.xml b/app/src/main/res/layout/activity_definicoes.xml
index 40c9bcb..2951792 100644
--- a/app/src/main/res/layout/activity_definicoes.xml
+++ b/app/src/main/res/layout/activity_definicoes.xml
@@ -196,6 +196,22 @@
android:padding="16dp"
android:layout_marginBottom="32dp">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_tela_inicial.xml b/app/src/main/res/layout/activity_tela_inicial.xml
index 69aa4b5..7282110 100644
--- a/app/src/main/res/layout/activity_tela_inicial.xml
+++ b/app/src/main/res/layout/activity_tela_inicial.xml
@@ -229,6 +229,16 @@
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_ativos.xml b/app/src/main/res/layout/fragment_ativos.xml
index b8fd40e..0cf8c2d 100644
--- a/app/src/main/res/layout/fragment_ativos.xml
+++ b/app/src/main/res/layout/fragment_ativos.xml
@@ -21,11 +21,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
+ android:fontFamily="sans-serif"
android:text="Ativos e Investimentos"
- android:textSize="25sp"
- android:textStyle="bold"
+ android:textAlignment="center"
+ android:textColor="#000000"
+ android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
diff --git a/app/src/main/res/layout/fragment_metas.xml b/app/src/main/res/layout/fragment_metas.xml
index 30504dc..f6017a4 100644
--- a/app/src/main/res/layout/fragment_metas.xml
+++ b/app/src/main/res/layout/fragment_metas.xml
@@ -28,7 +28,7 @@
android:layout_marginTop="50dp"
android:text="Metas Financeiras"
android:textSize="20sp"
- android:textStyle="bold"
+ android:fontFamily="sans-serif"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.147"
app:layout_constraintStart_toStartOf="parent"
diff --git a/app/src/main/res/layout/fragment_transacoes.xml b/app/src/main/res/layout/fragment_transacoes.xml
index ebf57a6..dc85f8c 100644
--- a/app/src/main/res/layout/fragment_transacoes.xml
+++ b/app/src/main/res/layout/fragment_transacoes.xml
@@ -30,9 +30,9 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
+ android:fontFamily="sans-serif"
android:text="Transações"
android:textSize="25sp"
- android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
@@ -57,7 +57,7 @@
android:layout_marginTop="20dp"
android:layout_marginEnd="25dp"
android:backgroundTint="@color/preto"
- android:text="Escanear Fatura"
+ android:text="Digitalizar Fatura"
app:cornerRadius="10sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView12" />