Compare commits

..

2 Commits

5 changed files with 315 additions and 316 deletions

View File

@@ -2,23 +2,24 @@ package com.example.pap;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.graphics.Bitmap; import android.graphics.Color;
import android.media.MediaMetadataRetriever;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.util.Base64; import android.util.Base64;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.Locale;
import retrofit2.Call; import retrofit2.Call;
import retrofit2.Callback; import retrofit2.Callback;
@@ -26,195 +27,215 @@ import retrofit2.Response;
public class DesafiosActivity extends AppCompatActivity { public class DesafiosActivity extends AppCompatActivity {
private ProgressBar progressAgua; private TextView tvStatusGeralIA;
private TextView tvStatusAgua; private TextView tvStatusAgua, tvStatusD1, tvStatusD2, tvStatusD3, tvStatusD4;
private Button btnGravarAgua, btnGravarEx1, btnGravarEx2; private Button btnVideoAgua, btnVideoD1, btnVideoD2, btnVideoD3, btnVideoD4;
private int coposBebidos = 0;
private final int META_COPOS = 8;
// Variável para sabermos qual botão o utilizador clicou private int desafioAtualSendoGravado = -1; // 0=Agua, 1=D1, 2=D2, 3=D3, 4=D4
private String desafioAtual = ""; private float litrosAgua = 0.0f;
// COLA A TUA CHAVE AQUI private final String MINHA_API_KEY = "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";
private final String MINHA_API_KEY = "sk-or-v1-e65c704789ff164d6ed1be48881dcfa83d9e7f359650f16cf7680dd822e5592bA";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_desafios); setContentView(R.layout.activity_desafios);
progressAgua = findViewById(R.id.progressAgua); tvStatusGeralIA = findViewById(R.id.tvStatusGeralIA);
tvStatusAgua = findViewById(R.id.tvStatusAgua); tvStatusAgua = findViewById(R.id.tvStatusAgua);
btnGravarAgua = findViewById(R.id.btnGravarAgua); tvStatusD1 = findViewById(R.id.tvStatusD1);
btnGravarEx1 = findViewById(R.id.btnGravarEx1); tvStatusD2 = findViewById(R.id.tvStatusD2);
btnGravarEx2 = findViewById(R.id.btnGravarEx2); tvStatusD3 = findViewById(R.id.tvStatusD3);
tvStatusD4 = findViewById(R.id.tvStatusD4);
// Carregar a água que já bebeu hoje btnVideoAgua = findViewById(R.id.btnVideoAgua);
SharedPreferences prefs = getSharedPreferences("DadosGamificacao", MODE_PRIVATE); btnVideoD1 = findViewById(R.id.btnVideoD1);
coposBebidos = prefs.getInt("agua_hoje", 0); btnVideoD2 = findViewById(R.id.btnVideoD2);
atualizarProgressoAgua(); btnVideoD3 = findViewById(R.id.btnVideoD3);
btnVideoD4 = findViewById(R.id.btnVideoD4);
// Launcher que recebe o vídeo da câmara findViewById(R.id.btnVoltarDesafios).setOnClickListener(v -> finish());
verificarResetMeiaNoite();
// Launcher da Câmara
ActivityResultLauncher<Intent> videoLauncher = registerForActivityResult( ActivityResultLauncher<Intent> videoLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(), new ActivityResultContracts.StartActivityForResult(),
result -> { result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) { if (result.getResultCode() == RESULT_OK && result.getData() != null) {
Uri videoUri = result.getData().getData(); Uri uriVideo = result.getData().getData();
extrairFrameEEnviarIA(videoUri); if (uriVideo != null) {
} else { enviarVideoParaIA(uriVideo);
Toast.makeText(this, "Vídeo cancelado.", Toast.LENGTH_SHORT).show(); }
desbloquearBotoes();
} }
}); });
// Configurar Botões (Avisamos qual é o desafio antes de abrir a câmara) // Configurar Botões
btnGravarAgua.setOnClickListener(v -> { btnVideoAgua.setOnClickListener(v -> { desafioAtualSendoGravado = 0; abrirCamera(videoLauncher); });
desafioAtual = "agua"; btnVideoD1.setOnClickListener(v -> { desafioAtualSendoGravado = 1; abrirCamera(videoLauncher); });
bloquearBotoes(); btnVideoD2.setOnClickListener(v -> { desafioAtualSendoGravado = 2; abrirCamera(videoLauncher); });
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); btnVideoD3.setOnClickListener(v -> { desafioAtualSendoGravado = 3; abrirCamera(videoLauncher); });
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 5); // 5 Segundos chega para beber água btnVideoD4.setOnClickListener(v -> { desafioAtualSendoGravado = 4; abrirCamera(videoLauncher); });
videoLauncher.launch(intent); }
});
btnGravarEx1.setOnClickListener(v -> { private void abrirCamera(ActivityResultLauncher<Intent> launcher) {
desafioAtual = "agachamento";
bloquearBotoes();
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE); Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10); intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);
videoLauncher.launch(intent); intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
}); launcher.launch(intent);
btnGravarEx2.setOnClickListener(v -> {
desafioAtual = "flexoes";
bloquearBotoes();
Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, 10);
videoLauncher.launch(intent);
});
findViewById(R.id.btnVoltarDesafios).setOnClickListener(v -> finish());
} }
private void extrairFrameEEnviarIA(Uri videoUri) { private void verificarResetMeiaNoite() {
Toast.makeText(this, "A extrair frame do vídeo...", Toast.LENGTH_SHORT).show(); SharedPreferences prefs = getSharedPreferences("DadosGamificacao", MODE_PRIVATE);
String dataHoje = new SimpleDateFormat("dd-MM-yyyy", Locale.getDefault()).format(new Date());
String dataGuardada = prefs.getString("data_5_desafios", "");
try { if (!dataHoje.equals(dataGuardada)) {
// Isto vai ao vídeo e tira uma "foto" a meio do tempo // Limpar tudo
MediaMetadataRetriever retriever = new MediaMetadataRetriever(); SharedPreferences.Editor editor = prefs.edit();
retriever.setDataSource(this, videoUri); editor.putString("data_5_desafios", dataHoje);
editor.putFloat("agua_litros", 0.0f);
editor.putBoolean("d1_concluido", false);
editor.putBoolean("d2_concluido", false);
editor.putBoolean("d3_concluido", false);
editor.putBoolean("d4_concluido", false);
// Zera a água para as estatísticas
editor.putInt("agua_hoje", 0);
editor.apply();
}
// Pega num frame do segundo 2 (2000000 microsegundos) carregarEstadosNaTela();
Bitmap frame = retriever.getFrameAtTime(2000000, MediaMetadataRetriever.OPTION_CLOSEST_SYNC); }
if (frame != null) { private void carregarEstadosNaTela() {
enviarParaIA(frame); SharedPreferences prefs = getSharedPreferences("DadosGamificacao", MODE_PRIVATE);
litrosAgua = prefs.getFloat("agua_litros", 0.0f);
tvStatusAgua.setText(String.format(Locale.getDefault(), "Progresso: %.1f / 2.0 L", litrosAgua));
atualizarTextoDesafio(tvStatusD1, prefs.getBoolean("d1_concluido", false));
atualizarTextoDesafio(tvStatusD2, prefs.getBoolean("d2_concluido", false));
atualizarTextoDesafio(tvStatusD3, prefs.getBoolean("d3_concluido", false));
atualizarTextoDesafio(tvStatusD4, prefs.getBoolean("d4_concluido", false));
}
private void atualizarTextoDesafio(TextView tv, boolean concluido) {
if (concluido) {
tv.setText("Estado: Concluído ✅");
tv.setTextColor(Color.parseColor("#10B981")); // Verde
} else { } else {
Toast.makeText(this, "Erro ao ler vídeo.", Toast.LENGTH_SHORT).show(); tv.setText("Estado: Pendente");
desbloquearBotoes(); tv.setTextColor(Color.parseColor("#EF4444")); // Vermelho
}
} catch (Exception e) {
Toast.makeText(this, "Erro técnico no vídeo.", Toast.LENGTH_SHORT).show();
desbloquearBotoes();
} }
} }
private void enviarParaIA(Bitmap bitmap) { private void enviarVideoParaIA(Uri uri) {
Toast.makeText(this, "IA a avaliar o teu desafio... ⏳", Toast.LENGTH_LONG).show(); tvStatusGeralIA.setText("A processar vídeo e enviar para a IA... ⏳");
bloquearBotoes(false);
ByteArrayOutputStream os = new ByteArrayOutputStream(); new Thread(() -> {
bitmap.compress(Bitmap.CompressFormat.JPEG, 50, os); String base64Video = converterVideo(uri);
String base64 = Base64.encodeToString(os.toByteArray(), Base64.NO_WRAP); if (base64Video == null) {
String dataUrl = "data:image/jpeg;base64," + base64; runOnUiThread(() -> { tvStatusGeralIA.setText("Erro ao ler vídeo."); bloquearBotoes(true); });
return;
}
// O SEGREDO ESTÁ AQUI: Perguntas diferentes para desafios diferentes String instrucao = "";
String ordem = ""; if (desafioAtualSendoGravado == 0) {
int pontosGanhos = 0; instrucao = "Analisa a pessoa a beber água. Devolve apenas os litros consumidos. Formato exato: Litros: [valor_decimal]";
} else {
if (desafioAtual.equals("agua")) { instrucao = "Analisa o exercício físico do vídeo. Verifica se a pessoa fez o movimento corretamente. Devolve apenas o formato: Status: Concluido ou Status: Falhou";
ordem = "Verifica se há uma pessoa a beber água ou com um copo/garrafa na mão. Responde APENAS 'SIM' se houver, ou 'NAO' se não houver. Não digas mais nada.";
pontosGanhos = 10;
} else if (desafioAtual.equals("agachamento")) {
ordem = "Verifica se há uma pessoa a fazer desporto ou na posição de agachamento. Responde APENAS 'SIM' ou 'NAO'.";
pontosGanhos = 50;
} else if (desafioAtual.equals("flexoes")) {
ordem = "Verifica se há uma pessoa no chão a fazer flexões ou desporto. Responde APENAS 'SIM' ou 'NAO'.";
pontosGanhos = 60;
} }
AiRequest request = new AiRequest(Collections.singletonList( AiRequest request = new AiRequest(Collections.singletonList(
new Message("user", java.util.Arrays.asList( new Message("user", java.util.Arrays.asList(
new ContentPart("text", ordem), new ContentPart("text", instrucao),
new ContentPart("image_url", new ImageUrl(dataUrl)) new ContentPart("video_url", new ImageUrl("data:video/mp4;base64," + base64Video))
)) ))
)); ));
// Guarda as variáveis para usar dentro da resposta
final int pontosDar = pontosGanhos;
AiConfig.getRetrofit().create(AiApi.class) AiConfig.getRetrofit().create(AiApi.class)
.analisarImagem("Bearer " + MINHA_API_KEY, request) .analisarImagem("Bearer " + MINHA_API_KEY, request)
.enqueue(new Callback<AiResponse>() { .enqueue(new Callback<AiResponse>() {
@Override @Override
public void onResponse(Call<AiResponse> call, Response<AiResponse> response) { public void onResponse(Call<AiResponse> call, Response<AiResponse> response) {
desbloquearBotoes();
if (response.isSuccessful() && response.body() != null) { if (response.isSuccessful() && response.body() != null) {
String respostaIA = response.body().choices.get(0).message.content.trim().toUpperCase(); String respostaIA = response.body().choices.get(0).message.content;
processarResposta(respostaIA);
if (respostaIA.contains("SIM")) {
sucessoDesafio(pontosDar);
} else { } else {
Toast.makeText(DesafiosActivity.this, "A IA diz que NÃO estás a fazer o desafio! ❌", Toast.LENGTH_LONG).show(); runOnUiThread(() -> { tvStatusGeralIA.setText("Erro na IA: " + response.code()); bloquearBotoes(true); });
}
} else {
Toast.makeText(DesafiosActivity.this, "Erro no servidor da IA.", Toast.LENGTH_SHORT).show();
} }
} }
@Override @Override
public void onFailure(Call<AiResponse> call, Throwable t) { public void onFailure(Call<AiResponse> call, Throwable t) {
desbloquearBotoes(); runOnUiThread(() -> { tvStatusGeralIA.setText("Erro de rede."); bloquearBotoes(true); });
Toast.makeText(DesafiosActivity.this, "Falha na Internet.", Toast.LENGTH_SHORT).show();
} }
}); });
}).start();
} }
private void sucessoDesafio(int pontos) { private void processarResposta(String texto) {
Toast.makeText(this, "✅ IA Aprovou! Ganhaste " + pontos + " pontos!", Toast.LENGTH_LONG).show();
SharedPreferences prefs = getSharedPreferences("DadosGamificacao", MODE_PRIVATE); SharedPreferences prefs = getSharedPreferences("DadosGamificacao", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit(); SharedPreferences.Editor editor = prefs.edit();
// Dá os pontos e sobe o contador de desafios // Dar pontos no perfil
int pontosAtuais = prefs.getInt("pontos", 0); SharedPreferences perfilPrefs = getSharedPreferences("MeusDadosApp", MODE_PRIVATE);
int desafiosAtuais = prefs.getInt("desafios", 0); SharedPreferences.Editor perfilEditor = perfilPrefs.edit();
editor.putInt("pontos", pontosAtuais + pontos);
editor.putInt("desafios", desafiosAtuais + 1);
// Se foi o desafio da água, sobe a barra de progresso runOnUiThread(() -> {
if (desafioAtual.equals("agua")) { if (desafioAtualSendoGravado == 0) {
coposBebidos++; if (texto.contains("Litros:")) {
if (coposBebidos > META_COPOS) coposBebidos = META_COPOS; try {
editor.putInt("agua_hoje", coposBebidos); String valorStr = texto.substring(texto.indexOf("Litros:") + 7).trim().replaceAll("[^0-9.]", "");
atualizarProgressoAgua(); float lido = Float.parseFloat(valorStr);
litrosAgua += lido;
editor.putFloat("agua_litros", litrosAgua);
editor.putInt("agua_hoje", (int) (litrosAgua / 0.25f)); // Atualiza Estatísticas
tvStatusGeralIA.setText("IA leu: +" + lido + " Litros!");
} catch (Exception e) {
tvStatusGeralIA.setText("Erro a ler os litros. Tenta de novo.");
}
}
} else {
if (texto.contains("Status: Concluido")) {
tvStatusGeralIA.setText("IA: Desafio Validado! ✅ +50 Pontos");
if (desafioAtualSendoGravado == 1) editor.putBoolean("d1_concluido", true);
if (desafioAtualSendoGravado == 2) editor.putBoolean("d2_concluido", true);
if (desafioAtualSendoGravado == 3) editor.putBoolean("d3_concluido", true);
if (desafioAtualSendoGravado == 4) editor.putBoolean("d4_concluido", true);
perfilEditor.putInt("pontos", perfilPrefs.getInt("pontos", 0) + 50);
perfilEditor.putInt("desafios_concluidos", perfilPrefs.getInt("desafios_concluidos", 0) + 1);
perfilEditor.apply();
} else {
tvStatusGeralIA.setText("IA: Desafio Falhou ou vídeo pouco claro. ❌");
}
}
editor.apply();
carregarEstadosNaTela();
bloquearBotoes(true);
});
} }
editor.apply(); // Guarda tudo! private void bloquearBotoes(boolean estado) {
btnVideoAgua.setEnabled(estado);
btnVideoD1.setEnabled(estado);
btnVideoD2.setEnabled(estado);
btnVideoD3.setEnabled(estado);
btnVideoD4.setEnabled(estado);
} }
private void atualizarProgressoAgua() { private String converterVideo(Uri uri) {
progressAgua.setProgress(coposBebidos); try {
tvStatusAgua.setText(coposBebidos + " de " + META_COPOS + " copos (" + (coposBebidos * 250) + "ml / 2L)"); InputStream is = getContentResolver().openInputStream(uri);
} ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int l;
private void bloquearBotoes() { byte[] b = new byte[4096];
btnGravarAgua.setEnabled(false); while ((l = is.read(b)) != -1) { buffer.write(b, 0, l); }
btnGravarEx1.setEnabled(false); return Base64.encodeToString(buffer.toByteArray(), Base64.NO_WRAP);
btnGravarEx2.setEnabled(false); } catch (Exception e) { return null; }
}
private void desbloquearBotoes() {
btnGravarAgua.setEnabled(true);
btnGravarEx1.setEnabled(true);
btnGravarEx2.setEnabled(true);
} }
} }

