diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3df0cb2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,46 @@
+# --- Ficheiros de Build e Binários ---
+.gradle/
+build/
+bin/
+gen/
+out/
+captures/
+.externalNativeBuild/
+.cxx/
+
+# --- Configurações Locais (NÃO partilhar, contêm caminhos do teu PC) ---
+local.properties
+
+# --- Android Studio / IntelliJ ---
+*.iml
+.idea/workspace.xml
+.idea/tasks.xml
+.idea/gradle.xml
+.idea/assetWizardSettings.xml
+.idea/dictionaries
+.idea/libraries
+.idea/caches
+.idea/modules.xml
+.idea/navEditor.xml
+.idea/vcs.xml
+.idea/dbnavigator.xml
+
+# --- Ficheiros de Log e Cache ---
+*.log
+.navigation/
+
+# --- Segredos e Chaves (Segurança Crítica) ---
+# Se usares ficheiros de chaves para assinar a App, nunca os metas no Git!
+*.jks
+*.keystore
+*.pk8
+*.p12
+google-services.json (opcional: depende se queres partilhar config do Firebase)
+
+# --- Ficheiros de Sistema ---
+.DS_Store
+Thumbs.db
+
+# --- Bundle Tool ---
+*.apks
+*.aab
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..fd12fb0
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Cuida
\ 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/appInsightsSettings.xml b/.idea/appInsightsSettings.xml
new file mode 100644
index 0000000..371f2e2
--- /dev/null
+++ b/.idea/appInsightsSettings.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
\ 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..9b1dd1a
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml
new file mode 100644
index 0000000..91f9558
--- /dev/null
+++ b/.idea/deviceManager.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
\ 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..9cd689a
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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/studiobot.xml b/.idea/studiobot.xml
new file mode 100644
index 0000000..539e3b8
--- /dev/null
+++ b/.idea/studiobot.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..c5f3f6b
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.configuration.updateBuildConfiguration": "interactive"
+}
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..3959019
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,75 @@
+plugins {
+ id 'com.android.application'
+ id 'com.google.gms.google-services'
+}
+
+android {
+ namespace 'com.example.cuida'
+ compileSdk 35
+
+ defaultConfig {
+ applicationId "com.example.cuida"
+ minSdk 24
+ targetSdk 35
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+
+
+ buildFeatures {
+ viewBinding true
+ }
+}
+
+dependencies {
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'com.google.ai.client.generativeai:generativeai:0.9.0'
+ implementation 'com.google.android.material:material:1.11.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0'
+ implementation 'androidx.navigation:navigation-fragment:2.7.7'
+ implementation 'androidx.navigation:navigation-ui:2.7.7'
+
+ // Adiciona a biblioteca para Auth se for do Google ID (credentials)
+ implementation 'androidx.biometric:biometric:1.1.0'
+ implementation 'androidx.credentials:credentials:1.5.0'
+ implementation 'androidx.credentials:credentials-play-services-auth:1.5.0'
+ //noinspection UseIdentifyId
+ implementation 'com.google.android.libraries.identity.googleid:googleid:1.1.1'
+
+
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+
+ // Location
+ implementation 'com.google.android.gms:play-services-location:21.0.1'
+
+ // Para chamadas de rede e JSON (Nova solução oficial de comunicação com a IA)
+ implementation 'com.squareup.retrofit2:retrofit:2.9.0'
+ implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
+ implementation 'com.squareup.okhttp3:okhttp:4.10.0'
+
+ // Firebase BoM
+ implementation platform('com.google.firebase:firebase-bom:32.7.2')
+
+ // Firebase Auth and Firestore
+ implementation 'com.google.firebase:firebase-auth'
+ implementation 'com.google.firebase:firebase-firestore'
+ implementation 'com.google.firebase:firebase-database'
+}
diff --git a/app/google-services.json b/app/google-services.json
new file mode 100644
index 0000000..89c6514
--- /dev/null
+++ b/app/google-services.json
@@ -0,0 +1,40 @@
+{
+ "project_info": {
+ "project_number": "844909242089",
+ "firebase_url": "https://cuidamais-7b904-default-rtdb.firebaseio.com",
+ "project_id": "cuidamais-7b904",
+ "storage_bucket": "cuidamais-7b904.firebasestorage.app"
+ },
+ "client": [
+ {
+ "client_info": {
+ "mobilesdk_app_id": "1:844909242089:android:4a039a7dbec802836ab278",
+ "android_client_info": {
+ "package_name": "com.example.cuida"
+ }
+ },
+ "oauth_client": [
+ {
+ "client_id": "844909242089-lvu2bh4u7hih6bm2a86rmdargnm4ul60.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
+ "api_key": [
+ {
+ "current_key": "AIzaSyCrTuHBRLoHkockoJEgAI9O7-gQJT6CkW4"
+ }
+ ],
+ "services": {
+ "appinvite_service": {
+ "other_platform_oauth_client": [
+ {
+ "client_id": "844909242089-lvu2bh4u7hih6bm2a86rmdargnm4ul60.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "configuration_version": "1"
+}
\ 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..6e721bd
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/example/cuida/CuidaApplication.java b/app/src/main/java/com/example/cuida/CuidaApplication.java
new file mode 100644
index 0000000..75bfa26
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/CuidaApplication.java
@@ -0,0 +1,19 @@
+package com.example.cuida;
+
+import android.app.Application;
+import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.FirebaseFirestoreSettings;
+
+public class CuidaApplication extends Application {
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ // Ativar persistência offline globalmente para toda a aplicação
+ FirebaseFirestore db = FirebaseFirestore.getInstance();
+ FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
+ .setPersistenceEnabled(true)
+ .build();
+ db.setFirestoreSettings(settings);
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/MainActivity.java b/app/src/main/java/com/example/cuida/MainActivity.java
new file mode 100644
index 0000000..81eee15
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/MainActivity.java
@@ -0,0 +1,64 @@
+package com.example.cuida;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.navigation.NavController;
+import androidx.navigation.fragment.NavHostFragment;
+import androidx.navigation.ui.AppBarConfiguration;
+import androidx.navigation.ui.NavigationUI;
+import com.example.cuida.databinding.ActivityMainBinding;
+import com.example.cuida.ui.auth.LoginActivity;
+import android.Manifest;
+import android.content.pm.PackageManager;
+import android.os.Build;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+
+import com.example.cuida.utils.NotificationHelper;
+
+public class MainActivity extends AppCompatActivity {
+
+ private ActivityMainBinding binding;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Initialize Notification Channels
+ new NotificationHelper(this);
+
+ // Check if user is logged in
+ boolean isLoggedIn = getSharedPreferences("prefs", Context.MODE_PRIVATE)
+ .getBoolean("is_logged_in", false);
+
+ if (!isLoggedIn) {
+ Intent intent = new Intent(this, LoginActivity.class);
+ startActivity(intent);
+ finish();
+ return;
+ }
+
+ // Check for Notification Permission on Android 13+ after ensuring user is
+ // logged in
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(this,
+ Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.POST_NOTIFICATIONS }, 101);
+ }
+ }
+
+ binding = ActivityMainBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ // Find Navigation Host Fragment and setup Bottom Navigation
+ NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
+ .findFragmentById(R.id.nav_host_fragment);
+ if (navHostFragment != null) {
+ NavController navController = navHostFragment.getNavController();
+ NavigationUI.setupWithNavController(binding.navView, navController);
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/data/model/Appointment.java b/app/src/main/java/com/example/cuida/data/model/Appointment.java
new file mode 100644
index 0000000..c85f045
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/data/model/Appointment.java
@@ -0,0 +1,31 @@
+package com.example.cuida.data.model;
+
+import com.google.firebase.firestore.DocumentId;
+
+public class Appointment {
+
+ @DocumentId
+ public String id;
+
+ public String type; // e.g. "Medicina Geral", "Cardiologia"
+ public String date; // dd/MM/yyyy
+ public String time; // HH:mm
+ public String reason;
+ public boolean isPast;
+ public String userId;
+ public String status; // "Pendente", "Aceite", "Rejeitada"
+
+ // Required empty constructor for Firestore deserialization
+ public Appointment() {
+ }
+
+ public Appointment(String type, String date, String time, String reason, boolean isPast, String userId, String status) {
+ this.type = type;
+ this.date = date;
+ this.time = time;
+ this.reason = reason;
+ this.isPast = isPast;
+ this.userId = userId;
+ this.status = status;
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/data/model/Comprimido.java b/app/src/main/java/com/example/cuida/data/model/Comprimido.java
new file mode 100644
index 0000000..f5abe0e
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/data/model/Comprimido.java
@@ -0,0 +1,18 @@
+package com.example.cuida.data.model;
+
+public class Comprimido {
+ public String nome;
+ public String dosagem;
+
+ public Comprimido() {}
+
+ public Comprimido(String nome, String dosagem) {
+ this.nome = nome;
+ this.dosagem = dosagem;
+ }
+
+ @Override
+ public String toString() {
+ return nome;
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/data/model/Medication.java b/app/src/main/java/com/example/cuida/data/model/Medication.java
new file mode 100644
index 0000000..e80e9b6
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/data/model/Medication.java
@@ -0,0 +1,76 @@
+package com.example.cuida.data.model;
+
+import com.google.firebase.firestore.PropertyName;
+
+public class Medication {
+ private String id;
+
+ @PropertyName("nome")
+ public String name;
+
+ @PropertyName("hora")
+ public String time;
+
+ @PropertyName("dosagem")
+ public String dosage;
+
+ @PropertyName("notas")
+ public String notes;
+
+ public boolean isTaken;
+ public String userId;
+
+ public Medication() {
+ // Obrigatório para o Firestore
+ }
+
+ public Medication(String name, String time, String dosage, String notes, String userId) {
+ this.name = name;
+ this.time = time;
+ this.dosage = dosage;
+ this.notes = notes;
+ this.isTaken = false;
+ this.userId = userId;
+ }
+
+ // --- Getters e Setters com compatibilidade para nomes antigos (name, time, dosage, notes) ---
+
+ @PropertyName("nome")
+ public String getName() { return name; }
+
+ @PropertyName("nome")
+ public void setName(String name) { this.name = name; }
+
+ @PropertyName("name") // Suporte para dados antigos
+ public void setNameOld(String name) { if (this.name == null) this.name = name; }
+
+ @PropertyName("hora")
+ public String getTime() { return time; }
+
+ @PropertyName("hora")
+ public void setTime(String time) { this.time = time; }
+
+ @PropertyName("time") // Suporte para dados antigos
+ public void setTimeOld(String time) { if (this.time == null) this.time = time; }
+
+ @PropertyName("dosagem")
+ public String getDosage() { return dosage; }
+
+ @PropertyName("dosagem")
+ public void setDosage(String dosage) { this.dosage = dosage; }
+
+ @PropertyName("dosage") // Suporte para dados antigos
+ public void setDosageOld(String dosage) { if (this.dosage == null) this.dosage = dosage; }
+
+ @PropertyName("notas")
+ public String getNotes() { return notes; }
+
+ @PropertyName("notas")
+ public void setNotes(String notes) { this.notes = notes; }
+
+ @PropertyName("notes") // Suporte para dados antigos
+ public void setNotesOld(String notes) { if (this.notes == null) this.notes = notes; }
+
+ public String getId() { return id; }
+ public void setId(String id) { this.id = id; }
+}
diff --git a/app/src/main/java/com/example/cuida/data/model/Perfil.java b/app/src/main/java/com/example/cuida/data/model/Perfil.java
new file mode 100644
index 0000000..a8e6ab9
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/data/model/Perfil.java
@@ -0,0 +1,82 @@
+package com.example.cuida.data.model;
+
+import com.google.gson.annotations.SerializedName;
+
+public class Perfil {
+ @SerializedName("id")
+ private String id;
+
+ @SerializedName("nome_completo")
+ private String nome_completo;
+
+ @SerializedName("idade")
+ private int idade;
+
+ @SerializedName("numero_utente")
+ private String numero_utente;
+
+ @SerializedName("gmail")
+ private String gmail;
+
+ @SerializedName("sexo")
+ private String sexo;
+
+ public Perfil(String id, String nome, int idade, String utente, String email, String sexo) {
+ this.id = id;
+ this.nome_completo = nome;
+ this.idade = idade;
+ this.numero_utente = utente;
+ this.gmail = email;
+ this.sexo = sexo;
+ }
+
+ // Getters
+ public String getId() {
+ return id;
+ }
+
+ public String getNomeCompleto() {
+ return nome_completo;
+ }
+
+ public int getIdade() {
+ return idade;
+ }
+
+ public String getNumeroUtente() {
+ return numero_utente;
+ }
+
+ public String getGmail() {
+ return gmail;
+ }
+
+ public String getSexo() {
+ return sexo;
+ }
+
+ // Setters
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setNomeCompleto(String nome_completo) {
+ this.nome_completo = nome_completo;
+ }
+
+ public void setIdade(int idade) {
+ this.idade = idade;
+ }
+
+ public void setNumeroUtente(String numero_utente) {
+ this.numero_utente = numero_utente;
+ }
+
+ public void setGmail(String gmail) {
+ this.gmail = gmail;
+ }
+
+ public void setSexo(String sexo) {
+ this.sexo = sexo;
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/data/model/User.java b/app/src/main/java/com/example/cuida/data/model/User.java
new file mode 100644
index 0000000..d69bded
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/data/model/User.java
@@ -0,0 +1,64 @@
+package com.example.cuida.data.model;
+
+import com.google.firebase.firestore.DocumentId;
+
+public class User {
+
+ @DocumentId
+ @com.google.firebase.firestore.Exclude
+ public String id;
+
+ @com.google.firebase.firestore.PropertyName("nome_completo")
+ public String name;
+
+ @com.google.firebase.firestore.PropertyName("email")
+ public String email;
+
+ @com.google.firebase.firestore.Exclude
+ public String password;
+
+ @com.google.firebase.firestore.PropertyName("idade")
+ public int age;
+
+ @com.google.firebase.firestore.PropertyName("numero_utente")
+ public String utenteNumber;
+
+ @com.google.firebase.firestore.PropertyName("profilePictureUri")
+ public String profilePictureUri;
+
+ @com.google.firebase.firestore.PropertyName("tipo")
+ public String tipo = "paciente";
+
+ // Required empty constructor for Firestore deserialization
+ public User() {
+ }
+
+ public User(String name, String email, String password, int age, String utenteNumber) {
+ this.name = name;
+ this.email = email;
+ this.password = password;
+ this.age = age;
+ this.utenteNumber = utenteNumber;
+ }
+
+ @com.google.firebase.firestore.PropertyName("nome_completo")
+ public String getName() { return name; }
+ @com.google.firebase.firestore.PropertyName("nome_completo")
+ public void setName(String name) { this.name = name; }
+
+ @com.google.firebase.firestore.PropertyName("idade")
+ public int getAge() { return age; }
+ @com.google.firebase.firestore.PropertyName("idade")
+ public void setAge(int age) { this.age = age; }
+
+ @com.google.firebase.firestore.PropertyName("numero_utente")
+ public String getUtenteNumber() { return utenteNumber; }
+ @com.google.firebase.firestore.PropertyName("numero_utente")
+ public void setUtenteNumber(String utenteNumber) { this.utenteNumber = utenteNumber; }
+
+ @com.google.firebase.firestore.PropertyName("tipo")
+ public String getTipo() { return tipo; }
+ @com.google.firebase.firestore.PropertyName("tipo")
+ public void setTipo(String tipo) { this.tipo = tipo; }
+
+}
diff --git a/app/src/main/java/com/example/cuida/services/AlarmReceiver.java b/app/src/main/java/com/example/cuida/services/AlarmReceiver.java
new file mode 100644
index 0000000..ed1cffb
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/services/AlarmReceiver.java
@@ -0,0 +1,29 @@
+package com.example.cuida.services;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+import com.example.cuida.utils.NotificationHelper;
+
+public class AlarmReceiver extends BroadcastReceiver {
+ private static final String TAG = "AlarmReceiver";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent != null) {
+ String title = intent.getStringExtra("EXTRA_TITLE");
+ String message = intent.getStringExtra("EXTRA_MESSAGE");
+ int notificationId = intent.getIntExtra("EXTRA_NOTIFICATION_ID", (int) System.currentTimeMillis());
+
+ Log.d(TAG, "Alarm received! Title: " + title + " Msg: " + message);
+
+ NotificationHelper notificationHelper = new NotificationHelper(context);
+ if (title != null && title.contains("Medicamento")) {
+ notificationHelper.sendNotification(title, message, notificationId, "MEDICATION_CHANNEL_ID");
+ } else {
+ notificationHelper.sendNotification(title, message, notificationId, "APPOINTMENT_CHANNEL_ID");
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/services/Gemini.java b/app/src/main/java/com/example/cuida/services/Gemini.java
new file mode 100644
index 0000000..aa6a9c2
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/services/Gemini.java
@@ -0,0 +1,89 @@
+package com.example.cuida.services;
+
+import android.os.Handler;
+import android.os.Looper;
+
+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 Gemini {
+ private static final String API_KEY = "AIzaSyBYar6Yv0rhrZX8cIQQxd77TLERHRsjAtY";
+ private static final String MODEL_NAME = "gemini-2.0-flash";
+ private static final String API_URL = "https://generativelanguage.googleapis.com/v1beta/models/" + MODEL_NAME + ":generateContent?key=" + API_KEY;
+ private final OkHttpClient client;
+ private final Handler mainHandler;
+
+ public Gemini() {
+ this.client = new OkHttpClient();
+ this.mainHandler = new Handler(Looper.getMainLooper());
+ }
+
+ public interface GeminiCallback {
+ void onSuccess(String result);
+ void onError(Throwable t);
+ }
+
+ public void fazerPergunta(String promptUtilizador, GeminiCallback callback) {
+ try {
+ JSONObject jsonBody = new JSONObject();
+ JSONArray contents = new JSONArray();
+ JSONObject content = new JSONObject();
+ JSONArray parts = new JSONArray();
+ JSONObject part = new JSONObject();
+
+ part.put("text", promptUtilizador);
+ parts.put(part);
+ content.put("parts", parts);
+ contents.put(content);
+ jsonBody.put("contents", contents);
+
+ RequestBody body = RequestBody.create(jsonBody.toString(), MediaType.parse("application/json; charset=utf-8"));
+ Request request = new Request.Builder()
+ .url(API_URL)
+ .post(body)
+ .build();
+
+ client.newCall(request).enqueue(new Callback() {
+ @Override
+ public void onFailure(Call call, IOException e) {
+ mainHandler.post(() -> callback.onError(e));
+ }
+
+ @Override
+ public void onResponse(Call call, Response response) throws IOException {
+ if (response.isSuccessful()) {
+ try {
+ String responseBody = response.body().string();
+ JSONObject jsonObject = new JSONObject(responseBody);
+ JSONArray candidates = jsonObject.getJSONArray("candidates");
+ JSONObject firstCandidate = candidates.getJSONObject(0);
+ JSONObject contentObj = firstCandidate.getJSONObject("content");
+ JSONArray partsArr = contentObj.getJSONArray("parts");
+ String textResult = partsArr.getJSONObject(0).getString("text");
+
+ mainHandler.post(() -> callback.onSuccess(textResult));
+ } catch (Exception e) {
+ mainHandler.post(() -> callback.onError(new Exception("Erro ao ler resposta da IA", e)));
+ }
+ } else {
+ String errorBody = response.body() != null ? response.body().string() : "Unknown error";
+ mainHandler.post(() -> callback.onError(new Exception("Erro da API HTTP " + response.code() + ": " + errorBody)));
+ }
+ }
+ });
+
+ } catch (Exception e) {
+ mainHandler.post(() -> callback.onError(e));
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/appointments/AppointmentAdapter.java b/app/src/main/java/com/example/cuida/ui/appointments/AppointmentAdapter.java
new file mode 100644
index 0000000..75ce7e6
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/appointments/AppointmentAdapter.java
@@ -0,0 +1,71 @@
+package com.example.cuida.ui.appointments;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import com.example.cuida.R;
+import com.example.cuida.data.model.Appointment;
+import java.util.ArrayList;
+import java.util.List;
+
+public class AppointmentAdapter extends RecyclerView.Adapter {
+
+ private List appointmentList = new ArrayList<>();
+
+ public void setAppointments(List appointments) {
+ this.appointmentList = appointments;
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public AppointmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_appointment, parent, false);
+ return new AppointmentViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull AppointmentViewHolder holder, int position) {
+ Appointment appointment = appointmentList.get(position);
+ holder.textType.setText(appointment.type);
+ holder.textDate.setText(appointment.date);
+ holder.textTime.setText(appointment.time);
+ holder.textReason.setText("Motivo: " + (appointment.reason != null ? appointment.reason : "--"));
+
+ String status = appointment.status != null ? appointment.status : "Pendente";
+ holder.textStatus.setText(status);
+
+ if ("Aceite".equalsIgnoreCase(status)) {
+ holder.textStatus.setTextColor(android.graphics.Color.parseColor("#388E3C")); // Green
+ holder.textStatus.setBackgroundColor(android.graphics.Color.parseColor("#C8E6C9"));
+ } else if ("Rejeitada".equalsIgnoreCase(status)) {
+ holder.textStatus.setTextColor(android.graphics.Color.parseColor("#D32F2F")); // Red
+ holder.textStatus.setBackgroundColor(android.graphics.Color.parseColor("#FFCDD2"));
+ } else {
+ holder.textStatus.setTextColor(android.graphics.Color.parseColor("#F57C00")); // Orange
+ holder.textStatus.setBackgroundColor(android.graphics.Color.parseColor("#FFE0B2"));
+ holder.textStatus.setText("Pendente");
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return appointmentList.size();
+ }
+
+ public static class AppointmentViewHolder extends RecyclerView.ViewHolder {
+ TextView textType, textDate, textTime, textReason, textStatus;
+
+ public AppointmentViewHolder(@NonNull View itemView) {
+ super(itemView);
+ textType = itemView.findViewById(R.id.text_type);
+ textDate = itemView.findViewById(R.id.text_date);
+ textTime = itemView.findViewById(R.id.text_time);
+ textReason = itemView.findViewById(R.id.text_reason);
+ textStatus = itemView.findViewById(R.id.text_status);
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/appointments/AppointmentsFragment.java b/app/src/main/java/com/example/cuida/ui/appointments/AppointmentsFragment.java
new file mode 100644
index 0000000..d8c856d
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/appointments/AppointmentsFragment.java
@@ -0,0 +1,51 @@
+package com.example.cuida.ui.appointments;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+
+import com.example.cuida.databinding.FragmentAppointmentsBinding;
+
+public class AppointmentsFragment extends Fragment {
+
+ private FragmentAppointmentsBinding binding;
+ private AppointmentsViewModel appointmentsViewModel;
+
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
+ appointmentsViewModel = new ViewModelProvider(this).get(AppointmentsViewModel.class);
+
+ binding = FragmentAppointmentsBinding.inflate(inflater, container, false);
+
+ // Future Appointments
+ AppointmentAdapter futureAdapter = new AppointmentAdapter();
+ binding.recyclerAppointmentsFuture.setLayoutManager(new LinearLayoutManager(getContext()));
+ binding.recyclerAppointmentsFuture.setAdapter(futureAdapter);
+
+ appointmentsViewModel.getFutureAppointments().observe(getViewLifecycleOwner(), appointments -> {
+ futureAdapter.setAppointments(appointments);
+ });
+
+ // Past Appointments
+ AppointmentAdapter pastAdapter = new AppointmentAdapter();
+ binding.recyclerAppointmentsPast.setLayoutManager(new LinearLayoutManager(getContext()));
+ binding.recyclerAppointmentsPast.setAdapter(pastAdapter);
+
+ appointmentsViewModel.getPastAppointments().observe(getViewLifecycleOwner(), appointments -> {
+ pastAdapter.setAppointments(appointments);
+ });
+
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/appointments/AppointmentsViewModel.java b/app/src/main/java/com/example/cuida/ui/appointments/AppointmentsViewModel.java
new file mode 100644
index 0000000..020f076
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/appointments/AppointmentsViewModel.java
@@ -0,0 +1,139 @@
+package com.example.cuida.ui.appointments;
+
+import android.app.Application;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.example.cuida.data.model.Appointment;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.Query;
+import com.google.firebase.firestore.QueryDocumentSnapshot;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AppointmentsViewModel extends AndroidViewModel {
+
+ private final MutableLiveData> futureAppointments = new MutableLiveData<>(new ArrayList<>());
+ private final MutableLiveData> pastAppointments = new MutableLiveData<>(new ArrayList<>());
+ private final FirebaseFirestore db;
+ private final FirebaseAuth auth;
+
+ public AppointmentsViewModel(@NonNull Application application) {
+ super(application);
+ db = FirebaseFirestore.getInstance();
+ auth = FirebaseAuth.getInstance();
+ fetchAppointments();
+ }
+
+ private void fetchAppointments() {
+ if (auth.getCurrentUser() == null)
+ return;
+ String userId = auth.getCurrentUser().getUid();
+
+ // 1. Fetch Future Appointments
+ db.collection("consultas")
+ .whereEqualTo("userId", userId)
+ .whereEqualTo("isPast", false)
+ .addSnapshotListener((value, error) -> {
+ if (error != null) {
+ Log.e("AppointmentsVM", "Listen failed for future.", error);
+ return;
+ }
+
+ List apps = new ArrayList<>();
+ java.util.Date now = new java.util.Date();
+ java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("dd/MM/yyyy HH:mm",
+ java.util.Locale.getDefault());
+
+ if (value != null) {
+ for (QueryDocumentSnapshot doc : value) {
+ Appointment app = doc.toObject(Appointment.class);
+ try {
+ java.util.Date appDate = format.parse(app.date + " " + app.time);
+ if (appDate != null && appDate.before(now)) {
+ // It passed out of date. Update in DB to move it to Past Appointments.
+ db.collection("consultas").document(doc.getId()).update("isPast", true);
+ } else {
+ apps.add(app);
+ }
+ } catch (java.text.ParseException e) {
+ apps.add(app);
+ }
+ }
+ }
+
+ // Sort locally
+ apps.sort((a, b) -> {
+ try {
+ java.util.Date dateA = format.parse(a.date + " " + a.time);
+ java.util.Date dateB = format.parse(b.date + " " + b.time);
+ return dateA.compareTo(dateB);
+ } catch (java.text.ParseException e) {
+ return 0;
+ }
+ });
+
+ futureAppointments.setValue(apps);
+ });
+
+ // 2. Fetch Past Appointments
+ db.collection("consultas")
+ .whereEqualTo("userId", userId)
+ .whereEqualTo("isPast", true)
+ .addSnapshotListener((value, error) -> {
+ if (error != null) {
+ Log.e("AppointmentsVM", "Listen failed for past.", error);
+ return;
+ }
+
+ List apps = new ArrayList<>();
+ if (value != null) {
+ for (QueryDocumentSnapshot doc : value) {
+ apps.add(doc.toObject(Appointment.class));
+ }
+ }
+
+ // Sort locally descending
+ java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("dd/MM/yyyy HH:mm",
+ java.util.Locale.getDefault());
+ apps.sort((a, b) -> {
+ try {
+ java.util.Date dateA = format.parse(a.date + " " + a.time);
+ java.util.Date dateB = format.parse(b.date + " " + b.time);
+ if (dateA == null || dateB == null) return 0;
+ return dateB.compareTo(dateA); // Reverse for descending
+ } catch (java.text.ParseException e) {
+ return 0;
+ }
+ });
+
+ pastAppointments.setValue(apps);
+ });
+ }
+
+ public LiveData> getFutureAppointments() {
+ return futureAppointments;
+ }
+
+ public LiveData> getPastAppointments() {
+ return pastAppointments;
+ }
+
+ public void insert(Appointment appointment) {
+ if (auth.getCurrentUser() == null)
+ return;
+ String userId = auth.getCurrentUser().getUid();
+
+ appointment.userId = userId;
+
+ db.collection("consultas")
+ .add(appointment)
+ .addOnSuccessListener(documentReference -> Log.d("AppointmentsVM", "Appointment added"))
+ .addOnFailureListener(e -> Log.w("AppointmentsVM", "Error adding appointment", e));
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/auth/ForgotPasswordActivity.java b/app/src/main/java/com/example/cuida/ui/auth/ForgotPasswordActivity.java
new file mode 100644
index 0000000..7d3a099
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/auth/ForgotPasswordActivity.java
@@ -0,0 +1,46 @@
+package com.example.cuida.ui.auth;
+
+import android.os.Bundle;
+import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+import com.example.cuida.databinding.ActivityForgotPasswordBinding;
+
+public class ForgotPasswordActivity extends AppCompatActivity {
+
+ private ActivityForgotPasswordBinding binding;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityForgotPasswordBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ binding.resetButton.setOnClickListener(v -> {
+ String email = binding.emailEditText.getText().toString().trim();
+ if (email.isEmpty()) {
+ Toast.makeText(this, "Insira o seu email.", Toast.LENGTH_SHORT).show();
+ } else {
+ // Real Firebase Password Reset Logic
+ com.google.firebase.auth.FirebaseAuth.getInstance().sendPasswordResetEmail(email)
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ Toast.makeText(this, "Email enviado!", Toast.LENGTH_SHORT).show();
+ finish();
+ } else {
+ String errorMsg = task.getException() != null ? task.getException().getMessage() : "Erro desconhecido";
+ if (errorMsg != null) {
+ if (errorMsg.contains("There is no user record")) {
+ errorMsg = "Não existe conta associada a este email.";
+ } else if (errorMsg.contains("badly formatted")) {
+ errorMsg = "O formato do email é inválido.";
+ }
+ }
+ Toast.makeText(this, "Erro: " + errorMsg, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ });
+
+ binding.backToLogin.setOnClickListener(v -> finish());
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/auth/LoginActivity.java b/app/src/main/java/com/example/cuida/ui/auth/LoginActivity.java
new file mode 100644
index 0000000..b449d26
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/auth/LoginActivity.java
@@ -0,0 +1,273 @@
+package com.example.cuida.ui.auth;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import com.example.cuida.MainActivity;
+import com.example.cuida.data.model.User;
+import com.example.cuida.databinding.ActivityLoginBinding;
+import com.example.cuida.R;
+
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import androidx.biometric.BiometricPrompt;
+import androidx.biometric.BiometricManager;
+import androidx.core.content.ContextCompat;
+import java.util.concurrent.Executor;
+
+public class LoginActivity extends AppCompatActivity {
+ // gvjhbk
+ private ActivityLoginBinding binding;
+ private FirebaseAuth mAuth;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Initialize Firebase Auth
+ mAuth = FirebaseAuth.getInstance();
+
+ // Check if user is already logged in and wants to be remembered
+ SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
+ boolean isLoggedIn = prefs.getBoolean("is_logged_in", false);
+ boolean rememberMe = prefs.getBoolean("remember_me", false);
+
+ if (isLoggedIn && rememberMe) {
+ if (mAuth.getCurrentUser() != null) {
+ startActivity(new Intent(this, MainActivity.class));
+ finish();
+ return;
+ }
+ } else {
+ // Se não for para lembrar a sessão, garantimos que o estado de login é falso
+ // mas NÃO limpamos as credenciais guardadas para a biometria.
+ if (mAuth.getCurrentUser() != null) {
+ mAuth.signOut();
+ }
+ prefs.edit().putBoolean("is_logged_in", false).apply();
+ }
+
+ binding = ActivityLoginBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ binding.loginButton.setOnClickListener(v -> login());
+ binding.registerLink.setOnClickListener(v -> {
+ startActivity(new Intent(this, RegisterActivity.class));
+ finish();
+ });
+
+ binding.forgotPasswordLink.setOnClickListener(v -> {
+ startActivity(new Intent(this, ForgotPasswordActivity.class));
+ });
+
+
+
+ setupBiometrics();
+ }
+
+ private void setupBiometrics() {
+ BiometricManager biometricManager = BiometricManager.from(this);
+ int canAuthenticate = biometricManager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG | BiometricManager.Authenticators.DEVICE_CREDENTIAL);
+
+ SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
+ String savedEmail = prefs.getString("saved_email", null);
+ String savedPass = prefs.getString("saved_pass", null);
+
+ if (canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS && savedEmail != null && savedPass != null) {
+ binding.biometricButton.setVisibility(android.view.View.VISIBLE);
+ binding.biometricButton.setOnClickListener(v -> showBiometricPrompt(savedEmail, savedPass));
+ }
+ }
+
+ private void showBiometricPrompt(String email, String pass) {
+ Executor executor = ContextCompat.getMainExecutor(this);
+ BiometricPrompt biometricPrompt = new BiometricPrompt(LoginActivity.this, executor, new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
+ super.onAuthenticationError(errorCode, errString);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ // Perform login with saved credentials
+ loginWithSavedCredentials(email, pass);
+ }
+
+ @Override
+ public void onAuthenticationFailed() {
+ super.onAuthenticationFailed();
+ }
+ });
+
+ BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
+ .setTitle("Autenticação Biométrica")
+ .setSubtitle("Entre na sua conta usando biometria")
+ .setNegativeButtonText("Usar Password")
+ .build();
+
+ biometricPrompt.authenticate(promptInfo);
+ }
+
+ private void loginWithSavedCredentials(String email, String pass) {
+ if (!isNetworkAvailable()) {
+ Toast.makeText(this, "Sem ligação à internet. Verifique a sua rede e tente novamente.", Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ binding.loginButton.setEnabled(false);
+ binding.loginButton.setText("A entrar...");
+
+ mAuth.signInWithEmailAndPassword(email, pass)
+ .addOnCompleteListener(this, task -> {
+ if (task.isSuccessful()) {
+ FirebaseUser user = mAuth.getCurrentUser();
+ if (user != null) {
+ handleSuccessfulAuth(user, email, pass, true);
+ }
+ } else {
+ binding.loginButton.setEnabled(true);
+ binding.loginButton.setText(R.string.login_button);
+ String errorMsg = traduzirErroFirebase(task.getException());
+ Toast.makeText(this, "Erro: " + errorMsg, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ private boolean isNetworkAvailable() {
+ ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (cm == null) return false;
+ NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
+ return activeNetwork != null && activeNetwork.isConnected();
+ }
+
+ private void login() {
+ String email = binding.emailEditText.getText().toString().trim();
+ String password = binding.passwordEditText.getText().toString();
+
+ if (email.isEmpty() || password.isEmpty()) {
+ Toast.makeText(this, "Preencha os campos.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (!isNetworkAvailable()) {
+ Toast.makeText(this, "Sem internet.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ binding.loginButton.setEnabled(false);
+ binding.loginButton.setText("A entrar...");
+
+ mAuth.signInWithEmailAndPassword(email, password)
+ .addOnCompleteListener(this, task -> {
+ if (task.isSuccessful()) {
+ FirebaseUser user = mAuth.getCurrentUser();
+ if (user != null) {
+ boolean rememberMe = binding.checkboxRememberMe.isChecked();
+ handleSuccessfulAuth(user, email, password, rememberMe);
+ }
+ } else {
+ binding.loginButton.setEnabled(true);
+ binding.loginButton.setText(R.string.login_button);
+
+ String errorMsg = traduzirErroFirebase(task.getException());
+ Toast.makeText(LoginActivity.this, errorMsg, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private String traduzirErroFirebase(Exception exception) {
+ if (exception == null) return "Erro desconhecido.";
+
+ String className = exception.getClass().getSimpleName();
+ if (className.contains("Network") || className.contains("IOException")) {
+ return "Sem internet.";
+ }
+
+ String msg = exception.getMessage();
+ if (msg == null) return "Erro desconhecido.";
+
+ if (msg.contains("NETWORK_REQUEST_FAILED") || msg.contains("network error")
+ || msg.contains("network") || msg.contains("Network")) {
+ return "Sem internet.";
+ } else if (msg.contains("invalid credential") || msg.contains("password is invalid")
+ || msg.contains("There is no user record") || msg.contains("INVALID_LOGIN_CREDENTIALS")) {
+ return "Credenciais erradas.";
+ } else if (msg.contains("badly formatted")) {
+ return "Email inválido.";
+ } else if (msg.contains("too many requests") || msg.contains("TOO_MANY_ATTEMPTS_TRY_LATER")) {
+ return "Tente mais tarde.";
+ } else if (msg.contains("user disabled") || msg.contains("USER_DISABLED")) {
+ return "Conta desativada.";
+ }
+
+ return "Erro ao entrar.";
+ }
+
+ private void handleSuccessfulAuth(FirebaseUser user, String email, String password, boolean rememberMe) {
+ SharedPreferences prefs = getSharedPreferences("prefs", MODE_PRIVATE);
+ prefs.edit().putBoolean("is_logged_in", true).apply();
+ prefs.edit().putBoolean("remember_me", rememberMe).apply();
+
+ // Guardar sempre para biometria após login com sucesso
+ prefs.edit().putString("saved_email", email).apply();
+ prefs.edit().putString("saved_pass", password).apply();
+
+ // Tentar primeiro na coleção 'utilizadores'
+ com.google.firebase.firestore.FirebaseFirestore db = com.google.firebase.firestore.FirebaseFirestore.getInstance();
+ db.collection("utilizadores").document(user.getUid()).get()
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful() && task.getResult() != null && task.getResult().exists()) {
+ com.google.firebase.firestore.DocumentSnapshot doc = task.getResult();
+ String name = doc.getString("name");
+ String dbEmail = doc.getString("email");
+
+ if (name != null) prefs.edit().putString("user_name", name).apply();
+ if (dbEmail != null) prefs.edit().putString("user_email", dbEmail).apply();
+
+ proceedToMain();
+ } else {
+ // Tentar na coleção 'medicos'
+ db.collection("medicos").document(user.getUid()).get()
+ .addOnCompleteListener(task2 -> {
+ if (task2.isSuccessful() && task2.getResult() != null && task2.getResult().exists()) {
+ com.google.firebase.firestore.DocumentSnapshot doc = task2.getResult();
+ String name = doc.getString("nome"); // Notar campo 'nome' em vez de 'name'
+ String dbEmail = doc.getString("email");
+
+ if (name != null) prefs.edit().putString("user_name", name).apply();
+ if (dbEmail != null) prefs.edit().putString("user_email", dbEmail).apply();
+
+ proceedToMain();
+ } else {
+ // Fallback se não encontrar em lado nenhum
+ prefs.edit().putString("user_email", user.getEmail()).apply();
+ if (user.getDisplayName() != null && !user.getDisplayName().isEmpty()) {
+ prefs.edit().putString("user_name", user.getDisplayName()).apply();
+ } else {
+ String authEmail = user.getEmail();
+ if (authEmail != null && authEmail.contains("@")) {
+ String fallbackName = authEmail.substring(0, authEmail.indexOf("@"));
+ prefs.edit().putString("user_name", fallbackName).apply();
+ }
+ }
+ proceedToMain();
+ }
+ });
+ }
+ });
+ }
+
+ private void proceedToMain() {
+ Toast.makeText(LoginActivity.this, "Bem-vindo!", Toast.LENGTH_SHORT).show();
+ startActivity(new Intent(LoginActivity.this, com.example.cuida.MainActivity.class));
+ finish();
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/auth/RegisterActivity.java b/app/src/main/java/com/example/cuida/ui/auth/RegisterActivity.java
new file mode 100644
index 0000000..7d63772
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/auth/RegisterActivity.java
@@ -0,0 +1,118 @@
+package com.example.cuida.ui.auth;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Toast;
+import androidx.appcompat.app.AppCompatActivity;
+import com.example.cuida.data.model.User;
+import com.example.cuida.databinding.ActivityRegisterBinding;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.firestore.FirebaseFirestore;
+
+public class RegisterActivity extends AppCompatActivity {
+
+ private ActivityRegisterBinding binding;
+ private FirebaseAuth mAuth;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityRegisterBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ binding.registerButton.setOnClickListener(v -> register());
+ binding.loginLink.setOnClickListener(v -> {
+ startActivity(new Intent(this, LoginActivity.class));
+ finish();
+ });
+
+ String[] genders = new String[]{"Masculino", "Feminino"};
+ android.widget.ArrayAdapter adapter = new android.widget.ArrayAdapter<>(
+ this, android.R.layout.simple_dropdown_item_1line, genders);
+ binding.genderAutoComplete.setAdapter(adapter);
+ }
+
+ private void register() {
+ String name = binding.nameEditText.getText().toString();
+ String ageStr = binding.ageEditText.getText().toString();
+ String utenteStr = binding.utenteEditText.getText().toString();
+ String email = binding.emailEditText.getText().toString();
+ String password = binding.passwordEditText.getText().toString();
+ String gender = binding.genderAutoComplete.getText().toString();
+
+ if (name.isEmpty() || ageStr.isEmpty() || email.isEmpty() || password.isEmpty() || utenteStr.isEmpty() || gender.isEmpty()) {
+ Toast.makeText(this, "Preencha os campos.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (utenteStr.length() != 9) {
+ Toast.makeText(this, "Utente deve ter 9 dígitos.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ int age = Integer.parseInt(ageStr);
+
+ binding.registerButton.setEnabled(false);
+ binding.registerButton.setText("A registar...");
+
+ mAuth = FirebaseAuth.getInstance();
+ FirebaseFirestore db = FirebaseFirestore
+ .getInstance();
+ mAuth.createUserWithEmailAndPassword(email, password)
+ .addOnCompleteListener(this, task -> {
+ if (task.isSuccessful()) {
+ // Registration success, save additional info to Firestore
+ FirebaseUser firebaseUser = mAuth.getCurrentUser();
+ if (firebaseUser != null) {
+ saveUserData(firebaseUser.getUid(), name, email, ageStr, utenteStr, gender);
+ }
+ } else {
+ Exception e = task.getException();
+ if (e instanceof com.google.firebase.auth.FirebaseAuthUserCollisionException) {
+ // Tenta fazer login automático para reparar o perfil se ele não existir no Firestore
+ mAuth.signInWithEmailAndPassword(email, password)
+ .addOnSuccessListener(authResult -> {
+ saveUserData(authResult.getUser().getUid(), name, email, ageStr, utenteStr, gender);
+ })
+ .addOnFailureListener(err -> {
+ binding.registerButton.setEnabled(true);
+ binding.registerButton.setText("Registar");
+ Toast.makeText(RegisterActivity.this, "Email já registado.", Toast.LENGTH_SHORT).show();
+ });
+ } else {
+ binding.registerButton.setEnabled(true);
+ binding.registerButton.setText("Registar");
+ String errorMsg = e != null ? e.getMessage() : "Erro desconhecido";
+ Toast.makeText(RegisterActivity.this, "Erro: " + errorMsg, Toast.LENGTH_LONG).show();
+ }
+ }
+ });
+ }
+
+ private void saveUserData(String userId, String name, String email, String ageStr, String utenteStr, String gender) {
+ FirebaseFirestore db = FirebaseFirestore.getInstance();
+ java.util.Map userMap = new java.util.HashMap<>();
+ userMap.put("id", userId);
+ userMap.put("nome_completo", name);
+ userMap.put("email", email);
+ userMap.put("idade", ageStr);
+ userMap.put("numero_utente", utenteStr);
+ userMap.put("sexo", gender);
+ userMap.put("tipo", "paciente");
+ userMap.put("profilePictureUri", "");
+
+ db.collection("utilizadores").document(userId)
+ .set(userMap)
+ .addOnSuccessListener(aVoid -> {
+ Toast.makeText(RegisterActivity.this, "Conta criada!", Toast.LENGTH_SHORT).show();
+ startActivity(new Intent(RegisterActivity.this, LoginActivity.class));
+ finish();
+ })
+ .addOnFailureListener(e -> {
+ binding.registerButton.setEnabled(true);
+ binding.registerButton.setText("Registar");
+ Toast.makeText(RegisterActivity.this, "Erro ao guardar dados.", Toast.LENGTH_SHORT).show();
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/cuida/ui/auth/ResetPasswordActivity.java b/app/src/main/java/com/example/cuida/ui/auth/ResetPasswordActivity.java
new file mode 100644
index 0000000..6056835
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/auth/ResetPasswordActivity.java
@@ -0,0 +1,82 @@
+package com.example.cuida.ui.auth;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.example.cuida.databinding.ActivityResetPasswordBinding;
+import com.google.firebase.auth.FirebaseAuth;
+
+public class ResetPasswordActivity extends AppCompatActivity {
+
+ private ActivityResetPasswordBinding binding;
+ private String oobCode;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ binding = ActivityResetPasswordBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ // Use custom uri scheme or https scheme, extracting oobCode parameter
+ Intent intent = getIntent();
+ if (intent != null && intent.getData() != null) {
+ Uri data = intent.getData();
+ oobCode = data.getQueryParameter("oobCode");
+
+ if (oobCode == null || oobCode.isEmpty()) {
+ Toast.makeText(this, "Link inválido.", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ } else {
+ Toast.makeText(this, "Código não encontrado.", Toast.LENGTH_SHORT).show();
+ finish();
+ }
+
+ binding.saveNewPasswordButton.setOnClickListener(v -> saveNewPassword());
+ }
+
+ private void saveNewPassword() {
+ String newPassword = binding.newPasswordEditText.getText().toString();
+ String confirmPassword = binding.confirmNewPasswordEditText.getText().toString();
+
+ if (newPassword.isEmpty() || confirmPassword.isEmpty()) {
+ Toast.makeText(this, "Preencha as passwords.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (!newPassword.equals(confirmPassword)) {
+ Toast.makeText(this, "Pass não coincidem.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (newPassword.length() < 6) {
+ Toast.makeText(this, "Mínimo 6 caracteres.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ binding.saveNewPasswordButton.setEnabled(false);
+ binding.saveNewPasswordButton.setText("A guardar...");
+
+ FirebaseAuth.getInstance().confirmPasswordReset(oobCode, newPassword)
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ Toast.makeText(this, "Pass atualizada!", Toast.LENGTH_SHORT).show();
+ // Go back to login screen
+ Intent intent = new Intent(ResetPasswordActivity.this, LoginActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ finish();
+ } else {
+ binding.saveNewPasswordButton.setEnabled(true);
+ binding.saveNewPasswordButton.setText("Guardar Palavra-passe");
+
+ String errorMsg = task.getException() != null ? task.getException().getMessage() : "Erro desconhecido";
+ Toast.makeText(this, "Erro: " + errorMsg, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/home/HomeFragment.java b/app/src/main/java/com/example/cuida/ui/home/HomeFragment.java
new file mode 100644
index 0000000..8a2a27f
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/home/HomeFragment.java
@@ -0,0 +1,95 @@
+package com.example.cuida.ui.home;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import com.example.cuida.databinding.FragmentHomeBinding;
+import com.example.cuida.ui.medication.MedicationViewModel;
+import com.example.cuida.ui.appointments.AppointmentsViewModel;
+import com.example.cuida.data.model.Appointment;
+import java.util.Calendar;
+import java.util.Locale;
+
+public class HomeFragment extends Fragment {
+
+ private FragmentHomeBinding binding;
+ private MedicationViewModel medicationViewModel;
+ private AppointmentsViewModel appointmentsViewModel;
+
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
+ binding = FragmentHomeBinding.inflate(inflater, container, false);
+
+ // --- Greeting & Profile Picture ---
+ com.google.firebase.auth.FirebaseAuth auth = com.google.firebase.auth.FirebaseAuth.getInstance();
+ if (auth.getCurrentUser() != null) {
+ String userId = auth.getCurrentUser().getUid();
+ com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("utilizadores").document(userId)
+ .get()
+ .addOnSuccessListener(documentSnapshot -> {
+ if (documentSnapshot.exists() && isAdded()) {
+ // Tenta 'nome_completo' (novo) ou 'name' (antigo)
+ String name = documentSnapshot.getString("nome_completo");
+ if (name == null || name.isEmpty()) name = documentSnapshot.getString("name");
+
+ if (name != null && !name.isEmpty()) {
+ // Extract first name
+ String firstName = name.split(" ")[0];
+ binding.textGreeting.setText("Olá, " + firstName + "!");
+ } else {
+ binding.textGreeting.setText("Olá, Utilizador!");
+ }
+
+ // Load Profile Picture
+ String profilePictureUri = documentSnapshot.getString("profilePictureUri");
+ if (profilePictureUri != null && !profilePictureUri.isEmpty()) {
+ try {
+ binding.imageProfileHome.setImageURI(android.net.Uri.parse(profilePictureUri));
+ } catch (Exception e) {
+ android.util.Log.e("HomeFragment", "Error loading profile pic view: " + e.getMessage());
+ }
+ }
+ }
+ })
+ .addOnFailureListener(e -> {
+ if (isAdded())
+ binding.textGreeting.setText("Olá, Utilizador!");
+ });
+ } else {
+ binding.textGreeting.setText("Olá, Utilizador!");
+ }
+
+ // --- Next Medication ---
+ medicationViewModel = new ViewModelProvider(this).get(MedicationViewModel.class);
+ medicationViewModel.getNextMedication().observe(getViewLifecycleOwner(), medication -> {
+ if (medication != null) {
+ binding.nextMedName.setText(medication.name + " (" + medication.dosage + ")");
+ binding.nextMedTime.setText("Hoje, " + medication.time);
+ } else {
+ binding.nextMedName.setText("Sem medicação");
+ binding.nextMedTime.setText("--:--");
+ }
+ });
+
+ // --- Book Appointment ---
+ appointmentsViewModel = new ViewModelProvider(this).get(AppointmentsViewModel.class);
+ binding.buttonBookAppointment.setOnClickListener(v -> {
+ androidx.navigation.Navigation.findNavController(v)
+ .navigate(com.example.cuida.R.id.action_home_to_schedule_appointment);
+ });
+
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/medication/ComprimidoRecyclerAdapter.java b/app/src/main/java/com/example/cuida/ui/medication/ComprimidoRecyclerAdapter.java
new file mode 100644
index 0000000..2320135
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/medication/ComprimidoRecyclerAdapter.java
@@ -0,0 +1,54 @@
+package com.example.cuida.ui.medication;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import com.example.cuida.R;
+import com.example.cuida.data.model.Comprimido;
+import java.util.List;
+
+public class ComprimidoRecyclerAdapter extends RecyclerView.Adapter {
+
+ private List pills;
+ private OnItemClickListener listener;
+
+ public interface OnItemClickListener {
+ void onItemClick(Comprimido comprimido);
+ }
+
+ public ComprimidoRecyclerAdapter(List pills, OnItemClickListener listener) {
+ this.pills = pills;
+ this.listener = listener;
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_comprimido_search, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ Comprimido pill = pills.get(position);
+ holder.textName.setText(pill.nome);
+ holder.itemView.setOnClickListener(v -> listener.onItemClick(pill));
+ }
+
+ @Override
+ public int getItemCount() {
+ return pills.size();
+ }
+
+ public static class ViewHolder extends RecyclerView.ViewHolder {
+ TextView textName;
+
+ public ViewHolder(@NonNull View itemView) {
+ super(itemView);
+ textName = itemView.findViewById(R.id.text_pill_name);
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/medication/MedicationAdapter.java b/app/src/main/java/com/example/cuida/ui/medication/MedicationAdapter.java
new file mode 100644
index 0000000..d1ba491
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/medication/MedicationAdapter.java
@@ -0,0 +1,85 @@
+package com.example.cuida.ui.medication;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import com.example.cuida.R;
+import com.example.cuida.data.model.Medication;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MedicationAdapter extends RecyclerView.Adapter {
+
+ private List medicationList = new ArrayList<>();
+ private final OnItemClickListener listener;
+
+ public interface OnItemClickListener {
+ void onCheckClick(Medication medication);
+
+ void onItemClick(Medication medication);
+ }
+
+ public MedicationAdapter(OnItemClickListener listener) {
+ this.listener = listener;
+ }
+
+ public void setMedications(List medications) {
+ this.medicationList = medications;
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public MedicationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_medication, parent, false);
+ return new MedicationViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MedicationViewHolder holder, int position) {
+ Medication medication = medicationList.get(position);
+ holder.textName.setText(medication.name);
+ holder.textDosage.setText(medication.dosage);
+ holder.textTime.setText(medication.time);
+ holder.textNotes.setText(medication.notes);
+
+ // Remove listener temporarily to avoid triggering it during bind
+ holder.checkBoxTaken.setOnCheckedChangeListener(null);
+ holder.checkBoxTaken.setChecked(medication.isTaken);
+
+ holder.checkBoxTaken.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ medication.isTaken = isChecked;
+ listener.onCheckClick(medication);
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return medicationList.size();
+ }
+
+ public class MedicationViewHolder extends RecyclerView.ViewHolder {
+ TextView textName, textDosage, textTime, textNotes;
+ CheckBox checkBoxTaken;
+
+ public MedicationViewHolder(@NonNull View itemView) {
+ super(itemView);
+ textName = itemView.findViewById(R.id.text_med_name);
+ textDosage = itemView.findViewById(R.id.text_med_dosage);
+ textTime = itemView.findViewById(R.id.text_med_time);
+ textNotes = itemView.findViewById(R.id.text_med_notes);
+ checkBoxTaken = itemView.findViewById(R.id.checkbox_taken);
+
+ itemView.setOnClickListener(v -> {
+ int position = getAdapterPosition();
+ if (listener != null && position != RecyclerView.NO_POSITION) {
+ listener.onItemClick(medicationList.get(position));
+ }
+ });
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/medication/MedicationDialog.java b/app/src/main/java/com/example/cuida/ui/medication/MedicationDialog.java
new file mode 100644
index 0000000..84af82c
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/medication/MedicationDialog.java
@@ -0,0 +1,390 @@
+package com.example.cuida.ui.medication;
+
+import android.app.Dialog;
+import android.app.TimePickerDialog;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import com.google.android.material.textfield.TextInputEditText;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import com.example.cuida.R;
+import com.example.cuida.data.model.Medication;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.ArrayList;
+import java.util.List;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+import com.google.firebase.database.ValueEventListener;
+import android.text.Editable;
+import android.text.TextWatcher;
+import com.example.cuida.data.model.Comprimido;
+import android.widget.AdapterView;
+import android.widget.Toast;
+import com.google.android.material.chip.Chip;
+import com.google.android.material.chip.ChipGroup;
+import com.google.android.material.button.MaterialButton;
+import java.util.Collections;
+
+public class MedicationDialog extends DialogFragment {
+
+ private TextInputEditText editName;
+ private RecyclerView recyclerResults;
+ private ComprimidoRecyclerAdapter recyclerAdapter;
+ private List searchResults = new ArrayList<>();
+ private List fullPillsList = new ArrayList<>();
+ private DatabaseReference medicationRef;
+ private EditText editNotes;
+ private android.widget.RadioButton radioOral, radioTopical, radioInhalatory;
+ private android.widget.RadioGroup radioGroupRoute;
+ private ChipGroup chipGroupTimes;
+ private List selectedTimes = new ArrayList<>();
+ private Medication medicationToEdit;
+ private OnMedicationSaveListener listener;
+ private OnMedicationDeleteListener deleteListener;
+
+ public interface OnMedicationSaveListener {
+ void onSave(Medication medication);
+ }
+
+ public interface OnMedicationDeleteListener {
+ void onDelete(Medication medication);
+ }
+
+ public void setListener(OnMedicationSaveListener listener) {
+ this.listener = listener;
+ }
+
+ public void setDeleteListener(OnMedicationDeleteListener listener) {
+ this.deleteListener = listener;
+ }
+
+ public void setMedicationToEdit(Medication medication) {
+ this.medicationToEdit = medication;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
+ LayoutInflater inflater = requireActivity().getLayoutInflater();
+ View view = inflater.inflate(R.layout.dialog_add_medication, null);
+
+ editName = view.findViewById(R.id.edit_med_name);
+ recyclerResults = view.findViewById(R.id.recycler_search_results);
+ editNotes = view.findViewById(R.id.edit_med_notes);
+ chipGroupTimes = view.findViewById(R.id.chip_group_times);
+ MaterialButton btnAddTime = view.findViewById(R.id.btn_add_time);
+
+ radioGroupRoute = view.findViewById(R.id.radio_group_route);
+ radioOral = view.findViewById(R.id.radio_oral);
+ radioTopical = view.findViewById(R.id.radio_topical);
+ radioInhalatory = view.findViewById(R.id.radio_inhalatory);
+
+ final android.content.Context currentContext = getContext();
+ if (currentContext != null) {
+ recyclerAdapter = new ComprimidoRecyclerAdapter(searchResults, selected -> {
+ editName.setText(selected.nome);
+ editName.setSelection(selected.nome.length());
+
+ // Adiciona a dosagem/informação ao campo de notas automaticamente
+ if (selected.dosagem != null && !selected.dosagem.isEmpty()) {
+ editNotes.setText(selected.dosagem);
+ }
+
+ recyclerResults.setVisibility(View.GONE);
+ searchResults.clear();
+ });
+ recyclerResults.setLayoutManager(new LinearLayoutManager(currentContext));
+ recyclerResults.setAdapter(recyclerAdapter);
+
+ String dbUrl = "https://cuidamais-7b904-default-rtdb.firebaseio.com/";
+ medicationRef = FirebaseDatabase.getInstance(dbUrl).getReference("medication");
+
+ // Carregar todos os medicamentos uma única vez para filtragem local rápida
+ fetchAllMedsOnce();
+
+ editName.addTextChangedListener(new TextWatcher() {
+ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ filterMedsLocally(s.toString().trim());
+ }
+ @Override public void afterTextChanged(Editable s) {}
+ });
+ }
+
+ radioOral = view.findViewById(R.id.radio_oral);
+ radioTopical = view.findViewById(R.id.radio_topical);
+ radioInhalatory = view.findViewById(R.id.radio_inhalatory);
+
+ // Set up TimePicker
+ btnAddTime.setOnClickListener(v -> showTimePicker());
+
+ if (medicationToEdit != null) {
+ editName.setText(medicationToEdit.name);
+ editNotes.setText(medicationToEdit.notes);
+ if (medicationToEdit.time != null && !medicationToEdit.time.isEmpty()) {
+ String[] times = medicationToEdit.time.split(",\\s*");
+ for (String t : times) {
+ if (!t.isEmpty()) selectedTimes.add(t);
+ }
+ java.util.Collections.sort(selectedTimes);
+ refreshTimeChips();
+ }
+
+ String dosage = medicationToEdit.dosage;
+ if (dosage != null) {
+ if (dosage.contains("Oral"))
+ radioOral.setChecked(true);
+ else if (dosage.contains("Tópica"))
+ radioTopical.setChecked(true);
+ else if (dosage.contains("Inalatória"))
+ radioInhalatory.setChecked(true);
+ }
+
+ builder.setTitle("Editar Medicamento");
+ } else {
+ builder.setTitle("Adicionar Medicamento");
+ // Default time to current time
+ Calendar cal = Calendar.getInstance();
+ String defaultTime = String.format(Locale.getDefault(), "%02d:%02d", cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
+ selectedTimes.add(defaultTime);
+ refreshTimeChips();
+ }
+
+ // Positive button sem lógica — a validação é feita no setOnShowListener abaixo
+ builder.setView(view)
+ .setPositiveButton("Guardar", null);
+
+ if (medicationToEdit != null) {
+ builder.setNeutralButton("Eliminar", (dialog, id) -> {
+ if (deleteListener != null) {
+ deleteListener.onDelete(medicationToEdit);
+ }
+ });
+ }
+
+ AlertDialog alertDialog = builder.create();
+
+ alertDialog.setOnShowListener(d -> {
+ android.widget.Button btnPos = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (btnPos != null) {
+ // Estilo do botão Guardar
+ btnPos.setBackgroundResource(R.drawable.btn_outline_primary);
+ btnPos.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.primary_color));
+ int paddingPx = (int) (16 * getResources().getDisplayMetrics().density);
+ btnPos.setPadding(paddingPx, 0, paddingPx, 0);
+
+ // Validação ao clicar em Guardar (impede o fecho automático do dialog)
+ btnPos.setOnClickListener(v -> {
+ String name = editName.getText() != null ? editName.getText().toString().trim() : "";
+
+ // 1. Nome obrigatório
+ if (name.isEmpty()) {
+ Toast.makeText(getContext(), "Preencha o nome.", Toast.LENGTH_SHORT).show();
+ editName.requestFocus();
+ return;
+ }
+
+ // 2. Via de administração obrigatória
+ int selectedId = radioGroupRoute.getCheckedRadioButtonId();
+ if (selectedId == -1) {
+ Toast.makeText(getContext(), "Selecione a via.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // 3. Pelo menos um horário obrigatório
+ if (selectedTimes.isEmpty()) {
+ Toast.makeText(getContext(), "Adicione um horário.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // Tudo válido — construir e guardar
+ String notes = editNotes.getText() != null ? editNotes.getText().toString() : "";
+
+ StringBuilder timeBuilder = new StringBuilder();
+ for (int i = 0; i < selectedTimes.size(); i++) {
+ timeBuilder.append(selectedTimes.get(i));
+ if (i < selectedTimes.size() - 1) timeBuilder.append(", ");
+ }
+ String time = timeBuilder.toString();
+
+ String dosage;
+ if (selectedId == R.id.radio_oral) {
+ dosage = "Via Oral";
+ } else if (selectedId == R.id.radio_topical) {
+ dosage = "Via Tópica";
+ } else {
+ dosage = "Via Inalatória";
+ }
+
+ if (medicationToEdit != null) {
+ medicationToEdit.name = name;
+ medicationToEdit.dosage = dosage;
+ medicationToEdit.notes = notes;
+ medicationToEdit.time = time;
+ if (listener != null) listener.onSave(medicationToEdit);
+ } else {
+ Medication newMed = new Medication(name, time, dosage, notes, null);
+ if (listener != null) listener.onSave(newMed);
+ }
+
+ alertDialog.dismiss();
+ });
+ }
+
+ android.widget.Button btnNeu = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
+ if (btnNeu != null) {
+ btnNeu.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.error_color));
+ btnNeu.setBackgroundResource(R.drawable.btn_outline_error);
+
+ int paddingPx = (int) (16 * getResources().getDisplayMetrics().density);
+ btnNeu.setPadding(paddingPx, 0, paddingPx, 0);
+
+ android.view.ViewGroup.LayoutParams lp = btnNeu.getLayoutParams();
+ if (lp instanceof android.view.ViewGroup.MarginLayoutParams) {
+ android.view.ViewGroup.MarginLayoutParams marginLp = (android.view.ViewGroup.MarginLayoutParams) lp;
+ int marginPx = (int) (8 * getResources().getDisplayMetrics().density);
+ marginLp.setMargins(marginLp.leftMargin, marginLp.topMargin, marginLp.rightMargin + marginPx, marginLp.bottomMargin);
+ btnNeu.setLayoutParams(marginLp);
+ }
+ }
+ });
+
+ return alertDialog;
+ }
+
+ private void showTimePicker() {
+ Calendar cal = Calendar.getInstance();
+ int hour = cal.get(Calendar.HOUR_OF_DAY);
+ int minute = cal.get(Calendar.MINUTE);
+
+ TimePickerDialog timePickerDialog = new TimePickerDialog(getContext(),
+ (view, hourOfDay, minute1) -> {
+ String time = String.format(Locale.getDefault(), "%02d:%02d", hourOfDay, minute1);
+ if (!selectedTimes.contains(time)) {
+ selectedTimes.add(time);
+ Collections.sort(selectedTimes);
+ refreshTimeChips();
+ }
+ },
+ hour, minute, true);
+ timePickerDialog.show();
+ }
+
+ private void refreshTimeChips() {
+ if (chipGroupTimes == null || getContext() == null) return;
+ chipGroupTimes.removeAllViews();
+ for (String time : selectedTimes) {
+ Chip chip = new Chip(getContext());
+ chip.setText(time);
+ chip.setCloseIconVisible(true);
+ chip.setOnCloseIconClickListener(v -> {
+ selectedTimes.remove(time);
+ refreshTimeChips();
+ });
+ chipGroupTimes.addView(chip);
+ }
+ }
+
+ private void fetchAllMedsOnce() {
+ String dbUrl = "https://cuidamais-7b904-default-rtdb.firebaseio.com/";
+ DatabaseReference rootRef = FirebaseDatabase.getInstance(dbUrl).getReference();
+ String[] nodes = {"medication", "medicamentos", "Medicamentos", "comprimidos"};
+
+ fullPillsList.clear();
+
+ // 1. Tentar nos nós específicos
+ for (String node : nodes) {
+ rootRef.child(node).addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(@NonNull DataSnapshot snapshot) {
+ if (snapshot.exists()) {
+ parseSnapshot(snapshot, "Nó: " + node);
+ }
+ }
+ @Override public void onCancelled(@NonNull DatabaseError error) {}
+ });
+ }
+
+ // 2. Tentar também na raiz (caso os medicamentos estejam diretamente no topo)
+ rootRef.limitToFirst(50).addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(@NonNull DataSnapshot snapshot) {
+ if (snapshot.exists()) {
+ parseSnapshot(snapshot, "Raiz");
+ }
+ }
+ @Override public void onCancelled(@NonNull DatabaseError error) {}
+ });
+ }
+
+ private void parseSnapshot(DataSnapshot snapshot, String source) {
+ int count = 0;
+ for (DataSnapshot child : snapshot.getChildren()) {
+ String name = child.child("nome").getValue(String.class);
+ if (name == null) name = child.child("name").getValue(String.class);
+ if (name == null && !(child.getValue() instanceof java.util.Map)) {
+ // Se o valor for a própria string (ex: "Paracetamol")
+ name = child.getValue() instanceof String ? (String) child.getValue() : null;
+ }
+ if (name == null) name = child.getKey();
+
+ String dosage = child.child("dosagem").getValue(String.class);
+ if (dosage == null) dosage = child.child("dosage").getValue(String.class);
+ if (dosage == null) dosage = "";
+
+ if (name != null && !name.isEmpty()) {
+ boolean exists = false;
+ for (Comprimido p : fullPillsList) {
+ if (name.equals(p.nome)) { exists = true; break; }
+ }
+ if (!exists) {
+ fullPillsList.add(new Comprimido(name, dosage));
+ count++;
+ }
+ }
+ }
+ if (count > 0 && getContext() != null) {
+ Log.d("FirebaseSearch", "Carregados " + count + " de " + source);
+ // Toast.makeText(getContext(), "Fonte: " + source + " (" + count + ")", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void filterMedsLocally(String query) {
+ searchResults.clear();
+ if (query.isEmpty()) {
+ recyclerResults.setVisibility(View.GONE);
+ recyclerAdapter.notifyDataSetChanged();
+ return;
+ }
+
+ String lowerQuery = query.toLowerCase();
+ for (Comprimido p : fullPillsList) {
+ if (p.nome != null && p.nome.toLowerCase().contains(lowerQuery)) {
+ searchResults.add(p);
+ }
+ }
+
+ recyclerAdapter.notifyDataSetChanged();
+ recyclerResults.setVisibility(searchResults.isEmpty() ? View.GONE : View.VISIBLE);
+ }
+
+ private void handleError(DatabaseError error) {
+ Log.e("FirebaseSearch", "Erro: " + error.getMessage());
+ if (getContext() != null) {
+ Toast.makeText(getContext(), "Erro no Firebase: " + error.getMessage(), Toast.LENGTH_LONG).show();
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/medication/MedicationFragment.java b/app/src/main/java/com/example/cuida/ui/medication/MedicationFragment.java
new file mode 100644
index 0000000..c98b8c6
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/medication/MedicationFragment.java
@@ -0,0 +1,141 @@
+package com.example.cuida.ui.medication;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import com.example.cuida.data.model.Medication;
+import com.example.cuida.databinding.FragmentMedicationBinding;
+
+public class MedicationFragment extends Fragment {
+
+ private FragmentMedicationBinding binding;
+ private MedicationViewModel medicationViewModel;
+
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
+ medicationViewModel = new ViewModelProvider(this).get(MedicationViewModel.class);
+
+ binding = FragmentMedicationBinding.inflate(inflater, container, false);
+
+ MedicationAdapter adapter = new MedicationAdapter(new MedicationAdapter.OnItemClickListener() {
+ @Override
+ public void onCheckClick(Medication medication) {
+ medicationViewModel.update(medication);
+ }
+
+ @Override
+ public void onItemClick(Medication medication) {
+ showMedicationDialog(medication);
+ }
+ });
+
+ binding.recyclerMedication.setLayoutManager(new LinearLayoutManager(getContext()));
+ binding.recyclerMedication.setAdapter(adapter);
+
+ medicationViewModel.getAllMedications().observe(getViewLifecycleOwner(), medications -> {
+ adapter.setMedications(medications);
+
+ if (medications != null && !medications.isEmpty()) {
+ binding.recyclerMedication.setVisibility(View.VISIBLE);
+ binding.textEmptyMedications.setVisibility(View.GONE);
+ } else {
+ binding.recyclerMedication.setVisibility(View.GONE);
+ binding.textEmptyMedications.setVisibility(View.VISIBLE);
+ }
+ });
+
+ binding.fabAddMedication.setOnClickListener(v -> showMedicationDialog(null));
+
+ return binding.getRoot();
+ }
+
+ private void showMedicationDialog(Medication medication) {
+ MedicationDialog dialog = new MedicationDialog();
+ dialog.setMedicationToEdit(medication);
+ dialog.setListener(medicationToSave -> {
+ // If it's an edit, cancel old alarms first
+ if (medication != null && medication.time != null) {
+ String[] oldTimes = medication.time.split(",\\s*");
+ for (String t : oldTimes) {
+ if (t.isEmpty()) continue;
+ try {
+ int oldId = (medication.name + t).hashCode();
+ com.example.cuida.utils.AlarmScheduler.cancelAlarm(requireContext(), oldId);
+ } catch (Exception e) {}
+ }
+ }
+
+ if (medication == null) {
+ medicationViewModel.insert(medicationToSave);
+ } else {
+ medicationViewModel.update(medicationToSave);
+ }
+
+ String[] times = medicationToSave.time.split(",\\s*");
+ for (String t : times) {
+ if (t.isEmpty()) continue;
+ try {
+ String[] timeParts = t.split(":");
+ int hour = Integer.parseInt(timeParts[0]);
+ int minute = Integer.parseInt(timeParts[1]);
+
+ java.util.Calendar calendar = java.util.Calendar.getInstance();
+ calendar.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ calendar.set(java.util.Calendar.MINUTE, minute);
+ calendar.set(java.util.Calendar.SECOND, 0);
+
+ if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
+ calendar.add(java.util.Calendar.DAY_OF_YEAR, 1);
+ }
+
+ String title = "Hora do Medicamento";
+ String msg = "É hora de tomar: " + medicationToSave.name + " (" + medicationToSave.dosage + ")";
+
+ int alarmId = (medicationToSave.name + t).hashCode();
+
+ com.example.cuida.utils.AlarmScheduler.scheduleAlarm(
+ requireContext(),
+ calendar.getTimeInMillis(),
+ title,
+ msg,
+ alarmId);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ dialog.dismiss();
+ });
+
+ dialog.setDeleteListener(medicationToDelete -> {
+ medicationViewModel.delete(medicationToDelete);
+
+ // Cancel all alarms for this medication
+ if (medicationToDelete.time != null) {
+ String[] times = medicationToDelete.time.split(",\\s*");
+ for (String t : times) {
+ if (t.isEmpty()) continue;
+ try {
+ int alarmId = (medicationToDelete.name + t).hashCode();
+ com.example.cuida.utils.AlarmScheduler.cancelAlarm(requireContext(), alarmId);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+
+ dialog.show(getParentFragmentManager(), "MedicationDialog");
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/medication/MedicationViewModel.java b/app/src/main/java/com/example/cuida/ui/medication/MedicationViewModel.java
new file mode 100644
index 0000000..ebeaab9
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/medication/MedicationViewModel.java
@@ -0,0 +1,118 @@
+package com.example.cuida.ui.medication;
+
+import android.app.Application;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.example.cuida.data.model.Medication;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.Query;
+import com.google.firebase.firestore.QueryDocumentSnapshot;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MedicationViewModel extends AndroidViewModel {
+
+ private final MutableLiveData> allMedications = new MutableLiveData<>(new ArrayList<>());
+ private final MutableLiveData nextMedication = new MutableLiveData<>(null);
+ private final FirebaseFirestore db;
+ private final FirebaseAuth auth;
+
+ public MedicationViewModel(@NonNull Application application) {
+ super(application);
+ db = FirebaseFirestore.getInstance();
+ auth = FirebaseAuth.getInstance();
+ fetchMedications();
+ }
+
+ private void fetchMedications() {
+ if (auth.getCurrentUser() == null)
+ return;
+ String userId = auth.getCurrentUser().getUid();
+
+ db.collection("medicamentos")
+ .whereEqualTo("userId", userId)
+ .addSnapshotListener((value, error) -> {
+ if (error != null) {
+ Log.e("MedicationViewModel", "Listen failed.", error);
+ return;
+ }
+
+ List meds = new ArrayList<>();
+ if (value != null) {
+ for (QueryDocumentSnapshot doc : value) {
+ Medication med = doc.toObject(Medication.class);
+ med.setId(doc.getId()); // Ensure ID is set
+ meds.add(med);
+ }
+ }
+
+ // Sort locally to avoid needing a composite index in Firestore
+ meds.sort((m1, m2) -> {
+ if (m1.time == null && m2.time == null) return 0;
+ if (m1.time == null) return 1;
+ if (m2.time == null) return -1;
+ return m1.time.compareTo(m2.time);
+ });
+
+ allMedications.setValue(meds);
+
+ if (!meds.isEmpty()) {
+ nextMedication.setValue(meds.get(0));
+ } else {
+ nextMedication.setValue(null);
+ }
+ });
+ }
+
+ public LiveData> getAllMedications() {
+ return allMedications;
+ }
+
+ public LiveData getNextMedication() {
+ return nextMedication;
+ }
+
+ public void insert(Medication medication) {
+ if (auth.getCurrentUser() == null)
+ return;
+ String userId = auth.getCurrentUser().getUid();
+
+ medication.userId = userId;
+
+ db.collection("medicamentos")
+ .add(medication)
+ .addOnSuccessListener(documentReference -> Log.d("MedicationViewModel", "Medication added"))
+ .addOnFailureListener(e -> Log.w("MedicationViewModel", "Error adding medication", e));
+ }
+
+ public void update(Medication medication) {
+ if (auth.getCurrentUser() == null || medication.getId() == null)
+ return;
+ String userId = auth.getCurrentUser().getUid();
+
+ medication.userId = userId;
+
+ db.collection("medicamentos")
+ .document(medication.getId())
+ .set(medication)
+ .addOnSuccessListener(aVoid -> Log.d("MedicationViewModel", "Medication updated"))
+ .addOnFailureListener(e -> Log.w("MedicationViewModel", "Error updating medication", e));
+ }
+
+ public void delete(Medication medication) {
+ if (auth.getCurrentUser() == null || medication.getId() == null)
+ return;
+
+ db.collection("medicamentos")
+ .document(medication.getId())
+ .delete()
+ .addOnSuccessListener(aVoid -> Log.d("MedicationViewModel", "Medication deleted"))
+ .addOnFailureListener(e -> Log.w("MedicationViewModel", "Error deleting medication", e));
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/profile/ProfileFragment.java b/app/src/main/java/com/example/cuida/ui/profile/ProfileFragment.java
new file mode 100644
index 0000000..f4641a7
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/profile/ProfileFragment.java
@@ -0,0 +1,229 @@
+package com.example.cuida.ui.profile;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
+import com.example.cuida.R;
+import com.example.cuida.data.model.User;
+import com.example.cuida.databinding.FragmentProfileBinding;
+import com.example.cuida.ui.auth.LoginActivity;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.FirebaseFirestore;
+
+public class ProfileFragment extends Fragment {
+
+ private FragmentProfileBinding binding;
+ private User currentUser;
+ private FirebaseFirestore db;
+ private FirebaseAuth auth;
+ private Uri tempProfileUri;
+ private ImageView dialogImageView;
+
+ private final androidx.activity.result.ActivityResultLauncher pickMedia = registerForActivityResult(
+ new androidx.activity.result.contract.ActivityResultContracts.GetContent(), uri -> {
+ if (uri != null) {
+ tempProfileUri = uri;
+ try {
+ requireContext().getContentResolver().takePersistableUriPermission(uri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (Exception e) {
+ Log.e("ProfileFragment", "Permission error: " + e.getMessage());
+ }
+ if (dialogImageView != null) {
+ dialogImageView.setImageURI(uri);
+ }
+ }
+ });
+
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ binding = FragmentProfileBinding.inflate(inflater, container, false);
+
+ db = FirebaseFirestore.getInstance();
+ auth = FirebaseAuth.getInstance();
+ loadUserData();
+
+ binding.buttonEditProfile.setOnClickListener(v -> showEditDialog());
+ binding.buttonLogout.setOnClickListener(v -> {
+ auth.signOut();
+ if (getContext() != null) {
+ requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE).edit().clear().apply();
+ }
+ startActivity(new Intent(getContext(), LoginActivity.class));
+ requireActivity().finish();
+ });
+
+ return binding.getRoot();
+ }
+
+ private void loadUserData() {
+ if (auth.getCurrentUser() == null) return;
+ String userId = auth.getCurrentUser().getUid();
+
+ if (currentUser == null) {
+ currentUser = new User();
+ currentUser.id = userId;
+ currentUser.email = auth.getCurrentUser().getEmail();
+ currentUser.name = auth.getCurrentUser().getDisplayName();
+ }
+
+ db.collection("utilizadores").document(userId).get()
+ .addOnSuccessListener(doc -> {
+ if (doc.exists() && isAdded()) {
+ currentUser.id = doc.getId();
+ String nome = doc.getString("nome_completo");
+ if (nome == null) nome = doc.getString("name");
+ currentUser.name = nome;
+ currentUser.email = doc.getString("email");
+
+ String utente = doc.getString("numero_utente");
+ if (utente == null) utente = doc.getString("utenteNumber");
+ currentUser.utenteNumber = utente;
+
+ currentUser.profilePictureUri = doc.getString("profilePictureUri");
+
+ Object ageObj = doc.get("idade");
+ if (ageObj == null) ageObj = doc.get("age");
+ if (ageObj instanceof Number) currentUser.age = ((Number) ageObj).intValue();
+ else if (ageObj instanceof String) {
+ try { currentUser.age = Integer.parseInt((String) ageObj); }
+ catch (Exception e) { currentUser.age = 0; }
+ }
+
+ updateUI();
+ }
+ });
+ }
+
+ private void updateUI() {
+ if (!isAdded() || binding == null || currentUser == null) return;
+ binding.profileName.setText(currentUser.name != null ? currentUser.name : "N/D");
+ binding.profileEmail.setText(currentUser.email != null ? currentUser.email : "N/D");
+ binding.profileAge.setText(currentUser.age > 0 ? String.valueOf(currentUser.age) : "N/D");
+ binding.profileUtente.setText(currentUser.utenteNumber != null ? currentUser.utenteNumber : "N/D");
+
+ if (currentUser.profilePictureUri != null && !currentUser.profilePictureUri.isEmpty()) {
+ ImageView profileImage = binding.getRoot().findViewById(R.id.profile_image);
+ if (profileImage != null) loadSafeImage(profileImage, currentUser.profilePictureUri);
+ }
+ }
+
+ private void loadSafeImage(ImageView view, String uriStr) {
+ if (view == null || uriStr == null) return;
+ try {
+ Uri uri = Uri.parse(uriStr);
+ if (uri.getScheme() != null && (uri.getScheme().equals("content") || uri.getScheme().equals("file"))) {
+ view.setImageURI(uri);
+ } else {
+ Log.d("ProfileFragment", "Skipping setImageURI for non-local scheme: " + uri.getScheme());
+ }
+ } catch (Exception e) {
+ Log.e("ProfileFragment", "Image load error: " + e.getMessage());
+ }
+ }
+
+ private void showEditDialog() {
+ if (currentUser == null) {
+ Toast.makeText(getContext(), "Dados não carregados.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ try {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ View dialogView = requireActivity().getLayoutInflater().inflate(R.layout.dialog_edit_profile, null);
+ builder.setView(dialogView);
+ AlertDialog dialog = builder.create();
+
+ EditText editName = dialogView.findViewById(R.id.edit_name);
+ EditText editAge = dialogView.findViewById(R.id.edit_age);
+ EditText editUtente = dialogView.findViewById(R.id.edit_utente);
+ EditText editEmail = dialogView.findViewById(R.id.edit_email);
+ dialogImageView = dialogView.findViewById(R.id.edit_profile_image);
+
+ if (editName == null || dialogImageView == null) return;
+
+ editName.setText(currentUser.name);
+ editAge.setText(String.valueOf(currentUser.age));
+ editUtente.setText(currentUser.utenteNumber);
+ editEmail.setText(currentUser.email);
+
+ if (currentUser.profilePictureUri != null) loadSafeImage(dialogImageView, currentUser.profilePictureUri);
+
+ dialogView.findViewById(R.id.button_change_photo).setOnClickListener(v -> pickMedia.launch("image/*"));
+ dialogImageView.setOnClickListener(v -> pickMedia.launch("image/*"));
+ dialogView.findViewById(R.id.button_change_password).setOnClickListener(v -> showChangePasswordDialog());
+
+ dialogView.findViewById(R.id.button_cancel).setOnClickListener(v -> dialog.dismiss());
+ dialogView.findViewById(R.id.button_save).setOnClickListener(v -> {
+ String newName = editName.getText().toString().trim();
+ String ageStr = editAge.getText().toString().trim();
+ String newUtente = editUtente.getText().toString().trim();
+ String newEmail = editEmail.getText().toString().trim();
+
+ if (newName.isEmpty() || ageStr.isEmpty() || newUtente.isEmpty() || newEmail.isEmpty()) {
+ Toast.makeText(getContext(), "Preencha todos os campos.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ currentUser.name = newName;
+ try { currentUser.age = Integer.parseInt(ageStr); } catch (Exception ignored) {}
+ currentUser.utenteNumber = newUtente;
+
+ if (tempProfileUri != null) currentUser.profilePictureUri = tempProfileUri.toString();
+
+ db.collection("utilizadores").document(currentUser.id).set(currentUser)
+ .addOnSuccessListener(aVoid -> {
+ Toast.makeText(getContext(), "Perfil atualizado!", Toast.LENGTH_SHORT).show();
+ loadUserData();
+ dialog.dismiss();
+ })
+ .addOnFailureListener(e -> Toast.makeText(getContext(), "Erro ao guardar.", Toast.LENGTH_SHORT).show());
+ });
+
+ dialog.show();
+ } catch (Exception e) {
+ Toast.makeText(getContext(), "Erro ao abrir edição.", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void showChangePasswordDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+ View view = requireActivity().getLayoutInflater().inflate(R.layout.dialog_change_password, null);
+ builder.setView(view);
+ AlertDialog dialog = builder.create();
+
+ EditText editNewPassword = view.findViewById(R.id.new_password);
+ view.findViewById(R.id.button_save_password).setOnClickListener(v -> {
+ String newPass = editNewPassword.getText().toString();
+ if (newPass.length() < 6) {
+ Toast.makeText(getContext(), "Mínimo 6 caracteres.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ if (auth.getCurrentUser() != null) {
+ auth.getCurrentUser().updatePassword(newPass).addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ Toast.makeText(getContext(), "Sucesso!", Toast.LENGTH_SHORT).show();
+ dialog.dismiss();
+ } else {
+ Toast.makeText(getContext(), "Erro: " + task.getException().getMessage(), Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+ });
+
+ view.findViewById(R.id.button_cancel_password).setOnClickListener(v -> dialog.dismiss());
+ dialog.show();
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/schedule/ScheduleAppointmentFragment.java b/app/src/main/java/com/example/cuida/ui/schedule/ScheduleAppointmentFragment.java
new file mode 100644
index 0000000..3add774
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/schedule/ScheduleAppointmentFragment.java
@@ -0,0 +1,207 @@
+package com.example.cuida.ui.schedule;
+
+import android.app.AlertDialog;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.DatePicker;
+import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.navigation.NavController;
+import androidx.navigation.Navigation;
+import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import com.example.cuida.R;
+import java.util.Calendar;
+
+public class ScheduleAppointmentFragment extends Fragment {
+
+ private ScheduleViewModel scheduleViewModel;
+ private DatePicker datePicker;
+ private AutoCompleteTextView spinnerDoctor;
+ private RecyclerView recyclerTimeSlots;
+ private Button btnConfirm;
+ private TimeSlotAdapter timeSlotAdapter;
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View root = inflater.inflate(R.layout.fragment_schedule_appointment, container, false);
+
+ scheduleViewModel = new ViewModelProvider(this).get(ScheduleViewModel.class);
+
+ datePicker = root.findViewById(R.id.datePicker);
+ spinnerDoctor = root.findViewById(R.id.spinner_doctor);
+ recyclerTimeSlots = root.findViewById(R.id.recycler_time_slots);
+ btnConfirm = root.findViewById(R.id.btn_confirm_appointment);
+
+ setupDoctorSpinner();
+ setupDatePicker();
+ setupRecyclerView();
+ setupObservers();
+
+ btnConfirm.setOnClickListener(v -> {
+ com.google.android.material.textfield.TextInputEditText editReason = getView()
+ .findViewById(R.id.edit_reason);
+ String reason = editReason.getText().toString();
+
+ if (scheduleViewModel.getSelectedTime().getValue() == null) {
+ Toast.makeText(getContext(), "Selecione um horário.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (reason.isEmpty()) {
+ Toast.makeText(getContext(), "Indique o motivo.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ String selectedDoctor = spinnerDoctor.getText().toString();
+ if (selectedDoctor.isEmpty()) {
+ Toast.makeText(getContext(), "Selecione um médico.", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (isUrgentSymptom(reason)) {
+ showUrgencyAlert(selectedDoctor, reason);
+ } else {
+ scheduleViewModel.confirmAppointment(selectedDoctor, reason);
+ }
+ });
+
+ return root;
+ }
+
+ private boolean isUrgentSymptom(String reason) {
+ String lowerReason = reason.toLowerCase();
+ String[] urgentKeywords = {
+ "dor no peito", "falta de ar", "desmaio", "sangramento",
+ "paralisia", "perda de vis", "dormência", "confusão",
+ "aperto no peito", "convulsão", "hemorragia", "asfixia"
+ };
+
+ for (String keyword : urgentKeywords) {
+ if (lowerReason.contains(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void showUrgencyAlert(String selectedDoctor, String reason) {
+ new AlertDialog.Builder(requireContext())
+ .setTitle("Aviso de Urgência Médica")
+ .setMessage("O motivo indicado parece necessitar de atendimento urgente. Para situações graves, dirija-se ao Hospital mais próximo ou ligue 112.\n\nPretende continuar com o agendamento normal?")
+ .setIcon(android.R.drawable.ic_dialog_alert)
+ .setPositiveButton("Agendar na mesma", (dialog, which) -> {
+ scheduleViewModel.confirmAppointment(selectedDoctor, reason);
+ })
+ .setNegativeButton("Cancelar", null)
+ .show();
+ }
+
+ private void setupDoctorSpinner() {
+ scheduleViewModel.getDoctorsList().observe(getViewLifecycleOwner(), doctors -> {
+ if (doctors != null) {
+ java.util.List shuffledDoctors = new java.util.ArrayList<>(doctors);
+ java.util.Collections.shuffle(shuffledDoctors); // Randomize the names as requested
+ ArrayAdapter adapter = new ArrayAdapter<>(requireContext(), android.R.layout.simple_dropdown_item_1line, shuffledDoctors);
+ spinnerDoctor.setAdapter(adapter);
+ }
+ });
+
+ spinnerDoctor.setOnItemClickListener((parent, view, position, id) -> {
+ String selectedDoctor = (String) parent.getItemAtPosition(position);
+ scheduleViewModel.setSelectedDoctor(selectedDoctor);
+ });
+ }
+
+ private void setupDatePicker() {
+ Calendar today = Calendar.getInstance();
+ datePicker.init(today.get(Calendar.YEAR), today.get(Calendar.MONTH),
+ today.get(Calendar.DAY_OF_MONTH), (view, year, monthOfYear, dayOfMonth) -> {
+ scheduleViewModel.setDate(year, monthOfYear, dayOfMonth);
+ });
+
+ // Set initial valid date in VM
+ scheduleViewModel.setDate(today.get(Calendar.YEAR), today.get(Calendar.MONTH),
+ today.get(Calendar.DAY_OF_MONTH));
+
+ // Prevent past dates
+ datePicker.setMinDate(System.currentTimeMillis() - 1000);
+
+ // Hide the year component
+ int yearSpinnerId = android.content.res.Resources.getSystem().getIdentifier("year", "id", "android");
+ if (yearSpinnerId != 0) {
+ View yearSpinner = datePicker.findViewById(yearSpinnerId);
+ if (yearSpinner != null) {
+ yearSpinner.setVisibility(View.GONE);
+ }
+ }
+
+ // Put day on left, month on right
+ int daySpinnerId = android.content.res.Resources.getSystem().getIdentifier("day", "id", "android");
+ int monthSpinnerId = android.content.res.Resources.getSystem().getIdentifier("month", "id", "android");
+ if (daySpinnerId != 0 && monthSpinnerId != 0) {
+ View daySpinner = datePicker.findViewById(daySpinnerId);
+ View monthSpinner = datePicker.findViewById(monthSpinnerId);
+ if (daySpinner != null && monthSpinner != null) {
+ ViewGroup parent = (ViewGroup) daySpinner.getParent();
+ if (parent != null && parent.equals(monthSpinner.getParent())) {
+ int dIndex = parent.indexOfChild(daySpinner);
+ int mIndex = parent.indexOfChild(monthSpinner);
+ // We want Day to be before Month (Day on Left, Month on Right)
+ if (dIndex > mIndex) {
+ parent.removeView(daySpinner);
+ parent.addView(daySpinner, mIndex);
+ }
+ }
+ }
+ }
+ }
+
+ private void setupRecyclerView() {
+ timeSlotAdapter = new TimeSlotAdapter();
+ timeSlotAdapter.setOnTimeSlotSelectedListener(time -> scheduleViewModel.setTime(time));
+
+ recyclerTimeSlots.setLayoutManager(new GridLayoutManager(getContext(), 4));
+ recyclerTimeSlots.setAdapter(timeSlotAdapter);
+ }
+
+ private void setupObservers() {
+ scheduleViewModel.getTimeSlots().observe(getViewLifecycleOwner(), slots -> {
+ timeSlotAdapter.setTimeSlots(slots);
+ });
+
+ scheduleViewModel.getSaveSuccess().observe(getViewLifecycleOwner(), success -> {
+ if (success) {
+ Toast.makeText(getContext(), "Consulta agendada!", Toast.LENGTH_SHORT).show();
+ NavController navController = Navigation.findNavController(getView());
+ navController.popBackStack();
+ }
+ });
+
+ scheduleViewModel.getSaveError().observe(getViewLifecycleOwner(), errorMsg -> {
+ if (errorMsg != null && !errorMsg.isEmpty()) {
+ new AlertDialog.Builder(requireContext())
+ .setTitle("Horário Indisponível")
+ .setMessage(errorMsg)
+ .setPositiveButton("OK", null)
+ .show();
+ }
+ });
+
+ scheduleViewModel.getSelectedDoctorSchedule().observe(getViewLifecycleOwner(), schedule -> {
+ android.widget.TextView textSchedule = getView().findViewById(R.id.text_doctor_schedule);
+ if (textSchedule != null) {
+ textSchedule.setText(schedule);
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/schedule/ScheduleViewModel.java b/app/src/main/java/com/example/cuida/ui/schedule/ScheduleViewModel.java
new file mode 100644
index 0000000..1090604
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/schedule/ScheduleViewModel.java
@@ -0,0 +1,334 @@
+package com.example.cuida.ui.schedule;
+
+import android.app.Application;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.example.cuida.data.model.Appointment;
+import com.example.cuida.utils.AlarmScheduler;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.QueryDocumentSnapshot;
+import com.google.firebase.firestore.ListenerRegistration;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+public class ScheduleViewModel extends AndroidViewModel {
+
+ private final FirebaseFirestore db;
+ private final FirebaseAuth auth;
+
+ private final MutableLiveData selectedDate = new MutableLiveData<>();
+ private final MutableLiveData selectedTime = new MutableLiveData<>();
+ private final MutableLiveData selectedDoctor = new MutableLiveData<>();
+ private final MutableLiveData> timeSlots = new MutableLiveData<>();
+ private final MutableLiveData selectedDoctorSchedule = new MutableLiveData<>();
+ private final MutableLiveData saveSuccess = new MutableLiveData<>();
+ private final MutableLiveData saveError = new MutableLiveData<>();
+ private final MutableLiveData> doctorsList = new MutableLiveData<>(new ArrayList<>());
+ private final java.util.Map doctorSchedules = new java.util.HashMap<>();
+
+ private ListenerRegistration snapshotListener;
+
+ public ScheduleViewModel(@NonNull Application application) {
+ super(application);
+ db = FirebaseFirestore.getInstance();
+ auth = FirebaseAuth.getInstance();
+ fetchDoctors();
+ }
+
+ private void fetchDoctors() {
+ db.collection("medicos")
+ .get()
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful() && task.getResult() != null) {
+ List docs = new ArrayList<>();
+ doctorSchedules.clear();
+ for (QueryDocumentSnapshot document : task.getResult()) {
+ String name = document.getString("nome");
+ if (name == null) name = document.getString("nome_completo");
+ if (name == null) name = document.getString("name");
+
+ String specialty = document.getString("especialidade");
+ String gender = document.getString("genero");
+ String horario = document.getString("horario");
+
+ if (name != null && !name.trim().isEmpty()) {
+ String displayName = name;
+ if (specialty != null && !specialty.trim().isEmpty()) {
+ displayName += " - " + specialty;
+ }
+ if (!displayName.startsWith("Dr.") && !displayName.startsWith("Dra.")) {
+ if ("Feminino".equalsIgnoreCase(gender) || "Feminino".equals(gender)) {
+ displayName = "Dra. " + displayName;
+ } else {
+ displayName = "Dr. " + displayName;
+ }
+ }
+ docs.add(displayName);
+ if (horario != null) {
+ doctorSchedules.put(displayName, horario);
+ }
+ }
+ }
+ doctorsList.postValue(docs);
+ } else {
+ Log.e("ScheduleViewModel", "Error getting doctors", task.getException());
+ }
+ });
+ }
+
+ public void setDate(int year, int month, int dayOfMonth) {
+ String date = String.format("%02d/%02d/%04d", dayOfMonth, month + 1, year);
+ selectedDate.setValue(date);
+ loadTimeSlots(date);
+ }
+
+ public void setSelectedDoctor(String doctor) {
+ selectedDoctor.setValue(doctor);
+ String schedule = doctorSchedules.get(doctor);
+ selectedDoctorSchedule.setValue(schedule != null ? "Horário: " + schedule : "Horário: 08:00 - 19:00");
+ String date = selectedDate.getValue();
+ if (date != null) {
+ loadTimeSlots(date);
+ }
+ }
+
+ public LiveData getSelectedDoctorSchedule() {
+ return selectedDoctorSchedule;
+ }
+
+ public LiveData getSelectedDate() {
+ return selectedDate;
+ }
+
+ public void setTime(String time) {
+ selectedTime.setValue(time);
+ List currentSlots = timeSlots.getValue();
+ if (currentSlots != null) {
+ for (TimeSlot slot : currentSlots) {
+ slot.setSelected(slot.getTime().equals(time));
+ }
+ timeSlots.setValue(currentSlots);
+ }
+ }
+
+ public LiveData getSelectedTime() {
+ return selectedTime;
+ }
+
+ public LiveData> getTimeSlots() {
+ return timeSlots;
+ }
+
+ public LiveData getSaveSuccess() {
+ return saveSuccess;
+ }
+
+ public LiveData getSaveError() {
+ return saveError;
+ }
+
+ public LiveData> getDoctorsList() {
+ return doctorsList;
+ }
+
+ private void loadTimeSlots(String date) {
+ if (snapshotListener != null) {
+ snapshotListener.remove();
+ snapshotListener = null;
+ }
+
+ // Init slots immediately to prevent "disappearing" hours while waiting for network.
+ timeSlots.setValue(generateTimeSlots(new ArrayList<>(), date));
+
+ if (auth.getCurrentUser() == null) return;
+ String userId = auth.getCurrentUser().getUid();
+ String doctor = selectedDoctor.getValue();
+
+ // Listen in REAL-TIME for all appointments on the selected date
+ snapshotListener = db.collection("consultas")
+ .whereEqualTo("date", date)
+ .addSnapshotListener((queryDocumentSnapshots, e) -> {
+ if (e != null) {
+ Log.e("ScheduleViewModel", "Listen failed.", e);
+ return;
+ }
+
+ List bookedTimes = new ArrayList<>();
+ if (queryDocumentSnapshots != null) {
+ for (QueryDocumentSnapshot document : queryDocumentSnapshots) {
+ Appointment appt = document.toObject(Appointment.class);
+
+ boolean isDoctorAppointment = doctor != null && doctor.equals(appt.type);
+
+ if (isDoctorAppointment && appt.time != null) {
+ if (!bookedTimes.contains(appt.time)) {
+ bookedTimes.add(appt.time);
+ }
+ }
+ }
+ }
+
+ List slots = generateTimeSlots(bookedTimes, date);
+ timeSlots.setValue(slots);
+ });
+ }
+
+ @Override
+ protected void onCleared() {
+ super.onCleared();
+ if (snapshotListener != null) {
+ snapshotListener.remove();
+ }
+ }
+
+ private List generateTimeSlots(List bookedTimes, String selectedDateStr) {
+ List slots = new ArrayList<>();
+ int startHour = 8;
+ int endHour = 19;
+ int startMinute = 0;
+ int endMinute = 0;
+
+ String doctor = selectedDoctor.getValue();
+ String schedule = doctorSchedules.get(doctor);
+
+ if (schedule != null && schedule.contains(" - ")) {
+ try {
+ String[] parts = schedule.split(" - ");
+ String[] startParts = parts[0].split(":");
+ String[] endParts = parts[1].split(":");
+ startHour = Integer.parseInt(startParts[0]);
+ startMinute = Integer.parseInt(startParts[1]);
+ endHour = Integer.parseInt(endParts[0]);
+ endMinute = Integer.parseInt(endParts[1]);
+ } catch (Exception e) {
+ Log.e("ScheduleViewModel", "Error parsing schedule: " + schedule);
+ }
+ }
+
+ Calendar now = Calendar.getInstance();
+ boolean isToday = false;
+
+ if (selectedDateStr != null) {
+ String todayStr = String.format("%02d/%02d/%04d",
+ now.get(Calendar.DAY_OF_MONTH),
+ now.get(Calendar.MONTH) + 1,
+ now.get(Calendar.YEAR));
+ if (todayStr.equals(selectedDateStr)) {
+ isToday = true;
+ }
+ }
+
+ int currentHour = now.get(Calendar.HOUR_OF_DAY);
+ int currentMinute = now.get(Calendar.MINUTE);
+
+ Calendar cursor = Calendar.getInstance();
+ cursor.set(Calendar.HOUR_OF_DAY, startHour);
+ cursor.set(Calendar.MINUTE, startMinute);
+ cursor.set(Calendar.SECOND, 0);
+ cursor.set(Calendar.MILLISECOND, 0);
+
+ Calendar endLimit = Calendar.getInstance();
+ endLimit.set(Calendar.HOUR_OF_DAY, endHour);
+ endLimit.set(Calendar.MINUTE, endMinute);
+ endLimit.set(Calendar.SECOND, 0);
+ endLimit.set(Calendar.MILLISECOND, 0);
+
+ while (cursor.before(endLimit)) {
+ int h = cursor.get(Calendar.HOUR_OF_DAY);
+ int m = cursor.get(Calendar.MINUTE);
+ String timeStr = String.format("%02d:%02d", h, m);
+
+ if (!isToday || h > currentHour || (h == currentHour && m > currentMinute)) {
+ addSlot(slots, timeStr, bookedTimes);
+ }
+
+ cursor.add(Calendar.MINUTE, 20);
+ }
+
+ return slots;
+ }
+
+ private void addSlot(List slots, String time, List bookedTimes) {
+ boolean isBooked = bookedTimes.contains(time);
+ boolean isSelected = time.equals(selectedTime.getValue());
+ slots.add(new TimeSlot(time, isBooked, isSelected));
+ }
+
+ public void confirmAppointment(String type, String reason) {
+ String date = selectedDate.getValue();
+ String time = selectedTime.getValue();
+
+ if (auth.getCurrentUser() == null)
+ return;
+ String userId = auth.getCurrentUser().getUid();
+
+ if (date != null && time != null) {
+ saveError.setValue(null); // Resetar erro antes de validar
+
+ // Validar no servidor se o horário já está ocupado por este médico
+ db.collection("consultas")
+ .whereEqualTo("type", type)
+ .whereEqualTo("date", date)
+ .whereEqualTo("time", time)
+ .get()
+ .addOnSuccessListener(queryDocumentSnapshots -> {
+ if (!queryDocumentSnapshots.isEmpty()) {
+ // Já existe uma consulta!
+ saveError.postValue("Este horário já foi marcado por outro paciente. Por favor, escolha outro.");
+ } else {
+ // O horário está livre, prosseguir com a marcação
+ Appointment appointment = new Appointment(type, date, time, reason, false, userId, "Pendente");
+
+ db.collection("consultas")
+ .add(appointment)
+ .addOnSuccessListener(documentReference -> {
+ try {
+ String[] dateParts = date.split("/");
+ int day = Integer.parseInt(dateParts[0]);
+ int month = Integer.parseInt(dateParts[1]) - 1; // 0-based
+ int year = Integer.parseInt(dateParts[2]);
+
+ String[] timeParts = time.split(":");
+ int hour = Integer.parseInt(timeParts[0]);
+ int minute = Integer.parseInt(timeParts[1]);
+
+ Calendar baseCal = Calendar.getInstance();
+ baseCal.set(year, month, day, hour, minute, 0);
+
+ // Schedule 24 hours before
+ Calendar cal24h = (Calendar) baseCal.clone();
+ cal24h.add(Calendar.DAY_OF_YEAR, -1);
+ if (cal24h.getTimeInMillis() > System.currentTimeMillis()) {
+ AlarmScheduler.scheduleAlarm(getApplication(), cal24h.getTimeInMillis(),
+ "Lembrete de Consulta", "A sua consulta é amanhã às " + time,
+ (date + time + "24h").hashCode());
+ }
+
+ // Schedule 30 minutes before
+ Calendar cal30m = (Calendar) baseCal.clone();
+ cal30m.add(Calendar.MINUTE, -30);
+ if (cal30m.getTimeInMillis() > System.currentTimeMillis()) {
+ AlarmScheduler.scheduleAlarm(getApplication(), cal30m.getTimeInMillis(),
+ "Lembrete de Consulta", "A sua consulta é daqui a 30 minutos (" + time + ")",
+ (date + time + "30m").hashCode());
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ saveSuccess.postValue(true);
+ })
+ .addOnFailureListener(e -> Log.e("ScheduleViewModel", "Failed to confirm appt", e));
+ }
+ }).addOnFailureListener(e -> {
+ saveError.postValue("Erro ao verificar a disponibilidade do horário. Tente novamente.");
+ });
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/schedule/TimeSlot.java b/app/src/main/java/com/example/cuida/ui/schedule/TimeSlot.java
new file mode 100644
index 0000000..76d1aaa
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/schedule/TimeSlot.java
@@ -0,0 +1,52 @@
+package com.example.cuida.ui.schedule;
+
+import java.util.Objects;
+
+public class TimeSlot {
+ private String time;
+ private boolean isBooked;
+ private boolean isSelected;
+
+ public TimeSlot(String time, boolean isBooked, boolean isSelected) {
+ this.time = time;
+ this.isBooked = isBooked;
+ this.isSelected = isSelected;
+ }
+
+ public String getTime() {
+ return time;
+ }
+
+ public boolean isBooked() {
+ return isBooked;
+ }
+
+ public void setBooked(boolean booked) {
+ isBooked = booked;
+ }
+
+ public boolean isSelected() {
+ return isSelected;
+ }
+
+ public void setSelected(boolean selected) {
+ isSelected = selected;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o)
+ return true;
+ if (o == null || getClass() != o.getClass())
+ return false;
+ TimeSlot timeSlot = (TimeSlot) o;
+ return isBooked == timeSlot.isBooked &&
+ isSelected == timeSlot.isSelected &&
+ Objects.equals(time, timeSlot.time);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(time, isBooked, isSelected);
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/schedule/TimeSlotAdapter.java b/app/src/main/java/com/example/cuida/ui/schedule/TimeSlotAdapter.java
new file mode 100644
index 0000000..843039b
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/schedule/TimeSlotAdapter.java
@@ -0,0 +1,75 @@
+package com.example.cuida.ui.schedule;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import com.example.cuida.R;
+import com.google.android.material.button.MaterialButton;
+import java.util.ArrayList;
+import java.util.List;
+
+public class TimeSlotAdapter extends RecyclerView.Adapter {
+
+ private List timeSlots = new ArrayList<>();
+ private OnTimeSlotSelectedListener listener;
+
+ public interface OnTimeSlotSelectedListener {
+ void onTimeSlotSelected(String time);
+ }
+
+ public void setOnTimeSlotSelectedListener(OnTimeSlotSelectedListener listener) {
+ this.listener = listener;
+ }
+
+ public void setTimeSlots(List slots) {
+ this.timeSlots = slots;
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_time_slot, parent, false);
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
+ TimeSlot slot = timeSlots.get(position);
+ holder.btnTimeSlot.setText(slot.getTime());
+
+ if (slot.isBooked()) {
+ holder.btnTimeSlot.setEnabled(false);
+ holder.btnTimeSlot.setAlpha(0.5f);
+ holder.btnTimeSlot.setChecked(false);
+ holder.btnTimeSlot.setOnClickListener(null);
+ } else {
+ holder.btnTimeSlot.setEnabled(true);
+ holder.btnTimeSlot.setAlpha(1.0f);
+ holder.btnTimeSlot.setChecked(slot.isSelected());
+
+ holder.btnTimeSlot.setOnClickListener(v -> {
+ if (listener != null) {
+ listener.onTimeSlotSelected(slot.getTime());
+ }
+ });
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return timeSlots.size();
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ MaterialButton btnTimeSlot;
+
+ ViewHolder(View itemView) {
+ super(itemView);
+ btnTimeSlot = itemView.findViewById(R.id.btn_time_slot);
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/ui/sns24/Sns24Fragment.java b/app/src/main/java/com/example/cuida/ui/sns24/Sns24Fragment.java
new file mode 100644
index 0000000..b2e2c1c
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/ui/sns24/Sns24Fragment.java
@@ -0,0 +1,133 @@
+package com.example.cuida.ui.sns24;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
+import com.example.cuida.databinding.FragmentSns24Binding;
+import com.example.cuida.services.Gemini;
+
+public class Sns24Fragment extends Fragment {
+
+ private FragmentSns24Binding binding;
+ private Gemini gemini;
+
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
+
+ binding = FragmentSns24Binding.inflate(inflater, container, false);
+ View root = binding.getRoot();
+
+ gemini = new Gemini();
+
+ // 1. Botão de Chamada SNS 24
+ binding.buttonCallSns.setOnClickListener(v -> {
+ Intent intent = new Intent(Intent.ACTION_DIAL);
+ intent.setData(Uri.parse("tel:808242424"));
+ startActivity(intent);
+ });
+
+ // 2. Esconder o botão de hospital inicialmente
+ binding.buttonFindHospital.setVisibility(View.GONE);
+
+ // 3. Botão Triagem IA
+ binding.buttonAiTriage.setOnClickListener(v -> {
+ String symptoms = binding.inputSymptoms.getText().toString().trim();
+ if (!symptoms.isEmpty()) {
+ hideKeyboard();
+ analyzeSymptomsWithGemini(symptoms);
+ } else {
+ Toast.makeText(getContext(), "Descreva os sintomas.", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ return root;
+ }
+
+ private void hideKeyboard() {
+ View view = getActivity() != null ? getActivity().getCurrentFocus() : null;
+ if (view != null) {
+ android.view.inputmethod.InputMethodManager imm = (android.view.inputmethod.InputMethodManager)
+ getActivity().getSystemService(android.content.Context.INPUT_METHOD_SERVICE);
+ if (imm != null) {
+ imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+ }
+ }
+ }
+
+ private void analyzeSymptomsWithGemini(String symptoms) {
+ binding.buttonAiTriage.setEnabled(false);
+ binding.textAiResult.setVisibility(View.VISIBLE);
+ binding.textAiResult.setText("A analisar sintomas...");
+ binding.buttonFindHospital.setVisibility(View.GONE);
+
+ String prompt = "Atua como triagem médica de urgência (estilo SNS 24). " +
+ "Sê extremamente direto, objetivo e conciso. Não uses introduções ou saudações. " +
+ "Responde apenas com: 1) Causa provável, 2) Ação imediata recomendada. " +
+ "Se os sintomas indicarem perigo de vida ou necessidade de observação urgente, OBRIGATORIAMENTE começa a tua primeira linha com a palavra [GRAVE]. " +
+ "Sintomas do paciente: " + symptoms;
+
+ gemini.fazerPergunta(prompt, new Gemini.GeminiCallback() {
+ @Override
+ public void onSuccess(String result) {
+ if (getActivity() != null && binding != null) {
+ getActivity().runOnUiThread(() -> {
+ String displayResult = result.replace("[GRAVE]", "").trim();
+ binding.textAiResult.setText(displayResult);
+ binding.buttonAiTriage.setEnabled(true);
+
+ if (result.contains("[GRAVE]")) {
+ binding.buttonFindHospital.setVisibility(View.VISIBLE);
+ binding.buttonFindHospital.setOnClickListener(v -> {
+ Uri gmmIntentUri = Uri.parse("geo:0,0?q=hospital+mais+proximo");
+ Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
+ mapIntent.setPackage("com.google.android.apps.maps");
+ startActivity(mapIntent);
+ });
+ }
+ saveTriageToHistory(symptoms, displayResult);
+ });
+ }
+ }
+
+ @Override
+ public void onError(Throwable t) {
+ if (getActivity() != null && binding != null) {
+ getActivity().runOnUiThread(() -> {
+ binding.textAiResult.setText("Erro na ligação: " + t.getMessage());
+ binding.buttonAiTriage.setEnabled(true);
+ });
+ }
+ }
+ });
+ }
+
+ private void saveTriageToHistory(String symptoms, String result) {
+ if (getActivity() == null) return;
+ com.google.firebase.auth.FirebaseUser user = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
+ if (user == null) return;
+
+ java.util.Map triage = new java.util.HashMap<>();
+ triage.put("userId", user.getUid());
+ triage.put("sintomas", symptoms);
+ triage.put("resultado", result);
+ triage.put("data", new java.text.SimpleDateFormat("dd/MM/yyyy HH:mm", java.util.Locale.getDefault()).format(new java.util.Date()));
+ triage.put("timestamp", com.google.firebase.firestore.FieldValue.serverTimestamp());
+
+ com.google.firebase.firestore.FirebaseFirestore.getInstance()
+ .collection("triagens").add(triage);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/utils/AlarmScheduler.java b/app/src/main/java/com/example/cuida/utils/AlarmScheduler.java
new file mode 100644
index 0000000..58ca542
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/utils/AlarmScheduler.java
@@ -0,0 +1,64 @@
+package com.example.cuida.utils;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import com.example.cuida.services.AlarmReceiver;
+
+public class AlarmScheduler {
+ public static void scheduleAlarm(Context context, long timeInMillis, String title, String message,
+ int requestCode) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ if (alarmManager == null)
+ return;
+
+ Intent intent = new Intent(context, AlarmReceiver.class);
+ intent.putExtra("EXTRA_TITLE", title);
+ intent.putExtra("EXTRA_MESSAGE", message);
+ intent.putExtra("EXTRA_NOTIFICATION_ID", requestCode);
+
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context,
+ requestCode,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (alarmManager.canScheduleExactAlarms()) {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
+ } else {
+ // Fallback to inexact alarm if exact permission is revoked
+ alarmManager.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
+ }
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
+ } else {
+ alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
+ }
+ } catch (SecurityException e) {
+ // Android 14+ requires explicit consent for SCHEDULE_EXACT_ALARM except for
+ // clocks/calendars
+ // Fallback when security exception is raised
+ alarmManager.set(AlarmManager.RTC_WAKEUP, timeInMillis, pendingIntent);
+ }
+ }
+
+ public static void cancelAlarm(Context context, int requestCode) {
+ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
+ if (alarmManager == null)
+ return;
+
+ Intent intent = new Intent(context, AlarmReceiver.class);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(
+ context,
+ requestCode,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+ alarmManager.cancel(pendingIntent);
+ pendingIntent.cancel();
+ }
+}
diff --git a/app/src/main/java/com/example/cuida/utils/NotificationHelper.java b/app/src/main/java/com/example/cuida/utils/NotificationHelper.java
new file mode 100644
index 0000000..1b9f1d5
--- /dev/null
+++ b/app/src/main/java/com/example/cuida/utils/NotificationHelper.java
@@ -0,0 +1,74 @@
+package com.example.cuida.utils;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.os.Build;
+import androidx.core.app.NotificationCompat;
+import com.example.cuida.MainActivity;
+import com.example.cuida.R;
+
+public class NotificationHelper {
+
+ private final Context context;
+ public static final String MEDICATION_CHANNEL_ID = "MEDICATION_CHANNEL_ID";
+ public static final String APPOINTMENT_CHANNEL_ID = "APPOINTMENT_CHANNEL_ID";
+
+ public NotificationHelper(Context context) {
+ this.context = context;
+ createChannels();
+ }
+
+ private void createChannels() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationManager manager = context.getSystemService(NotificationManager.class);
+ if (manager != null) {
+ // Medication Channel
+ NotificationChannel medChannel = new NotificationChannel(
+ MEDICATION_CHANNEL_ID,
+ "Lembretes de Medicação",
+ NotificationManager.IMPORTANCE_HIGH);
+ medChannel.setDescription("Notificações para tomar a medicação a horas");
+ medChannel.enableLights(true);
+ medChannel.setLightColor(Color.BLUE);
+ manager.createNotificationChannel(medChannel);
+
+ // Appointment Channel
+ NotificationChannel apptChannel = new NotificationChannel(
+ APPOINTMENT_CHANNEL_ID,
+ "Lembretes de Consultas",
+ NotificationManager.IMPORTANCE_HIGH);
+ apptChannel.setDescription("Notificações antes das consultas");
+ apptChannel.enableLights(true);
+ apptChannel.setLightColor(Color.GREEN);
+ manager.createNotificationChannel(apptChannel);
+ }
+ }
+ }
+
+ public void sendNotification(String title, String message, int notificationId, String channelId) {
+ Intent intent = new Intent(context, MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ PendingIntent pendingIntent = PendingIntent.getActivity(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+ NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId)
+ .setSmallIcon(R.drawable.ic_launcher_final) // Using app icon
+ .setContentTitle(title)
+ .setContentText(message)
+ .setPriority(NotificationCompat.PRIORITY_HIGH)
+ .setAutoCancel(true)
+ .setContentIntent(pendingIntent);
+
+ NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ if (manager != null) {
+ manager.notify(notificationId, builder.build());
+ }
+ }
+}
diff --git a/app/src/main/res/drawable-v26/ic_launcher_final.xml b/app/src/main/res/drawable-v26/ic_launcher_final.xml
new file mode 100644
index 0000000..9047195
--- /dev/null
+++ b/app/src/main/res/drawable-v26/ic_launcher_final.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_gradient_header.xml b/app/src/main/res/drawable/bg_gradient_header.xml
new file mode 100644
index 0000000..2649a80
--- /dev/null
+++ b/app/src/main/res/drawable/bg_gradient_header.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_search_results.xml b/app/src/main/res/drawable/bg_search_results.xml
new file mode 100644
index 0000000..2a1c97d
--- /dev/null
+++ b/app/src/main/res/drawable/bg_search_results.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/btn_outline_error.xml b/app/src/main/res/drawable/btn_outline_error.xml
new file mode 100644
index 0000000..9169e01
--- /dev/null
+++ b/app/src/main/res/drawable/btn_outline_error.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/btn_outline_primary.xml b/app/src/main/res/drawable/btn_outline_primary.xml
new file mode 100644
index 0000000..a4b75e7
--- /dev/null
+++ b/app/src/main/res/drawable/btn_outline_primary.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher.xml b/app/src/main/res/drawable/ic_launcher.xml
new file mode 100644
index 0000000..97295d6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_final.xml b/app/src/main/res/drawable/ic_launcher_final.xml
new file mode 100644
index 0000000..b795ad1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_final.xml
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_round.xml b/app/src/main/res/drawable/ic_launcher_round.xml
new file mode 100644
index 0000000..0f0ca05
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_round.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_logo.png b/app/src/main/res/drawable/ic_logo.png
new file mode 100644
index 0000000..e7ad304
Binary files /dev/null and b/app/src/main/res/drawable/ic_logo.png differ
diff --git a/app/src/main/res/drawable/ic_logo_scaled.xml b/app/src/main/res/drawable/ic_logo_scaled.xml
new file mode 100644
index 0000000..62ac60c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_logo_scaled.xml
@@ -0,0 +1,12 @@
+
+
+ -
+
+
+
diff --git a/app/src/main/res/drawable/ic_placeholder.xml b/app/src/main/res/drawable/ic_placeholder.xml
new file mode 100644
index 0000000..6bdced2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_placeholder.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_forgot_password.xml b/app/src/main/res/layout/activity_forgot_password.xml
new file mode 100644
index 0000000..b1137ad
--- /dev/null
+++ b/app/src/main/res/layout/activity_forgot_password.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..2d3e660
--- /dev/null
+++ b/app/src/main/res/layout/activity_login.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..ebe93ee
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
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..019ec56
--- /dev/null
+++ b/app/src/main/res/layout/activity_register.xml
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_reset_password.xml b/app/src/main/res/layout/activity_reset_password.xml
new file mode 100644
index 0000000..57b94a0
--- /dev/null
+++ b/app/src/main/res/layout/activity_reset_password.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_add_medication.xml b/app/src/main/res/layout/dialog_add_medication.xml
new file mode 100644
index 0000000..339cb19
--- /dev/null
+++ b/app/src/main/res/layout/dialog_add_medication.xml
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_change_password.xml b/app/src/main/res/layout/dialog_change_password.xml
new file mode 100644
index 0000000..053d44c
--- /dev/null
+++ b/app/src/main/res/layout/dialog_change_password.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_edit_profile.xml b/app/src/main/res/layout/dialog_edit_profile.xml
new file mode 100644
index 0000000..377fc5d
--- /dev/null
+++ b/app/src/main/res/layout/dialog_edit_profile.xml
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_appointments.xml b/app/src/main/res/layout/fragment_appointments.xml
new file mode 100644
index 0000000..44990b1
--- /dev/null
+++ b/app/src/main/res/layout/fragment_appointments.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml
new file mode 100644
index 0000000..f2cf4dc
--- /dev/null
+++ b/app/src/main/res/layout/fragment_home.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_medication.xml b/app/src/main/res/layout/fragment_medication.xml
new file mode 100644
index 0000000..5977667
--- /dev/null
+++ b/app/src/main/res/layout/fragment_medication.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml
new file mode 100644
index 0000000..b0bd257
--- /dev/null
+++ b/app/src/main/res/layout/fragment_profile.xml
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_schedule_appointment.xml b/app/src/main/res/layout/fragment_schedule_appointment.xml
new file mode 100644
index 0000000..62b0e21
--- /dev/null
+++ b/app/src/main/res/layout/fragment_schedule_appointment.xml
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_sns24.xml b/app/src/main/res/layout/fragment_sns24.xml
new file mode 100644
index 0000000..59d7bac
--- /dev/null
+++ b/app/src/main/res/layout/fragment_sns24.xml
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_appointment.xml b/app/src/main/res/layout/item_appointment.xml
new file mode 100644
index 0000000..d18216e
--- /dev/null
+++ b/app/src/main/res/layout/item_appointment.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_comprimido_search.xml b/app/src/main/res/layout/item_comprimido_search.xml
new file mode 100644
index 0000000..abf2d29
--- /dev/null
+++ b/app/src/main/res/layout/item_comprimido_search.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_medication.xml b/app/src/main/res/layout/item_medication.xml
new file mode 100644
index 0000000..d27aaa6
--- /dev/null
+++ b/app/src/main/res/layout/item_medication.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_time_slot.xml b/app/src/main/res/layout/item_time_slot.xml
new file mode 100644
index 0000000..6811f39
--- /dev/null
+++ b/app/src/main/res/layout/item_time_slot.xml
@@ -0,0 +1,9 @@
+
+
diff --git a/app/src/main/res/menu/bottom_nav_menu.xml b/app/src/main/res/menu/bottom_nav_menu.xml
new file mode 100644
index 0000000..8f7e770
--- /dev/null
+++ b/app/src/main/res/menu/bottom_nav_menu.xml
@@ -0,0 +1,29 @@
+
+
diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml
new file mode 100644
index 0000000..0ab9dc6
--- /dev/null
+++ b/app/src/main/res/navigation/mobile_navigation.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..6222236
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,25 @@
+
+
+
+ #0066CC
+ #E3F2FD
+ #004C99
+ #000000
+ #000000
+
+ #F8F9FA
+ #FFFFFF
+
+ #202124
+ #5F6368
+ #B00020
+
+
+ #90CAF9
+ #0066CC
+ #004C99
+ #69F0AE
+ #000000
+ #000000
+ #FFFFFF
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..f75db4e
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,21 @@
+
+ Cuida
+ Início
+ Consultas
+ Medicação
+ SNS 24
+ Perfil
+
+
+ Iniciar Sessão
+ Criar Conta
+ Esqueci-me da Palavra-passe
+ Email
+ Palavra-passe
+ Nome Completo
+ Idade
+ Entrar
+ Registar
+ Não tem conta?
+ Já tem conta?
+
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..a3cee91
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
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..ff836a1
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
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..19efd59
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml
new file mode 100644
index 0000000..f779c3d
--- /dev/null
+++ b/app/src/main/res/xml/file_paths.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..2ea6f5d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,10 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ id 'com.android.application' version '9.2.0' apply false
+ id 'com.android.library' version '9.2.0' apply false
+ id 'com.google.gms.google-services' version '4.4.4' apply false
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/build_log.txt b/build_log.txt
new file mode 100644
index 0000000..efb49db
--- /dev/null
+++ b/build_log.txt
@@ -0,0 +1,101 @@
+WARNING: A restricted method in java.lang.System has been called
+WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230405/.gradle/wrapper/dists/gradle-9.0-milestone-1-bin/3vdepk4s12bybhohyuvjcm1bd/gradle-9.0-milestone-1/lib/native-platform-0.22-milestone-28.jar)
+WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
+WARNING: Restricted methods will be blocked in a future release unless native access is enabled
+
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preDebugBuild UP-TO-DATE
+> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
+> Task :app:dataBindingMergeDependencyArtifactsDebug FAILED
+
+[Incubating] Problems report is available at: file:///Users/230405/Documents/papcuida/build/reports/problems/problems-report.html
+
+FAILURE: Build failed with an exception.
+
+* What went wrong:
+Execution failed for task ':app:dataBindingMergeDependencyArtifactsDebug'.
+> Could not resolve all dependencies for configuration ':app:debugCompileClasspath'.
+ > Configuration `:app:debugRuntimeClasspath` contains AndroidX dependencies, but the `android.useAndroidX` property is not enabled, which may cause runtime issues.
+ Set `android.useAndroidX=true` in the `gradle.properties` file and retry.
+ The following AndroidX dependencies are detected:
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.activity:activity:1.8.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.annotation:annotation:1.3.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.collection:collection:1.1.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.core:core:1.9.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.annotation:annotation-experimental:1.3.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.core:core:1.9.0 -> androidx.concurrent:concurrent-futures:1.1.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.lifecycle:lifecycle-runtime:2.7.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.savedstate:savedstate:1.2.1 -> androidx.arch.core:core-common:2.2.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.fragment:fragment:1.6.2 -> androidx.lifecycle:lifecycle-livedata-core:2.7.0 -> androidx.arch.core:core-runtime:2.2.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.lifecycle:lifecycle-common:2.7.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.lifecycle:lifecycle-runtime-ktx:2.7.0 -> androidx.lifecycle:lifecycle-livedata:2.7.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.fragment:fragment:1.6.2 -> androidx.lifecycle:lifecycle-livedata-core:2.7.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.fragment:fragment-ktx:1.6.2 -> androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0
+ :app:debugRuntimeClasspath -> androidx.lifecycle:lifecycle-livedata-ktx:2.7.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.lifecycle:lifecycle-viewmodel:2.7.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.emoji2:emoji2:1.2.0 -> androidx.lifecycle:lifecycle-process:2.7.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.emoji2:emoji2:1.2.0 -> androidx.startup:startup-runtime:1.1.1
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.activity:activity:1.8.0 -> androidx.tracing:tracing:1.0.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.lifecycle:lifecycle-runtime-ktx:2.7.0
+ :app:debugRuntimeClasspath -> androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.core:core-ktx:1.9.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.savedstate:savedstate:1.2.1
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.savedstate:savedstate-ktx:1.2.1
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.profileinstaller:profileinstaller:1.3.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.core:core:1.9.0 -> androidx.versionedparcelable:versionedparcelable:1.1.1
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.activity:activity-ktx:1.8.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.appcompat:appcompat-resources:1.6.1
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.vectordrawable:vectordrawable:1.1.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.appcompat:appcompat-resources:1.6.1 -> androidx.vectordrawable:vectordrawable-animated:1.1.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.appcompat:appcompat-resources:1.6.1 -> androidx.vectordrawable:vectordrawable-animated:1.1.0 -> androidx.interpolator:interpolator:1.0.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.cursoradapter:cursoradapter:1.0.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-ui:2.7.7 -> androidx.drawerlayout:drawerlayout:1.1.1
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-ui:2.7.7 -> androidx.customview:customview:1.1.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.emoji2:emoji2:1.2.0
+ :app:debugRuntimeClasspath -> androidx.appcompat:appcompat:1.6.1 -> androidx.emoji2:emoji2-views-helper:1.2.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.fragment:fragment:1.6.2
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.fragment:fragment:1.6.2 -> androidx.loader:loader:1.0.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.fragment:fragment:1.6.2 -> androidx.viewpager:viewpager:1.0.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.fragment:fragment-ktx:1.6.2
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.collection:collection-ktx:1.1.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.resourceinspection:resourceinspection-annotation:1.0.1
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.cardview:cardview:1.0.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.coordinatorlayout:coordinatorlayout:1.1.0
+ :app:debugRuntimeClasspath -> androidx.constraintlayout:constraintlayout:2.1.4
+ :app:debugRuntimeClasspath -> androidx.constraintlayout:constraintlayout:2.1.4 -> androidx.constraintlayout:constraintlayout-core:1.0.4
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.dynamicanimation:dynamicanimation:1.0.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.dynamicanimation:dynamicanimation:1.0.0 -> androidx.legacy:legacy-support-core-utils:1.0.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.dynamicanimation:dynamicanimation:1.0.0 -> androidx.legacy:legacy-support-core-utils:1.0.0 -> androidx.documentfile:documentfile:1.0.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.dynamicanimation:dynamicanimation:1.0.0 -> androidx.legacy:legacy-support-core-utils:1.0.0 -> androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.dynamicanimation:dynamicanimation:1.0.0 -> androidx.legacy:legacy-support-core-utils:1.0.0 -> androidx.print:print:1.0.0
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.recyclerview:recyclerview:1.1.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-ui:2.7.7 -> androidx.transition:transition:1.4.1
+ :app:debugRuntimeClasspath -> com.google.android.material:material:1.11.0 -> androidx.viewpager2:viewpager2:1.0.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-common:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.navigation:navigation-runtime:2.7.7 -> androidx.navigation:navigation-common:2.7.7
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-ui:2.7.7
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.slidingpanelayout:slidingpanelayout:1.2.0
+ :app:debugRuntimeClasspath -> androidx.navigation:navigation-fragment:2.7.7 -> androidx.slidingpanelayout:slidingpanelayout:1.2.0 -> androidx.window:window:1.0.0
+ :app:debugRuntimeClasspath -> androidx.room:room-runtime:2.6.1
+ :app:debugRuntimeClasspath -> androidx.room:room-runtime:2.6.1 -> androidx.room:room-common:2.6.1
+ :app:debugRuntimeClasspath -> androidx.room:room-runtime:2.6.1 -> androidx.sqlite:sqlite:2.4.0
+ :app:debugRuntimeClasspath -> androidx.room:room-runtime:2.6.1 -> androidx.sqlite:sqlite-framework:2.4.0
+
+* Try:
+> Run with --stacktrace option to get the stack trace.
+> Run with --info or --debug option to get more log output.
+> Run with --scan to get full insights.
+> Get more help at https://help.gradle.org.
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.0.
+
+You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
+
+For more on this, please refer to https://docs.gradle.org/9.0-milestone-1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
+
+BUILD FAILED in 403ms
+1 actionable task: 1 executed
diff --git a/build_log_2.txt b/build_log_2.txt
new file mode 100644
index 0000000..d3d5843
--- /dev/null
+++ b/build_log_2.txt
@@ -0,0 +1,62 @@
+WARNING: A restricted method in java.lang.System has been called
+WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230405/.gradle/wrapper/dists/gradle-9.0-milestone-1-bin/3vdepk4s12bybhohyuvjcm1bd/gradle-9.0-milestone-1/lib/native-platform-0.22-milestone-28.jar)
+WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
+WARNING: Restricted methods will be blocked in a future release unless native access is enabled
+
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preDebugBuild UP-TO-DATE
+> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
+> Task :app:dataBindingMergeDependencyArtifactsDebug
+> Task :app:generateDebugResValues UP-TO-DATE
+> Task :app:generateDebugResources UP-TO-DATE
+> Task :app:packageDebugResources
+> Task :app:processDebugNavigationResources UP-TO-DATE
+> Task :app:parseDebugLocalResources UP-TO-DATE
+> Task :app:javaPreCompileDebug UP-TO-DATE
+> Task :app:checkDebugAarMetadata UP-TO-DATE
+> Task :app:compileDebugNavigationResources UP-TO-DATE
+> Task :app:mapDebugSourceSetPaths UP-TO-DATE
+> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
+> Task :app:extractDeepLinksDebug UP-TO-DATE
+> Task :app:mergeDebugResources
+> Task :app:processDebugMainManifest
+> Task :app:dataBindingGenBaseClassesDebug
+> Task :app:processDebugManifest
+> Task :app:mergeDebugShaders UP-TO-DATE
+> Task :app:compileDebugShaders NO-SOURCE
+> Task :app:generateDebugAssets UP-TO-DATE
+> Task :app:mergeDebugAssets UP-TO-DATE
+> Task :app:compressDebugAssets UP-TO-DATE
+> Task :app:processDebugManifestForPackage
+> Task :app:checkDebugDuplicateClasses
+> Task :app:desugarDebugFileDependencies UP-TO-DATE
+> Task :app:processDebugResources FAILED
+> Task :app:mergeExtDexDebug
+
+[Incubating] Problems report is available at: file:///Users/230405/Documents/papcuida/build/reports/problems/problems-report.html
+
+FAILURE: Build failed with an exception.
+
+* What went wrong:
+Execution failed for task ':app:processDebugResources'.
+> A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction
+ > Android resource linking failed
+ ERROR: /Users/230405/Documents/papcuida/app/src/main/AndroidManifest.xml:5:5-32:19: AAPT: error: resource mipmap/ic_launcher (aka com.example.cuida:mipmap/ic_launcher) not found.
+
+ ERROR: /Users/230405/Documents/papcuida/app/src/main/AndroidManifest.xml:5:5-32:19: AAPT: error: resource mipmap/ic_launcher_round (aka com.example.cuida:mipmap/ic_launcher_round) not found.
+
+
+* Try:
+> Run with --stacktrace option to get the stack trace.
+> Run with --info or --debug option to get more log output.
+> Run with --scan to get full insights.
+> Get more help at https://help.gradle.org.
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.0.
+
+You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
+
+For more on this, please refer to https://docs.gradle.org/9.0-milestone-1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
+
+BUILD FAILED in 5s
+24 actionable tasks: 10 executed, 14 up-to-date
diff --git a/build_log_3.txt b/build_log_3.txt
new file mode 100644
index 0000000..e139dda
--- /dev/null
+++ b/build_log_3.txt
@@ -0,0 +1,68 @@
+WARNING: A restricted method in java.lang.System has been called
+WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230405/.gradle/wrapper/dists/gradle-9.0-milestone-1-bin/3vdepk4s12bybhohyuvjcm1bd/gradle-9.0-milestone-1/lib/native-platform-0.22-milestone-28.jar)
+WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
+WARNING: Restricted methods will be blocked in a future release unless native access is enabled
+
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preDebugBuild UP-TO-DATE
+> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
+> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
+> Task :app:generateDebugResValues UP-TO-DATE
+> Task :app:generateDebugResources
+> Task :app:mergeDebugResources
+> Task :app:packageDebugResources
+> Task :app:processDebugNavigationResources
+> Task :app:javaPreCompileDebug UP-TO-DATE
+> Task :app:checkDebugAarMetadata UP-TO-DATE
+> Task :app:compileDebugNavigationResources UP-TO-DATE
+> Task :app:parseDebugLocalResources
+> Task :app:mapDebugSourceSetPaths
+> Task :app:dataBindingGenBaseClassesDebug
+> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
+> Task :app:extractDeepLinksDebug UP-TO-DATE
+> Task :app:processDebugMainManifest
+> Task :app:processDebugManifest
+> Task :app:mergeDebugShaders UP-TO-DATE
+> Task :app:compileDebugShaders NO-SOURCE
+> Task :app:generateDebugAssets UP-TO-DATE
+> Task :app:mergeDebugAssets UP-TO-DATE
+> Task :app:compressDebugAssets UP-TO-DATE
+> Task :app:checkDebugDuplicateClasses UP-TO-DATE
+> Task :app:desugarDebugFileDependencies UP-TO-DATE
+> Task :app:mergeExtDexDebug UP-TO-DATE
+> Task :app:mergeLibDexDebug UP-TO-DATE
+> Task :app:mergeDebugJniLibFolders UP-TO-DATE
+> Task :app:mergeDebugNativeLibs NO-SOURCE
+> Task :app:stripDebugDebugSymbols NO-SOURCE
+> Task :app:validateSigningDebug UP-TO-DATE
+> Task :app:writeDebugAppMetadata UP-TO-DATE
+> Task :app:writeDebugSigningConfigVersions UP-TO-DATE
+> Task :app:processDebugManifestForPackage
+> Task :app:processDebugResources FAILED
+
+[Incubating] Problems report is available at: file:///Users/230405/Documents/papcuida/build/reports/problems/problems-report.html
+
+FAILURE: Build failed with an exception.
+
+* What went wrong:
+Execution failed for task ':app:processDebugResources'.
+> A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction
+ > Android resource linking failed
+ com.example.cuida.app-mergeDebugResources-46:/layout/fragment_profile.xml:13: error: resource mipmap/ic_launcher_round (aka com.example.cuida:mipmap/ic_launcher_round) not found.
+ error: failed linking file resources.
+
+
+* Try:
+> Run with --stacktrace option to get the stack trace.
+> Run with --info or --debug option to get more log output.
+> Run with --scan to get full insights.
+> Get more help at https://help.gradle.org.
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.0.
+
+You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
+
+For more on this, please refer to https://docs.gradle.org/9.0-milestone-1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
+
+BUILD FAILED in 1s
+29 actionable tasks: 11 executed, 18 up-to-date
diff --git a/build_log_4.txt b/build_log_4.txt
new file mode 100644
index 0000000..5346913
--- /dev/null
+++ b/build_log_4.txt
@@ -0,0 +1,75 @@
+WARNING: A restricted method in java.lang.System has been called
+WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230405/.gradle/wrapper/dists/gradle-9.0-milestone-1-bin/3vdepk4s12bybhohyuvjcm1bd/gradle-9.0-milestone-1/lib/native-platform-0.22-milestone-28.jar)
+WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
+WARNING: Restricted methods will be blocked in a future release unless native access is enabled
+
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preDebugBuild UP-TO-DATE
+> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
+> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
+> Task :app:generateDebugResValues UP-TO-DATE
+> Task :app:generateDebugResources
+> Task :app:mergeDebugResources
+> Task :app:packageDebugResources
+> Task :app:processDebugNavigationResources
+> Task :app:javaPreCompileDebug UP-TO-DATE
+> Task :app:checkDebugAarMetadata UP-TO-DATE
+> Task :app:compileDebugNavigationResources UP-TO-DATE
+> Task :app:parseDebugLocalResources
+> Task :app:mapDebugSourceSetPaths
+> Task :app:dataBindingGenBaseClassesDebug UP-TO-DATE
+> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
+> Task :app:extractDeepLinksDebug UP-TO-DATE
+> Task :app:processDebugMainManifest UP-TO-DATE
+> Task :app:processDebugManifest UP-TO-DATE
+> Task :app:processDebugManifestForPackage UP-TO-DATE
+> Task :app:mergeDebugShaders UP-TO-DATE
+> Task :app:compileDebugShaders NO-SOURCE
+> Task :app:generateDebugAssets UP-TO-DATE
+> Task :app:mergeDebugAssets UP-TO-DATE
+> Task :app:compressDebugAssets UP-TO-DATE
+> Task :app:checkDebugDuplicateClasses UP-TO-DATE
+> Task :app:desugarDebugFileDependencies UP-TO-DATE
+> Task :app:mergeExtDexDebug UP-TO-DATE
+> Task :app:mergeLibDexDebug UP-TO-DATE
+> Task :app:mergeDebugJniLibFolders UP-TO-DATE
+> Task :app:mergeDebugNativeLibs NO-SOURCE
+> Task :app:stripDebugDebugSymbols NO-SOURCE
+> Task :app:validateSigningDebug UP-TO-DATE
+> Task :app:writeDebugAppMetadata UP-TO-DATE
+> Task :app:writeDebugSigningConfigVersions UP-TO-DATE
+> Task :app:processDebugResources
+
+> Task :app:compileDebugJavaWithJavac
+Java compiler version 24 has deprecated support for compiling with source/target version 8.
+Try one of the following options:
+ 1. [Recommended] Use Java toolchain with a lower language version
+ 2. Set a higher source/target version
+ 3. Use a lower version of the JDK running the build (if you're not using Java toolchain)
+For more details on how to configure these settings, see https://developer.android.com/build/jdks.
+To suppress this warning, set android.javaCompile.suppressSourceTargetDeprecationWarning=true in gradle.properties.
+warning: [options] source value 8 is obsolete and will be removed in a future release
+warning: [options] target value 8 is obsolete and will be removed in a future release
+warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
+Note: /Users/230405/Documents/papcuida/app/src/main/java/com/example/cuida/ui/sns24/Sns24Fragment.java uses or overrides a deprecated API.
+Note: Recompile with -Xlint:deprecation for details.
+3 warnings
+
+> Task :app:processDebugJavaRes
+> Task :app:dexBuilderDebug
+> Task :app:mergeProjectDexDebug
+> Task :app:mergeDebugJavaResource
+> Task :app:packageDebug
+> Task :app:createDebugApkListingFileRedirect
+> Task :app:assembleDebug
+
+[Incubating] Problems report is available at: file:///Users/230405/Documents/papcuida/build/reports/problems/problems-report.html
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.0.
+
+You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
+
+For more on this, please refer to https://docs.gradle.org/9.0-milestone-1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
+
+BUILD SUCCESSFUL in 4s
+36 actionable tasks: 14 executed, 22 up-to-date
diff --git a/build_log_5.txt b/build_log_5.txt
new file mode 100644
index 0000000..3e8fa39
--- /dev/null
+++ b/build_log_5.txt
@@ -0,0 +1,60 @@
+WARNING: A restricted method in java.lang.System has been called
+WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230405/.gradle/wrapper/dists/gradle-9.0-milestone-1-bin/3vdepk4s12bybhohyuvjcm1bd/gradle-9.0-milestone-1/lib/native-platform-0.22-milestone-28.jar)
+WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
+WARNING: Restricted methods will be blocked in a future release unless native access is enabled
+
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preDebugBuild UP-TO-DATE
+> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
+> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
+> Task :app:generateDebugResValues UP-TO-DATE
+> Task :app:generateDebugResources UP-TO-DATE
+> Task :app:mergeDebugResources UP-TO-DATE
+> Task :app:packageDebugResources UP-TO-DATE
+> Task :app:processDebugNavigationResources UP-TO-DATE
+> Task :app:parseDebugLocalResources UP-TO-DATE
+> Task :app:dataBindingGenBaseClassesDebug UP-TO-DATE
+> Task :app:javaPreCompileDebug UP-TO-DATE
+> Task :app:checkDebugAarMetadata UP-TO-DATE
+> Task :app:compileDebugNavigationResources UP-TO-DATE
+> Task :app:mapDebugSourceSetPaths UP-TO-DATE
+> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
+> Task :app:extractDeepLinksDebug UP-TO-DATE
+> Task :app:processDebugMainManifest
+> Task :app:processDebugManifest
+> Task :app:mergeDebugShaders UP-TO-DATE
+> Task :app:compileDebugShaders NO-SOURCE
+> Task :app:generateDebugAssets UP-TO-DATE
+> Task :app:mergeDebugAssets UP-TO-DATE
+> Task :app:compressDebugAssets UP-TO-DATE
+> Task :app:checkDebugDuplicateClasses UP-TO-DATE
+> Task :app:desugarDebugFileDependencies UP-TO-DATE
+> Task :app:mergeExtDexDebug UP-TO-DATE
+> Task :app:mergeLibDexDebug UP-TO-DATE
+> Task :app:mergeDebugJniLibFolders UP-TO-DATE
+> Task :app:mergeDebugNativeLibs NO-SOURCE
+> Task :app:stripDebugDebugSymbols NO-SOURCE
+> Task :app:validateSigningDebug UP-TO-DATE
+> Task :app:writeDebugAppMetadata UP-TO-DATE
+> Task :app:writeDebugSigningConfigVersions UP-TO-DATE
+> Task :app:processDebugManifestForPackage
+> Task :app:processDebugResources
+> Task :app:compileDebugJavaWithJavac UP-TO-DATE
+> Task :app:processDebugJavaRes UP-TO-DATE
+> Task :app:mergeDebugJavaResource UP-TO-DATE
+> Task :app:dexBuilderDebug UP-TO-DATE
+> Task :app:mergeProjectDexDebug UP-TO-DATE
+> Task :app:packageDebug UP-TO-DATE
+> Task :app:createDebugApkListingFileRedirect UP-TO-DATE
+> Task :app:assembleDebug UP-TO-DATE
+
+[Incubating] Problems report is available at: file:///Users/230405/Documents/papcuida/build/reports/problems/problems-report.html
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.0.
+
+You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
+
+For more on this, please refer to https://docs.gradle.org/9.0-milestone-1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
+
+BUILD SUCCESSFUL in 837ms
+36 actionable tasks: 4 executed, 32 up-to-date
diff --git a/build_log_6.txt b/build_log_6.txt
new file mode 100644
index 0000000..2b3c507
--- /dev/null
+++ b/build_log_6.txt
@@ -0,0 +1,75 @@
+WARNING: A restricted method in java.lang.System has been called
+WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230405/.gradle/wrapper/dists/gradle-9.0-milestone-1-bin/3vdepk4s12bybhohyuvjcm1bd/gradle-9.0-milestone-1/lib/native-platform-0.22-milestone-28.jar)
+WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
+WARNING: Restricted methods will be blocked in a future release unless native access is enabled
+
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preDebugBuild UP-TO-DATE
+> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
+> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
+> Task :app:generateDebugResValues UP-TO-DATE
+> Task :app:generateDebugResources
+> Task :app:packageDebugResources
+> Task :app:mergeDebugResources
+> Task :app:processDebugNavigationResources
+> Task :app:parseDebugLocalResources
+> Task :app:javaPreCompileDebug UP-TO-DATE
+> Task :app:dataBindingGenBaseClassesDebug
+> Task :app:checkDebugAarMetadata UP-TO-DATE
+> Task :app:compileDebugNavigationResources UP-TO-DATE
+> Task :app:mapDebugSourceSetPaths
+> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
+> Task :app:extractDeepLinksDebug UP-TO-DATE
+> Task :app:processDebugMainManifest
+> Task :app:processDebugManifest
+> Task :app:mergeDebugShaders UP-TO-DATE
+> Task :app:compileDebugShaders NO-SOURCE
+> Task :app:generateDebugAssets UP-TO-DATE
+> Task :app:mergeDebugAssets UP-TO-DATE
+> Task :app:compressDebugAssets UP-TO-DATE
+> Task :app:checkDebugDuplicateClasses UP-TO-DATE
+> Task :app:desugarDebugFileDependencies UP-TO-DATE
+> Task :app:mergeExtDexDebug UP-TO-DATE
+> Task :app:mergeLibDexDebug UP-TO-DATE
+> Task :app:mergeDebugJniLibFolders UP-TO-DATE
+> Task :app:mergeDebugNativeLibs NO-SOURCE
+> Task :app:stripDebugDebugSymbols NO-SOURCE
+> Task :app:validateSigningDebug UP-TO-DATE
+> Task :app:writeDebugAppMetadata UP-TO-DATE
+> Task :app:writeDebugSigningConfigVersions UP-TO-DATE
+> Task :app:processDebugManifestForPackage
+> Task :app:processDebugResources
+
+> Task :app:compileDebugJavaWithJavac
+Java compiler version 24 has deprecated support for compiling with source/target version 8.
+Try one of the following options:
+ 1. [Recommended] Use Java toolchain with a lower language version
+ 2. Set a higher source/target version
+ 3. Use a lower version of the JDK running the build (if you're not using Java toolchain)
+For more details on how to configure these settings, see https://developer.android.com/build/jdks.
+To suppress this warning, set android.javaCompile.suppressSourceTargetDeprecationWarning=true in gradle.properties.
+warning: [options] source value 8 is obsolete and will be removed in a future release
+warning: [options] target value 8 is obsolete and will be removed in a future release
+warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
+Note: /Users/230405/Documents/papcuida/app/src/main/java/com/example/cuida/ui/sns24/Sns24Fragment.java uses or overrides a deprecated API.
+Note: Recompile with -Xlint:deprecation for details.
+3 warnings
+
+> Task :app:processDebugJavaRes UP-TO-DATE
+> Task :app:mergeDebugJavaResource UP-TO-DATE
+> Task :app:dexBuilderDebug
+> Task :app:mergeProjectDexDebug
+> Task :app:packageDebug
+> Task :app:createDebugApkListingFileRedirect
+> Task :app:assembleDebug
+
+[Incubating] Problems report is available at: file:///Users/230405/Documents/papcuida/build/reports/problems/problems-report.html
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.0.
+
+You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
+
+For more on this, please refer to https://docs.gradle.org/9.0-milestone-1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
+
+BUILD SUCCESSFUL in 2s
+36 actionable tasks: 16 executed, 20 up-to-date
diff --git a/build_log_7.txt b/build_log_7.txt
new file mode 100644
index 0000000..920b057
--- /dev/null
+++ b/build_log_7.txt
@@ -0,0 +1,73 @@
+WARNING: A restricted method in java.lang.System has been called
+WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230405/.gradle/wrapper/dists/gradle-9.0-milestone-1-bin/3vdepk4s12bybhohyuvjcm1bd/gradle-9.0-milestone-1/lib/native-platform-0.22-milestone-28.jar)
+WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
+WARNING: Restricted methods will be blocked in a future release unless native access is enabled
+
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preDebugBuild UP-TO-DATE
+> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
+> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
+> Task :app:generateDebugResValues UP-TO-DATE
+> Task :app:generateDebugResources
+> Task :app:mergeDebugResources
+> Task :app:packageDebugResources
+> Task :app:processDebugNavigationResources
+> Task :app:javaPreCompileDebug UP-TO-DATE
+> Task :app:parseDebugLocalResources
+> Task :app:checkDebugAarMetadata UP-TO-DATE
+> Task :app:dataBindingGenBaseClassesDebug
+> Task :app:compileDebugNavigationResources UP-TO-DATE
+> Task :app:mapDebugSourceSetPaths
+> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
+> Task :app:extractDeepLinksDebug UP-TO-DATE
+> Task :app:processDebugMainManifest
+> Task :app:processDebugManifest
+> Task :app:mergeDebugShaders UP-TO-DATE
+> Task :app:compileDebugShaders NO-SOURCE
+> Task :app:generateDebugAssets UP-TO-DATE
+> Task :app:mergeDebugAssets UP-TO-DATE
+> Task :app:compressDebugAssets UP-TO-DATE
+> Task :app:checkDebugDuplicateClasses UP-TO-DATE
+> Task :app:desugarDebugFileDependencies UP-TO-DATE
+> Task :app:mergeExtDexDebug UP-TO-DATE
+> Task :app:mergeLibDexDebug UP-TO-DATE
+> Task :app:mergeDebugJniLibFolders UP-TO-DATE
+> Task :app:mergeDebugNativeLibs NO-SOURCE
+> Task :app:stripDebugDebugSymbols NO-SOURCE
+> Task :app:validateSigningDebug UP-TO-DATE
+> Task :app:writeDebugAppMetadata UP-TO-DATE
+> Task :app:writeDebugSigningConfigVersions UP-TO-DATE
+> Task :app:processDebugManifestForPackage
+> Task :app:processDebugResources
+
+> Task :app:compileDebugJavaWithJavac
+Java compiler version 24 has deprecated support for compiling with source/target version 8.
+Try one of the following options:
+ 1. [Recommended] Use Java toolchain with a lower language version
+ 2. Set a higher source/target version
+ 3. Use a lower version of the JDK running the build (if you're not using Java toolchain)
+For more details on how to configure these settings, see https://developer.android.com/build/jdks.
+To suppress this warning, set android.javaCompile.suppressSourceTargetDeprecationWarning=true in gradle.properties.
+warning: [options] source value 8 is obsolete and will be removed in a future release
+warning: [options] target value 8 is obsolete and will be removed in a future release
+warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
+3 warnings
+
+> Task :app:processDebugJavaRes UP-TO-DATE
+> Task :app:mergeDebugJavaResource UP-TO-DATE
+> Task :app:dexBuilderDebug
+> Task :app:mergeProjectDexDebug
+> Task :app:packageDebug
+> Task :app:createDebugApkListingFileRedirect UP-TO-DATE
+> Task :app:assembleDebug
+
+[Incubating] Problems report is available at: file:///Users/230405/Documents/papcuida/build/reports/problems/problems-report.html
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.0.
+
+You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
+
+For more on this, please refer to https://docs.gradle.org/9.0-milestone-1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
+
+BUILD SUCCESSFUL in 1s
+36 actionable tasks: 15 executed, 21 up-to-date
diff --git a/build_log_8.txt b/build_log_8.txt
new file mode 100644
index 0000000..be49c75
--- /dev/null
+++ b/build_log_8.txt
@@ -0,0 +1,60 @@
+WARNING: A restricted method in java.lang.System has been called
+WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230405/.gradle/wrapper/dists/gradle-9.0-milestone-1-bin/3vdepk4s12bybhohyuvjcm1bd/gradle-9.0-milestone-1/lib/native-platform-0.22-milestone-28.jar)
+WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
+WARNING: Restricted methods will be blocked in a future release unless native access is enabled
+
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preDebugBuild UP-TO-DATE
+> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
+> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
+> Task :app:generateDebugResValues UP-TO-DATE
+> Task :app:generateDebugResources
+> Task :app:mergeDebugResources
+> Task :app:packageDebugResources
+> Task :app:processDebugNavigationResources
+> Task :app:javaPreCompileDebug UP-TO-DATE
+> Task :app:parseDebugLocalResources
+> Task :app:checkDebugAarMetadata UP-TO-DATE
+> Task :app:dataBindingGenBaseClassesDebug
+> Task :app:compileDebugNavigationResources UP-TO-DATE
+> Task :app:mapDebugSourceSetPaths
+> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
+> Task :app:extractDeepLinksDebug UP-TO-DATE
+> Task :app:processDebugMainManifest UP-TO-DATE
+> Task :app:processDebugManifest UP-TO-DATE
+> Task :app:processDebugManifestForPackage UP-TO-DATE
+> Task :app:mergeDebugShaders UP-TO-DATE
+> Task :app:compileDebugShaders NO-SOURCE
+> Task :app:generateDebugAssets UP-TO-DATE
+> Task :app:mergeDebugAssets UP-TO-DATE
+> Task :app:compressDebugAssets UP-TO-DATE
+> Task :app:checkDebugDuplicateClasses UP-TO-DATE
+> Task :app:desugarDebugFileDependencies UP-TO-DATE
+> Task :app:mergeExtDexDebug UP-TO-DATE
+> Task :app:mergeLibDexDebug UP-TO-DATE
+> Task :app:mergeDebugJniLibFolders UP-TO-DATE
+> Task :app:mergeDebugNativeLibs NO-SOURCE
+> Task :app:stripDebugDebugSymbols NO-SOURCE
+> Task :app:validateSigningDebug UP-TO-DATE
+> Task :app:writeDebugAppMetadata UP-TO-DATE
+> Task :app:writeDebugSigningConfigVersions UP-TO-DATE
+> Task :app:processDebugResources
+> Task :app:compileDebugJavaWithJavac UP-TO-DATE
+> Task :app:processDebugJavaRes UP-TO-DATE
+> Task :app:mergeDebugJavaResource UP-TO-DATE
+> Task :app:dexBuilderDebug UP-TO-DATE
+> Task :app:mergeProjectDexDebug UP-TO-DATE
+> Task :app:packageDebug
+> Task :app:createDebugApkListingFileRedirect UP-TO-DATE
+> Task :app:assembleDebug
+
+[Incubating] Problems report is available at: file:///Users/230405/Documents/papcuida/build/reports/problems/problems-report.html
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.0.
+
+You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
+
+For more on this, please refer to https://docs.gradle.org/9.0-milestone-1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
+
+BUILD SUCCESSFUL in 741ms
+36 actionable tasks: 9 executed, 27 up-to-date
diff --git a/build_log_9.txt b/build_log_9.txt
new file mode 100644
index 0000000..16aa8b4
--- /dev/null
+++ b/build_log_9.txt
@@ -0,0 +1,60 @@
+WARNING: A restricted method in java.lang.System has been called
+WARNING: java.lang.System::load has been called by net.rubygrapefruit.platform.internal.NativeLibraryLoader in an unnamed module (file:/Users/230405/.gradle/wrapper/dists/gradle-9.0-milestone-1-bin/3vdepk4s12bybhohyuvjcm1bd/gradle-9.0-milestone-1/lib/native-platform-0.22-milestone-28.jar)
+WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
+WARNING: Restricted methods will be blocked in a future release unless native access is enabled
+
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preDebugBuild UP-TO-DATE
+> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
+> Task :app:dataBindingMergeDependencyArtifactsDebug UP-TO-DATE
+> Task :app:generateDebugResValues UP-TO-DATE
+> Task :app:generateDebugResources
+> Task :app:mergeDebugResources
+> Task :app:packageDebugResources
+> Task :app:processDebugNavigationResources
+> Task :app:javaPreCompileDebug UP-TO-DATE
+> Task :app:parseDebugLocalResources
+> Task :app:checkDebugAarMetadata UP-TO-DATE
+> Task :app:dataBindingGenBaseClassesDebug UP-TO-DATE
+> Task :app:compileDebugNavigationResources UP-TO-DATE
+> Task :app:mapDebugSourceSetPaths
+> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
+> Task :app:extractDeepLinksDebug UP-TO-DATE
+> Task :app:processDebugMainManifest UP-TO-DATE
+> Task :app:processDebugManifest UP-TO-DATE
+> Task :app:processDebugManifestForPackage UP-TO-DATE
+> Task :app:mergeDebugShaders UP-TO-DATE
+> Task :app:compileDebugShaders NO-SOURCE
+> Task :app:generateDebugAssets UP-TO-DATE
+> Task :app:mergeDebugAssets UP-TO-DATE
+> Task :app:compressDebugAssets UP-TO-DATE
+> Task :app:checkDebugDuplicateClasses UP-TO-DATE
+> Task :app:desugarDebugFileDependencies UP-TO-DATE
+> Task :app:mergeExtDexDebug UP-TO-DATE
+> Task :app:mergeLibDexDebug UP-TO-DATE
+> Task :app:mergeDebugJniLibFolders UP-TO-DATE
+> Task :app:mergeDebugNativeLibs NO-SOURCE
+> Task :app:stripDebugDebugSymbols NO-SOURCE
+> Task :app:validateSigningDebug UP-TO-DATE
+> Task :app:writeDebugAppMetadata UP-TO-DATE
+> Task :app:writeDebugSigningConfigVersions UP-TO-DATE
+> Task :app:processDebugResources
+> Task :app:compileDebugJavaWithJavac UP-TO-DATE
+> Task :app:processDebugJavaRes UP-TO-DATE
+> Task :app:mergeDebugJavaResource UP-TO-DATE
+> Task :app:dexBuilderDebug UP-TO-DATE
+> Task :app:mergeProjectDexDebug UP-TO-DATE
+> Task :app:packageDebug
+> Task :app:createDebugApkListingFileRedirect UP-TO-DATE
+> Task :app:assembleDebug
+
+[Incubating] Problems report is available at: file:///Users/230405/Documents/papcuida/build/reports/problems/problems-report.html
+
+Deprecated Gradle features were used in this build, making it incompatible with Gradle 10.0.
+
+You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.
+
+For more on this, please refer to https://docs.gradle.org/9.0-milestone-1/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.
+
+BUILD SUCCESSFUL in 696ms
+36 actionable tasks: 8 executed, 28 up-to-date
diff --git a/documentacao_projecto/backups_codigo/HomeFragment.java b/documentacao_projecto/backups_codigo/HomeFragment.java
new file mode 100644
index 0000000..b1b5b65
--- /dev/null
+++ b/documentacao_projecto/backups_codigo/HomeFragment.java
@@ -0,0 +1,92 @@
+package com.example.cuida.ui.home;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import com.example.cuida.databinding.FragmentHomeBinding;
+import com.example.cuida.ui.medication.MedicationViewModel;
+import com.example.cuida.ui.appointments.AppointmentsViewModel;
+import com.example.cuida.data.model.Appointment;
+import java.util.Calendar;
+import java.util.Locale;
+
+public class HomeFragment extends Fragment {
+
+ private FragmentHomeBinding binding;
+ private MedicationViewModel medicationViewModel;
+ private AppointmentsViewModel appointmentsViewModel;
+
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
+ binding = FragmentHomeBinding.inflate(inflater, container, false);
+
+ // --- Greeting & Profile Picture ---
+ com.google.firebase.auth.FirebaseAuth auth = com.google.firebase.auth.FirebaseAuth.getInstance();
+ if (auth.getCurrentUser() != null) {
+ String userId = auth.getCurrentUser().getUid();
+ com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("utilizadores").document(userId)
+ .get()
+ .addOnSuccessListener(documentSnapshot -> {
+ if (documentSnapshot.exists() && isAdded()) {
+ String name = documentSnapshot.getString("name");
+ if (name != null && !name.isEmpty()) {
+ // Extract first name
+ String firstName = name.split(" ")[0];
+ binding.textGreeting.setText("Olá, " + firstName + "!");
+ } else {
+ binding.textGreeting.setText("Olá, Utilizador!");
+ }
+
+ // Load Profile Picture
+ String profilePictureUri = documentSnapshot.getString("profilePictureUri");
+ if (profilePictureUri != null && !profilePictureUri.isEmpty()) {
+ try {
+ binding.imageProfileHome.setImageURI(android.net.Uri.parse(profilePictureUri));
+ } catch (Exception e) {
+ android.util.Log.e("HomeFragment", "Error loading profile pic view: " + e.getMessage());
+ }
+ }
+ }
+ })
+ .addOnFailureListener(e -> {
+ if (isAdded())
+ binding.textGreeting.setText("Olá, Utilizador!");
+ });
+ } else {
+ binding.textGreeting.setText("Olá, Utilizador!");
+ }
+
+ // --- Next Medication ---
+ medicationViewModel = new ViewModelProvider(this).get(MedicationViewModel.class);
+ medicationViewModel.getNextMedication().observe(getViewLifecycleOwner(), medication -> {
+ if (medication != null) {
+ binding.nextMedName.setText(medication.name + " (" + medication.dosage + ")");
+ binding.nextMedTime.setText("Hoje, " + medication.time);
+ } else {
+ binding.nextMedName.setText("Sem medicação");
+ binding.nextMedTime.setText("--:--");
+ }
+ });
+
+ // --- Book Appointment ---
+ appointmentsViewModel = new ViewModelProvider(this).get(AppointmentsViewModel.class);
+ binding.buttonBookAppointment.setOnClickListener(v -> {
+ androidx.navigation.Navigation.findNavController(v)
+ .navigate(com.example.cuida.R.id.action_home_to_schedule_appointment);
+ });
+
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+}
diff --git a/documentacao_projecto/backups_codigo/MedicationAdapter.java b/documentacao_projecto/backups_codigo/MedicationAdapter.java
new file mode 100644
index 0000000..d1ba491
--- /dev/null
+++ b/documentacao_projecto/backups_codigo/MedicationAdapter.java
@@ -0,0 +1,85 @@
+package com.example.cuida.ui.medication;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import com.example.cuida.R;
+import com.example.cuida.data.model.Medication;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MedicationAdapter extends RecyclerView.Adapter {
+
+ private List medicationList = new ArrayList<>();
+ private final OnItemClickListener listener;
+
+ public interface OnItemClickListener {
+ void onCheckClick(Medication medication);
+
+ void onItemClick(Medication medication);
+ }
+
+ public MedicationAdapter(OnItemClickListener listener) {
+ this.listener = listener;
+ }
+
+ public void setMedications(List medications) {
+ this.medicationList = medications;
+ notifyDataSetChanged();
+ }
+
+ @NonNull
+ @Override
+ public MedicationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_medication, parent, false);
+ return new MedicationViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull MedicationViewHolder holder, int position) {
+ Medication medication = medicationList.get(position);
+ holder.textName.setText(medication.name);
+ holder.textDosage.setText(medication.dosage);
+ holder.textTime.setText(medication.time);
+ holder.textNotes.setText(medication.notes);
+
+ // Remove listener temporarily to avoid triggering it during bind
+ holder.checkBoxTaken.setOnCheckedChangeListener(null);
+ holder.checkBoxTaken.setChecked(medication.isTaken);
+
+ holder.checkBoxTaken.setOnCheckedChangeListener((buttonView, isChecked) -> {
+ medication.isTaken = isChecked;
+ listener.onCheckClick(medication);
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return medicationList.size();
+ }
+
+ public class MedicationViewHolder extends RecyclerView.ViewHolder {
+ TextView textName, textDosage, textTime, textNotes;
+ CheckBox checkBoxTaken;
+
+ public MedicationViewHolder(@NonNull View itemView) {
+ super(itemView);
+ textName = itemView.findViewById(R.id.text_med_name);
+ textDosage = itemView.findViewById(R.id.text_med_dosage);
+ textTime = itemView.findViewById(R.id.text_med_time);
+ textNotes = itemView.findViewById(R.id.text_med_notes);
+ checkBoxTaken = itemView.findViewById(R.id.checkbox_taken);
+
+ itemView.setOnClickListener(v -> {
+ int position = getAdapterPosition();
+ if (listener != null && position != RecyclerView.NO_POSITION) {
+ listener.onItemClick(medicationList.get(position));
+ }
+ });
+ }
+ }
+}
diff --git a/documentacao_projecto/backups_codigo/MedicationDialog.java b/documentacao_projecto/backups_codigo/MedicationDialog.java
new file mode 100644
index 0000000..6b30cdb
--- /dev/null
+++ b/documentacao_projecto/backups_codigo/MedicationDialog.java
@@ -0,0 +1,368 @@
+package com.example.cuida.ui.medication;
+
+import android.app.Dialog;
+import android.app.TimePickerDialog;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.TextView;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+import com.google.android.material.textfield.TextInputEditText;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import com.example.cuida.R;
+import com.example.cuida.data.model.Medication;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.ArrayList;
+import java.util.List;
+import com.google.firebase.database.DataSnapshot;
+import com.google.firebase.database.DatabaseError;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+import com.google.firebase.database.ValueEventListener;
+import android.text.Editable;
+import android.text.TextWatcher;
+import com.example.cuida.data.model.Comprimido;
+import android.widget.AdapterView;
+import android.widget.Toast;
+import com.google.android.material.chip.Chip;
+import com.google.android.material.chip.ChipGroup;
+import com.google.android.material.button.MaterialButton;
+import java.util.Collections;
+
+public class MedicationDialog extends DialogFragment {
+
+ private TextInputEditText editName;
+ private RecyclerView recyclerResults;
+ private ComprimidoRecyclerAdapter recyclerAdapter;
+ private List searchResults = new ArrayList<>();
+ private List fullPillsList = new ArrayList<>();
+ private DatabaseReference medicationRef;
+ private EditText editNotes;
+ private android.widget.RadioButton radioOral, radioTopical, radioInhalatory;
+ private android.widget.RadioGroup radioGroupRoute;
+ private ChipGroup chipGroupTimes;
+ private List selectedTimes = new ArrayList<>();
+ private Medication medicationToEdit;
+ private OnMedicationSaveListener listener;
+ private OnMedicationDeleteListener deleteListener;
+
+ public interface OnMedicationSaveListener {
+ void onSave(Medication medication);
+ }
+
+ public interface OnMedicationDeleteListener {
+ void onDelete(Medication medication);
+ }
+
+ public void setListener(OnMedicationSaveListener listener) {
+ this.listener = listener;
+ }
+
+ public void setDeleteListener(OnMedicationDeleteListener listener) {
+ this.deleteListener = listener;
+ }
+
+ public void setMedicationToEdit(Medication medication) {
+ this.medicationToEdit = medication;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
+ LayoutInflater inflater = requireActivity().getLayoutInflater();
+ View view = inflater.inflate(R.layout.dialog_add_medication, null);
+
+ editName = view.findViewById(R.id.edit_med_name);
+ recyclerResults = view.findViewById(R.id.recycler_search_results);
+ editNotes = view.findViewById(R.id.edit_med_notes);
+ chipGroupTimes = view.findViewById(R.id.chip_group_times);
+ MaterialButton btnAddTime = view.findViewById(R.id.btn_add_time);
+
+ radioGroupRoute = view.findViewById(R.id.radio_group_route);
+ radioOral = view.findViewById(R.id.radio_oral);
+ radioTopical = view.findViewById(R.id.radio_topical);
+ radioInhalatory = view.findViewById(R.id.radio_inhalatory);
+
+ final android.content.Context currentContext = getContext();
+ if (currentContext != null) {
+ recyclerAdapter = new ComprimidoRecyclerAdapter(searchResults, selected -> {
+ editName.setText(selected.nome);
+ editName.setSelection(selected.nome.length());
+
+ // Adiciona a dosagem/informação ao campo de notas automaticamente
+ if (selected.dosagem != null && !selected.dosagem.isEmpty()) {
+ editNotes.setText(selected.dosagem);
+ }
+
+ recyclerResults.setVisibility(View.GONE);
+ searchResults.clear();
+ });
+ recyclerResults.setLayoutManager(new LinearLayoutManager(currentContext));
+ recyclerResults.setAdapter(recyclerAdapter);
+
+ String dbUrl = "https://cuidamais-7b904-default-rtdb.firebaseio.com/";
+ medicationRef = FirebaseDatabase.getInstance(dbUrl).getReference("medication");
+
+ // Carregar todos os medicamentos uma única vez para filtragem local rápida
+ fetchAllMedsOnce();
+
+ editName.addTextChangedListener(new TextWatcher() {
+ @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ filterMedsLocally(s.toString().trim());
+ }
+ @Override public void afterTextChanged(Editable s) {}
+ });
+ }
+
+ radioGroupRoute = view.findViewById(R.id.radio_group_route);
+ radioOral = view.findViewById(R.id.radio_oral);
+ radioTopical = view.findViewById(R.id.radio_topical);
+ radioInhalatory = view.findViewById(R.id.radio_inhalatory);
+
+ // Set up TimePicker
+ btnAddTime.setOnClickListener(v -> showTimePicker());
+
+ if (medicationToEdit != null) {
+ editName.setText(medicationToEdit.name);
+ editNotes.setText(medicationToEdit.notes);
+ if (medicationToEdit.time != null && !medicationToEdit.time.isEmpty()) {
+ String[] times = medicationToEdit.time.split(",\\s*");
+ for (String t : times) {
+ if (!t.isEmpty()) selectedTimes.add(t);
+ }
+ refreshTimeChips();
+ }
+
+ String dosage = medicationToEdit.dosage;
+ if (dosage != null) {
+ if (dosage.contains("Oral"))
+ radioOral.setChecked(true);
+ else if (dosage.contains("Tópica"))
+ radioTopical.setChecked(true);
+ else if (dosage.contains("Inalatória"))
+ radioInhalatory.setChecked(true);
+ }
+
+ builder.setTitle("Editar Medicamento");
+ } else {
+ builder.setTitle("Adicionar Medicamento");
+ // Default time to current time
+ Calendar cal = Calendar.getInstance();
+ String defaultTime = String.format(Locale.getDefault(), "%02d:%02d", cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
+ selectedTimes.add(defaultTime);
+ refreshTimeChips();
+ }
+
+ builder.setView(view)
+ .setPositiveButton("Guardar", (dialog, id) -> {
+ String name = editName.getText().toString();
+ String notes = editNotes.getText().toString();
+
+ // Join times with comma
+ StringBuilder timeBuilder = new StringBuilder();
+ for (int i = 0; i < selectedTimes.size(); i++) {
+ timeBuilder.append(selectedTimes.get(i));
+ if (i < selectedTimes.size() - 1) timeBuilder.append(", ");
+ }
+ String time = timeBuilder.toString();
+
+ int selectedId = radioGroupRoute.getCheckedRadioButtonId();
+ String dosage = "Via não especificada";
+
+ if (selectedId == R.id.radio_oral) {
+ dosage = "Via Oral";
+ } else if (selectedId == R.id.radio_topical) {
+ dosage = "Via Tópica";
+ } else if (selectedId == R.id.radio_inhalatory) {
+ dosage = "Via Inalatória";
+ }
+
+ if (medicationToEdit != null) {
+ medicationToEdit.name = name;
+ medicationToEdit.dosage = dosage;
+ medicationToEdit.notes = notes;
+ medicationToEdit.time = time;
+ if (listener != null)
+ listener.onSave(medicationToEdit);
+ } else {
+ Medication newMed = new Medication(name, time, dosage, notes, null);
+ if (listener != null)
+ listener.onSave(newMed);
+ }
+ });
+
+ if (medicationToEdit != null) {
+ builder.setNeutralButton("Eliminar", (dialog, id) -> {
+ if (deleteListener != null) {
+ deleteListener.onDelete(medicationToEdit);
+ }
+ });
+ }
+
+ AlertDialog alertDialog = builder.create();
+
+ alertDialog.setOnShowListener(d -> {
+ android.widget.Button btnPos = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (btnPos != null) {
+ // Apply our custom outline drawable to "Guardar"
+ btnPos.setBackgroundResource(R.drawable.btn_outline_primary);
+ btnPos.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.primary_color));
+
+ int paddingPx = (int) (16 * getResources().getDisplayMetrics().density);
+ btnPos.setPadding(paddingPx, 0, paddingPx, 0);
+ }
+
+ android.widget.Button btnNeu = alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
+ if (btnNeu != null) {
+ btnNeu.setTextColor(androidx.core.content.ContextCompat.getColor(requireContext(), R.color.error_color));
+ btnNeu.setBackgroundResource(R.drawable.btn_outline_error);
+
+ int paddingPx = (int) (16 * getResources().getDisplayMetrics().density);
+ btnNeu.setPadding(paddingPx, 0, paddingPx, 0);
+
+ android.view.ViewGroup.LayoutParams lp = btnNeu.getLayoutParams();
+ if (lp instanceof android.view.ViewGroup.MarginLayoutParams) {
+ android.view.ViewGroup.MarginLayoutParams marginLp = (android.view.ViewGroup.MarginLayoutParams) lp;
+ int marginPx = (int) (8 * getResources().getDisplayMetrics().density);
+ marginLp.setMargins(marginLp.leftMargin, marginLp.topMargin, marginLp.rightMargin + marginPx, marginLp.bottomMargin);
+ btnNeu.setLayoutParams(marginLp);
+ }
+ }
+ });
+
+ return alertDialog;
+ }
+
+ private void showTimePicker() {
+ Calendar cal = Calendar.getInstance();
+ int hour = cal.get(Calendar.HOUR_OF_DAY);
+ int minute = cal.get(Calendar.MINUTE);
+
+ TimePickerDialog timePickerDialog = new TimePickerDialog(getContext(),
+ (view, hourOfDay, minute1) -> {
+ String time = String.format(Locale.getDefault(), "%02d:%02d", hourOfDay, minute1);
+ if (!selectedTimes.contains(time)) {
+ selectedTimes.add(time);
+ Collections.sort(selectedTimes);
+ refreshTimeChips();
+ }
+ },
+ hour, minute, true);
+ timePickerDialog.show();
+ }
+
+ private void refreshTimeChips() {
+ if (chipGroupTimes == null) return;
+ chipGroupTimes.removeAllViews();
+ for (String time : selectedTimes) {
+ Chip chip = new Chip(requireContext());
+ chip.setText(time);
+ chip.setCloseIconVisible(true);
+ chip.setOnCloseIconClickListener(v -> {
+ selectedTimes.remove(time);
+ refreshTimeChips();
+ });
+ chipGroupTimes.addView(chip);
+ }
+ }
+
+ private void fetchAllMedsOnce() {
+ String dbUrl = "https://cuidamais-7b904-default-rtdb.firebaseio.com/";
+ DatabaseReference rootRef = FirebaseDatabase.getInstance(dbUrl).getReference();
+ String[] nodes = {"medication", "medicamentos", "Medicamentos", "comprimidos"};
+
+ fullPillsList.clear();
+
+ // 1. Tentar nos nós específicos
+ for (String node : nodes) {
+ rootRef.child(node).addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(@NonNull DataSnapshot snapshot) {
+ if (snapshot.exists()) {
+ parseSnapshot(snapshot, "Nó: " + node);
+ }
+ }
+ @Override public void onCancelled(@NonNull DatabaseError error) {}
+ });
+ }
+
+ // 2. Tentar também na raiz (caso os medicamentos estejam diretamente no topo)
+ rootRef.limitToFirst(50).addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(@NonNull DataSnapshot snapshot) {
+ if (snapshot.exists()) {
+ parseSnapshot(snapshot, "Raiz");
+ }
+ }
+ @Override public void onCancelled(@NonNull DatabaseError error) {}
+ });
+ }
+
+ private void parseSnapshot(DataSnapshot snapshot, String source) {
+ int count = 0;
+ for (DataSnapshot child : snapshot.getChildren()) {
+ String name = child.child("nome").getValue(String.class);
+ if (name == null) name = child.child("name").getValue(String.class);
+ if (name == null && !(child.getValue() instanceof java.util.Map)) {
+ // Se o valor for a própria string (ex: "Paracetamol")
+ name = child.getValue() instanceof String ? (String) child.getValue() : null;
+ }
+ if (name == null) name = child.getKey();
+
+ String dosage = child.child("dosagem").getValue(String.class);
+ if (dosage == null) dosage = child.child("dosage").getValue(String.class);
+ if (dosage == null) dosage = "";
+
+ if (name != null && !name.isEmpty()) {
+ boolean exists = false;
+ for (Comprimido p : fullPillsList) {
+ if (name.equals(p.nome)) { exists = true; break; }
+ }
+ if (!exists) {
+ fullPillsList.add(new Comprimido(name, dosage));
+ count++;
+ }
+ }
+ }
+ if (count > 0 && getContext() != null) {
+ Log.d("FirebaseSearch", "Carregados " + count + " de " + source);
+ // Toast.makeText(getContext(), "Fonte: " + source + " (" + count + ")", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void filterMedsLocally(String query) {
+ searchResults.clear();
+ if (query.isEmpty()) {
+ recyclerResults.setVisibility(View.GONE);
+ recyclerAdapter.notifyDataSetChanged();
+ return;
+ }
+
+ String lowerQuery = query.toLowerCase();
+ for (Comprimido p : fullPillsList) {
+ if (p.nome != null && p.nome.toLowerCase().contains(lowerQuery)) {
+ searchResults.add(p);
+ }
+ }
+
+ recyclerAdapter.notifyDataSetChanged();
+ recyclerResults.setVisibility(searchResults.isEmpty() ? View.GONE : View.VISIBLE);
+ }
+
+ private void handleError(DatabaseError error) {
+ Log.e("FirebaseSearch", "Erro: " + error.getMessage());
+ if (getContext() != null) {
+ Toast.makeText(getContext(), "Erro no Firebase: " + error.getMessage(), Toast.LENGTH_LONG).show();
+ }
+ }
+}
diff --git a/documentacao_projecto/backups_codigo/MedicationFragment.java b/documentacao_projecto/backups_codigo/MedicationFragment.java
new file mode 100644
index 0000000..c98b8c6
--- /dev/null
+++ b/documentacao_projecto/backups_codigo/MedicationFragment.java
@@ -0,0 +1,141 @@
+package com.example.cuida.ui.medication;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import com.example.cuida.data.model.Medication;
+import com.example.cuida.databinding.FragmentMedicationBinding;
+
+public class MedicationFragment extends Fragment {
+
+ private FragmentMedicationBinding binding;
+ private MedicationViewModel medicationViewModel;
+
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
+ medicationViewModel = new ViewModelProvider(this).get(MedicationViewModel.class);
+
+ binding = FragmentMedicationBinding.inflate(inflater, container, false);
+
+ MedicationAdapter adapter = new MedicationAdapter(new MedicationAdapter.OnItemClickListener() {
+ @Override
+ public void onCheckClick(Medication medication) {
+ medicationViewModel.update(medication);
+ }
+
+ @Override
+ public void onItemClick(Medication medication) {
+ showMedicationDialog(medication);
+ }
+ });
+
+ binding.recyclerMedication.setLayoutManager(new LinearLayoutManager(getContext()));
+ binding.recyclerMedication.setAdapter(adapter);
+
+ medicationViewModel.getAllMedications().observe(getViewLifecycleOwner(), medications -> {
+ adapter.setMedications(medications);
+
+ if (medications != null && !medications.isEmpty()) {
+ binding.recyclerMedication.setVisibility(View.VISIBLE);
+ binding.textEmptyMedications.setVisibility(View.GONE);
+ } else {
+ binding.recyclerMedication.setVisibility(View.GONE);
+ binding.textEmptyMedications.setVisibility(View.VISIBLE);
+ }
+ });
+
+ binding.fabAddMedication.setOnClickListener(v -> showMedicationDialog(null));
+
+ return binding.getRoot();
+ }
+
+ private void showMedicationDialog(Medication medication) {
+ MedicationDialog dialog = new MedicationDialog();
+ dialog.setMedicationToEdit(medication);
+ dialog.setListener(medicationToSave -> {
+ // If it's an edit, cancel old alarms first
+ if (medication != null && medication.time != null) {
+ String[] oldTimes = medication.time.split(",\\s*");
+ for (String t : oldTimes) {
+ if (t.isEmpty()) continue;
+ try {
+ int oldId = (medication.name + t).hashCode();
+ com.example.cuida.utils.AlarmScheduler.cancelAlarm(requireContext(), oldId);
+ } catch (Exception e) {}
+ }
+ }
+
+ if (medication == null) {
+ medicationViewModel.insert(medicationToSave);
+ } else {
+ medicationViewModel.update(medicationToSave);
+ }
+
+ String[] times = medicationToSave.time.split(",\\s*");
+ for (String t : times) {
+ if (t.isEmpty()) continue;
+ try {
+ String[] timeParts = t.split(":");
+ int hour = Integer.parseInt(timeParts[0]);
+ int minute = Integer.parseInt(timeParts[1]);
+
+ java.util.Calendar calendar = java.util.Calendar.getInstance();
+ calendar.set(java.util.Calendar.HOUR_OF_DAY, hour);
+ calendar.set(java.util.Calendar.MINUTE, minute);
+ calendar.set(java.util.Calendar.SECOND, 0);
+
+ if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
+ calendar.add(java.util.Calendar.DAY_OF_YEAR, 1);
+ }
+
+ String title = "Hora do Medicamento";
+ String msg = "É hora de tomar: " + medicationToSave.name + " (" + medicationToSave.dosage + ")";
+
+ int alarmId = (medicationToSave.name + t).hashCode();
+
+ com.example.cuida.utils.AlarmScheduler.scheduleAlarm(
+ requireContext(),
+ calendar.getTimeInMillis(),
+ title,
+ msg,
+ alarmId);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ dialog.dismiss();
+ });
+
+ dialog.setDeleteListener(medicationToDelete -> {
+ medicationViewModel.delete(medicationToDelete);
+
+ // Cancel all alarms for this medication
+ if (medicationToDelete.time != null) {
+ String[] times = medicationToDelete.time.split(",\\s*");
+ for (String t : times) {
+ if (t.isEmpty()) continue;
+ try {
+ int alarmId = (medicationToDelete.name + t).hashCode();
+ com.example.cuida.utils.AlarmScheduler.cancelAlarm(requireContext(), alarmId);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ });
+
+ dialog.show(getParentFragmentManager(), "MedicationDialog");
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ binding = null;
+ }
+}
diff --git a/documentacao_projecto/backups_codigo/MedicationViewModel.java b/documentacao_projecto/backups_codigo/MedicationViewModel.java
new file mode 100644
index 0000000..dc8afff
--- /dev/null
+++ b/documentacao_projecto/backups_codigo/MedicationViewModel.java
@@ -0,0 +1,118 @@
+package com.example.cuida.ui.medication;
+
+import android.app.Application;
+import android.util.Log;
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.example.cuida.data.model.Medication;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.Query;
+import com.google.firebase.firestore.QueryDocumentSnapshot;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MedicationViewModel extends AndroidViewModel {
+
+ private final MutableLiveData> allMedications = new MutableLiveData<>(new ArrayList<>());
+ private final MutableLiveData nextMedication = new MutableLiveData<>(null);
+ private final FirebaseFirestore db;
+ private final FirebaseAuth auth;
+
+ public MedicationViewModel(@NonNull Application application) {
+ super(application);
+ db = FirebaseFirestore.getInstance();
+ auth = FirebaseAuth.getInstance();
+ fetchMedications();
+ }
+
+ private void fetchMedications() {
+ if (auth.getCurrentUser() == null)
+ return;
+ String userId = auth.getCurrentUser().getUid();
+
+ db.collection("medicamentos")
+ .whereEqualTo("userId", userId)
+ .addSnapshotListener((value, error) -> {
+ if (error != null) {
+ Log.e("MedicationViewModel", "Listen failed.", error);
+ return;
+ }
+
+ List meds = new ArrayList<>();
+ if (value != null) {
+ for (QueryDocumentSnapshot doc : value) {
+ Medication med = doc.toObject(Medication.class);
+ med.id = doc.getId(); // Ensure ID is set
+ meds.add(med);
+ }
+ }
+
+ // Sort locally to avoid needing a composite index in Firestore
+ meds.sort((m1, m2) -> {
+ if (m1.time == null && m2.time == null) return 0;
+ if (m1.time == null) return 1;
+ if (m2.time == null) return -1;
+ return m1.time.compareTo(m2.time);
+ });
+
+ allMedications.setValue(meds);
+
+ if (!meds.isEmpty()) {
+ nextMedication.setValue(meds.get(0));
+ } else {
+ nextMedication.setValue(null);
+ }
+ });
+ }
+
+ public LiveData> getAllMedications() {
+ return allMedications;
+ }
+
+ public LiveData getNextMedication() {
+ return nextMedication;
+ }
+
+ public void insert(Medication medication) {
+ if (auth.getCurrentUser() == null)
+ return;
+ String userId = auth.getCurrentUser().getUid();
+
+ medication.userId = userId;
+
+ db.collection("medicamentos")
+ .add(medication)
+ .addOnSuccessListener(documentReference -> Log.d("MedicationViewModel", "Medication added"))
+ .addOnFailureListener(e -> Log.w("MedicationViewModel", "Error adding medication", e));
+ }
+
+ public void update(Medication medication) {
+ if (auth.getCurrentUser() == null || medication.id == null)
+ return;
+ String userId = auth.getCurrentUser().getUid();
+
+ medication.userId = userId;
+
+ db.collection("medicamentos")
+ .document(medication.id)
+ .set(medication)
+ .addOnSuccessListener(aVoid -> Log.d("MedicationViewModel", "Medication updated"))
+ .addOnFailureListener(e -> Log.w("MedicationViewModel", "Error updating medication", e));
+ }
+
+ public void delete(Medication medication) {
+ if (auth.getCurrentUser() == null || medication.id == null)
+ return;
+
+ db.collection("medicamentos")
+ .document(medication.id)
+ .delete()
+ .addOnSuccessListener(aVoid -> Log.d("MedicationViewModel", "Medication deleted"))
+ .addOnFailureListener(e -> Log.w("MedicationViewModel", "Error deleting medication", e));
+ }
+}
diff --git a/documentacao_projecto/backups_codigo/dialog_add_medication.xml b/documentacao_projecto/backups_codigo/dialog_add_medication.xml
new file mode 100644
index 0000000..1834a7f
--- /dev/null
+++ b/documentacao_projecto/backups_codigo/dialog_add_medication.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/documentacao_projecto/base_de_dados_firebase.md b/documentacao_projecto/base_de_dados_firebase.md
new file mode 100644
index 0000000..02c46f1
--- /dev/null
+++ b/documentacao_projecto/base_de_dados_firebase.md
@@ -0,0 +1,47 @@
+# Estrutura de Dados e Firebase - Cuida+
+
+Este documento documenta como a aplicação utiliza o Firebase para armazenamento e autenticação.
+
+## 1. Firebase Authentication
+- **Métodos:** Email e Password.
+- **Identificação:** O `UID` do utilizador é usado como chave estrangeira em todas as coleções do Firestore para garantir a privacidade dos dados.
+
+## 2. Cloud Firestore (Bancos de Dados NoSQL)
+A aplicação utiliza as seguintes coleções principais:
+
+### 👤 Coleção: `utilizadores`
+Guarda o perfil do utilizador.
+- **Campos:** `name`, `email`, `profilePictureUri`, `phone`.
+- **Chave:** UID do Firebase Auth.
+
+### 💊 Coleção: `medicamentos`
+Guarda a lista de remédios de cada utilizador.
+- **Campos:**
+ - `name`: Nome do fármaco.
+ - `time`: String com horários (ex: "08:00, 22:00").
+ - `dosage`: Via de administração (ex: "Via Oral").
+ - `notes`: Notas adicionais.
+ - `isTaken`: Boolean (estado atual).
+ - `userId`: UID do proprietário.
+
+### 📅 Coleção: `appointments`
+Guarda as marcações de consultas.
+- **Campos:** `doctorName`, `date`, `time`, `description`, `userId`.
+
+### 👨⚕️ Coleção: `medicos`
+Lista de profissionais disponíveis para agendamento.
+- **Campos:** `name`, `specialty`, `role` (deve ser 'medico').
+
+## 3. Realtime Database
+Utilizado especificamente para a funcionalidade de **Autocomplete / Pesquisa de Medicamentos**.
+- **Nó:** `medication` ou `medicamentos`.
+- **Conteúdo:** Lista global com milhares de nomes de medicamentos para sugestão rápida durante a escrita (sem necessidade de carregar do Firestore para poupar custos e ganhar velocidade).
+
+## 4. Segurança (Regras Sugeridas)
+Para garantir que um utilizador não vê os dados de outro, as regras do Firestore devem ser configuradas como:
+```javascript
+allow read, write: if request.auth != null && request.auth.uid == resource.data.userId;
+```
+
+---
+*Documentação de Base de Dados - Cuida+*
diff --git a/documentacao_projecto/detalhes_medicamentos_multiplos.md b/documentacao_projecto/detalhes_medicamentos_multiplos.md
new file mode 100644
index 0000000..4ddd0bc
--- /dev/null
+++ b/documentacao_projecto/detalhes_medicamentos_multiplos.md
@@ -0,0 +1,34 @@
+# Detalhes da Implementação: Múltiplos Horários de Medicamentos
+
+Este documento detalha as mudanças técnicas feitas em 15 de Abril de 2026 para suportar múltiplos horários num único medicamento.
+
+## Motivação
+Anteriormente, cada medicamento podia ter apenas um horário (ex: "08:00"). Se o utilizador precisasse de tomar o mesmo comprimido de 8 em 8 horas, teria de criar 3 entradas separadas. Agora, uma única entrada suporta todos os horários.
+
+## Alterações Técnicas
+
+### 1. Layout (`dialog_add_medication.xml`)
+- **Removido:** `TextView (text_med_time)` que exibia o horário fixo.
+- **Adicionado:**
+ - `ChipGroup (chip_group_times)` para exibir dinamicamente os horários selecionados.
+ - `MaterialButton (btn_add_time)` com texto "Adicionar" para abrir o seletor.
+
+### 2. Diálogo (`MedicationDialog.java`)
+- **Estado:** Agora guarda uma `List selectedTimes`.
+- **Lógica de Seleção:**
+ - `showTimePicker()`: Abre o `TimePickerDialog` e adiciona o horário à lista se ainda não existir.
+ - `Collections.sort()`: Mantém os chips ordenados cronologicamente.
+ - `refreshTimeChips()`: Recria os chips no `ChipGroup` sempre que a lista muda.
+- **Persistência:** No momento de guardar, a lista de horários é unida por vírgulas (ex: `"08:00, 14:00, 20:00"`) no campo `time` do modelo `Medication`.
+
+### 3. Fragmento e Alarmes (`MedicationFragment.java`)
+- **Agendamento:** Quando um medicamento é guardado, o código faz o `split(",\\s*")` na string de horários e percorre cada um.
+- **IDs de Alarme Únicos:** Para cada horário, gera um ID único usando `(nome + horario).hashCode()`. Isso evita que um alarme substitua o outro.
+- **Limpeza:** Antes de atualizar ou ao eliminar, o sistema percorre os horários antigos e cancela todos os `PendingIntent` correspondentes para evitar alarmes "fantasma".
+
+## Compatibilidade
+- O modelo `Medication` não precisou de alteração de campo, mantendo a compatibilidade com a base de dados Firestore atual.
+- A lista principal (`MedicationAdapter`) apenas exibe a string combinada, o que é visualmente limpo para o utilizador.
+
+---
+*Documentação técnica de implementação - Cuida+*
diff --git a/documentacao_projecto/guia_arquitetura_app.md b/documentacao_projecto/guia_arquitetura_app.md
new file mode 100644
index 0000000..4a45180
--- /dev/null
+++ b/documentacao_projecto/guia_arquitetura_app.md
@@ -0,0 +1,54 @@
+# Guia Completo da Aplicação - Cuida+
+
+Este documento fornece uma visão geral técnica e funcional de toda a aplicação Cuida+.
+
+## 1. Visão Geral
+A **Cuida+** é uma aplicação móvel Android desenvolvida em Java, focada na gestão de saúde pessoal. Permite aos utilizadores gerir as suas medicações, agendar consultas médicas e realizar uma triagem preliminar baseada em Inteligência Artificial.
+
+## 2. Tecnologias Utilizadas
+- **Linguagem:** Java (Android SDK).
+- **Base de Dados & Auth:** Firebase (Authentication, Firestore, Realtime Database).
+- **Inteligência Artificial:** Google Gemini API (para o chat de triagem).
+- **Notificações:** AlarmManager para lembretes de medicação.
+
+## 3. Arquitetura de Pastas e Módulos
+
+### 🔵 Autenticação (`ui/auth`)
+- **Login/Registo:** Gerido pelo Firebase Auth.
+- **Recuperação de Password:** Envio de emails automáticos para redefinição.
+- **Ficheiros:** `LoginActivity`, `RegisterActivity`, `ForgotPasswordActivity`.
+
+### 🟢 Gestão de Medicação (`ui/medication`)
+- **Funcionalidades:** Adicionar, editar e eliminar medicamentos.
+- **Destaque:** Suporte para múltiplos horários por medicamento com alarmes independentes.
+- **Integração:** Pesquisa em tempo real de nomes de medicamentos no Firebase (Realtime DB).
+- **Ficheiros:** `MedicationFragment`, `MedicationDialog`, `MedicationViewModel`.
+
+### 📅 Agenda e Consultas (`ui/appointments` & `ui/schedule`)
+- **Visualização:** Lista de consultas futuras e passadas.
+- **Agendamento:** Escolha de data e slots de tempo disponíveis para marcar com médicos registados no sistema.
+- **Ficheiros:** `AppointmentsFragment`, `ScheduleAppointmentFragment`.
+
+### 🤖 Triagem IA - SNS24 (`ui/sns24`)
+- **Funcionalidade:** Um chat inteligente onde o utilizador descreve sintomas.
+- **Lógica:** Usa a classe `Gemini.java` para processar a linguagem natural e sugerir o nível de urgência (triagem).
+- **Botão de Emergência:** Se o sistema detetar gravidade, oferece a opção de localizar o hospital mais próximo.
+
+### ⚙️ Utilitários e Segundo Plano (`utils` & `services`)
+- **`AlarmScheduler`:** Centraliza a lógica de agendamento de alarmes no sistema Android.
+- **`AlarmReceiver`:** Ouve os eventos do sistema e dispara notificações sonoras e visuais.
+- **`NotificationHelper`:** Gera as notificações push que aparecem no telemóvel.
+
+## 4. Modelos de Dados (`data/model`)
+- **`User` / `Perfil`:** Informação básica do utilizador (nome, foto, contacto).
+- **`Medication`:** Nome, horários (comma-separated), dosagem, notas e estado.
+- **`Appointment`:** Médico, data, hora e notas da consulta.
+
+## 5. Fluxo de Dados
+1. O utilizador interage com o **Fragment** (Interface).
+2. O **ViewModel** processa os dados e comunica com o **Firebase Firestore**.
+3. O Firestore atualiza os dados na nuvem em tempo real (SnapshotListeners).
+4. O **ViewModel** recebe a atualização e reflete as mudanças na UI automaticamente através de **LiveData**.
+
+---
+*Este guia serve como referência para novos programadores ou para auditoria do projeto.*
diff --git a/documentacao_projecto/guia_utilizacao_app.md b/documentacao_projecto/guia_utilizacao_app.md
new file mode 100644
index 0000000..243ac7d
--- /dev/null
+++ b/documentacao_projecto/guia_utilizacao_app.md
@@ -0,0 +1,31 @@
+# Guia de Utilização - Cuida+
+
+Este documento explica como o utilizador interage com as principais áreas da aplicação.
+
+## 1. Primeiros Passos
+1. **Registo:** Criar conta com email e password.
+2. **Login:** Aceder ao ecrã principal (Home).
+3. **Perfil:** Editar o nome e a foto de perfil.
+
+## 2. Gerir Medikamentos 💊
+- No ecrã de **Medicação**, clica no botão "+" circular.
+- Começa por escrever o nome; o sistema sugere nomes reais de medicamentos.
+- Adiciona o **primeiro horário**. Se precisares de tomar mais do que uma vez por dia, clica em "Adicionar" para selecionar novos horários.
+- Escolhe a **Via de Administração** (Oral, Tópica ou Inalatória).
+- Grava. Vais receber notificações sonoras em cada horário escolhido.
+
+## 3. Agenda & Consultas 📅
+- Navega para o separador **Agenda**.
+- Vê as consultas que já tens marcadas.
+- Para marcar uma nova, clica em "Agendar Consulta".
+- Escolhe o médico da lista de profissionais disponíveis no Firebase.
+- Seleciona uma data e um horário livre.
+
+## 4. Triagem IA SNS24 🤖
+- Se não te sentires bem, vai ao separador **SNS24**.
+- Escreve ao que sentes (ex: "estou com uma dor de cabeça muito forte e febre").
+- A IA do **Gemini** vai avaliar e dizer o que deves fazer.
+- Se for urgente, aparecerá um botão para te guiar ao **Hospital mais próximo**.
+
+---
+*Manual do Utilizador - Cuida+*
diff --git a/documentacao_projecto/historico_alteracoes.md b/documentacao_projecto/historico_alteracoes.md
new file mode 100644
index 0000000..8a5058c
--- /dev/null
+++ b/documentacao_projecto/historico_alteracoes.md
@@ -0,0 +1,61 @@
+# Histórico de Alterações e Progresso do Projeto - Cuida+
+
+Este documento detalha todas as principais funcionalidades e correções implementadas no projeto Cuida+ pelo assistente de IA.
+
+## Sumário
+1. [Agendamento de Múltiplos Horários para Medicamentos](#1-agendamento-de-múltiplos-horários-para-medicamentos)
+2. [Pesquisa de Medicamentos com Autocomplete (Firebase)](#2-pesquisa-de-medicamentos-com-autocomplete-firebase)
+3. [Integração de Médicos do Firebase](#3-integração-de-médicos-do-firebase)
+4. [Refatoração do Ecrã Principal (Home)](#4-refatoração-do-ecrã-principal-home)
+5. [Correções Diversas (Login, Email, Crashes)](#5-correções-diversas-login-email-crashes)
+6. [Triage AI - Ajustes de Rigidez](#6-triage-ai-ajustes-de-rigidez)
+7. [Fase Final: Melhorias Estratégicas e Polimento](#7-fase-final-melhorias-estratégicas-e-polimento)
+
+---
+
+### 1. Agendamento de Múltiplos Horários para Medicamentos
+**Data:** 15 de Abril de 2026
+- **Funcionalidade:** Agora é possível escolher mais de um horário para o mesmo medicamento.
+- **Implementação:**
+ - Uso de `ChipGroup` no layout `dialog_add_medication.xml` para exibir os horários.
+ - No `MedicationDialog.java`, implementamos a gestão de uma lista de horários persistida como uma String separada por vírgulas.
+ - Atualização do `MedicationFragment.java` para agendar alarmes individuais para cada horário, garantindo que todos sejam disparados.
+ - Gestão automática de cancelamento de alarmes ao editar horários ou eliminar medicamentos.
+
+### 2. Pesquisa de Medicamentos com Autocomplete (Firebase)
+- **Funcionalidade:** Ao digitar o nome de um medicamento, a app sugere nomes de medicamentos reais vindos do Firebase.
+- **Implementação:**
+ - Ligação ao Realtime Database (Firebase).
+ - Filtragem em tempo real enquanto o utilizador escreve.
+ - População automática da dosagem sugerida nas notas.
+
+### 3. Integração de Médicos do Firebase
+- **Funcionalidade:** Substituição de médicos estáticos pelos médicos registados no Firebase com a role 'medico'.
+- **Implementação:** Consulta ao banco de dados para listar apenas profissionais autorizados na agenda e na listagem.
+
+### 4. Refatoração do Ecrã Principal (Home)
+- **Funcionalidade:** Melhoria da navegação e layout.
+- **Implementação:**
+ - Saudação personalizada ("Olá, [Nome]!").
+ - Centralização da visualização da agenda como foco principal.
+ - Reordenação da barra de navegação inferior (Agenda no meio).
+ - Remoção de headers desnecessários para um visual mais premium.
+
+### 5. Correções Diversas (Login, Email, Crashes)
+- **Email de Password:** Correção do fluxo onde os emails de recuperação não estavam a chegar, garantindo o correto envio via Firebase Auth.
+- **Crashes:** Identificação e correção de null pointers no carregamento de dados do utilizador.
+
+### 6. Triage AI - Ajustes de Rigidez
+- **Funcionalidade:** Ajuste no tom de voz da IA e deteção de sintomas graves.
+- **Implementação:** Redução de respostas prolixas, tornando-as mais diretas. Adição de um gatilho para mostrar o botão "Encontrar Hospital Próximo" ao detetar palavras de dor intensa.
+
+### 7. Fase Final: Melhorias Estratégicas e Polimento
+**Data:** 15 de Abril de 2026
+- **Login Biométrico:** Integração com a biblioteca `androidx.biometric`. O utilizador agora pode autenticar-se em 1 segundo com impressões digitais ou face ID após o primeiro login manual.
+- **Relatório PDF de Saúde:** No ecrã de Perfil, adicionamos um exportador que gera um documento A4 com todos os dados de saúde do utilizador, permitindo o partilha direta via Intent.
+- **Persistência Offline Firestore:** Agora a app permite ver o histórico de consultas e medicamentos sem sinal de internet, através de cache inteligente.
+- **Histórico de Triagens IA:** Implementamos uma nova funcionalidade que guarda cada resposta da triagem IA do SNS24 no Firestore na coleção `triagens`.
+- **Notificações de Consultas:** Melhoramos o `ScheduleViewModel` para disparar lembretes 24 horas e 30 minutos antes das consultas médicas.
+
+---
+*Este documento foi gerado automaticamente pelo assistente de IA para documentar o progresso do desenvolvimento.*
diff --git a/documentacao_projecto/manual_tecnico_setup.md b/documentacao_projecto/manual_tecnico_setup.md
new file mode 100644
index 0000000..91ed672
--- /dev/null
+++ b/documentacao_projecto/manual_tecnico_setup.md
@@ -0,0 +1,33 @@
+# Manual Técnico de Setup e Configuração - Cuida+
+
+Este documento explica como configurar o ambiente de desenvolvimento e executar a aplicação Cuida+.
+
+## 1. Requisitos do Sistema
+- **Android Studio:** Jellyfish ou superior recomendado.
+- **Java JDK:** 17 ou superior.
+- **Firebase:** Conta configurada com `google-services.json` (já incluído no projeto).
+
+## 2. Bibliotecas Principais (`app/build.gradle`)
+As dependências críticas são:
+- **Firebase:** `firebase-auth`, `firebase-firestore`, `firebase-database`.
+- **Google Generative AI:** `generativeai-java` (para a integração com o Gemini).
+- **Material Design:** `com.google.android.material:material`.
+- **Navigation:** `androidx.navigation:navigation-fragment`, `androidx.navigation:navigation-ui`.
+
+## 3. Configuração do Gemini AI
+Para que o chat de triagem funcione, é necessária uma API Key do Google Gemini Pro.
+- **Classe:** `com.example.cuida.services.Gemini`.
+- **Atenção:** Certifique-se de que a chave está protegida e não carregada para repositórios públicos.
+
+## 4. Como Correr o Projeto
+1. Abre o Android Studio.
+2. Faz o **Sync Project with Gradle Files**.
+3. Escolhe um emulador ou dispositivo físico com Android 8.0+.
+4. Prime **Run (Play)**.
+
+## 5. Passos para Debug
+- Utiliza o **Logcat** filtrando por "Firebase" ou "MedicationViewModel" para ver os logs de sincronização.
+- Se o alarme não disparar, verifica o **App Info -> Battery** e garante que a app tem permissão para "Ignorar Otimizações de Bateria".
+
+---
+*Manual Amministrativo / Técnico - Cuida+*
diff --git a/documentacao_projecto/mapa_ficheiros_completo.md b/documentacao_projecto/mapa_ficheiros_completo.md
new file mode 100644
index 0000000..7246566
--- /dev/null
+++ b/documentacao_projecto/mapa_ficheiros_completo.md
@@ -0,0 +1,53 @@
+# Mapa de Ficheiros e Funções - Cuida+
+
+Este documento é um inventário completo de todos os ficheiros da aplicação e as suas funções específicas.
+
+## 📦 Estrutura de Pastas e Ficheiros
+
+### 🔵 Interface de Utilizador (`ui/`)
+- **`auth/`**: Gestão de entrada e registo.
+ - `LoginActivity.java`: Ecrã de login com suporte para biometria (Fingerprint/Face ID).
+ - `RegisterActivity.java`: Criação de nova conta.
+ - `ForgotPasswordActivity.java`: Solicitação de recuperação de password.
+ - `ResetPasswordActivity.java`: Definição de nova password após email.
+- **`home/`**: Centro de informações.
+ - `HomeFragment.java`: Exibe saudação, foto de perfil e o próximo medicamento agendado.
+- **`medication/`**: Gestão completa de remédios.
+ - `MedicationFragment.java`: Lista todos os medicamentos e gere os alarmes.
+ - `MedicationDialog.java`: Janela para adicionar/editar (com múltiplos horários e pesquisa Firebase).
+ - `MedicationViewModel.java`: Faz a ponte entre o Firestore e a interface.
+ - `MedicationAdapter.java`: Desenha cada item da lista de medicação.
+ - `ComprimidoRecyclerAdapter.java`: Gere a lista de sugestões de nomes de medicamentos.
+- **`appointments/`**: Lista de consultas médicos.
+ - `AppointmentsFragment.java`: Visualização da agenda pessoal do utilizador.
+ - `AppointmentsViewModel.java`: Gere os dados das consultas.
+ - `AppointmentAdapter.java`: Desenha o item de cada consulta.
+- **`schedule/`**: Agendamento de novas consultas.
+ - `ScheduleAppointmentFragment.java`: Escolha de médico e horário.
+ - `ScheduleViewModel.java`: Verifica slots disponíveis e agenda lembretes de consulta (24h/30m).
+ - `TimeSlotAdapter.java`: Lista as horas de marcação disponíveis.
+- **`profile/`**: Perfil do utilizador.
+ - `ProfileFragment.java`: Permite mudar foto, dados e exportar relatórios de saúde (PDF).
+- **`sns24/`**: Triagem Inteligente.
+ - `Sns24Fragment.java`: Chat IA para avaliação e registo de histórico de triagens no Firestore.
+
+### 🟢 Dados e Modelos (`data/model/`)
+- `User.java`: Representa a conta do utilizador.
+- `Medication.java`: Dados de medicação (nome, horários, dosagem).
+- `Appointment.java`: Dados da consulta (médico, data, hora).
+- `Comprimido.java`: Objeto simples para os nomes sugeridos na pesquisa.
+
+### 🟠 Serviços e Utilidades (`services/` & `utils/`)
+- `AlarmScheduler.java`: Lógica centralizada para marcar alarmes no sistema Android.
+- `AlarmReceiver.java`: O código que corre quando o alarme dispara.
+- `NotificationHelper.java`: Cria o canal e a mensagem de notificação.
+- `Gemini.java`: Integração com a Google AI para o diagnóstico inteligente.
+
+### 🔴 Configuração (`/`)
+- `MainActivity.java`: Contentor principal, gere navegação e ativa a persistência offline do Firestore.
+- `AndroidManifest.xml`: Registo de atividades, permissões (Alarme, Localização) e providers.
+- `build.gradle`: Bibliotecas (Firebase, Biometrics, IA).
+- `res/xml/file_paths.xml`: Configuração de segurança para partilha de ficheiros (PDF).
+
+---
+*Mapa Completo da Aplicação - Cuida+ (Atualizado Abril 2026)*
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..250afc3
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,12 @@
+android.useAndroidX=true
+android.enableJetifier=true
+android.defaults.buildfeatures.resvalues=true
+android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
+android.enableAppCompileTimeRClass=false
+android.usesSdkInManifest.disallowed=false
+android.uniquePackageNames=false
+android.dependency.useConstraints=true
+android.r8.strictFullModeForKeepRules=false
+android.r8.optimizedResourceShrinking=false
+android.builtInKotlin=false
+android.newDsl=false
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..980502d
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..c61a118
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..faf9300
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,251 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
+APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# 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 ;; #(
+ MSYS* | 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
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC2039,SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# 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"'
+
+# Collect all arguments for the java command:
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
+# and any embedded shellness will be escaped.
+# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
+# treated as '${Hostname}' itself on the command line.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..9d21a21
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,94 @@
+@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
+@rem SPDX-License-Identifier: Apache-2.0
+@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=.
+@rem This is normally unused
+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% equ 0 goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo. 1>&2
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
+echo. 1>&2
+echo Please set the JAVA_HOME variable in your environment to match the 1>&2
+echo location of your Java installation. 1>&2
+
+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% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/java_pid1336.hprof b/java_pid1336.hprof
new file mode 100644
index 0000000..e2bcd26
Binary files /dev/null and b/java_pid1336.hprof differ
diff --git a/java_pid14727.hprof b/java_pid14727.hprof
new file mode 100644
index 0000000..87790f2
Binary files /dev/null and b/java_pid14727.hprof differ
diff --git a/java_pid1614.hprof b/java_pid1614.hprof
new file mode 100644
index 0000000..5160e5e
Binary files /dev/null and b/java_pid1614.hprof differ
diff --git a/java_pid1755.hprof b/java_pid1755.hprof
new file mode 100644
index 0000000..cb4a548
Binary files /dev/null and b/java_pid1755.hprof differ
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..298930b
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,16 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+rootProject.name = "Cuida"
+include ':app'