commit a5ee481b391e47170f49ab948125ecd1494070ff
Author: 230412 <230412@epvc.pt>
Date: Tue Mar 10 15:34:35 2026 +0000
first commit
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aa724b7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..9cf91de
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+PAP
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..b86273d
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..b268ef3
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..639c779
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..74dd639
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
new file mode 100644
index 0000000..68b0ad3
--- /dev/null
+++ b/app/build.gradle.kts
@@ -0,0 +1,46 @@
+plugins {
+ alias(libs.plugins.android.application)
+}
+
+android {
+ namespace = "com.example.pap"
+ compileSdk = 36
+
+ defaultConfig {
+ applicationId = "com.example.pap"
+ minSdk = 24
+ targetSdk = 36
+ versionCode = 1
+ versionName = "1.0"
+
+ testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(
+ getDefaultProguardFile("proguard-android-optimize.txt"),
+ "proguard-rules.pro"
+ )
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+ }
+}
+
+dependencies {
+
+ implementation(libs.appcompat)
+ implementation(libs.material)
+ implementation(libs.activity)
+ implementation(libs.constraintlayout)
+ testImplementation(libs.junit)
+ androidTestImplementation(libs.ext.junit)
+ androidTestImplementation(libs.espresso.core)
+ 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")
+}
\ No newline at end of file
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 0000000..481bb43
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/app/src/androidTest/java/com/example/pap/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/pap/ExampleInstrumentedTest.java
new file mode 100644
index 0000000..f703a1d
--- /dev/null
+++ b/app/src/androidTest/java/com/example/pap/ExampleInstrumentedTest.java
@@ -0,0 +1,26 @@
+package com.example.pap;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ assertEquals("com.example.pap", appContext.getPackageName());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..6f76c78
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 0000000..3df565b
--- /dev/null
+++ b/app/src/main/java/com/example/pap/ChatActivity.java
@@ -0,0 +1,138 @@
+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.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class ChatActivity extends AppCompatActivity {
+
+ private LinearLayout chatLayout;
+ private ScrollView chatScrollView;
+ private EditText etNovaMensagem;
+ private Button btnEnviarMensagem;
+ private Button btnVoltarChat;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_chat);
+
+ // 1. Associar os componentes do layout
+ 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);
+
+ // 2. Clique no botão de Enviar
+ btnEnviarMensagem.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String mensagemUser = etNovaMensagem.getText().toString().trim();
+
+ if (!mensagemUser.isEmpty()) {
+ // Adiciona a mensagem do utilizador no ecrã
+ adicionarMensagemBalao(mensagemUser, true);
+
+ // Limpa a caixa de texto
+ etNovaMensagem.setText("");
+
+ // Simula a IA a processar e a responder
+ simularRespostaDaIA(mensagemUser);
+ }
+ }
+ });
+
+ // 3. Clique no botão de Voltar
+ btnVoltarChat.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+ }
+
+ // 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);
+
+ // 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);
+
+ // 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
+ }
+
+ 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/DesafiosActivity.java b/app/src/main/java/com/example/pap/DesafiosActivity.java
new file mode 100644
index 0000000..7080206
--- /dev/null
+++ b/app/src/main/java/com/example/pap/DesafiosActivity.java
@@ -0,0 +1,147 @@
+package com.example.pap;
+
+import android.content.ActivityNotFoundException;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.MediaStore;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class DesafiosActivity extends AppCompatActivity {
+
+ private TextView tvPontos, tvStatusDesafio1, tvStatusDesafio2;
+ private Button btnVideoDesafio1, btnVideoDesafio2, btnVoltarDesafios;
+
+ private int pontosAtuais = 0;
+ private SharedPreferences sharedPreferences;
+
+ // Variável para saber qual desafio está a ser gravado no momento
+ private int desafioAtualEmGravacao = -1;
+
+ // Ferramenta para abrir a câmara de vídeo e receber o resultado
+ private final ActivityResultLauncher videoLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ if (result.getResultCode() == RESULT_OK) {
+ // O utilizador gravou o vídeo com sucesso!
+ Toast.makeText(this, "Vídeo capturado! A enviar para a IA...", Toast.LENGTH_SHORT).show();
+ simularAnaliseDaIA(desafioAtualEmGravacao);
+ } else {
+ Toast.makeText(this, "Gravação cancelada.", Toast.LENGTH_SHORT).show();
+ }
+ }
+ );
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_desafios);
+
+ // 1. Inicializar os componentes
+ tvPontos = findViewById(R.id.tvPontos);
+ tvStatusDesafio1 = findViewById(R.id.tvStatusDesafio1);
+ tvStatusDesafio2 = findViewById(R.id.tvStatusDesafio2);
+ btnVideoDesafio1 = findViewById(R.id.btnVideoDesafio1);
+ btnVideoDesafio2 = findViewById(R.id.btnVideoDesafio2);
+ btnVoltarDesafios = findViewById(R.id.btnVoltarDesafios);
+
+ // 2. Carregar os dados guardados no telemóvel
+ sharedPreferences = getSharedPreferences("AppEmagrecimento", MODE_PRIVATE);
+ carregarDadosGuardados();
+
+ // 3. Configurar os cliques para gravar vídeo
+ btnVideoDesafio1.setOnClickListener(v -> abrirCameraVideo(1));
+ btnVideoDesafio2.setOnClickListener(v -> abrirCameraVideo(2));
+
+ // 4. Botão de voltar
+ btnVoltarDesafios.setOnClickListener(v -> finish());
+ }
+
+ // Função para abrir a câmara do telemóvel em modo VÍDEO
+ private void abrirCameraVideo(int numeroDoDesafio) {
+ desafioAtualEmGravacao = numeroDoDesafio;
+ Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+
+ try {
+ videoLauncher.launch(intent);
+ } catch (ActivityNotFoundException e) {
+ // Caso o emulador não tenha uma câmara configurada, abre a galeria para selecionar um vídeo
+ Toast.makeText(this, "Câmara não encontrada. Selecione um vídeo da galeria.", Toast.LENGTH_LONG).show();
+ Intent galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
+ galleryIntent.setType("video/*");
+ videoLauncher.launch(galleryIntent);
+ }
+ }
+
+ // Função que simula o tempo de pensamento da Inteligência Artificial
+ private void simularAnaliseDaIA(int numeroDoDesafio) {
+ TextView tvStatusAtual = (numeroDoDesafio == 1) ? tvStatusDesafio1 : tvStatusDesafio2;
+ Button btnAtual = (numeroDoDesafio == 1) ? btnVideoDesafio1 : btnVideoDesafio2;
+
+ // Muda a interface para mostrar que a IA está a trabalhar
+ tvStatusAtual.setText("A IA está a analisar os teus movimentos...");
+ tvStatusAtual.setTextColor(Color.parseColor("#2196F3")); // Azul
+ btnAtual.setEnabled(false);
+ btnAtual.setText("A processar...");
+
+ // Simula uma espera de 3.5 segundos (tempo que a IA demoraria a analisar o vídeo)
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+
+ // Depois de 3.5 segundos, a IA aprova o desafio!
+ int pontosGanhos = (numeroDoDesafio == 1) ? 20 : 30;
+
+ // Atualizar UI
+ tvStatusAtual.setText("Aprovado pela IA! +" + pontosGanhos + " pts");
+ tvStatusAtual.setTextColor(Color.parseColor("#4CAF50")); // Verde
+ btnAtual.setText("Desafio Concluído");
+ btnAtual.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#81C784")));
+
+ // Adicionar pontos e guardar
+ adicionarPontosEGuardar(numeroDoDesafio, pontosGanhos);
+
+ }, 3500); // 3500 milissegundos = 3.5 segundos
+ }
+
+ private void adicionarPontosEGuardar(int numeroDoDesafio, int pontos) {
+ pontosAtuais += pontos;
+ tvPontos.setText(String.valueOf(pontosAtuais));
+
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putInt("pontosTotais", pontosAtuais);
+ editor.putBoolean("desafio_" + numeroDoDesafio + "_concluido", true);
+ editor.apply();
+ }
+
+ private void carregarDadosGuardados() {
+ pontosAtuais = sharedPreferences.getInt("pontosTotais", 0);
+ tvPontos.setText(String.valueOf(pontosAtuais));
+
+ // Verificar se o Desafio 1 já foi feito anteriormente
+ if (sharedPreferences.getBoolean("desafio_1_concluido", false)) {
+ tvStatusDesafio1.setText("Aprovado pela IA!");
+ tvStatusDesafio1.setTextColor(Color.parseColor("#4CAF50"));
+ btnVideoDesafio1.setEnabled(false);
+ btnVideoDesafio1.setText("Desafio Concluído");
+ btnVideoDesafio1.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#81C784")));
+ }
+
+ // Verificar se o Desafio 2 já foi feito anteriormente
+ if (sharedPreferences.getBoolean("desafio_2_concluido", false)) {
+ tvStatusDesafio2.setText("Aprovado pela IA!");
+ tvStatusDesafio2.setTextColor(Color.parseColor("#4CAF50"));
+ btnVideoDesafio2.setEnabled(false);
+ btnVideoDesafio2.setText("Desafio Concluído");
+ btnVideoDesafio2.setBackgroundTintList(android.content.res.ColorStateList.valueOf(Color.parseColor("#81C784")));
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap/EstatisticasActivity.java b/app/src/main/java/com/example/pap/EstatisticasActivity.java
new file mode 100644
index 0000000..2bd6fdd
--- /dev/null
+++ b/app/src/main/java/com/example/pap/EstatisticasActivity.java
@@ -0,0 +1,26 @@
+package com.example.pap;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class EstatisticasActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_estatisticas);
+
+ Button btnVoltarHome = findViewById(R.id.btnVoltarHome);
+
+ // Ao clicar no botão voltar, a janela de estatísticas fecha
+ // e o utilizador regressa naturalmente à HomeActivity que estava por baixo
+ btnVoltarHome.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+ }
+}
\ 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
new file mode 100644
index 0000000..49c8148
--- /dev/null
+++ b/app/src/main/java/com/example/pap/FotoActivity.java
@@ -0,0 +1,146 @@
+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.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;
+
+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;
+
+ // 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();
+ }
+ }
+ );
+
+ @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);
+
+ // Configurar a RecyclerView
+ listaIngredientes = new ArrayList<>();
+ recyclerView.setLayoutManager(new LinearLayoutManager(this));
+ adapter = new IngredientesAdapter(listaIngredientes);
+ recyclerView.setAdapter(adapter);
+
+ // Configurar o clique no botão de apagar (lixo) na lista
+ adapter.setOnItemClickListener(new IngredientesAdapter.OnItemClickListener() {
+ @Override
+ public void onDeleteClick(int position) {
+ removerIngrediente(position);
+ }
+ });
+
+ 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
new file mode 100644
index 0000000..5dafab2
--- /dev/null
+++ b/app/src/main/java/com/example/pap/HomeActivity.java
@@ -0,0 +1,135 @@
+package com.example.pap;
+
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.text.InputType;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.cardview.widget.CardView;
+
+public class HomeActivity extends AppCompatActivity {
+
+ private SharedPreferences sharedPreferences;
+
+ // Agora são 6 cartões!
+ private CardView cardPerfil, cardEstatisticas, cardTirarFoto, cardChat, cardDesafios, cardDefinicoes;
+
+ @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);
+
+ // 2. Ligar os cartões do XML ao código
+ cardPerfil = findViewById(R.id.cardPerfil);
+ cardEstatisticas = findViewById(R.id.cardEstatisticas);
+ cardTirarFoto = findViewById(R.id.cardTirarFoto);
+ cardChat = findViewById(R.id.cardChat);
+ cardDesafios = findViewById(R.id.cardDesafios);
+ cardDefinicoes = findViewById(R.id.cardDefinicoes);
+
+ // 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));
+ });
+
+ cardEstatisticas.setOnClickListener(v -> {
+ Toast.makeText(this, "A abrir Estatísticas...", Toast.LENGTH_SHORT).show();
+ // startActivity(new Intent(HomeActivity.this, EstatisticasActivity.class));
+ });
+
+ cardTirarFoto.setOnClickListener(v -> {
+ startActivity(new Intent(HomeActivity.this, FotoActivity.class));
+ });
+
+ cardChat.setOnClickListener(v -> {
+ startActivity(new Intent(HomeActivity.this, ChatActivity.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));
+ });
+
+ // 4. 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)
+ private void verificarAtualizacaoSemanal() {
+ long dataUltimaAtualizacao = sharedPreferences.getLong("data_ultima_atualizacao", 0);
+ long dataAtual = System.currentTimeMillis();
+
+ if (dataUltimaAtualizacao == 0) {
+ 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;
+ long tempoNecessario = 10 * 1000;
+
+ if (dataAtual - dataUltimaAtualizacao >= tempoNecessario) {
+ mostrarPopupAtualizacao();
+ }
+ }
+ }
+
+ // Função que cria o pop-up para atualizar o peso
+ 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.setCancelable(false);
+
+ LinearLayout layout = new LinearLayout(this);
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setPadding(50, 40, 50, 10);
+
+ final EditText etNovoPeso = new EditText(this);
+ etNovoPeso.setHint("Novo Peso (ex: 75.5)");
+ etNovoPeso.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
+ layout.addView(etNovoPeso);
+
+ final EditText etNovaAltura = new EditText(this);
+ etNovaAltura.setHint("Nova Altura (ex: 1.75)");
+ etNovaAltura.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
+ layout.addView(etNovaAltura);
+
+ builder.setView(layout);
+ builder.setPositiveButton("Atualizar Dados", null);
+
+ AlertDialog dialog = builder.create();
+ dialog.show();
+
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
+ String pesoStr = etNovoPeso.getText().toString().trim();
+ String alturaStr = etNovaAltura.getText().toString().trim();
+
+ if (!pesoStr.isEmpty() && !alturaStr.isEmpty()) {
+ float novoPeso = Float.parseFloat(pesoStr);
+ float novaAltura = Float.parseFloat(alturaStr);
+
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ editor.putFloat("pesoAtual", novoPeso);
+ editor.putFloat("alturaAtual", 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();
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap/Ingrediente.java b/app/src/main/java/com/example/pap/Ingrediente.java
new file mode 100644
index 0000000..3354670
--- /dev/null
+++ b/app/src/main/java/com/example/pap/Ingrediente.java
@@ -0,0 +1,25 @@
+package com.example.pap;
+
+// Esta classe é um modelo simples para guardar os dados de um ingrediente
+public class Ingrediente {
+ private String nome;
+ private int calorias;
+
+ public Ingrediente(String nome, int calorias) {
+ this.nome = nome;
+ this.calorias = calorias;
+ }
+
+ public String getNome() {
+ return nome;
+ }
+
+ public int getCalorias() {
+ return calorias;
+ }
+
+ // Vamos precisar disto para alterar a quantidade se quisermos no futuro
+ public void setCalorias(int calorias) {
+ this.calorias = calorias;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap/IngredientesAdapter.java b/app/src/main/java/com/example/pap/IngredientesAdapter.java
new file mode 100644
index 0000000..a7d0fdf
--- /dev/null
+++ b/app/src/main/java/com/example/pap/IngredientesAdapter.java
@@ -0,0 +1,79 @@
+package com.example.pap;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import java.util.ArrayList;
+
+// O Adapter é o motor que liga os dados à lista visual
+public class IngredientesAdapter extends RecyclerView.Adapter {
+
+ private ArrayList listaIngredientes;
+ private OnItemClickListener listener;
+
+ // Interface para avisar a Activity que um item foi apagado
+ public interface OnItemClickListener {
+ void onDeleteClick(int position);
+ }
+
+ public void setOnItemClickListener(OnItemClickListener listener) {
+ this.listener = listener;
+ }
+
+ // Construtor
+ public IngredientesAdapter(ArrayList listaIngredientes) {
+ this.listaIngredientes = listaIngredientes;
+ }
+
+ // Cria a "caixinha" visual para cada linha
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.activity_ingrediente, parent, false);
+ return new ViewHolder(view, listener);
+ }
+
+ // Preenche a "caixinha" com os dados do ingrediente atual
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ Ingrediente ingredienteAtual = listaIngredientes.get(position);
+ holder.tvNome.setText(ingredienteAtual.getNome());
+ holder.tvCalorias.setText(ingredienteAtual.getCalorias() + " kcal");
+ }
+
+ @Override
+ public int getItemCount() {
+ return listaIngredientes.size();
+ }
+
+ // Classe interna que guarda as referências aos elementos do layout de cada linha
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ public TextView tvNome;
+ public TextView tvCalorias;
+ public ImageButton btnDelete;
+
+ public ViewHolder(@NonNull View itemView, final OnItemClickListener listener) {
+ super(itemView);
+ tvNome = itemView.findViewById(R.id.tvNomeIngrediente);
+ tvCalorias = itemView.findViewById(R.id.tvCaloriasIngrediente);
+ btnDelete = itemView.findViewById(R.id.btnRemoverIngrediente);
+
+ // Configura o clique no botão de lixo
+ btnDelete.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (listener != null) {
+ int position = getAdapterPosition();
+ if (position != RecyclerView.NO_POSITION) {
+ listener.onDeleteClick(position);
+ }
+ }
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap/LoginActivity.java b/app/src/main/java/com/example/pap/LoginActivity.java
new file mode 100644
index 0000000..aa1441e
--- /dev/null
+++ b/app/src/main/java/com/example/pap/LoginActivity.java
@@ -0,0 +1,93 @@
+package com.example.pap;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+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;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_login);
+
+ // 1. Inicializar os componentes
+ 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());
+
+ 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();
+
+ // Validação básica
+ if (email.isEmpty() || password.isEmpty()) {
+ Toast.makeText(this, "Preencha o email e a password!", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ UserCredentials credentials = new UserCredentials(email, password);
+
+ // 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) {
+
+ // 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
+
+ } 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();
+ } else {
+ Toast.makeText(LoginActivity.this, "Erro " + response.code() + "! Link: " + urlQueFalhou, Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ Toast.makeText(LoginActivity.this, "Falha na ligação: " + t.getMessage(), Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap/MainActivity.java b/app/src/main/java/com/example/pap/MainActivity.java
new file mode 100644
index 0000000..87851ba
--- /dev/null
+++ b/app/src/main/java/com/example/pap/MainActivity.java
@@ -0,0 +1,24 @@
+package com.example.pap;
+
+import android.os.Bundle;
+
+import androidx.activity.EdgeToEdge;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+
+public class MainActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ setContentView(R.layout.activity_main);
+ ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
+ Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
+ v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
+ return insets;
+ });
+ }
+}
\ 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
new file mode 100644
index 0000000..83651e3
--- /dev/null
+++ b/app/src/main/java/com/example/pap/PerfilActivity.java
@@ -0,0 +1,39 @@
+package com.example.pap;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import androidx.appcompat.app.AppCompatActivity;
+
+public class PerfilActivity extends AppCompatActivity {
+
+ @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);
+
+ // Voltar à Home
+ btnVoltar.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ finish();
+ }
+ });
+
+ // 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();
+ }
+ });
+ }
+}
\ 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
new file mode 100644
index 0000000..ce9e85e
--- /dev/null
+++ b/app/src/main/java/com/example/pap/RegisterActivity.java
@@ -0,0 +1,134 @@
+package com.example.pap;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+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
+ etRegNome = findViewById(R.id.etRegNome);
+ etRegEmail = findViewById(R.id.etRegEmail);
+ etRegPassword = findViewById(R.id.etRegPassword);
+ etRegAltura = findViewById(R.id.etRegAltura);
+ etRegPeso = findViewById(R.id.etRegPeso);
+ 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());
+ 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();
+
+ // 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;
+ }
+
+ if (password.length() < 6) {
+ Toast.makeText(this, "A password precisa de pelo menos 6 caracteres.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ float peso = Float.parseFloat(pesoStr);
+ float altura = Float.parseFloat(alturaStr);
+
+ // FASE 1: Criar a conta (Email e Password) no Supabase Auth
+ UserCredentials credentials = new UserCredentials(email, password);
+
+ api.signUp(SupabaseConfig.API_KEY, credentials).enqueue(new Callback() {
+ @Override
+ public void onResponse(Call call, Response response) {
+ if (response.isSuccessful() && response.body() != null) {
+
+ // 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;
+
+ // 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();
+ }
+ }
+ }
+
+ @Override
+ public void onFailure(Call call, Throwable t) {
+ Toast.makeText(RegisterActivity.this, "Falha de rede (Sem internet?): " + t.getMessage(), Toast.LENGTH_SHORT).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
new file mode 100644
index 0000000..f832fd1
--- /dev/null
+++ b/app/src/main/java/com/example/pap/SupabaseApi.java
@@ -0,0 +1,65 @@
+package com.example.pap;
+
+import retrofit2.Call;
+import retrofit2.http.Body;
+import retrofit2.http.Header;
+import retrofit2.http.POST;
+
+public interface SupabaseApi {
+
+ // Rota para criar a conta
+ @POST("auth/v1/signup")
+ Call signUp(@Header("apikey") String apiKey, @Body UserCredentials credentials);
+
+ // 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
+ );
+}
+
+// ---- Classes Auxiliares para Formatarem os Dados ----
+
+class UserCredentials {
+ String email;
+ String password;
+
+ public UserCredentials(String email, String password) {
+ this.email = email;
+ this.password = password;
+ }
+}
+
+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;
+ }
+}
\ 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
new file mode 100644
index 0000000..5393356
--- /dev/null
+++ b/app/src/main/java/com/example/pap/SupabaseConfig.java
@@ -0,0 +1,10 @@
+package com.example.pap;
+
+public class SupabaseConfig {
+
+ // O URL limpo, apenas com a barra no final!
+ public static final String URL = "https://lkjbbbgavoyknuxaskho.supabase.co";
+
+ // A tua chave está perfeita
+ public static final String API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxramJiYmdhdm95a251eGFza2hvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzI2MjM4OTMsImV4cCI6MjA4ODE5OTg5M30.HV9ZXYCaF1V8dZwPxv_p5_gDi9cN_ioumDm9mgmEQPU";
+}
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ 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
new file mode 100644
index 0000000..29894ac
--- /dev/null
+++ b/app/src/main/res/layout/activity_chat.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_desafios.xml b/app/src/main/res/layout/activity_desafios.xml
new file mode 100644
index 0000000..bf52ad3
--- /dev/null
+++ b/app/src/main/res/layout/activity_desafios.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_estatisticas.xml b/app/src/main/res/layout/activity_estatisticas.xml
new file mode 100644
index 0000000..e558c20
--- /dev/null
+++ b/app/src/main/res/layout/activity_estatisticas.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_foto.xml b/app/src/main/res/layout/activity_foto.xml
new file mode 100644
index 0000000..b0cc3e5
--- /dev/null
+++ b/app/src/main/res/layout/activity_foto.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml
new file mode 100644
index 0000000..5e90290
--- /dev/null
+++ b/app/src/main/res/layout/activity_home.xml
@@ -0,0 +1,418 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_ingrediente.xml b/app/src/main/res/layout/activity_ingrediente.xml
new file mode 100644
index 0000000..04ea85b
--- /dev/null
+++ b/app/src/main/res/layout/activity_ingrediente.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
new file mode 100644
index 0000000..3eb75e8
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..86a5d97
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_perfil.xml b/app/src/main/res/layout/activity_perfil.xml
new file mode 100644
index 0000000..fbbc9ec
--- /dev/null
+++ b/app/src/main/res/layout/activity_perfil.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_register.xml b/app/src/main/res/layout/activity_register.xml
new file mode 100644
index 0000000..9cdaf93
--- /dev/null
+++ b/app/src/main/res/layout/activity_register.xml
@@ -0,0 +1,152 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..6929219
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..c8524cd
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,5 @@
+
+
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..f74cb97
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ PAP
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..bc7a9e5
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..4df9255
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/example/pap/ExampleUnitTest.java b/app/src/test/java/com/example/pap/ExampleUnitTest.java
new file mode 100644
index 0000000..769e726
--- /dev/null
+++ b/app/src/test/java/com/example/pap/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.pap;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..3756278
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,4 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ alias(libs.plugins.android.application) apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..4387edc
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..91c0a69
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,22 @@
+[versions]
+agp = "8.13.2"
+junit = "4.13.2"
+junitVersion = "1.3.0"
+espressoCore = "3.7.0"
+appcompat = "1.7.1"
+material = "1.13.0"
+activity = "1.12.4"
+constraintlayout = "2.2.1"
+
+[libraries]
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
+constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..c15e141
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Mar 03 15:23:41 WET 2026
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..8911994
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,24 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "PAP"
+include(":app")
+
\ No newline at end of file