corrigir os novos erros amnhã e adicionar qual tipo de sexo a pessoa é no register

This commit is contained in:
2026-06-18 11:50:09 +01:00
parent 05f30539ad
commit 33a1d49b26
6 changed files with 288 additions and 100 deletions

View File

@@ -1,7 +1,9 @@
package com.example.pap;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import java.util.concurrent.TimeUnit;
public class AiConfig {
private static Retrofit retrofit;
@@ -9,8 +11,17 @@ public class AiConfig {
public static Retrofit getRetrofit() {
if (retrofit == null) {
// NOVO: Adiciona paciência ao Android (Timeout de 60 segundos)
OkHttpClient clienteComPaciencia = new OkHttpClient.Builder()
.connectTimeout(60, TimeUnit.SECONDS)
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(clienteComPaciencia) // Liga o cliente paciente
.addConverterFactory(GsonConverterFactory.create())
.build();
}

View File

@@ -2,6 +2,7 @@ package com.example.pap;
import android.content.Intent;
import android.os.Bundle;
import android.text.method.ScrollingMovementMethod;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
@@ -18,7 +19,7 @@ public class ChatActivity extends AppCompatActivity {
private Button btnEnviar;
private TextView btnVoltarChat;
// NÃO TE ESQUEÇAS DE COLAR A TUA CHAVE AQUI!
// A TUA CHAVE (cuidado na escola com ela)
private final String MINHA_API_KEY = "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";
@Override
@@ -31,17 +32,17 @@ public class ChatActivity extends AppCompatActivity {
btnEnviar = findViewById(R.id.btnEnviarChat);
btnVoltarChat = findViewById(R.id.btnVoltarChat);
// --- LÓGICA DO BOTÃO VOLTAR PARA O HOME ---
// Faz com que o texto do chat consiga rolar se for muito grande
tvChatLog.setMovementMethod(new ScrollingMovementMethod());
btnVoltarChat.setOnClickListener(v -> {
// Cria a intenção de ir para a MainActivity (Home)
Intent intent = new Intent(ChatActivity.this, HomeActivity.class);
// Esta linha garante que não ficas com mil ecrãs abertos uns por cima dos outros
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
finish(); // Fecha o Chat
finish();
});
// Receber a análise da foto se existir
// Receber a análise da foto (se veio de lá)
String analiseComida = getIntent().getStringExtra("analise_comida");
if (analiseComida != null && !analiseComida.isEmpty()) {
tvChatLog.setText("IA: Analisei o teu prato.\n" + analiseComida + "\n\nO que queres saber mais?");
@@ -59,9 +60,10 @@ public class ChatActivity extends AppCompatActivity {
private void perguntarIA(String texto) {
tvChatLog.append("\n\nIA: A pensar... ⏳");
btnEnviar.setEnabled(false); // Bloqueia o botão para não haver spam
AiRequest request = new AiRequest(java.util.Arrays.asList(
new Message("system", Collections.singletonList(new ContentPart("text", "És um nutricionista de Portugal. Responde SEMPRE de forma muito curta (máximo 2 frases). Nunca uses asteriscos."))),
new Message("system", Collections.singletonList(new ContentPart("text", "És um nutricionista de Portugal. Responde SEMPRE de forma muito curta (máximo 3 frases). Nunca uses asteriscos."))),
new Message("user", Collections.singletonList(new ContentPart("text", texto)))
));
@@ -70,17 +72,23 @@ public class ChatActivity extends AppCompatActivity {
.enqueue(new Callback<AiResponse>() {
@Override
public void onResponse(Call<AiResponse> call, Response<AiResponse> response) {
btnEnviar.setEnabled(true);
if (response.isSuccessful() && response.body() != null) {
String resposta = response.body().choices.get(0).message.content;
String limpa = resposta.replace("**", "").replace("*", "");
String atual = tvChatLog.getText().toString();
tvChatLog.setText(atual.replace("IA: A pensar... ⏳", "IA: " + limpa));
} else {
// Se a API chumbou mas houve resposta (Erro 400, 429...)
String atual = tvChatLog.getText().toString();
tvChatLog.setText(atual.replace("IA: A pensar... ⏳", "IA: Tive um pequeno bloqueio (Erro " + response.code() + "). Tenta outra vez!"));
}
}
@Override
public void onFailure(Call<AiResponse> call, Throwable t) {
btnEnviar.setEnabled(true);
String atual = tvChatLog.getText().toString();
tvChatLog.setText(atual.replace("IA: A pensar... ⏳", "IA: Erro de rede."));
tvChatLog.setText(atual.replace("IA: A pensar... ⏳", "IA: Erro de comunicação (O servidor demorou muito a responder). Tenta novamente."));
}
});
}

View File

@@ -1,18 +1,26 @@
package com.example.pap;
import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Base64;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
@@ -34,6 +42,9 @@ public class DesafiosActivity extends AppCompatActivity {
private int desafioAtualSendoGravado = -1; // 0=Agua, 1=D1, 2=D2, 3=D3, 4=D4
private float litrosAgua = 0.0f;
private ActivityResultLauncher<Intent> videoLauncher;
private AlertDialog popupCarregamento; // A janela de loading
private final String MINHA_API_KEY = "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";
@Override
@@ -42,6 +53,9 @@ public class DesafiosActivity extends AppCompatActivity {
setContentView(R.layout.activity_desafios);
tvStatusGeralIA = findViewById(R.id.tvStatusGeralIA);
// Esconde o texto antigo do topo, já não precisamos dele!
tvStatusGeralIA.setVisibility(View.GONE);
tvStatusAgua = findViewById(R.id.tvStatusAgua);
tvStatusD1 = findViewById(R.id.tvStatusD1);
tvStatusD2 = findViewById(R.id.tvStatusD2);
@@ -58,7 +72,7 @@ public class DesafiosActivity extends AppCompatActivity {
verificarResetMeiaNoite();
ActivityResultLauncher<Intent> videoLauncher = registerForActivityResult(
videoLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
@@ -69,18 +83,41 @@ public class DesafiosActivity extends AppCompatActivity {
}
});
btnVideoAgua.setOnClickListener(v -> { desafioAtualSendoGravado = 0; abrirCamera(videoLauncher); });
btnVideoD1.setOnClickListener(v -> { desafioAtualSendoGravado = 1; abrirCamera(videoLauncher); });
btnVideoD2.setOnClickListener(v -> { desafioAtualSendoGravado = 2; abrirCamera(videoLauncher); });
btnVideoD3.setOnClickListener(v -> { desafioAtualSendoGravado = 3; abrirCamera(videoLauncher); });
btnVideoD4.setOnClickListener(v -> { desafioAtualSendoGravado = 4; abrirCamera(videoLauncher); });
btnVideoAgua.setOnClickListener(v -> { desafioAtualSendoGravado = 0; verificarPermissaoEAbrir(); });
btnVideoD1.setOnClickListener(v -> { desafioAtualSendoGravado = 1; verificarPermissaoEAbrir(); });
btnVideoD2.setOnClickListener(v -> { desafioAtualSendoGravado = 2; verificarPermissaoEAbrir(); });
btnVideoD3.setOnClickListener(v -> { desafioAtualSendoGravado = 3; verificarPermissaoEAbrir(); });
btnVideoD4.setOnClickListener(v -> { desafioAtualSendoGravado = 4; verificarPermissaoEAbrir(); });
}
private void abrirCamera(ActivityResultLauncher<Intent> launcher) {
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
launcher.launch(intent);
private void verificarPermissaoEAbrir() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, 100);
} else {
abrirCameraReal();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 100) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
abrirCameraReal();
} else {
Toast.makeText(this, "Precisas de dar permissão da câmara para fazer o desafio!", Toast.LENGTH_SHORT).show();
}
}
}
private void abrirCameraReal() {
try {
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);
videoLauncher.launch(intent);
} catch (Exception e) {
Toast.makeText(this, "Erro: Não foi possível iniciar a câmara.", Toast.LENGTH_LONG).show();
}
}
private void verificarResetMeiaNoite() {
@@ -97,7 +134,6 @@ public class DesafiosActivity extends AppCompatActivity {
editor.putBoolean("d3_concluido", false);
editor.putBoolean("d4_concluido", false);
// Zera a água e as calorias dos desafios diários
editor.putInt("agua_hoje", 0);
editor.putInt("calorias_desafios", 0);
editor.apply();
@@ -128,14 +164,46 @@ public class DesafiosActivity extends AppCompatActivity {
}
}
// ==========================================
// NOVOS POP-UPS DE LOADING E RESULTADOS
// ==========================================
private void mostrarLoading() {
runOnUiThread(() -> {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("A analisar o vídeo... ⏳");
builder.setMessage("A Inteligência Artificial está a avaliar o teu desempenho. Por favor, aguarda um momento.");
builder.setCancelable(false); // Impede que o utilizador feche sem querer
popupCarregamento = builder.create();
popupCarregamento.show();
});
}
private void mostrarResultadoFinal(String titulo, String mensagem) {
runOnUiThread(() -> {
// Fecha a janela de "A aguardar..."
if (popupCarregamento != null && popupCarregamento.isShowing()) {
popupCarregamento.dismiss();
}
// Abre a janela com o resultado final
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(titulo);
builder.setMessage(mensagem);
builder.setPositiveButton("OK", null);
builder.show();
});
}
private void enviarVideoParaIA(Uri uri) {
tvStatusGeralIA.setText("A processar vídeo e enviar para a IA... ⏳");
bloquearBotoes(false);
mostrarLoading(); // Mostra a janela de loading logo aqui!
new Thread(() -> {
String base64Video = converterVideo(uri);
if (base64Video == null) {
runOnUiThread(() -> { tvStatusGeralIA.setText("Erro ao ler vídeo."); bloquearBotoes(true); });
mostrarResultadoFinal("Erro no Vídeo ⚠️", "Não foi possível ler o vídeo gravado.");
runOnUiThread(() -> bloquearBotoes(true));
return;
}
@@ -162,12 +230,14 @@ public class DesafiosActivity extends AppCompatActivity {
String respostaIA = response.body().choices.get(0).message.content;
processarResposta(respostaIA);
} else {
runOnUiThread(() -> { tvStatusGeralIA.setText("Erro na IA: " + response.code()); bloquearBotoes(true); });
mostrarResultadoFinal("Erro de Servidor ⚠️", "Erro ao contactar a IA. Código: " + response.code());
runOnUiThread(() -> bloquearBotoes(true));
}
}
@Override
public void onFailure(Call<AiResponse> call, Throwable t) {
runOnUiThread(() -> { tvStatusGeralIA.setText("Erro de rede."); bloquearBotoes(true); });
mostrarResultadoFinal("Sem Internet 🌐", "Não conseguimos ligar à IA. Verifica a tua rede.");
runOnUiThread(() -> bloquearBotoes(true));
}
});
}).start();
@@ -191,10 +261,13 @@ public class DesafiosActivity extends AppCompatActivity {
editor.putFloat("agua_litros", litrosAgua);
editor.putInt("agua_hoje", (int) (litrosAgua / 0.25f));
tvStatusGeralIA.setText("IA leu: +" + lido + " Litros!");
// Mostra o resultado da Água!
mostrarResultadoFinal("Bom trabalho! 💧", "A IA detetou que bebeste +" + lido + " Litros!");
} catch (Exception e) {
tvStatusGeralIA.setText("Erro a ler os litros. Tenta de novo.");
mostrarResultadoFinal("Oops! 🤔", "A IA teve dificuldade em ler a quantidade exata. Tenta gravar de novo.");
}
} else {
mostrarResultadoFinal("Oops! 🤔", "A IA não percebeu o vídeo. Garante que o copo ou garrafa se vê bem!");
}
} else {
if (texto.contains("Status: Concluido")) {
@@ -205,17 +278,17 @@ public class DesafiosActivity extends AppCompatActivity {
if (desafioAtualSendoGravado == 3) { editor.putBoolean("d3_concluido", true); caloriasAQueimar = 2; }
if (desafioAtualSendoGravado == 4) { editor.putBoolean("d4_concluido", true); caloriasAQueimar = 3; }
// Soma as calorias queimadas e atualiza a pontuação
int caloriasTotaisQueimadas = prefs.getInt("calorias_desafios", 0) + caloriasAQueimar;
editor.putInt("calorias_desafios", caloriasTotaisQueimadas);
tvStatusGeralIA.setText("IA: Desafio Validado! ✅ +50 Pontos | 🔥 +" + caloriasAQueimar + " kcal");
perfilEditor.putInt("pontos", perfilPrefs.getInt("pontos", 0) + 50);
perfilEditor.putInt("desafios_concluidos", perfilPrefs.getInt("desafios_concluidos", 0) + 1);
perfilEditor.apply();
// Mostra o resultado do Exercício!
mostrarResultadoFinal("Desafio Validado! ✅", "Ganhaste +50 Pontos e queimaste " + caloriasAQueimar + " kcal. Continua assim!");
} else {
tvStatusGeralIA.setText("IA: Desafio Falhou ou vídeo pouco claro. ❌");
mostrarResultadoFinal("Desafio Falhou ", "A IA acha que o movimento não foi claro ou bem feito. Tenta outra vez!");
}
}
editor.apply();

View File

@@ -16,12 +16,15 @@ import retrofit2.Response;
public class EstatisticasActivity extends AppCompatActivity {
private TextView tvValorIMC, tvStatusIMC, tvCaloriasMeta, tvDicaIA, tvCaloriasQueimadas;
private TextView tvProtGramas, tvHidrGramas, tvGordGramas, tvComida1;
private TextView tvValorIMC, tvStatusIMC, tvCaloriasMeta, tvCaloriasQueimadas;
private TextView tvProtGramas, tvHidrGramas, tvGordGramas, tvComida1, tvCaloriasConsumidas;
private TextView tvAguaStats;
private ProgressBar progressProt, progressHidr, progressGord;
// COLOCA A TUA API KEY AQUI
// AS 3 TEXTVIEWS DAS NOSSAS IAS ESPECIALIZADAS
private TextView tvAiTopGeral, tvAiMacrosConselho, tvAiProximaRefeicao;
private int metaCaloriasDiarias = 0;
private final String MINHA_API_KEY = "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";
@Override
@@ -32,32 +35,35 @@ public class EstatisticasActivity extends AppCompatActivity {
tvValorIMC = findViewById(R.id.tvValorIMC);
tvStatusIMC = findViewById(R.id.tvStatusIMC);
tvCaloriasMeta = findViewById(R.id.tvCaloriasMeta);
tvDicaIA = findViewById(R.id.tvDicaIA);
tvCaloriasConsumidas = findViewById(R.id.tvCaloriasConsumidas);
tvCaloriasQueimadas = findViewById(R.id.tvCaloriasQueimadas);
tvProtGramas = findViewById(R.id.tvProtGramas);
tvHidrGramas = findViewById(R.id.tvHidrGramas);
tvGordGramas = findViewById(R.id.tvGordGramas);
tvComida1 = findViewById(R.id.tvComida1);
tvAguaStats = findViewById(R.id.tvAguaStats);
progressProt = findViewById(R.id.progressProt);
progressHidr = findViewById(R.id.progressHidr);
progressGord = findViewById(R.id.progressGord);
// Ligar os IDs das 3 novas caixas de IA
tvAiTopGeral = findViewById(R.id.tvAiTopGeral);
tvAiMacrosConselho = findViewById(R.id.tvAiMacrosConselho);
tvAiProximaRefeicao = findViewById(R.id.tvAiProximaRefeicao);
findViewById(R.id.btnVoltarStats).setOnClickListener(v -> finish());
// Carrega os painéis locais
// Atualizar painéis locais
calcularIMC();
calcularTMB();
carregarMacrosDaIA();
carregarAgua();
carregarCaloriasQueimadas();
// CHAMA A IA PARA AVALIAR O ESTADO GERAL DO UTILIZADOR
gerarInsightInteligente();
// ARRANCAR AS 3 IAS EM SIMULTÂNEO!
iniciarComiteIAs();
}
private void calcularIMC() {
@@ -92,7 +98,7 @@ public class EstatisticasActivity extends AppCompatActivity {
SharedPreferences prefs = getSharedPreferences("MeusDadosApp", MODE_PRIVATE);
float peso = prefs.getFloat("peso", 0);
float alturaMetros = prefs.getFloat("altura", 0);
String sexo = prefs.getString("sexo", "Masculino"); // Assumido Masculino como padrão
String sexo = prefs.getString("sexo", "Masculino");
int idade = prefs.getInt("idade", 20);
if (peso > 0 && alturaMetros > 0) {
@@ -106,7 +112,8 @@ public class EstatisticasActivity extends AppCompatActivity {
}
double caloriasTotais = tmb * 1.2;
tvCaloriasMeta.setText(String.valueOf((int) caloriasTotais));
metaCaloriasDiarias = (int) caloriasTotais;
tvCaloriasMeta.setText(String.valueOf(metaCaloriasDiarias));
} else {
tvCaloriasMeta.setText("--");
}
@@ -115,11 +122,16 @@ public class EstatisticasActivity extends AppCompatActivity {
private void carregarMacrosDaIA() {
SharedPreferences prefs = getSharedPreferences("DadosSaude", MODE_PRIVATE);
int caloriasConsumidasHoje = prefs.getInt("cal_hoje", 0);
int prot = prefs.getInt("prot_hoje", 0);
int hidr = prefs.getInt("hidr_hoje", 0);
int gord = prefs.getInt("gord_hoje", 0);
String ultimaComida = prefs.getString("ultimo_prato", "Ainda não leste nada hoje.");
int caloriasQueFaltam = metaCaloriasDiarias - caloriasConsumidasHoje;
if (caloriasQueFaltam < 0) caloriasQueFaltam = 0;
tvCaloriasConsumidas.setText("Consumido: " + caloriasConsumidasHoje + " kcal / Faltam: " + caloriasQueFaltam + " kcal");
tvProtGramas.setText("Proteína: " + prot + "g");
tvHidrGramas.setText("Hidratos: " + hidr + "g");
tvGordGramas.setText("Gordura: " + gord + "g");
@@ -145,12 +157,10 @@ public class EstatisticasActivity extends AppCompatActivity {
}
// ==========================================
// NOVA FUNÇÃO: O CÉREBRO DA IA NAS ESTATÍSTICAS
// FUNÇÃO MASTER: GERE AS 3 IAS DE SEGUIDA
// ==========================================
private void gerarInsightInteligente() {
tvDicaIA.setText("A analisar os teus dados diários... ⏳");
// 1. Recolher a informação toda das gavetas (SharedPreferences)
private void iniciarComiteIAs() {
// Puxar os dados locais necessários
SharedPreferences prefsDados = getSharedPreferences("MeusDadosApp", MODE_PRIVATE);
SharedPreferences prefsSaude = getSharedPreferences("DadosSaude", MODE_PRIVATE);
SharedPreferences prefsGam = getSharedPreferences("DadosGamificacao", MODE_PRIVATE);
@@ -159,49 +169,102 @@ public class EstatisticasActivity extends AppCompatActivity {
float altura = prefsDados.getFloat("altura", 0);
float imc = (peso > 0 && altura > 0) ? (peso / (altura * altura)) : 0;
int calConsumidas = prefsSaude.getInt("cal_hoje", 0);
int prot = prefsSaude.getInt("prot_hoje", 0);
int hidr = prefsSaude.getInt("hidr_hoje", 0);
int gord = prefsSaude.getInt("gord_hoje", 0);
String ultimaComida = prefsSaude.getString("ultimo_prato", "Nenhuma");
float litrosAgua = prefsGam.getInt("agua_hoje", 0) * 0.25f;
int kcalQueimadas = prefsGam.getInt("calorias_desafios", 0);
// 2. Criar o Prompt a explicar o cenário à IA
String promptDaIA = "És o treinador e nutricionista pessoal do utilizador. " +
"Os dados dele hoje são: IMC = " + String.format(Locale.getDefault(), "%.1f", imc) + ", " +
"Água bebida = " + litrosAgua + " Litros, Calorias Queimadas = " + kcalQueimadas + " kcal. " +
"Macros consumidos: Proteína=" + prot + "g, Hidratos=" + hidr + "g, Gordura=" + gord + "g. " +
"Avalia estes números de forma realista. Se ele bebeu pouca água, avisa-o. Se tem pouca proteína, alerta-o. " +
"Regra de Ouro: Escreve apenas 2 frases curtas, diretas e motivadoras. NÃO USES ASTERISCOS.";
// Chamar os 3 consultores assíncronos
chamarIa3TopGeral(imc, litrosAgua, kcalQueimadas, calConsumidas);
chamarIa1Macros(prot, hidr, gord);
chamarIa2ProximaRefeicao(ultimaComida);
}
// 3. Enviar o pedido usando a classe AiApi que já tens (enviamos só texto, sem imagem)
// --- IA 3: O DIAGNÓSTICO DO TOPO ---
private void chamarIa3TopGeral(float imc, float agua, int queimadas, int consumidas) {
tvAiTopGeral.setText("A avaliar o teu dia geral... ⏳");
String prompt = "És um Médico Nutricionista Clínico. Analisa estes dados globais de hoje do paciente: " +
"IMC=" + String.format(Locale.getDefault(), "%.1f", imc) + ", Água=" + agua + "L, " +
"Calorias Gastas em Exercício=" + queimadas + "kcal, Calorias Consumidas=" + consumidas + "kcal (Meta=" + metaCaloriasDiarias + "). " +
"Dá um diagnóstico curto, sério e aponta os riscos de saúde imediatos se os valores forem maus. " +
"Regra: Apenas 2 frases em Português de Portugal. Não uses asteriscos.";
fazerPedidoIA(prompt, new AiCallback() {
@Override
public void onSucesso(String resposta) { tvAiTopGeral.setText(resposta); }
@Override
public void onFalha() { tvAiTopGeral.setText("Não foi possível processar o diagnóstico geral de saúde."); }
});
}
// --- IA 1: O AJUSTADOR DE MACROS ---
private void chamarIa1Macros(int prot, int hidr, int gord) {
tvAiMacrosConselho.setText("A analisar balanço de macronutrientes... ⏳");
String prompt = "És um Especialista em Macronutrientes de Alta Performance. O atleta consumiu hoje: " +
"Proteína=" + prot + "g, Hidratos=" + hidr + "g, Gordura=" + gord + "g. " +
"Com base nestes números exatos, diz o que ele deve ajustar ou cortar na próxima refeição de hoje. " +
"Regra: Resposta direta, sem saudações, apenas 2 frases em Português de Portugal. Não uses asteriscos.";
fazerPedidoIA(prompt, new AiCallback() {
@Override
public void onSucesso(String resposta) { tvAiMacrosConselho.setText(resposta); }
@Override
public void onFalha() { tvAiMacrosConselho.setText("Impossível ligar ao consultor de macros."); }
});
}
// --- IA 2: O PLANER DE REFEIÇÕES ---
private void chamarIa2ProximaRefeicao(String ultimaComida) {
tvAiProximaRefeicao.setText("A planear receita de compensação... ⏳");
String prompt = "És um Chef Nutricional Inteligente. A última refeição que o utilizador registou foi: '" + ultimaComida + "'. " +
"Com base apenas nisto, sugere concretamente uma opção de refeição (ou snack) saudável e equilibrada para ele comer a seguir. " +
"Regra: Dá uma sugestão específica de comida em 2 frases curtas, em Português de Portugal. Não uses asteriscos.";
fazerPedidoIA(prompt, new AiCallback() {
@Override
public void onSucesso(String resposta) { tvAiProximaRefeicao.setText(resposta); }
@Override
public void onFalha() { tvAiProximaRefeicao.setText("Ativa a internet para veres a sugestão do Chef IA."); }
});
}
// --- MOTOR GENÉRICO DE NETWORK PARA RETROFIT ---
private void fazerPedidoIA(String prompt, final AiCallback callback) {
AiRequest request = new AiRequest(Collections.singletonList(
new Message("user", Collections.singletonList(new ContentPart("text", promptDaIA)))
new Message("user", Collections.singletonList(new ContentPart("text", prompt)))
));
AiConfig.getRetrofit().create(AiApi.class)
.analisarImagem("Bearer " + MINHA_API_KEY, request) // Usamos o mesmo método mas sem enviar a foto
.analisarImagem("Bearer " + MINHA_API_KEY, request)
.enqueue(new Callback<AiResponse>() {
@Override
public void onResponse(Call<AiResponse> call, Response<AiResponse> response) {
if (response.isSuccessful() && response.body() != null) {
try {
String resposta = response.body().choices.get(0).message.content;
// Limpa qualquer formatação extra que a IA tente mandar
String dicaLimpa = resposta.replace("**", "").replace("*", "");
tvDicaIA.setText(dicaLimpa);
} catch (Exception e) {
tvDicaIA.setText("Continua o bom trabalho! Mantém o foco na água e macros.");
}
} else {
tvDicaIA.setText("Servidor indisponível no momento. Foca-te em beber água!");
}
String texto = response.body().choices.get(0).message.content;
String limpo = texto.replace("**", "").replace("*", "");
callback.onSucesso(limpo.trim());
} catch (Exception e) { callback.onFalha(); }
} else { callback.onFalha(); }
}
@Override
public void onFailure(Call<AiResponse> call, Throwable t) {
tvDicaIA.setText("Estás offline. Regista os dados localmente!");
callback.onFalha();
}
});
}
// Interface interna para gerir as respostas assíncronas em paralelo
interface AiCallback {
void onSucesso(String resposta);
void onFalha();
}
}

View File

@@ -38,7 +38,7 @@ public class FotoActivity extends AppCompatActivity {
private Bitmap imagemCapturada;
private String textoAnalise = "";
// MANTÉM A TUA CHAVE AQUI
// A TUA CHAVE DA API
private final String MINHA_API_KEY = "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";
@Override
@@ -46,12 +46,13 @@ public class FotoActivity extends AppCompatActivity {
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); // Ligado ao XML
btnCorrigir = findViewById(R.id.btnCorrigir);
tvResultadoIA = findViewById(R.id.tvResultadoIA);
ActivityResultLauncher<Intent> camLauncher = registerForActivityResult(
@@ -86,6 +87,7 @@ public class FotoActivity extends AppCompatActivity {
});
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);
@@ -100,7 +102,7 @@ public class FotoActivity extends AppCompatActivity {
startActivity(intent);
});
// NOVO: Clique para corrigir erro
// Clique para corrigir o erro da IA
btnCorrigir.setOnClickListener(v -> mostrarPopupCorrecao());
findViewById(R.id.btnVoltarFoto).setOnClickListener(v -> finish());
@@ -113,12 +115,12 @@ public class FotoActivity extends AppCompatActivity {
ivFotoComida.setImageBitmap(imagemCapturada);
btnAnalisarIA.setVisibility(View.VISIBLE);
btnIrParaChat.setVisibility(View.GONE);
btnCorrigir.setVisibility(View.GONE); // Esconde a correção até analisar
btnCorrigir.setVisibility(View.GONE);
tvResultadoIA.setText("Pronto para analisar.");
}
}
// Função melhorada que aceita a comida certa se o utilizador corrigir
// Função blindada contra erros da IA
private void enviarParaIA(String comidaCerta) {
tvResultadoIA.setText("A processar... ⏳");
btnAnalisarIA.setEnabled(false);
@@ -131,23 +133,22 @@ public class FotoActivity extends AppCompatActivity {
String ordemParaIA;
if (comidaCerta == null) {
// Análise normal
ordemParaIA = "És um nutricionista prático. Identifica a comida e dá os valores de forma SUPER RESUMIDA. " +
"REGRAS: 1. Português de Portugal. 2. SEM asteriscos. 3. Máximo 4 linhas. " +
"Formato exato: \n" +
// 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: [Uma frase curta].";
"Dica: [Frase curta sem asteriscos].";
} else {
// Análise forçada com a correção do utilizador
ordemParaIA = "Atenção: A tua análise anterior falhou. O prato na imagem é na verdade: '" + comidaCerta + "'. " +
"Esquece tudo o resto e foca-te em dar os valores reais APENAS para '" + comidaCerta + "'. " +
"Usa este formato exato: \n" +
// 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 de saúde curta e sem asteriscos].";
"Dica: [Frase curta sem asteriscos].";
}
AiRequest request = new AiRequest(Collections.singletonList(
@@ -167,9 +168,15 @@ public class FotoActivity extends AppCompatActivity {
try {
String resposta = response.body().choices.get(0).message.content;
textoAnalise = resposta.replace("**", "").replace("*", "");
tvResultadoIA.setText(textoAnalise);
// Mostra os botões
// 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);
@@ -181,8 +188,8 @@ public class FotoActivity extends AppCompatActivity {
// Guarda a nova resposta
extrairEGuardarDados(textoAnalise);
} catch (Exception e) { tvResultadoIA.setText("Erro na resposta."); }
} else { tvResultadoIA.setText("Erro: " + response.code()); }
} 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) {
@@ -209,7 +216,7 @@ public class FotoActivity extends AppCompatActivity {
builder.setPositiveButton("Re-Analisar", (dialog, which) -> {
String comidaCerta = input.getText().toString().trim();
if (!comidaCerta.isEmpty()) {
enviarParaIA(comidaCerta); // Manda o texto escrito pelo user
enviarParaIA(comidaCerta);
} else {
Toast.makeText(FotoActivity.this, "Tens de escrever a comida!", Toast.LENGTH_SHORT).show();
}

View File

@@ -53,10 +53,10 @@
app:cardElevation="0dp"
app:cardBackgroundColor="#F8FAFC">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="16dp" android:gravity="center_vertical">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="" android:textSize="24sp" android:layout_marginEnd="12dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="👑" android:textSize="24sp" android:layout_marginEnd="12dp"/>
<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="Insight da IA" android:textSize="12sp" android:textStyle="bold" android:textColor="#0284C7"/>
<TextView android:id="@+id/tvDicaIA" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Continua a registar as tuas refeições para ver dicas." android:textSize="14sp" android:textColor="#334155" android:layout_marginTop="2dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Diagnóstico Geral da IA" android:textSize="12sp" android:textStyle="bold" android:textColor="#0284C7"/>
<TextView android:id="@+id/tvAiTopGeral" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="A calcular o teu diagnóstico de saúde hoje..." android:textSize="14sp" android:textColor="#334155" android:layout_marginTop="2dp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
@@ -125,7 +125,6 @@
android:orientation="horizontal"
android:layout_marginBottom="16dp"
android:baselineAligned="false">
<androidx.cardview.widget.CardView
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -159,28 +158,41 @@
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginBottom="8dp"
app:cardCornerRadius="24dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#F2F2F7">
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="20dp">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Macronutrientes Hoje" android:textStyle="bold" android:textColor="#1C1C1E" android:textSize="16sp" android:layout_marginBottom="12dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Macronutrientes Hoje" android:textStyle="bold" android:textColor="#1C1C1E" android:textSize="16sp" />
<TextView android:id="@+id/tvCaloriasConsumidas" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Consumido: 0 / Faltam: -- kcal" android:textSize="13sp" android:textColor="#8E8E93" android:layout_marginBottom="12dp"/>
<TextView android:id="@+id/tvProtGramas" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Proteína: 0g" android:textSize="13sp" android:textColor="#3A3A3C"/>
<ProgressBar android:id="@+id/progressProt" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="6dp" android:max="150" android:progress="0" android:progressTint="#1C1C1E" android:layout_marginBottom="12dp"/>
<ProgressBar android:id="@+id/progressProt" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="6dp" android:max="150" android:progress="0" android:progressTint="#3B82F6" android:layout_marginBottom="12dp"/>
<TextView android:id="@+id/tvHidrGramas" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hidratos: 0g" android:textSize="13sp" android:textColor="#3A3A3C"/>
<ProgressBar android:id="@+id/progressHidr" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="6dp" android:max="250" android:progress="0" android:progressTint="#1C1C1E" android:layout_marginBottom="12dp"/>
<ProgressBar android:id="@+id/progressHidr" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="6dp" android:max="250" android:progress="0" android:progressTint="#F59E0B" android:layout_marginBottom="12dp"/>
<TextView android:id="@+id/tvGordGramas" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Gordura: 0g" android:textSize="13sp" android:textColor="#3A3A3C"/>
<ProgressBar android:id="@+id/progressGord" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="6dp" android:max="80" android:progress="0" android:progressTint="#1C1C1E" android:layout_marginBottom="8dp"/>
<ProgressBar android:id="@+id/progressGord" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="6dp" android:max="80" android:progress="0" android:progressTint="#EF4444" android:layout_marginBottom="8dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardCornerRadius="20dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#F0FDF4"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="16dp" android:gravity="center_vertical">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="📊" android:textSize="24sp" android:layout_marginEnd="12dp"/>
<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="Ajustador de Macros IA" android:textSize="12sp" android:textStyle="bold" android:textColor="#16A34A"/>
<TextView android:id="@+id/tvAiMacrosConselho" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="A ler os teus rácios de macros..." android:textSize="14sp" android:textColor="#166534" android:layout_marginTop="2dp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
app:cardCornerRadius="24dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#F2F2F7">
@@ -190,6 +202,20 @@
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="20dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#FFF7ED"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="16dp" android:gravity="center_vertical">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="💡" android:textSize="24sp" android:layout_marginEnd="12dp"/>
<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="Sugestão de Próxima Refeição IA" android:textSize="12sp" android:textStyle="bold" android:textColor="#EA580C"/>
<TextView android:id="@+id/tvAiProximaRefeicao" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="A planear o teu próximo prato..." android:textSize="14sp" android:textColor="#9A3412" android:layout_marginTop="2dp"/>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</ScrollView>
</LinearLayout>