diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 68b0ad3..235163c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -43,4 +43,9 @@ dependencies { implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:converter-gson:2.9.0") implementation("com.squareup.okhttp3:logging-interceptor:4.9.0") + implementation("androidx.camera:camera-core:1.3.1") + implementation("androidx.camera:camera-camera2:1.3.1") + implementation("androidx.camera:camera-lifecycle:1.3.1") + implementation("androidx.camera:camera-view:1.3.1") + implementation("com.squareup.okhttp3:okhttp:4.12.0") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6f76c78..1f486f2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> + + analisarImagem( + @Header("Authorization") String authHeader, + @Body AiRequest request + ); +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/AiConfig.java b/app/src/main/java/com/example/pap/AiConfig.java new file mode 100644 index 0000000..69c4c08 --- /dev/null +++ b/app/src/main/java/com/example/pap/AiConfig.java @@ -0,0 +1,19 @@ +package com.example.pap; + +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class AiConfig { + private static Retrofit retrofit; + private static final String BASE_URL = "https://openrouter.ai/api/v1/"; + + public static Retrofit getRetrofit() { + if (retrofit == null) { + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .build(); + } + return retrofit; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/AiModels.java b/app/src/main/java/com/example/pap/AiModels.java new file mode 100644 index 0000000..0d901f8 --- /dev/null +++ b/app/src/main/java/com/example/pap/AiModels.java @@ -0,0 +1,54 @@ +package com.example.pap; + +import java.util.List; + +class AiRequest { + // A MAGIA ESTÁ AQUI: Isto escolhe automaticamente a IA visual que estiver a funcionar hoje! + String model = "openrouter/free"; + List messages; + + AiRequest(List messages) { this.messages = messages; } +} + +class Message { + String role; + List content; + + Message(String role, List content) { + this.role = role; + this.content = content; + } +} + +class ContentPart { + String type; + String text; + ImageUrl image_url; + + ContentPart(String type, String text) { + this.type = type; + this.text = text; + } + + ContentPart(String type, ImageUrl image_url) { + this.type = type; + this.image_url = image_url; + } +} + +class ImageUrl { + String url; + ImageUrl(String url) { this.url = url; } +} + +class AiResponse { + List choices; + + class Choice { + MessageResponse message; + } + + class MessageResponse { + String content; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/ChatActivity.java b/app/src/main/java/com/example/pap/ChatActivity.java index 3df565b..2e57238 100644 --- a/app/src/main/java/com/example/pap/ChatActivity.java +++ b/app/src/main/java/com/example/pap/ChatActivity.java @@ -1,138 +1,168 @@ package com.example.pap; import android.graphics.Color; -import android.graphics.drawable.GradientDrawable; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.view.Gravity; -import android.view.View; -import android.widget.Button; import android.widget.EditText; +import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; +import android.widget.Toast; + import androidx.appcompat.app.AppCompatActivity; +import org.json.JSONArray; +import org.json.JSONObject; + +import java.io.IOException; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + public class ChatActivity extends AppCompatActivity { + private EditText etMensagem; + private ImageButton btnEnviar; private LinearLayout chatLayout; private ScrollView chatScrollView; - private EditText etNovaMensagem; - private Button btnEnviarMensagem; - private Button btnVoltarChat; + + // A TUA CHAVE DA GOOGLE VEM PARA AQUI! + private static final String GEMINI_API_KEY = "AQ.Ab8RN6IfhsFBO3UOpK3vYd7BrR2nfFb-mVE--nvqRnR46hB36A"; + + // O motor do Gemini 1.5 Flash + private static final String GEMINI_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=" + GEMINI_API_KEY; + + private OkHttpClient client; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_chat); - // 1. Associar os componentes do layout + etMensagem = findViewById(R.id.etMensagem); + btnEnviar = findViewById(R.id.btnEnviar); chatLayout = findViewById(R.id.chatLayout); chatScrollView = findViewById(R.id.chatScrollView); - etNovaMensagem = findViewById(R.id.etNovaMensagem); - btnEnviarMensagem = findViewById(R.id.btnEnviarMensagem); - btnVoltarChat = findViewById(R.id.btnVoltarChat); - // Mensagem inicial da IA ao abrir o ecrã - adicionarMensagemBalao("Olá! Sou o teu IA Coach de Saúde. Como te posso ajudar hoje na tua jornada de emagrecimento?", false); + client = new OkHttpClient(); - // 2. Clique no botão de Enviar - btnEnviarMensagem.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - String mensagemUser = etNovaMensagem.getText().toString().trim(); + // Dá as boas vindas + adicionarBalaoNoEcra("Olá! Sou o NutriChat AI 🤖. O que comeste hoje ou que dúvidas tens sobre a tua dieta?", false); - if (!mensagemUser.isEmpty()) { - // Adiciona a mensagem do utilizador no ecrã - adicionarMensagemBalao(mensagemUser, true); + btnEnviar.setOnClickListener(v -> { + String pergunta = etMensagem.getText().toString().trim(); + if (pergunta.isEmpty()) { + Toast.makeText(this, "Escreve alguma merda primeiro!", Toast.LENGTH_SHORT).show(); + return; + } - // Limpa a caixa de texto - etNovaMensagem.setText(""); + // 1. Mostrar a mensagem do utilizador no ecrã + adicionarBalaoNoEcra(pergunta, true); + etMensagem.setText(""); // Limpa a caixa de texto - // Simula a IA a processar e a responder - simularRespostaDaIA(mensagemUser); + // 2. Chamar a Inteligência Artificial + chamarGemini(pergunta); + }); + } + + // Função que desenha os balões de conversa no ecrã + private void adicionarBalaoNoEcra(String texto, boolean isUser) { + runOnUiThread(() -> { + TextView tv = new TextView(this); + tv.setText(texto); + tv.setTextSize(16f); + tv.setPadding(30, 20, 30, 20); + tv.setTextColor(isUser ? Color.WHITE : Color.parseColor("#1E293B")); + + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, + LinearLayout.LayoutParams.WRAP_CONTENT + ); + params.setMargins(0, 10, 0, 10); + + // Se for o utilizador, o balão vai para a direita e fica azul + if (isUser) { + params.gravity = Gravity.END; + tv.setBackgroundResource(R.drawable.balao_user); // Já vamos criar isto! + } else { + // Se for a IA, o balão vai para a esquerda e fica branco/cinza + params.gravity = Gravity.START; + tv.setBackgroundResource(R.drawable.balao_ai); // Já vamos criar isto! + } + + tv.setLayoutParams(params); + chatLayout.addView(tv); + + // Faz scroll automático para o fundo para ver a mensagem nova + chatScrollView.post(() -> chatScrollView.fullScroll(ScrollView.FOCUS_DOWN)); + }); + } + + // Função que vai à internet falar com o cérebro da Google + private void chamarGemini(String pergunta) { + try { + // Instrução secreta para ele agir como Nutricionista + String promptCompleto = "Aja como um nutricionista simpático e profissional. Responda de forma curta e direta à seguinte mensagem do utilizador: " + pergunta; + + // Construir o JSON que o Gemini exige + JSONObject part = new JSONObject(); + part.put("text", promptCompleto); + JSONArray parts = new JSONArray(); + parts.put(part); + JSONObject content = new JSONObject(); + content.put("parts", parts); + JSONArray contents = new JSONArray(); + contents.put(content); + JSONObject jsonBody = new JSONObject(); + jsonBody.put("contents", contents); + + RequestBody body = RequestBody.create(jsonBody.toString(), MediaType.get("application/json; charset=utf-8")); + Request request = new Request.Builder() + .url(GEMINI_URL) + .post(body) + .build(); + + // Fazer a chamada fora da Thread principal para a app não encravar + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + adicionarBalaoNoEcra("Oops! Fiquei sem internet ou deu merda na ligação. 🔌", false); } - } - }); - // 3. Clique no botão de Voltar - btnVoltarChat.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } - }); - } + @Override + public void onResponse(Call call, Response response) throws IOException { + if (response.isSuccessful()) { + try { + String respostaJSON = response.body().string(); + JSONObject jsonObject = new JSONObject(respostaJSON); + // Escarafunchar o JSON para tirar só o texto da resposta + String respostaIA = jsonObject.getJSONArray("candidates") + .getJSONObject(0) + .getJSONObject("content") + .getJSONArray("parts") + .getJSONObject(0) + .getString("text"); - // Passo 3: Função para desenhar os balões de conversa no ecrã - private void adicionarMensagemBalao(String texto, boolean isUser) { - TextView textView = new TextView(this); - textView.setText(texto); - textView.setTextSize(16f); - textView.setPadding(32, 24, 32, 24); + // Mostrar a resposta no ecrã + adicionarBalaoNoEcra(respostaIA, false); - // Configurar as margens e o alinhamento (Direita = Utilizador, Esquerda = IA) - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ); - params.setMargins(0, 16, 0, 16); + } catch (Exception e) { + adicionarBalaoNoEcra("Deu um nó no meu cérebro ao ler os dados. 🤯", false); + } + } else { + adicionarBalaoNoEcra("Erro da Google: " + response.code(), false); + } + } + }); - // Configurar o design arredondado do balão - GradientDrawable background = new GradientDrawable(); - background.setCornerRadius(32f); - - if (isUser) { - // Estilo do Utilizador (Fundo Verde, Texto Branco, Alinhado à direita) - params.gravity = Gravity.END; - background.setColor(Color.parseColor("#4CAF50")); - textView.setTextColor(Color.WHITE); - } else { - // Estilo da IA (Fundo Cinza/Branco, Texto Escuro, Alinhado à esquerda) - params.gravity = Gravity.START; - background.setColor(Color.parseColor("#FFFFFF")); - textView.setTextColor(Color.parseColor("#2E3D32")); - textView.setElevation(4f); // Dá uma pequena sombra ao balão da IA + } catch (Exception e) { + e.printStackTrace(); } - - textView.setLayoutParams(params); - textView.setBackground(background); - - // Adicionar o balão ao ecrã - chatLayout.addView(textView); - - // Fazer scroll automático para baixo para ver a nova mensagem - chatScrollView.post(new Runnable() { - @Override - public void run() { - chatScrollView.fullScroll(ScrollView.FOCUS_DOWN); - } - }); - } - - // Passo 4: Função para simular a inteligência da IA - private void simularRespostaDaIA(String perguntaUtilizador) { - perguntaUtilizador = perguntaUtilizador.toLowerCase(); - final String resposta; - - // Regras simples para fingir que a IA está a entender a pergunta - if (perguntaUtilizador.contains("água") || perguntaUtilizador.contains("agua")) { - resposta = "A hidratação é fundamental! O ideal é beberes pelo menos 2 a 3 litros de água por dia. Já bebeste algum copo hoje?"; - } else if (perguntaUtilizador.contains("comer") || perguntaUtilizador.contains("fome") || perguntaUtilizador.contains("dieta")) { - resposta = "Se sentires fome entre as refeições, opta por snacks saudáveis como uma peça de fruta ou um punhado de frutos secos. Evita os doces!"; - } else if (perguntaUtilizador.contains("treino") || perguntaUtilizador.contains("exercício") || perguntaUtilizador.contains("correr")) { - resposta = "Excelente! O exercício acelera o metabolismo. Lembra-te que a consistência é mais importante do que a intensidade. 30 minutos de caminhada já fazem a diferença."; - } else { - resposta = "Essa é uma ótima questão! O segredo do emagrecimento é o défice calórico aliado a bons hábitos. Continua focado, estou aqui para te apoiar em cada passo!"; - } - - // Simular um tempo de "pensamento" da IA (espera 1.5 segundos antes de mostrar a resposta) - new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { - @Override - public void run() { - adicionarMensagemBalao(resposta, false); - } - }, 1500); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/DefinicoesActivity.java b/app/src/main/java/com/example/pap/DefinicoesActivity.java new file mode 100644 index 0000000..bb59432 --- /dev/null +++ b/app/src/main/java/com/example/pap/DefinicoesActivity.java @@ -0,0 +1,237 @@ +package com.example.pap; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.text.InputType; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Toast; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.app.AppCompatDelegate; +import androidx.appcompat.widget.SwitchCompat; + +import java.util.HashMap; +import java.util.Map; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class DefinicoesActivity extends AppCompatActivity { + + private SwitchCompat switchDarkMode; + private Button btnEditarNome, btnMudarEmail, btnMudarPass, btnVoltarDef; + private SharedPreferences sharedPreferences; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_definicoes); + + // Iniciar a memória local do telemóvel + sharedPreferences = getSharedPreferences("MeusDadosApp", MODE_PRIVATE); + + // Ligar os elementos do design ao código + switchDarkMode = findViewById(R.id.switchDarkMode); + btnEditarNome = findViewById(R.id.btnEditarNome); + btnMudarEmail = findViewById(R.id.btnMudarEmail); + btnMudarPass = findViewById(R.id.btnMudarPass); + btnVoltarDef = findViewById(R.id.btnVoltarDef); + + // 1. Lógica do Modo Escuro + boolean isDarkMode = sharedPreferences.getBoolean("dark_mode", false); + switchDarkMode.setChecked(isDarkMode); + + switchDarkMode.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); + } else { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); + } + sharedPreferences.edit().putBoolean("dark_mode", isChecked).apply(); + }); + + // Configurar os cliques nos botões + btnEditarNome.setOnClickListener(v -> mostrarPopupNome()); + btnMudarEmail.setOnClickListener(v -> mostrarPopupEmail()); + btnMudarPass.setOnClickListener(v -> mostrarPopupPassword()); + btnVoltarDef.setOnClickListener(v -> finish()); + } + + // --- FUNÇÕES DOS POP-UPS --- + + private void mostrarPopupNome() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Mudar Nome"); + + final EditText input = new EditText(this); + input.setHint("Novo nome"); + builder.setView(input); + + builder.setPositiveButton("Guardar", (dialog, which) -> { + String novoNome = input.getText().toString().trim(); + if (!novoNome.isEmpty()) { + sharedPreferences.edit().putString("nome", novoNome).apply(); + Toast.makeText(this, "Nome atualizado com sucesso!", Toast.LENGTH_SHORT).show(); + } + }); + builder.show(); + } + + private void mostrarPopupEmail() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Alterar Email"); + + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(50, 20, 50, 0); + + final EditText etPassAtual = new EditText(this); + etPassAtual.setHint("A tua Password atual"); + etPassAtual.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(etPassAtual); + + final EditText etNovoEmail = new EditText(this); + etNovoEmail.setHint("Novo Email"); + layout.addView(etNovoEmail); + + builder.setView(layout); + builder.setPositiveButton("Alterar", (dialog, which) -> { + String passAtual = etPassAtual.getText().toString().trim(); + String novoEmail = etNovoEmail.getText().toString().trim(); + String emailAtual = sharedPreferences.getString("email", ""); + + if (passAtual.isEmpty() || novoEmail.isEmpty()) { + Toast.makeText(this, "Preenche todos os campos!", Toast.LENGTH_SHORT).show(); + return; + } + + SupabaseApi api = SupabaseConfig.getRetrofit().create(SupabaseApi.class); + UserCredentials creds = new UserCredentials(emailAtual, passAtual); + + // Confirma a identidade do utilizador (Mini-login) + api.login(SupabaseConfig.SUPABASE_KEY, creds).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + String freshToken = response.body().access_token; + + // Envia o pedido de alteração de email para o Supabase + Map updates = new HashMap<>(); + updates.put("email", novoEmail); + + api.updateUserData(SupabaseConfig.SUPABASE_KEY, "Bearer " + freshToken, updates).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + // Apenas avisa o utilizador. O email só muda após clicar no link! + Toast.makeText(DefinicoesActivity.this, "Link enviado! Verifica a tua nova caixa de correio para confirmar.", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(DefinicoesActivity.this, "Erro ao tentar enviar o email de confirmação.", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(DefinicoesActivity.this, "Verifica a tua ligação à internet!", Toast.LENGTH_SHORT).show(); + } + }); + } else { + Toast.makeText(DefinicoesActivity.this, "A password atual está incorreta!", Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(DefinicoesActivity.this, "Verifica a tua ligação à internet!", Toast.LENGTH_SHORT).show(); + } + }); + }); + builder.show(); + } + + private void mostrarPopupPassword() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle("Alterar Password"); + + LinearLayout layout = new LinearLayout(this); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setPadding(50, 20, 50, 0); + + final EditText etAntiga = new EditText(this); + etAntiga.setHint("Password Antiga"); + etAntiga.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(etAntiga); + + final EditText etNova = new EditText(this); + etNova.setHint("Nova Password (min. 6 caracteres)"); + etNova.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(etNova); + + final EditText etConfirma = new EditText(this); + etConfirma.setHint("Confirma a Nova Password"); + etConfirma.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + layout.addView(etConfirma); + + builder.setView(layout); + builder.setPositiveButton("Atualizar", (dialog, which) -> { + String pAntiga = etAntiga.getText().toString().trim(); + String pNova = etNova.getText().toString().trim(); + String pConfirma = etConfirma.getText().toString().trim(); + String emailAtual = sharedPreferences.getString("email", ""); + + if (pAntiga.isEmpty() || pNova.isEmpty() || pConfirma.isEmpty()) { + Toast.makeText(this, "Preenche todos os campos!", Toast.LENGTH_SHORT).show(); + return; + } + + if (!pNova.equals(pConfirma) || pNova.length() < 6) { + Toast.makeText(this, "As novas passwords não coincidem ou são muito curtas!", Toast.LENGTH_LONG).show(); + return; + } + + SupabaseApi api = SupabaseConfig.getRetrofit().create(SupabaseApi.class); + UserCredentials creds = new UserCredentials(emailAtual, pAntiga); + + // Confirma a identidade do utilizador (Mini-login) + api.login(SupabaseConfig.SUPABASE_KEY, creds).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + String freshToken = response.body().access_token; + + // Atualiza a palavra-passe no Supabase + Map updates = new HashMap<>(); + updates.put("password", pNova); + + api.updateUserData(SupabaseConfig.SUPABASE_KEY, "Bearer " + freshToken, updates).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(DefinicoesActivity.this, "Password alterada com sucesso!", Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(DefinicoesActivity.this, "Erro no servidor ao mudar password.", Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(DefinicoesActivity.this, "Verifica a tua ligação à internet!", Toast.LENGTH_SHORT).show(); + } + }); + } else { + Toast.makeText(DefinicoesActivity.this, "A password antiga está incorreta!", Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(DefinicoesActivity.this, "Verifica a tua ligação à internet!", Toast.LENGTH_SHORT).show(); + } + }); + }); + builder.show(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/EditarPerfilActivity.java b/app/src/main/java/com/example/pap/EditarPerfilActivity.java new file mode 100644 index 0000000..580445b --- /dev/null +++ b/app/src/main/java/com/example/pap/EditarPerfilActivity.java @@ -0,0 +1,30 @@ +package com.example.pap; + +import android.os.Bundle; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; +import androidx.appcompat.app.AppCompatActivity; + +public class EditarPerfilActivity extends AppCompatActivity { + + private EditText etEditNome, etEditEmail, etEditPassword; + private Button btnGuardarPerfil; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_editar_perfil); + + etEditNome = findViewById(R.id.etEditNome); + etEditEmail = findViewById(R.id.etEditEmail); + etEditPassword = findViewById(R.id.etEditPassword); + btnGuardarPerfil = findViewById(R.id.btnGuardarPerfil); + + btnGuardarPerfil.setOnClickListener(v -> { + // Mais tarde ligamos esta merda ao Supabase! + Toast.makeText(this, "Dados atualizados com sucesso, caralho! ✅", Toast.LENGTH_SHORT).show(); + finish(); // Fecha e volta atrás + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/FotoActivity.java b/app/src/main/java/com/example/pap/FotoActivity.java index 49c8148..cfdaddb 100644 --- a/app/src/main/java/com/example/pap/FotoActivity.java +++ b/app/src/main/java/com/example/pap/FotoActivity.java @@ -1,146 +1,108 @@ package com.example.pap; -import android.content.ActivityNotFoundException; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.provider.MediaStore; +import android.util.Base64; import android.view.View; import android.widget.Button; 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.AppCompatActivity; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; -import java.util.ArrayList; + +import java.io.ByteArrayOutputStream; +import java.util.Collections; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; public class FotoActivity extends AppCompatActivity { private ImageView ivFotoComida; - private Button btnTirarFoto, btnVoltar, btnAddManual, btnConfirmar; - private LinearLayout layoutResultadosIA; - private TextView tvTotalCalorias, tvNomePrato; - private RecyclerView recyclerView; - private IngredientesAdapter adapter; - private ArrayList listaIngredientes; + private Button btnTirarFoto, btnAnalisarIA, btnVoltarFoto; + private TextView tvResultadoIA; + private Bitmap imagemCapturada; - // Lançador da Câmara - private final ActivityResultLauncher cameraLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == RESULT_OK && result.getData() != null) { - // Recebe a miniatura da foto - Bundle extras = result.getData().getExtras(); - Bitmap imageBitmap = (Bitmap) extras.get("data"); - ivFotoComida.setImageBitmap(imageBitmap); - - // Inicia a simulação da IA - Toast.makeText(this, "A analisar a imagem com IA...", Toast.LENGTH_SHORT).show(); - simularAnaliseIA(); - } - } - ); + // COLA A TUA CHAVE DO OPENROUTER AQUI (Aquela que tiraste sem telemóvel e sem VPN) + private final String MINHA_API_KEY = "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"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_foto); - // Inicializar vistas ivFotoComida = findViewById(R.id.ivFotoComida); - btnTirarFoto = findViewById(R.id.btnTirarFotoComida); - btnVoltar = findViewById(R.id.btnVoltarFoto); - layoutResultadosIA = findViewById(R.id.layoutResultadosIA); - tvTotalCalorias = findViewById(R.id.tvTotalCalorias); - tvNomePrato = findViewById(R.id.tvNomePratoDetectado); - recyclerView = findViewById(R.id.recyclerViewIngredientes); - btnAddManual = findViewById(R.id.btnAddIngredienteManual); - btnConfirmar = findViewById(R.id.btnConfirmarRefeicao); + btnTirarFoto = findViewById(R.id.btnTirarFoto); + btnAnalisarIA = findViewById(R.id.btnAnalisarIA); + tvResultadoIA = findViewById(R.id.tvResultadoIA); + btnVoltarFoto = findViewById(R.id.btnVoltarFoto); - // Configurar a RecyclerView - listaIngredientes = new ArrayList<>(); - recyclerView.setLayoutManager(new LinearLayoutManager(this)); - adapter = new IngredientesAdapter(listaIngredientes); - recyclerView.setAdapter(adapter); + ActivityResultLauncher cameraLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK && result.getData() != null) { + Bundle extras = result.getData().getExtras(); + imagemCapturada = (Bitmap) extras.get("data"); - // Configurar o clique no botão de apagar (lixo) na lista - adapter.setOnItemClickListener(new IngredientesAdapter.OnItemClickListener() { + ivFotoComida.setPadding(0, 0, 0, 0); + ivFotoComida.setImageBitmap(imagemCapturada); + + btnTirarFoto.setVisibility(View.GONE); + btnAnalisarIA.setVisibility(View.VISIBLE); + tvResultadoIA.setText("Prato detetado! Clica em Analisar."); + } + } + ); + + btnTirarFoto.setOnClickListener(v -> cameraLauncher.launch(new Intent(MediaStore.ACTION_IMAGE_CAPTURE))); + + btnAnalisarIA.setOnClickListener(v -> { if (imagemCapturada != null) enviarParaIA(); }); + btnVoltarFoto.setOnClickListener(v -> finish()); + } + + private void enviarParaIA() { + tvResultadoIA.setText("A procurar servidor livre e a analisar... ⏳"); + btnAnalisarIA.setEnabled(false); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + imagemCapturada.compress(Bitmap.CompressFormat.JPEG, 60, outputStream); + String base64Image = Base64.encodeToString(outputStream.toByteArray(), Base64.NO_WRAP); + + String dataUrl = "data:image/jpeg;base64," + base64Image; + String prompt = "Identifica a comida nesta imagem e diz-me as calorias e macronutrientes num pequeno parágrafo."; + + ContentPart textPart = new ContentPart("text", prompt); + ContentPart imagePart = new ContentPart("image_url", new ImageUrl(dataUrl)); + Message message = new Message("user", java.util.Arrays.asList(textPart, imagePart)); + AiRequest request = new AiRequest(Collections.singletonList(message)); + + AiApi api = AiConfig.getRetrofit().create(AiApi.class); + api.analisarImagem("Bearer " + MINHA_API_KEY, request).enqueue(new Callback() { @Override - public void onDeleteClick(int position) { - removerIngrediente(position); + public void onResponse(Call call, Response response) { + btnAnalisarIA.setEnabled(true); + if (response.isSuccessful() && response.body() != null) { + try { + String resultado = response.body().choices.get(0).message.content; + tvResultadoIA.setText(resultado); + } catch (Exception e) { tvResultadoIA.setText("Erro ao ler texto da IA."); } + } else { + try { + String erroReal = response.errorBody() != null ? response.errorBody().string() : "Vazio"; + tvResultadoIA.setText("ERRO: " + response.code() + "\n" + erroReal); + } catch (Exception e) { tvResultadoIA.setText("Erro desconhecido."); } + } + } + + @Override + public void onFailure(Call call, Throwable t) { + btnAnalisarIA.setEnabled(true); + tvResultadoIA.setText("Falha na ligação à Internet."); } }); - - btnTirarFoto.setOnClickListener(v -> abrirCamera()); - btnVoltar.setOnClickListener(v -> finish()); - - // Botão para simular adicionar um ingrediente extra manualmente - btnAddManual.setOnClickListener(v -> { - listaIngredientes.add(new Ingrediente("Azeite Extra (1 c.sopa)", 120)); - adapter.notifyItemInserted(listaIngredientes.size() - 1); - calcularTotal(); - Toast.makeText(this, "Ingrediente adicionado!", Toast.LENGTH_SHORT).show(); - }); - - btnConfirmar.setOnClickListener(v -> { - Toast.makeText(this, "Refeição registada no diário!", Toast.LENGTH_LONG).show(); - finish(); // Fecha o ecrã após confirmar - }); - } - - private void abrirCamera() { - Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - try { - cameraLauncher.launch(takePictureIntent); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, "Erro ao abrir a câmara.", Toast.LENGTH_SHORT).show(); - } - } - - // Simula a resposta da IA após 2 segundos - private void simularAnaliseIA() { - new Handler(Looper.getMainLooper()).postDelayed(() -> { - // 1. Mostra a área de resultados - layoutResultadosIA.setVisibility(View.VISIBLE); - btnTirarFoto.setText("Tirar Outra Foto"); - - // 2. Simula os dados detetados - tvNomePrato.setText("Prato detetado: Salada César com Frango"); - listaIngredientes.clear(); - listaIngredientes.add(new Ingrediente("Peito de Frango Grelhado (150g)", 240)); - listaIngredientes.add(new Ingrediente("Alface Romana (200g)", 30)); - listaIngredientes.add(new Ingrediente("Molho César (2 c.sopa)", 160)); - listaIngredientes.add(new Ingrediente("Croutons (30g)", 120)); - listaIngredientes.add(new Ingrediente("Queijo Parmesão (20g)", 80)); - - // 3. Atualiza a lista e o total - adapter.notifyDataSetChanged(); - calcularTotal(); - - }, 2000); // Espera 2 segundos - } - - // Remove um item da lista e atualiza o total - private void removerIngrediente(int position) { - Ingrediente removido = listaIngredientes.get(position); - listaIngredientes.remove(position); - adapter.notifyItemRemoved(position); - calcularTotal(); - Toast.makeText(this, "Removido: " + removido.getNome(), Toast.LENGTH_SHORT).show(); - } - - // Percorre a lista e soma as calorias - private void calcularTotal() { - int total = 0; - for (Ingrediente ing : listaIngredientes) { - total += ing.getCalorias(); - } - tvTotalCalorias.setText(total + " kcal"); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/HomeActivity.java b/app/src/main/java/com/example/pap/HomeActivity.java index 5dafab2..a3cf471 100644 --- a/app/src/main/java/com/example/pap/HomeActivity.java +++ b/app/src/main/java/com/example/pap/HomeActivity.java @@ -6,6 +6,7 @@ import android.os.Bundle; import android.text.InputType; import android.widget.EditText; import android.widget.LinearLayout; +import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -15,58 +16,46 @@ public class HomeActivity extends AppCompatActivity { private SharedPreferences sharedPreferences; - // Agora são 6 cartões! - private CardView cardPerfil, cardEstatisticas, cardTirarFoto, cardChat, cardDesafios, cardDefinicoes; + // Os 5 cartões principais do teu novo design + private CardView cardTirarFoto, cardChat, cardEstatisticas, cardDesafios, cardPerfil; + private TextView tvSaudacaoHome; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_home); - // 1. Iniciar a memória do telemóvel - sharedPreferences = getSharedPreferences("AppEmagrecimento", MODE_PRIVATE); + // 1. Iniciar a memória do telemóvel (Onde guardámos os dados no Registo) + sharedPreferences = getSharedPreferences("MeusDadosApp", MODE_PRIVATE); - // 2. Ligar os cartões do XML ao código - cardPerfil = findViewById(R.id.cardPerfil); - cardEstatisticas = findViewById(R.id.cardEstatisticas); + // 2. Ligar ao XML cardTirarFoto = findViewById(R.id.cardTirarFoto); cardChat = findViewById(R.id.cardChat); + cardEstatisticas = findViewById(R.id.cardEstatisticas); cardDesafios = findViewById(R.id.cardDesafios); - cardDefinicoes = findViewById(R.id.cardDefinicoes); + cardPerfil = findViewById(R.id.cardPerfil); + tvSaudacaoHome = findViewById(R.id.tvSaudacaoHome); - // 3. Configurar os cliques para abrir os outros ecrãs - cardPerfil.setOnClickListener(v -> { - Toast.makeText(this, "A abrir O Meu Perfil...", Toast.LENGTH_SHORT).show(); - // startActivity(new Intent(HomeActivity.this, PerfilActivity.class)); - }); + // 3. Preencher o nome do utilizador no cabeçalho + String nome = sharedPreferences.getString("nome", "Guerreiro"); + tvSaudacaoHome.setText("Olá, " + nome + "! 👋"); - cardEstatisticas.setOnClickListener(v -> { - Toast.makeText(this, "A abrir Estatísticas...", Toast.LENGTH_SHORT).show(); - // startActivity(new Intent(HomeActivity.this, EstatisticasActivity.class)); - }); + // 4. ATIVAR OS CLIQUES (Os Intents para abrir os outros ecrãs) + cardTirarFoto.setOnClickListener(v -> startActivity(new Intent(HomeActivity.this, FotoActivity.class))); - cardTirarFoto.setOnClickListener(v -> { - startActivity(new Intent(HomeActivity.this, FotoActivity.class)); - }); + cardChat.setOnClickListener(v -> startActivity(new Intent(HomeActivity.this, ChatActivity.class))); - cardChat.setOnClickListener(v -> { - startActivity(new Intent(HomeActivity.this, ChatActivity.class)); - }); + cardEstatisticas.setOnClickListener(v -> startActivity(new Intent(HomeActivity.this, EstatisticasActivity.class))); - cardDesafios.setOnClickListener(v -> { - startActivity(new Intent(HomeActivity.this, DesafiosActivity.class)); - }); + cardDesafios.setOnClickListener(v -> startActivity(new Intent(HomeActivity.this, DesafiosActivity.class))); - cardDefinicoes.setOnClickListener(v -> { - Toast.makeText(this, "A abrir Definições...", Toast.LENGTH_SHORT).show(); - // startActivity(new Intent(HomeActivity.this, DefinicoesActivity.class)); - }); + cardPerfil.setOnClickListener(v -> startActivity(new Intent(HomeActivity.this, PerfilActivity.class))); - // 4. Verifica se já passou o tempo para pedir o novo peso + // 5. Verifica se já passou o tempo para pedir o novo peso verificarAtualizacaoSemanal(); } - // Função que calcula se já passou o tempo (1 semana ou 10 segundos) + // Função que calcula o tempo passado private void verificarAtualizacaoSemanal() { long dataUltimaAtualizacao = sharedPreferences.getLong("data_ultima_atualizacao", 0); long dataAtual = System.currentTimeMillis(); @@ -75,7 +64,7 @@ public class HomeActivity extends AppCompatActivity { sharedPreferences.edit().putLong("data_ultima_atualizacao", dataAtual).apply(); } else { // TEMPO CONFIGURADO PARA TESTES: 10 segundos! - // Para a PAP final usa: long tempoNecessario = 7L * 24 * 60 * 60 * 1000; + // Quando fores apresentar a PAP, podes mudar isto para 1 semana. long tempoNecessario = 10 * 1000; if (dataAtual - dataUltimaAtualizacao >= tempoNecessario) { @@ -84,11 +73,11 @@ public class HomeActivity extends AppCompatActivity { } } - // Função que cria o pop-up para atualizar o peso + // Função que cria o pop-up no ecrã para atualizar peso e altura private void mostrarPopupAtualizacao() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("Hora da pesagem! ⚖️"); - builder.setMessage("Já passou 1 semana! Atualiza o teu peso e altura para acompanharmos a tua evolução."); + builder.setMessage("Já passou algum tempo! Atualiza o teu peso e altura para acompanharmos a tua evolução."); builder.setCancelable(false); LinearLayout layout = new LinearLayout(this); @@ -119,16 +108,17 @@ public class HomeActivity extends AppCompatActivity { float novoPeso = Float.parseFloat(pesoStr); float novaAltura = Float.parseFloat(alturaStr); + // Grava os novos dados com as chaves corretas no bloco de notas SharedPreferences.Editor editor = sharedPreferences.edit(); - editor.putFloat("pesoAtual", novoPeso); - editor.putFloat("alturaAtual", novaAltura); + editor.putFloat("peso", novoPeso); + editor.putFloat("altura", novaAltura); editor.putLong("data_ultima_atualizacao", System.currentTimeMillis()); editor.apply(); Toast.makeText(HomeActivity.this, "Dados atualizados com sucesso! 💪", Toast.LENGTH_SHORT).show(); dialog.dismiss(); } else { - Toast.makeText(HomeActivity.this, "Tens de preencher os dois campos!", Toast.LENGTH_SHORT).show(); + Toast.makeText(HomeActivity.this, "Tens de preencher os dois campos, caralho!", Toast.LENGTH_SHORT).show(); } }); } diff --git a/app/src/main/java/com/example/pap/LoginActivity.java b/app/src/main/java/com/example/pap/LoginActivity.java index aa1441e..3827f07 100644 --- a/app/src/main/java/com/example/pap/LoginActivity.java +++ b/app/src/main/java/com/example/pap/LoginActivity.java @@ -1,8 +1,9 @@ package com.example.pap; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; -import android.view.View; +import android.util.Log; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; @@ -12,82 +13,74 @@ import androidx.appcompat.app.AppCompatActivity; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; public class LoginActivity extends AppCompatActivity { private EditText etLoginEmail, etLoginPassword; private Button btnLogin; private TextView tvGoToRegister; - private SupabaseApi api; + private SharedPreferences sharedPreferences; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); - // 1. Inicializar os componentes + // Ligar o código aos IDs do ecrã etLoginEmail = findViewById(R.id.etLoginEmail); etLoginPassword = findViewById(R.id.etLoginPassword); btnLogin = findViewById(R.id.btnLogin); tvGoToRegister = findViewById(R.id.tvGoToRegister); - // 2. Iniciar a comunicação com a API do Supabase - Retrofit retrofit = new Retrofit.Builder() - .baseUrl(SupabaseConfig.URL) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - api = retrofit.create(SupabaseApi.class); - - // 3. Configurar os cliques - btnLogin.setOnClickListener(v -> efetuarLogin()); + // Iniciar a memória do telemóvel + sharedPreferences = getSharedPreferences("MeusDadosApp", MODE_PRIVATE); + // Enviar para o Registo tvGoToRegister.setOnClickListener(v -> { Intent intent = new Intent(LoginActivity.this, RegisterActivity.class); startActivity(intent); }); - } - private void efetuarLogin() { - String email = etLoginEmail.getText().toString().trim(); - String password = etLoginPassword.getText().toString().trim(); + // Botão de Entrar + btnLogin.setOnClickListener(v -> { + String email = etLoginEmail.getText().toString().trim(); + String password = etLoginPassword.getText().toString().trim(); - // Validação básica - if (email.isEmpty() || password.isEmpty()) { - Toast.makeText(this, "Preencha o email e a password!", Toast.LENGTH_SHORT).show(); - return; - } + if (email.isEmpty() || password.isEmpty()) { + Toast.makeText(this, "Preenche o email e a password!", Toast.LENGTH_SHORT).show(); + return; + } - UserCredentials credentials = new UserCredentials(email, password); + UserCredentials credentials = new UserCredentials(email, password); + SupabaseApi api = SupabaseConfig.getRetrofit().create(SupabaseApi.class); - // Faz o pedido de Login ao Supabase - api.login(SupabaseConfig.API_KEY, credentials).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { + api.login(SupabaseConfig.SUPABASE_KEY, credentials).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { - // Login com sucesso! Vai para a Home - Toast.makeText(LoginActivity.this, "Login com sucesso!", Toast.LENGTH_SHORT).show(); - Intent intent = new Intent(LoginActivity.this, HomeActivity.class); - startActivity(intent); - finish(); // Fecha a tela de login + // Guardar o token e o email para usar nas Definições + String token = response.body().access_token; + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("access_token", token); + editor.putString("email", email); + editor.apply(); - } else { - // SE DER ERRO (ex: 404, 400), MOSTRA O LINK EXATO E O CÓDIGO - String urlQueFalhou = response.raw().request().url().toString(); - if (response.code() == 400) { - Toast.makeText(LoginActivity.this, "Email ou password incorretos!", Toast.LENGTH_LONG).show(); + Toast.makeText(LoginActivity.this, "Entraste com sucesso! 🚀", Toast.LENGTH_SHORT).show(); + Intent intent = new Intent(LoginActivity.this, HomeActivity.class); + startActivity(intent); + finish(); } else { - Toast.makeText(LoginActivity.this, "Erro " + response.code() + "! Link: " + urlQueFalhou, Toast.LENGTH_LONG).show(); + Toast.makeText(LoginActivity.this, "Dados errados! Verifica a tua password e email.", Toast.LENGTH_LONG).show(); + Log.e("SUPABASE", "Erro Login: " + response.code()); } } - } - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(LoginActivity.this, "Falha na ligação: " + t.getMessage(), Toast.LENGTH_SHORT).show(); - } + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(LoginActivity.this, "Falha na ligação! Verifica a internet.", Toast.LENGTH_LONG).show(); + } + }); }); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/PerfilActivity.java b/app/src/main/java/com/example/pap/PerfilActivity.java index 83651e3..5f4caa8 100644 --- a/app/src/main/java/com/example/pap/PerfilActivity.java +++ b/app/src/main/java/com/example/pap/PerfilActivity.java @@ -1,39 +1,51 @@ package com.example.pap; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; -import android.view.View; import android.widget.Button; +import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; public class PerfilActivity extends AppCompatActivity { + private TextView tvPerfilNome, tvPerfilPontos, tvPerfilDesafios, tvPerfilSequencia; + private Button btnDefinicoes, btnVoltarPerfil; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_perfil); - Button btnVoltar = findViewById(R.id.btnVoltarPerfil); - Button btnSair = findViewById(R.id.btnSair); + tvPerfilNome = findViewById(R.id.tvPerfilNome); + tvPerfilPontos = findViewById(R.id.tvPerfilPontos); + tvPerfilDesafios = findViewById(R.id.tvPerfilDesafios); + tvPerfilSequencia = findViewById(R.id.tvPerfilSequencia); + btnDefinicoes = findViewById(R.id.btnDefinicoes); + btnVoltarPerfil = findViewById(R.id.btnVoltarPerfil); - // Voltar à Home - btnVoltar.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - finish(); - } + btnDefinicoes.setOnClickListener(v -> { + startActivity(new Intent(PerfilActivity.this, DefinicoesActivity.class)); }); - // Terminar Sessão (Voltar ao Login e limpar a Home) - btnSair.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(PerfilActivity.this, LoginActivity.class); - // Estas flags garantem que o utilizador não consegue clicar em "Voltar" no telemóvel e ir para a Home de novo - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - startActivity(intent); - finish(); - } - }); + btnVoltarPerfil.setOnClickListener(v -> finish()); + } + + // A MAGIA ACONTECE AQUI: Atualiza os dados sempre que o ecrã aparece! + @Override + protected void onResume() { + super.onResume(); + + SharedPreferences prefs = getSharedPreferences("MeusDadosApp", MODE_PRIVATE); + + String nome = prefs.getString("nome", "Utilizador"); + int pontos = prefs.getInt("pontos", 0); + int desafios = prefs.getInt("desafios_concluidos", 0); + int streak = prefs.getInt("sequencia_diaria", 1); + + tvPerfilNome.setText(nome); + tvPerfilPontos.setText(String.valueOf(pontos)); + tvPerfilDesafios.setText(String.valueOf(desafios)); + tvPerfilSequencia.setText(String.valueOf(streak)); } } \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/RegisterActivity.java b/app/src/main/java/com/example/pap/RegisterActivity.java index ce9e85e..908430d 100644 --- a/app/src/main/java/com/example/pap/RegisterActivity.java +++ b/app/src/main/java/com/example/pap/RegisterActivity.java @@ -1,8 +1,9 @@ package com.example.pap; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; -import android.view.View; +import android.util.Log; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; @@ -12,22 +13,19 @@ import androidx.appcompat.app.AppCompatActivity; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; public class RegisterActivity extends AppCompatActivity { private EditText etRegNome, etRegEmail, etRegPassword, etRegAltura, etRegPeso; private Button btnRegister; private TextView tvGoToLogin; - private SupabaseApi api; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_register); - // 1. Ligar os elementos do design ao código + // Ligar o código aos IDs do ecrã etRegNome = findViewById(R.id.etRegNome); etRegEmail = findViewById(R.id.etRegEmail); etRegPassword = findViewById(R.id.etRegPassword); @@ -36,99 +34,63 @@ public class RegisterActivity extends AppCompatActivity { btnRegister = findViewById(R.id.btnRegister); tvGoToLogin = findViewById(R.id.tvGoToLogin); - // 2. Configurar o Retrofit (A ponte de comunicação com o Supabase) - Retrofit retrofit = new Retrofit.Builder() - .baseUrl(SupabaseConfig.URL) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - api = retrofit.create(SupabaseApi.class); - - // 3. Configurar os cliques dos botões - btnRegister.setOnClickListener(v -> efetuarRegisto()); + // Se o utilizador já tem conta, volta para o Login tvGoToLogin.setOnClickListener(v -> finish()); - } - private void efetuarRegisto() { - String nome = etRegNome.getText().toString().trim(); - String email = etRegEmail.getText().toString().trim(); - String password = etRegPassword.getText().toString().trim(); - String pesoStr = etRegPeso.getText().toString().trim(); - String alturaStr = etRegAltura.getText().toString().trim(); + // Quando clica no botão de Registar + btnRegister.setOnClickListener(v -> { + String nome = etRegNome.getText().toString().trim(); + String email = etRegEmail.getText().toString().trim(); + String password = etRegPassword.getText().toString().trim(); + String alturaStr = etRegAltura.getText().toString().trim(); + String pesoStr = etRegPeso.getText().toString().trim(); - // Verificações para garantir que o utilizador preencheu tudo - if (nome.isEmpty() || email.isEmpty() || password.isEmpty() || pesoStr.isEmpty() || alturaStr.isEmpty()) { - Toast.makeText(this, "Preencha todos os campos!", Toast.LENGTH_SHORT).show(); - return; - } + // 1. Verificar se não há campos vazios + if (nome.isEmpty() || email.isEmpty() || password.isEmpty() || alturaStr.isEmpty() || pesoStr.isEmpty()) { + Toast.makeText(this, "Por favor, preenche todos os campos!", Toast.LENGTH_SHORT).show(); + return; + } - if (password.length() < 6) { - Toast.makeText(this, "A password precisa de pelo menos 6 caracteres.", Toast.LENGTH_SHORT).show(); - return; - } + // 2. GUARDAR NOME, ALTURA E PESO NA MEMÓRIA (SharedPreferences) + SharedPreferences prefs = getSharedPreferences("MeusDadosApp", MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putString("nome", nome); + editor.putString("email", email); + editor.putFloat("altura", Float.parseFloat(alturaStr)); + editor.putFloat("peso", Float.parseFloat(pesoStr)); + editor.apply(); - float peso = Float.parseFloat(pesoStr); - float altura = Float.parseFloat(alturaStr); + // 3. Preparar os dados para enviar para o Supabase + UserCredentials credentials = new UserCredentials(email, password); - // FASE 1: Criar a conta (Email e Password) no Supabase Auth - UserCredentials credentials = new UserCredentials(email, password); + // Vai buscar o Retrofit ao ficheiro SupabaseConfig + SupabaseApi api = SupabaseConfig.getRetrofit().create(SupabaseApi.class); - api.signUp(SupabaseConfig.API_KEY, credentials).enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful() && response.body() != null) { + // 4. Fazer o Registo na Internet usando a chave configurada + api.signUp(SupabaseConfig.SUPABASE_KEY, credentials).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful()) { + Toast.makeText(RegisterActivity.this, "Conta criada! Verifica o teu email.", Toast.LENGTH_LONG).show(); - // Sucesso! A conta foi criada. Vamos extrair o ID e o Token - String userId = response.body().id != null ? response.body().id : response.body().user.id; - String token = "Bearer " + response.body().access_token; + // NOVO FLUXO: Mandar para o ecrã de espera com os dados passados em segurança! + Intent intent = new Intent(RegisterActivity.this, VerificacaoActivity.class); + intent.putExtra("email_registo", email); + intent.putExtra("password_registo", password); + startActivity(intent); + finish(); - // FASE 2: Guardar o Nome, Peso e Altura na tabela "profiles" - salvarPerfil(userId, nome, email, peso, altura, token); - - } else { - String urlQueFalhou = response.raw().request().url().toString(); - if (response.code() == 400) { - Toast.makeText(RegisterActivity.this, "Erro 400: Este email já existe ou a password é fraca.", Toast.LENGTH_LONG).show(); } else { - Toast.makeText(RegisterActivity.this, "Erro " + response.code() + " no Auth! Link: " + urlQueFalhou, Toast.LENGTH_LONG).show(); + Toast.makeText(RegisterActivity.this, "Erro ao criar conta. O email já existe?", Toast.LENGTH_LONG).show(); } } - } - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(RegisterActivity.this, "Falha de rede (Sem internet?): " + t.getMessage(), Toast.LENGTH_SHORT).show(); - } + @Override + public void onFailure(Call call, Throwable t) { + // Sem internet ou falha de ligação + Toast.makeText(RegisterActivity.this, "Falha na ligação! Verifica a tua internet.", Toast.LENGTH_LONG).show(); + } + }); }); } - - // Método responsável por guardar os dados extras na tabela profiles - private void salvarPerfil(String id, String nome, String email, float peso, float altura, String token) { - ProfileData profile = new ProfileData(id, nome, email, peso, altura); - - api.insertProfile(SupabaseConfig.API_KEY, token, "application/json", "return=minimal", profile) - .enqueue(new Callback() { - @Override - public void onResponse(Call call, Response response) { - if (response.isSuccessful()) { - // TUDO CORREU BEM! - Toast.makeText(RegisterActivity.this, "Conta e Perfil criados com sucesso!", Toast.LENGTH_LONG).show(); - finish(); // Volta para o ecrã de login - } else { - // DEU ERRO A GUARDAR O PESO E ALTURA. VAMOS LER O ERRO EXATO! - try { - String erroExato = response.errorBody().string(); - // Esta mensagem vai mostrar-te o que está mal configurado no painel do Supabase - Toast.makeText(RegisterActivity.this, "ERRO DA BASE DE DADOS: " + erroExato, Toast.LENGTH_LONG).show(); - } catch (Exception e) { - Toast.makeText(RegisterActivity.this, "Erro a ler a resposta da base de dados.", Toast.LENGTH_SHORT).show(); - } - } - } - - @Override - public void onFailure(Call call, Throwable t) { - Toast.makeText(RegisterActivity.this, "Erro ao conectar à base de dados.", Toast.LENGTH_SHORT).show(); - } - }); - } } \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/SupabaseApi.java b/app/src/main/java/com/example/pap/SupabaseApi.java index f832fd1..2604e8c 100644 --- a/app/src/main/java/com/example/pap/SupabaseApi.java +++ b/app/src/main/java/com/example/pap/SupabaseApi.java @@ -3,30 +3,29 @@ package com.example.pap; import retrofit2.Call; import retrofit2.http.Body; import retrofit2.http.Header; +import retrofit2.http.PUT; import retrofit2.http.POST; public interface SupabaseApi { - // Rota para criar a conta + // 1. Rota para criar a conta @POST("auth/v1/signup") Call signUp(@Header("apikey") String apiKey, @Body UserCredentials credentials); - // Rota para fazer login + // 2. Rota para fazer login @POST("auth/v1/token?grant_type=password") Call login(@Header("apikey") String apiKey, @Body UserCredentials credentials); - // Rota para guardar o Nome, Peso e Altura na base de dados - @POST("rest/v1/profiles") - Call insertProfile( - @Header("apikey") String apiKey, - @Header("Authorization") String token, - @Header("Content-Type") String contentType, - @Header("Prefer") String prefer, - @Body ProfileData profile + // 3. Rota para atualizar a palavra-passe ou dados do utilizador (CORRIGIDO PARA @PUT) + @PUT("auth/v1/user") + Call updateUserData( + @Header("apikey") String apikey, + @Header("Authorization") String accessToken, + @Body java.util.Map updates ); } -// ---- Classes Auxiliares para Formatarem os Dados ---- +// ---- Classes Auxiliares ---- class UserCredentials { String email; @@ -38,28 +37,6 @@ class UserCredentials { } } -class ProfileData { - String id; - String nome; - String email; - float peso; - float altura; - - public ProfileData(String id, String nome, String email, float peso, float altura) { - this.id = id; - this.nome = nome; - this.email = email; - this.peso = peso; - this.altura = altura; - } -} - class SupabaseResponse { - String id; - String access_token; - UserResponse user; - - class UserResponse { - String id; - } + public String access_token; } \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/SupabaseConfig.java b/app/src/main/java/com/example/pap/SupabaseConfig.java index 5393356..c712656 100644 --- a/app/src/main/java/com/example/pap/SupabaseConfig.java +++ b/app/src/main/java/com/example/pap/SupabaseConfig.java @@ -1,10 +1,31 @@ package com.example.pap; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + public class SupabaseConfig { - // O URL limpo, apenas com a barra no final! - public static final String URL = "https://lkjbbbgavoyknuxaskho.supabase.co"; + // ========================================================================= + // CONFIGURAÇÃO DO NOVO PROJETO (SUBSTITUI PELOS TEUS DADOS DO SUPABASE) + // ========================================================================= - // A tua chave está perfeita - public static final String API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxramJiYmdhdm95a251eGFza2hvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI2MjM4OTMsImV4cCI6MjA4ODE5OTg5M30.HV9ZXYCaF1V8dZwPxv_p5_gDi9cN_ioumDm9mgmEQPU"; -} + // 1. O teu Project URL (Ex: https://xyzabc.supabase.co/) + public static final String BASE_URL = "https://zfwfimmptccyzxvscbrg.supabase.co/"; + // 2. A tua API Key (A que diz 'anon' e 'public') + public static final String SUPABASE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inpmd2ZpbW1wdGNjeXp4dnNjYnJnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzcyODQ5MDMsImV4cCI6MjA5Mjg2MDkwM30.aE_JSy6OGx3aPfzraCCQ_CtQeA8qFSBGhgkaGBJDHos"; + + // ========================================================================= + + private static Retrofit retrofit = null; + + // Método que cria e entrega a ligação configurada para o resto da App + public static Retrofit getRetrofit() { + if (retrofit == null) { + retrofit = new Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) // Transforma JSON em código Java + .build(); + } + return retrofit; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap/VerificacaoActivity.java b/app/src/main/java/com/example/pap/VerificacaoActivity.java new file mode 100644 index 0000000..57d88b2 --- /dev/null +++ b/app/src/main/java/com/example/pap/VerificacaoActivity.java @@ -0,0 +1,75 @@ +package com.example.pap; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.widget.Button; +import android.widget.Toast; +import androidx.appcompat.app.AppCompatActivity; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class VerificacaoActivity extends AppCompatActivity { + + private Button btnJaConfirmei, btnVoltarLoginVerificacao; + private String emailGuardado, passwordGuardada; + private SharedPreferences sharedPreferences; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_verificacao); + + btnJaConfirmei = findViewById(R.id.btnJaConfirmei); + btnVoltarLoginVerificacao = findViewById(R.id.btnVoltarLoginVerificacao); + sharedPreferences = getSharedPreferences("MeusDadosApp", MODE_PRIVATE); + + // Receber o email e a password do ecrã de Registo + emailGuardado = getIntent().getStringExtra("email_registo"); + passwordGuardada = getIntent().getStringExtra("password_registo"); + + btnJaConfirmei.setOnClickListener(v -> { + Toast.makeText(this, "A verificar...", Toast.LENGTH_SHORT).show(); + tentarFazerLogin(); + }); + + btnVoltarLoginVerificacao.setOnClickListener(v -> { + startActivity(new Intent(VerificacaoActivity.this, LoginActivity.class)); + finish(); + }); + } + + private void tentarFazerLogin() { + SupabaseApi api = SupabaseConfig.getRetrofit().create(SupabaseApi.class); + UserCredentials creds = new UserCredentials(emailGuardado, passwordGuardada); + + api.login(SupabaseConfig.SUPABASE_KEY, creds).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if (response.isSuccessful() && response.body() != null) { + // SUCESSO! O email foi confirmado e o login funcionou + String token = response.body().access_token; + + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("access_token", token); + editor.putString("email", emailGuardado); + editor.apply(); + + Toast.makeText(VerificacaoActivity.this, "Email confirmado com sucesso! Bem-vindo!", Toast.LENGTH_LONG).show(); + startActivity(new Intent(VerificacaoActivity.this, HomeActivity.class)); + finish(); + } else { + // ERRO! Provavelmente o gajo ainda não clicou no link do email + Toast.makeText(VerificacaoActivity.this, "Ainda não confirmaste! Vai ao teu email e clica no link.", Toast.LENGTH_LONG).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(VerificacaoActivity.this, "Sem ligação à internet!", Toast.LENGTH_SHORT).show(); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/balao_ai.xml b/app/src/main/res/drawable/balao_ai.xml new file mode 100644 index 0000000..ac98348 --- /dev/null +++ b/app/src/main/res/drawable/balao_ai.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/balao_user.xml b/app/src/main/res/drawable/balao_user.xml new file mode 100644 index 0000000..db55b4b --- /dev/null +++ b/app/src/main/res/drawable/balao_user.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/fundo_input_ingredientes.xml b/app/src/main/res/drawable/fundo_input_ingredientes.xml new file mode 100644 index 0000000..0573399 --- /dev/null +++ b/app/src/main/res/drawable/fundo_input_ingredientes.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_online_dot.xml b/app/src/main/res/drawable/ic_online_dot.xml new file mode 100644 index 0000000..1c35696 --- /dev/null +++ b/app/src/main/res/drawable/ic_online_dot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/retangulo_mira.xml b/app/src/main/res/drawable/retangulo_mira.xml new file mode 100644 index 0000000..42de42b --- /dev/null +++ b/app/src/main/res/drawable/retangulo_mira.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml index 29894ac..412e7b7 100644 --- a/app/src/main/res/layout/activity_chat.xml +++ b/app/src/main/res/layout/activity_chat.xml @@ -1,83 +1,148 @@ + android:background="#F8FAFC"> + android:orientation="horizontal"> -