297 lines
13 KiB
Java
297 lines
13 KiB
Java
package com.example.pap;
|
|
|
|
import android.content.Intent;
|
|
import android.content.SharedPreferences;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.ImageDecoder;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.provider.MediaStore;
|
|
import android.util.Base64;
|
|
import android.view.View;
|
|
import android.widget.Button;
|
|
import android.widget.EditText;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import androidx.activity.result.ActivityResultLauncher;
|
|
import androidx.activity.result.contract.ActivityResultContracts;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.util.Collections;
|
|
|
|
import retrofit2.Call;
|
|
import retrofit2.Callback;
|
|
import retrofit2.Response;
|
|
|
|
public class FotoActivity extends AppCompatActivity {
|
|
|
|
private ImageView ivFotoComida;
|
|
private Button btnTirarFoto, btnGaleria, btnAnalisarIA, btnIrParaChat, btnCorrigir;
|
|
private TextView tvResultadoIA;
|
|
private Bitmap imagemCapturada;
|
|
private String textoAnalise = "";
|
|
|
|
// A TUA CHAVE DA API
|
|
private final String MINHA_API_KEY = "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";
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
setContentView(R.layout.activity_foto);
|
|
|
|
// Ligar ao XML
|
|
ivFotoComida = findViewById(R.id.ivFotoComida);
|
|
btnTirarFoto = findViewById(R.id.btnTirarFoto);
|
|
btnGaleria = findViewById(R.id.btnGaleria);
|
|
btnAnalisarIA = findViewById(R.id.btnAnalisarIA);
|
|
btnIrParaChat = findViewById(R.id.btnIrParaChat);
|
|
btnCorrigir = findViewById(R.id.btnCorrigir);
|
|
tvResultadoIA = findViewById(R.id.tvResultadoIA);
|
|
|
|
ActivityResultLauncher<Intent> camLauncher = registerForActivityResult(
|
|
new ActivityResultContracts.StartActivityForResult(),
|
|
result -> {
|
|
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
|
Bundle extras = result.getData().getExtras();
|
|
if (extras != null && extras.containsKey("data")) {
|
|
imagemCapturada = (Bitmap) extras.get("data");
|
|
mostrarImagemPreparada();
|
|
}
|
|
}
|
|
});
|
|
|
|
ActivityResultLauncher<Intent> galLauncher = registerForActivityResult(
|
|
new ActivityResultContracts.StartActivityForResult(),
|
|
result -> {
|
|
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
|
Uri uri = result.getData().getData();
|
|
try {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
ImageDecoder.Source source = ImageDecoder.createSource(this.getContentResolver(), uri);
|
|
imagemCapturada = ImageDecoder.decodeBitmap(source);
|
|
} else {
|
|
imagemCapturada = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
|
|
}
|
|
mostrarImagemPreparada();
|
|
} catch (IOException e) {
|
|
tvResultadoIA.setText("Erro ao processar imagem.");
|
|
}
|
|
}
|
|
});
|
|
|
|
btnTirarFoto.setOnClickListener(v -> camLauncher.launch(new Intent(MediaStore.ACTION_IMAGE_CAPTURE)));
|
|
|
|
btnGaleria.setOnClickListener(v -> {
|
|
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
|
|
galLauncher.launch(intent);
|
|
});
|
|
|
|
// Clique para analisar pela primeira vez
|
|
btnAnalisarIA.setOnClickListener(v -> enviarParaIA(null));
|
|
|
|
btnIrParaChat.setOnClickListener(v -> {
|
|
Intent intent = new Intent(FotoActivity.this, ChatActivity.class);
|
|
intent.putExtra("analise_comida", textoAnalise);
|
|
startActivity(intent);
|
|
});
|
|
|
|
// Clique para corrigir o erro da IA
|
|
btnCorrigir.setOnClickListener(v -> mostrarPopupCorrecao());
|
|
|
|
findViewById(R.id.btnVoltarFoto).setOnClickListener(v -> finish());
|
|
}
|
|
|
|
private void mostrarImagemPreparada() {
|
|
if (imagemCapturada != null) {
|
|
ivFotoComida.setVisibility(View.VISIBLE);
|
|
ivFotoComida.setPadding(0, 0, 0, 0);
|
|
ivFotoComida.setImageBitmap(imagemCapturada);
|
|
btnAnalisarIA.setVisibility(View.VISIBLE);
|
|
btnIrParaChat.setVisibility(View.GONE);
|
|
btnCorrigir.setVisibility(View.GONE);
|
|
tvResultadoIA.setText("Pronto para analisar.");
|
|
}
|
|
}
|
|
|
|
// Função blindada contra erros da IA
|
|
private void enviarParaIA(String comidaCerta) {
|
|
tvResultadoIA.setText("A processar... ⏳");
|
|
btnAnalisarIA.setEnabled(false);
|
|
btnIrParaChat.setVisibility(View.GONE);
|
|
btnCorrigir.setVisibility(View.GONE);
|
|
|
|
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
|
imagemCapturada.compress(Bitmap.CompressFormat.JPEG, 50, os);
|
|
String base64 = Base64.encodeToString(os.toByteArray(), Base64.NO_WRAP);
|
|
|
|
String ordemParaIA;
|
|
if (comidaCerta == null) {
|
|
// Regras super restritas para a primeira análise
|
|
ordemParaIA = "És uma API de nutrição. Avalia a foto. É ESTRITAMENTE PROIBIDO usar texto de conversa, saudações ou tags de segurança. " +
|
|
"Responde APENAS E SÓ neste formato exato:\n" +
|
|
"Prato: [Nome]\n" +
|
|
"Calorias: [Valor] kcal\n" +
|
|
"Macros: [X]g Proteína, [X]g Hidratos, [X]g Gordura\n" +
|
|
"Dica: [Frase curta sem asteriscos].";
|
|
} else {
|
|
// Regras super restritas para a correção
|
|
ordemParaIA = "Atenção: ignora a imagem. O utilizador confirmou que o prato é '" + comidaCerta + "'. " +
|
|
"É ESTRITAMENTE PROIBIDO usar texto de conversa ou avisos de segurança (ex: User:safe). " +
|
|
"Responde APENAS E SÓ com os valores nutricionais médios para '" + comidaCerta + "' neste formato exato:\n" +
|
|
"Prato: " + comidaCerta + "\n" +
|
|
"Calorias: [Valor] kcal\n" +
|
|
"Macros: [X]g Proteína, [X]g Hidratos, [X]g Gordura\n" +
|
|
"Dica: [Frase curta sem asteriscos].";
|
|
}
|
|
|
|
AiRequest request = new AiRequest(Collections.singletonList(
|
|
new Message("user", java.util.Arrays.asList(
|
|
new ContentPart("text", ordemParaIA),
|
|
new ContentPart("image_url", new ImageUrl("data:image/jpeg;base64," + base64))
|
|
))
|
|
));
|
|
|
|
AiConfig.getRetrofit().create(AiApi.class)
|
|
.analisarImagem("Bearer " + MINHA_API_KEY, request)
|
|
.enqueue(new Callback<AiResponse>() {
|
|
@Override
|
|
public void onResponse(Call<AiResponse> call, Response<AiResponse> response) {
|
|
btnAnalisarIA.setEnabled(true);
|
|
if (response.isSuccessful() && response.body() != null) {
|
|
try {
|
|
String resposta = response.body().choices.get(0).message.content;
|
|
textoAnalise = resposta.replace("**", "").replace("*", "");
|
|
|
|
// O NOSSO ESCUDO: Se a resposta não tiver a palavra "Calorias", a IA deu tilt!
|
|
if (!textoAnalise.contains("Calorias:") || !textoAnalise.contains("Macros:")) {
|
|
tvResultadoIA.setText("A IA ficou confusa com o prato 😵\u200D💫. Clica em 'Corrigir' e tenta ser mais específico (Ex: Bife com Arroz).");
|
|
btnCorrigir.setVisibility(View.VISIBLE);
|
|
return; // Pára tudo aqui, não guarda lixo na memória!
|
|
}
|
|
|
|
tvResultadoIA.setText(textoAnalise);
|
|
btnIrParaChat.setVisibility(View.VISIBLE);
|
|
btnCorrigir.setVisibility(View.VISIBLE);
|
|
|
|
// Se ele estiver a corrigir, apagamos o erro passado!
|
|
if (comidaCerta != null) {
|
|
desfazerUltimoErro();
|
|
}
|
|
|
|
// Guarda a nova resposta
|
|
extrairEGuardarDados(textoAnalise);
|
|
|
|
} catch (Exception e) { tvResultadoIA.setText("Erro na leitura da resposta."); }
|
|
} else { tvResultadoIA.setText("Erro no servidor: " + response.code()); }
|
|
}
|
|
@Override
|
|
public void onFailure(Call<AiResponse> call, Throwable t) {
|
|
btnAnalisarIA.setEnabled(true);
|
|
tvResultadoIA.setText("Sem internet.");
|
|
}
|
|
});
|
|
}
|
|
|
|
// --- POPUP PARA CORREÇÃO MANUAL ---
|
|
private void mostrarPopupCorrecao() {
|
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
builder.setTitle("Corrigir a IA 🤔");
|
|
builder.setMessage("A IA enganou-se? O que estava realmente no teu prato?");
|
|
|
|
final EditText input = new EditText(this);
|
|
input.setHint("Ex: Hambúrguer de Frango");
|
|
|
|
LinearLayout layout = new LinearLayout(this);
|
|
layout.setPadding(50, 20, 50, 0);
|
|
layout.addView(input);
|
|
builder.setView(layout);
|
|
|
|
builder.setPositiveButton("Re-Analisar", (dialog, which) -> {
|
|
String comidaCerta = input.getText().toString().trim();
|
|
if (!comidaCerta.isEmpty()) {
|
|
enviarParaIA(comidaCerta);
|
|
} else {
|
|
Toast.makeText(FotoActivity.this, "Tens de escrever a comida!", Toast.LENGTH_SHORT).show();
|
|
}
|
|
});
|
|
|
|
builder.setNegativeButton("Cancelar", null);
|
|
builder.show();
|
|
}
|
|
|
|
// --- FUNÇÃO PARA SALVAR OS DADOS E A DICA ---
|
|
private void extrairEGuardarDados(String texto) {
|
|
try {
|
|
int indexNomeStart = texto.indexOf("Prato: ") + 7;
|
|
int indexNomeEnd = texto.indexOf("\n", indexNomeStart);
|
|
String nomePrato = texto.substring(indexNomeStart, indexNomeEnd).trim();
|
|
|
|
int calorias = extrairNumero(texto, "Calorias: ", " kcal");
|
|
int proteina = extrairNumero(texto, "Macros: ", "g Proteína");
|
|
int hidratos = extrairNumero(texto, "Proteína, ", "g Hidratos");
|
|
int gordura = extrairNumero(texto, "Hidratos, ", "g Gordura");
|
|
|
|
String dicaIA = "Continua a registar refeições para ver dicas.";
|
|
if (texto.contains("Dica: ")) {
|
|
int indexDica = texto.indexOf("Dica: ") + 6;
|
|
dicaIA = texto.substring(indexDica).trim();
|
|
}
|
|
|
|
SharedPreferences prefs = getSharedPreferences("DadosSaude", MODE_PRIVATE);
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
|
|
editor.putString("ultimo_prato", nomePrato);
|
|
editor.putString("ultima_dica_ia", dicaIA);
|
|
|
|
editor.putInt("cal_hoje", prefs.getInt("cal_hoje", 0) + calorias);
|
|
editor.putInt("prot_hoje", prefs.getInt("prot_hoje", 0) + proteina);
|
|
editor.putInt("hidr_hoje", prefs.getInt("hidr_hoje", 0) + hidratos);
|
|
editor.putInt("gord_hoje", prefs.getInt("gord_hoje", 0) + gordura);
|
|
|
|
// GUAAAAARDA O ERRO PARA PODERMOS APAGAR SE O GAJO CLICAR EM "CORRIGIR"
|
|
editor.putInt("ultimo_erro_cal", calorias);
|
|
editor.putInt("ultimo_erro_prot", proteina);
|
|
editor.putInt("ultimo_erro_hidr", hidratos);
|
|
editor.putInt("ultimo_erro_gord", gordura);
|
|
|
|
editor.apply();
|
|
} catch (Exception e) {}
|
|
}
|
|
|
|
// --- FUNÇÃO PARA REMOVER O ÚLTIMO PRATO QUE FOI MAL LIDO ---
|
|
private void desfazerUltimoErro() {
|
|
SharedPreferences prefs = getSharedPreferences("DadosSaude", MODE_PRIVATE);
|
|
SharedPreferences.Editor editor = prefs.edit();
|
|
|
|
int calAntiga = prefs.getInt("ultimo_erro_cal", 0);
|
|
int protAntiga = prefs.getInt("ultimo_erro_prot", 0);
|
|
int hidrAntiga = prefs.getInt("ultimo_erro_hidr", 0);
|
|
int gordAntiga = prefs.getInt("ultimo_erro_gord", 0);
|
|
|
|
editor.putInt("cal_hoje", prefs.getInt("cal_hoje", 0) - calAntiga);
|
|
editor.putInt("prot_hoje", prefs.getInt("prot_hoje", 0) - protAntiga);
|
|
editor.putInt("hidr_hoje", prefs.getInt("hidr_hoje", 0) - hidrAntiga);
|
|
editor.putInt("gord_hoje", prefs.getInt("gord_hoje", 0) - gordAntiga);
|
|
|
|
editor.apply();
|
|
}
|
|
|
|
private int extrairNumero(String texto, String inicio, String fim) {
|
|
try {
|
|
int start = texto.indexOf(inicio) + inicio.length();
|
|
int end = texto.indexOf(fim, start);
|
|
String valorString = texto.substring(start, end).trim();
|
|
valorString = valorString.replaceAll("[^0-9]", "");
|
|
return Integer.parseInt(valorString);
|
|
} catch (Exception e) {
|
|
return 0;
|
|
}
|
|
}
|
|
} |