View File

@@ -5,7 +5,6 @@ import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.RadioGroup; import android.widget.RadioGroup;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@@ -27,7 +26,6 @@ public class RegisterActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register); setContentView(R.layout.activity_register);
// Ligar o código aos IDs do ecrã
etRegNome = findViewById(R.id.etRegNome); etRegNome = findViewById(R.id.etRegNome);
etRegEmail = findViewById(R.id.etRegEmail); etRegEmail = findViewById(R.id.etRegEmail);
etRegPassword = findViewById(R.id.etRegPassword); etRegPassword = findViewById(R.id.etRegPassword);
@@ -38,10 +36,8 @@ public class RegisterActivity extends AppCompatActivity {
btnRegister = findViewById(R.id.btnRegister); btnRegister = findViewById(R.id.btnRegister);
tvGoToLogin = findViewById(R.id.tvGoToLogin); tvGoToLogin = findViewById(R.id.tvGoToLogin);
// Se o utilizador já tem conta, volta para o Login
tvGoToLogin.setOnClickListener(v -> finish()); tvGoToLogin.setOnClickListener(v -> finish());
// Quando clica no botão de Registar
btnRegister.setOnClickListener(v -> { btnRegister.setOnClickListener(v -> {
String nome = etRegNome.getText().toString().trim(); String nome = etRegNome.getText().toString().trim();
String email = etRegEmail.getText().toString().trim(); String email = etRegEmail.getText().toString().trim();
@@ -50,65 +46,54 @@ public class RegisterActivity extends AppCompatActivity {
String alturaStr = etRegAltura.getText().toString().trim(); String alturaStr = etRegAltura.getText().toString().trim();
String pesoStr = etRegPeso.getText().toString().trim(); String pesoStr = etRegPeso.getText().toString().trim();
// 1. Verificar se não há campos vazios
if (nome.isEmpty() || email.isEmpty() || password.isEmpty() || idadeStr.isEmpty() || alturaStr.isEmpty() || pesoStr.isEmpty()) { if (nome.isEmpty() || email.isEmpty() || password.isEmpty() || idadeStr.isEmpty() || alturaStr.isEmpty() || pesoStr.isEmpty()) {
Toast.makeText(this, "Por favor, preenche todos os campos!", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Por favor, preenche todos os campos!", Toast.LENGTH_SHORT).show();
return; return;
} }
// 1.1 Verificar se o utilizador escolheu o sexo
int selectedSexoId = radioGroupSexo.getCheckedRadioButtonId(); int selectedSexoId = radioGroupSexo.getCheckedRadioButtonId();
if (selectedSexoId == -1) { if (selectedSexoId == -1) {
Toast.makeText(this, "Por favor, escolhe o teu sexo!", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Por favor, escolhe o teu sexo!", Toast.LENGTH_SHORT).show();
return; return;
} }
String sexoSelecionado = ""; String sexoSelecionado = (selectedSexoId == R.id.radioMasculino) ? "Masculino" : "Feminino";
if (selectedSexoId == R.id.radioMasculino) { int idade = Integer.parseInt(idadeStr);
sexoSelecionado = "Masculino"; float altura = Float.parseFloat(alturaStr);
} else if (selectedSexoId == R.id.radioFeminino) { float peso = Float.parseFloat(pesoStr);
sexoSelecionado = "Feminino";
}
// 2. GUARDAR TODOS OS DADOS NA MEMÓRIA (SharedPreferences) // Grava no telemóvel para acesso rápido
SharedPreferences prefs = getSharedPreferences("MeusDadosApp", MODE_PRIVATE); SharedPreferences prefs = getSharedPreferences("MeusDadosApp", MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit(); SharedPreferences.Editor editor = prefs.edit();
editor.putString("nome", nome); editor.putString("nome", nome);
editor.putString("email", email); editor.putString("email", email);
editor.putString("sexo", sexoSelecionado); editor.putString("sexo", sexoSelecionado);
editor.putInt("idade", Integer.parseInt(idadeStr)); editor.putInt("idade", idade);
editor.putFloat("altura", Float.parseFloat(alturaStr)); editor.putFloat("altura", altura);
editor.putFloat("peso", Float.parseFloat(pesoStr)); editor.putFloat("peso", peso);
editor.apply(); editor.apply();
// 3. Preparar os dados para enviar para o Supabase // Empacota tudo para enviar para o Supabase
UserCredentials credentials = new UserCredentials(email, password); UserCredentials credentials = new UserCredentials(email, password, nome, idade, altura, peso, sexoSelecionado);
// Vai buscar o Retrofit ao ficheiro SupabaseConfig
SupabaseApi api = SupabaseConfig.getRetrofit().create(SupabaseApi.class); SupabaseApi api = SupabaseConfig.getRetrofit().create(SupabaseApi.class);
// 4. Fazer o Registo na Internet usando a chave configurada
api.signUp(SupabaseConfig.SUPABASE_KEY, credentials).enqueue(new Callback<SupabaseResponse>() { api.signUp(SupabaseConfig.SUPABASE_KEY, credentials).enqueue(new Callback<SupabaseResponse>() {
@Override @Override
public void onResponse(Call<SupabaseResponse> call, Response<SupabaseResponse> response) { public void onResponse(Call<SupabaseResponse> call, Response<SupabaseResponse> response) {
if (response.isSuccessful()) { if (response.isSuccessful()) {
Toast.makeText(RegisterActivity.this, "Conta criada! Verifica o teu email.", Toast.LENGTH_LONG).show(); Toast.makeText(RegisterActivity.this, "Conta criada no Supabase! Verifica o teu email.", Toast.LENGTH_LONG).show();
// Mandar para o ecrã de espera
Intent intent = new Intent(RegisterActivity.this, VerificacaoActivity.class); Intent intent = new Intent(RegisterActivity.this, VerificacaoActivity.class);
intent.putExtra("email_registo", email); intent.putExtra("email_registo", email);
intent.putExtra("password_registo", password); intent.putExtra("password_registo", password);
startActivity(intent); startActivity(intent);
finish(); finish();
} else { } else {
Toast.makeText(RegisterActivity.this, "Erro ao criar conta. O email já existe?", Toast.LENGTH_LONG).show(); Toast.makeText(RegisterActivity.this, "Erro ao criar conta online.", Toast.LENGTH_LONG).show();
} }
} }
@Override @Override
public void onFailure(Call<SupabaseResponse> call, Throwable t) { public void onFailure(Call<SupabaseResponse> call, Throwable t) {
Toast.makeText(RegisterActivity.this, "Falha na ligação! Verifica a tua internet.", Toast.LENGTH_LONG).show(); Toast.makeText(RegisterActivity.this, "Falha na ligação à internet.", Toast.LENGTH_LONG).show();
} }
}); });
}); });

View File

@@ -5,6 +5,8 @@ import retrofit2.http.Body;
import retrofit2.http.Header; import retrofit2.http.Header;
import retrofit2.http.PUT; import retrofit2.http.PUT;
import retrofit2.http.POST; import retrofit2.http.POST;
import java.util.HashMap;
import java.util.Map;
public interface SupabaseApi { public interface SupabaseApi {
@@ -16,7 +18,7 @@ public interface SupabaseApi {
@POST("auth/v1/token?grant_type=password") @POST("auth/v1/token?grant_type=password")
Call<SupabaseResponse> login(@Header("apikey") String apiKey, @Body UserCredentials credentials); Call<SupabaseResponse> login(@Header("apikey") String apiKey, @Body UserCredentials credentials);
// 3. Rota para atualizar a palavra-passe ou dados do utilizador (CORRIGIDO PARA @PUT) // 3. Rota para atualizar a palavra-passe ou dados do utilizador
@PUT("auth/v1/user") @PUT("auth/v1/user")
Call<Void> updateUserData( Call<Void> updateUserData(
@Header("apikey") String apikey, @Header("apikey") String apikey,
@@ -30,11 +32,27 @@ public interface SupabaseApi {
class UserCredentials { class UserCredentials {
String email; String email;
String password; String password;
Map<String, Object> data; // O Supabase exige que os dados extras fiquem aqui dentro!
// Construtor 1: Usado para o LOGIN (só precisa de email e password)
public UserCredentials(String email, String password) { public UserCredentials(String email, String password) {
this.email = email; this.email = email;
this.password = password; this.password = password;
} }
// Construtor 2: Usado para o REGISTO (arruma os dados de saúde na pasta "data")
public UserCredentials(String email, String password, String nome, int idade, float altura, float peso, String sexo) {
this.email = email;
this.password = password;
// Empacotar os dados exatamente como o Supabase pede
this.data = new HashMap<>();
this.data.put("nome", nome);
this.data.put("idade", idade);
this.data.put("altura", altura);
this.data.put("peso", peso);
this.data.put("sexo", sexo);
}
} }
class SupabaseResponse { class SupabaseResponse {

View File

@@ -1,4 +1,4 @@
muda<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -5,12 +5,12 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:background="#FFFFFF" android:background="#FFFFFF"
android:padding="24dp"> android:padding="20dp">
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="32dp"> android:layout_marginBottom="16dp">
<TextView <TextView
android:id="@+id/btnVoltarDesafios" android:id="@+id/btnVoltarDesafios"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -18,19 +18,28 @@
android:text="Voltar" android:text="Voltar"
android:textSize="16sp" android:textSize="16sp"
android:textColor="#8E8E93" android:textColor="#8E8E93"
android:layout_alignParentStart="true"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:padding="8dp" /> android:padding="8dp"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Missões Diárias" android:text="5 Desafios do Dia"
android:textSize="18sp" android:textSize="18sp"
android:textStyle="bold" android:textStyle="bold"
android:textColor="#1C1C1E" android:textColor="#1C1C1E"
android:layout_centerInParent="true" /> android:layout_centerInParent="true" />
</RelativeLayout> </RelativeLayout>
<TextView
android:id="@+id/tvStatusGeralIA"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Grava um vídeo para a IA avaliar."
android:textColor="#03A9F4"
android:textStyle="bold"
android:textAlignment="center"
android:layout_marginBottom="16dp"/>
<ScrollView <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -38,147 +47,113 @@
android:scrollbars="none"> android:scrollbars="none">
<LinearLayout <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
app:cardCornerRadius="24dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#F0F9FF"> <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:paddingBottom="24dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="💧 Hidratação"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#0284C7"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+10 pts / copo"
android:textSize="12sp"
android:textColor="#0284C7"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Beber 250ml de água"
android:textSize="14sp"
android:textColor="#0369A1"
android:layout_marginTop="4dp"/>
<ProgressBar
android:id="@+id/progressAgua"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="8dp"
android:layout_marginTop="16dp"
android:progress="3"
android:max="8"
android:progressTint="#0EA5E9"/>
<TextView
android:id="@+id/tvStatusAgua"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="3 de 8 copos (750ml / 2L)"
android:textSize="12sp"
android:textColor="#0369A1"
android:textAlignment="center"
android:layout_marginTop="8dp"/>
<Button
android:id="@+id/btnGravarAgua"
android:layout_width="match_parent"
android:layout_height="54dp"
android:text="Gravar Golo"
android:backgroundTint="#0284C7"
android:textColor="#FFFFFF"
app:cornerRadius="16dp"
android:layout_marginTop="16dp"
android:elevation="0dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Outros Desafios"
android:textSize="14sp"
android:textStyle="bold"
android:textColor="#8E8E93"
android:layout_marginBottom="12dp"
android:layout_marginStart="4dp"/>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
app:cardCornerRadius="24dp" app:cardCornerRadius="16dp"
app:cardElevation="0dp" app:cardElevation="0dp"
app:cardBackgroundColor="#F2F2F7"> app:cardBackgroundColor="#E0F2FE">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:padding="16dp">
<RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Desafio 1: Hidratação" android:textSize="16sp" android:textStyle="bold" android:textColor="#0284C7"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="🦵 Agachamentos (15x)" android:textSize="16sp" android:textStyle="bold" android:textColor="#1C1C1E"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Grava-te a beber água." android:textSize="14sp" android:textColor="#0369A1" android:layout_marginBottom="8dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="+50 pts" android:textSize="12sp" android:textColor="#8E8E93" android:layout_alignParentEnd="true"/>
</RelativeLayout> <TextView android:id="@+id/tvStatusAgua" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Progresso: 0.0 / 2.0 L" android:textSize="14sp" android:textStyle="bold" android:textColor="#1C1C1E" android:layout_marginBottom="8dp"/>
<Button
android:id="@+id/btnGravarEx1" <Button android:id="@+id/btnVideoAgua" android:layout_width="match_parent" android:layout_height="50dp" android:text="Gravar Vídeo (Água)" android:backgroundTint="#0284C7" android:textColor="#FFFFFF" app:cornerRadius="8dp"/>
android:layout_width="match_parent"
android:layout_height="54dp"
android:text="Gravar Exercício"
android:backgroundTint="#1C1C1E"
android:textColor="#FFFFFF"
app:cornerRadius="16dp"
android:layout_marginTop="16dp"
android:elevation="0dp"/>
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView <androidx.cardview.widget.CardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="32dp" android:layout_marginBottom="16dp"
app:cardCornerRadius="24dp" app:cardCornerRadius="16dp"
app:cardElevation="0dp" app:cardElevation="0dp"
app:cardBackgroundColor="#F2F2F7"> app:cardBackgroundColor="#F2F2F7">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:padding="16dp">
<RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Desafio 2: Flexões" android:textSize="16sp" android:textStyle="bold" android:textColor="#1C1C1E"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="💪 Flexões (10x)" android:textSize="16sp" android:textStyle="bold" android:textColor="#1C1C1E"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Grava 10 seg de flexões." android:textSize="14sp" android:textColor="#3A3A3C" android:layout_marginBottom="8dp"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="+60 pts" android:textSize="12sp" android:textColor="#8E8E93" android:layout_alignParentEnd="true"/>
</RelativeLayout> <TextView android:id="@+id/tvStatusD1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Estado: Pendente" android:textSize="14sp" android:textStyle="bold" android:textColor="#EF4444" android:layout_marginBottom="8dp"/>
<Button
android:id="@+id/btnGravarEx2" <Button android:id="@+id/btnVideoD1" android:layout_width="match_parent" android:layout_height="50dp" android:text="Gravar Vídeo" android:backgroundTint="#1C1C1E" android:textColor="#FFFFFF" app:cornerRadius="8dp"/>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="54dp" android:layout_height="wrap_content"
android:text="Gravar Exercício" android:layout_marginBottom="16dp"
android:backgroundTint="#1C1C1E" app:cardCornerRadius="16dp"
android:textColor="#FFFFFF" app:cardElevation="0dp"
app:cornerRadius="16dp" app:cardBackgroundColor="#F2F2F7">
android:layout_marginTop="16dp" <LinearLayout
android:elevation="0dp"/> 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="Desafio 3: Agachamentos" android:textSize="16sp" android:textStyle="bold" android:textColor="#1C1C1E"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Grava 10 seg de agachamentos." android:textSize="14sp" android:textColor="#3A3A3C" android:layout_marginBottom="8dp"/>
<TextView android:id="@+id/tvStatusD2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Estado: Pendente" android:textSize="14sp" android:textStyle="bold" android:textColor="#EF4444" android:layout_marginBottom="8dp"/>
<Button android:id="@+id/btnVideoD2" android:layout_width="match_parent" android:layout_height="50dp" android:text="Gravar Vídeo" android:backgroundTint="#1C1C1E" android:textColor="#FFFFFF" app:cornerRadius="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="16dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#F2F2F7">
<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="Desafio 4: Prancha" android:textSize="16sp" android:textStyle="bold" android:textColor="#1C1C1E"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Mantém a prancha por 10 seg." android:textSize="14sp" android:textColor="#3A3A3C" android:layout_marginBottom="8dp"/>
<TextView android:id="@+id/tvStatusD3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Estado: Pendente" android:textSize="14sp" android:textStyle="bold" android:textColor="#EF4444" android:layout_marginBottom="8dp"/>
<Button android:id="@+id/btnVideoD3" android:layout_width="match_parent" android:layout_height="50dp" android:text="Gravar Vídeo" android:backgroundTint="#1C1C1E" android:textColor="#FFFFFF" app:cornerRadius="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="16dp"
app:cardElevation="0dp"
app:cardBackgroundColor="#F2F2F7">
<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="Desafio 5: Saltos de Tesoura" android:textSize="16sp" android:textStyle="bold" android:textColor="#1C1C1E"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Grava 10 seg de jumping jacks." android:textSize="14sp" android:textColor="#3A3A3C" android:layout_marginBottom="8dp"/>
<TextView android:id="@+id/tvStatusD4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Estado: Pendente" android:textSize="14sp" android:textStyle="bold" android:textColor="#EF4444" android:layout_marginBottom="8dp"/>
<Button android:id="@+id/btnVideoD4" android:layout_width="match_parent" android:layout_height="50dp" android:text="Gravar Vídeo" android:backgroundTint="#1C1C1E" android:textColor="#FFFFFF" app:cornerRadius="8dp"/>
</LinearLayout> </LinearLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>