diff --git a/.gradle/9.3.1/checksums/checksums.lock b/.gradle/9.3.1/checksums/checksums.lock
index 5f22b1f..d462a6f 100644
Binary files a/.gradle/9.3.1/checksums/checksums.lock and b/.gradle/9.3.1/checksums/checksums.lock differ
diff --git a/.gradle/9.3.1/checksums/md5-checksums.bin b/.gradle/9.3.1/checksums/md5-checksums.bin
index 30f5cae..d5018c3 100644
Binary files a/.gradle/9.3.1/checksums/md5-checksums.bin and b/.gradle/9.3.1/checksums/md5-checksums.bin differ
diff --git a/.gradle/9.3.1/checksums/sha1-checksums.bin b/.gradle/9.3.1/checksums/sha1-checksums.bin
index 1b5df63..7898cc7 100644
Binary files a/.gradle/9.3.1/checksums/sha1-checksums.bin and b/.gradle/9.3.1/checksums/sha1-checksums.bin differ
diff --git a/.gradle/9.3.1/fileHashes/fileHashes.bin b/.gradle/9.3.1/fileHashes/fileHashes.bin
index 47e2735..f0d86bc 100644
Binary files a/.gradle/9.3.1/fileHashes/fileHashes.bin and b/.gradle/9.3.1/fileHashes/fileHashes.bin differ
diff --git a/.gradle/9.3.1/fileHashes/fileHashes.lock b/.gradle/9.3.1/fileHashes/fileHashes.lock
index 2e1cc63..3309572 100644
Binary files a/.gradle/9.3.1/fileHashes/fileHashes.lock and b/.gradle/9.3.1/fileHashes/fileHashes.lock differ
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
deleted file mode 100644
index c96dea6..0000000
--- a/.idea/caches/deviceStreaming.xml
+++ /dev/null
@@ -1,1610 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index c794b1f..9ae987f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -45,6 +45,7 @@ dependencies {
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
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ea0b8f2..fcd57eb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,6 +12,7 @@
+
+
+
+
diff --git a/app/src/main/java/com/example/cuida/MainActivity.java b/app/src/main/java/com/example/cuida/MainActivity.java
index 597b4f5..81eee15 100644
--- a/app/src/main/java/com/example/cuida/MainActivity.java
+++ b/app/src/main/java/com/example/cuida/MainActivity.java
@@ -13,8 +13,10 @@ 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 {
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
index c999f8c..804bf0e 100644
--- a/app/src/main/java/com/example/cuida/ui/auth/LoginActivity.java
+++ b/app/src/main/java/com/example/cuida/ui/auth/LoginActivity.java
@@ -12,6 +12,10 @@ 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
@@ -56,6 +60,69 @@ public class LoginActivity extends AppCompatActivity {
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.biometric_button.setVisibility(android.view.View.VISIBLE);
+ binding.biometric_button.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) {
+ binding.loginButton.setEnabled(false);
+ binding.loginButton.setText("A entrar...");
+
+ mAuth.signInWithEmailAndPassword(email, pass)
+ .addOnCompleteListener(this, task -> {
+ if (task.isSuccessful()) {
+ startActivity(new Intent(LoginActivity.this, MainActivity.class));
+ finish();
+ } else {
+ binding.loginButton.setEnabled(true);
+ binding.loginButton.setText(R.string.login_button);
+ Toast.makeText(this, "Erro no login biométrico. Use a password.", Toast.LENGTH_SHORT).show();
+ }
+ });
}
private void login() {
@@ -108,6 +175,12 @@ public class LoginActivity extends AppCompatActivity {
prefs.edit().putBoolean("is_logged_in", true).apply();
prefs.edit().putBoolean("remember_me", rememberMe).apply();
+
+ // Save for biometrics if remember me is on
+ if (rememberMe) {
+ prefs.edit().putString("saved_email", email).apply();
+ prefs.edit().putString("saved_pass", password).apply();
+ }
if (fetchTask.isSuccessful() && fetchTask.getResult() != null
&& fetchTask.getResult().exists()) {
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
index 08558e8..6b30cdb 100644
--- a/app/src/main/java/com/example/cuida/ui/medication/MedicationDialog.java
+++ b/app/src/main/java/com/example/cuida/ui/medication/MedicationDialog.java
@@ -31,6 +31,10 @@ 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 {
@@ -43,7 +47,8 @@ public class MedicationDialog extends DialogFragment {
private EditText editNotes;
private android.widget.RadioButton radioOral, radioTopical, radioInhalatory;
private android.widget.RadioGroup radioGroupRoute;
- private TextView textTime;
+ private ChipGroup chipGroupTimes;
+ private List selectedTimes = new ArrayList<>();
private Medication medicationToEdit;
private OnMedicationSaveListener listener;
private OnMedicationDeleteListener deleteListener;
@@ -78,7 +83,8 @@ public class MedicationDialog extends DialogFragment {
editName = view.findViewById(R.id.edit_med_name);
recyclerResults = view.findViewById(R.id.recycler_search_results);
editNotes = view.findViewById(R.id.edit_med_notes);
- textTime = view.findViewById(R.id.text_med_time);
+ 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);
@@ -124,12 +130,18 @@ public class MedicationDialog extends DialogFragment {
radioInhalatory = view.findViewById(R.id.radio_inhalatory);
// Set up TimePicker
- textTime.setOnClickListener(v -> showTimePicker());
+ btnAddTime.setOnClickListener(v -> showTimePicker());
if (medicationToEdit != null) {
editName.setText(medicationToEdit.name);
editNotes.setText(medicationToEdit.notes);
- textTime.setText(medicationToEdit.time);
+ 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) {
@@ -146,14 +158,23 @@ public class MedicationDialog extends DialogFragment {
builder.setTitle("Adicionar Medicamento");
// Default time to current time
Calendar cal = Calendar.getInstance();
- updateTimeLabel(cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
+ 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();
- String time = textTime.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";
@@ -227,25 +248,32 @@ public class MedicationDialog extends DialogFragment {
int hour = cal.get(Calendar.HOUR_OF_DAY);
int minute = cal.get(Calendar.MINUTE);
- if (medicationToEdit != null) {
- try {
- String[] parts = medicationToEdit.time.split(":");
- hour = Integer.parseInt(parts[0]);
- minute = Integer.parseInt(parts[1]);
- } catch (Exception e) {
- // Use current time if parsing fails
- }
- }
-
TimePickerDialog timePickerDialog = new TimePickerDialog(getContext(),
- (view, hourOfDay, minute1) -> updateTimeLabel(hourOfDay, minute1),
+ (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 updateTimeLabel(int hourOfDay, int minute) {
- String time = String.format(Locale.getDefault(), "%02d:%02d", hourOfDay, minute);
- textTime.setText(time);
+ 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() {
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
index e3a10e8..c98b8c6 100644
--- a/app/src/main/java/com/example/cuida/ui/medication/MedicationFragment.java
+++ b/app/src/main/java/com/example/cuida/ui/medication/MedicationFragment.java
@@ -58,36 +58,55 @@ public class MedicationFragment extends Fragment {
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);
}
- try {
- String[] timeParts = medicationToSave.time.split(":");
- int hour = Integer.parseInt(timeParts[0]);
- int minute = Integer.parseInt(timeParts[1]);
+ 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);
+ 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);
+ 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();
}
-
- String title = "Hora do Medicamento";
- String msg = "É hora de tomar: " + medicationToSave.name + " (" + medicationToSave.dosage + ")";
- com.example.cuida.utils.AlarmScheduler.scheduleAlarm(
- requireContext(),
- calendar.getTimeInMillis(),
- title,
- msg,
- medicationToSave.name.hashCode());
- } catch (Exception e) {
- e.printStackTrace();
}
dialog.dismiss();
@@ -96,13 +115,18 @@ public class MedicationFragment extends Fragment {
dialog.setDeleteListener(medicationToDelete -> {
medicationViewModel.delete(medicationToDelete);
- // Cancel alarm if configured
- try {
- com.example.cuida.utils.AlarmScheduler.cancelAlarm(
- requireContext(),
- medicationToDelete.name.hashCode());
- } catch (Exception e) {
- e.printStackTrace();
+ // 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();
+ }
+ }
}
});
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
index 031011a..03c6d7f 100644
--- a/app/src/main/java/com/example/cuida/ui/profile/ProfileFragment.java
+++ b/app/src/main/java/com/example/cuida/ui/profile/ProfileFragment.java
@@ -11,6 +11,18 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.Toast;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.pdf.PdfDocument;
+import androidx.core.content.FileProvider;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import com.example.cuida.data.model.Medication;
+import com.example.cuida.data.model.Appointment;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
@@ -47,6 +59,8 @@ public class ProfileFragment extends Fragment {
getActivity().finish();
});
+ binding.buttonExportReport.setOnClickListener(v -> exportMonthlyReport());
+
return binding.getRoot();
}
@@ -285,9 +299,119 @@ public class ProfileFragment extends Fragment {
dialog.show();
}
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- binding = null;
+ private void exportMonthlyReport() {
+ if (currentUser == null || auth.getCurrentUser() == null) return;
+ String userId = auth.getCurrentUser().getUid();
+
+ Toast.makeText(getContext(), "A gerar relatório...", Toast.LENGTH_SHORT).show();
+
+ db.collection("medicamentos").whereEqualTo("userId", userId).get()
+ .addOnSuccessListener(medSnapshots -> {
+ List meds = new ArrayList<>();
+ for (com.google.firebase.firestore.DocumentSnapshot doc : medSnapshots) {
+ meds.add(doc.toObject(Medication.class));
+ }
+
+ db.collection("consultas").whereEqualTo("userId", userId).get()
+ .addOnSuccessListener(apptSnapshots -> {
+ List appts = new ArrayList<>();
+ for (com.google.firebase.firestore.DocumentSnapshot doc : apptSnapshots) {
+ appts.add(doc.toObject(Appointment.class));
+ }
+
+ generateAndSharePDF(meds, appts);
+ });
+ });
+ }
+
+ private void generateAndSharePDF(List meds, List appts) {
+ PdfDocument document = new PdfDocument();
+ PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(595, 842, 1).create(); // A4 size
+ PdfDocument.Page page = document.startPage(pageInfo);
+ Canvas canvas = page.getCanvas();
+ Paint paint = new Paint();
+
+ int y = 50;
+ paint.setTextSize(24);
+ paint.setFakeBoldText(true);
+ canvas.drawText("Relatório Mensal de Saúde - Cuida+", 50, y, paint);
+
+ y += 40;
+ paint.setTextSize(14);
+ paint.setFakeBoldText(false);
+ canvas.drawText("Utilizador: " + currentUser.name, 50, y, paint);
+ y += 20;
+ canvas.drawText("Data: " + java.text.DateFormat.getDateTimeInstance().format(new java.util.Date()), 50, y, paint);
+
+ y += 40;
+ paint.setFakeBoldText(true);
+ paint.setTextSize(18);
+ canvas.drawText("Medicação Atual:", 50, y, paint);
+ y += 25;
+ paint.setFakeBoldText(false);
+ paint.setTextSize(12);
+
+ if (meds.isEmpty()) {
+ canvas.drawText("Nenhuma medicação registada.", 70, y, paint);
+ y += 20;
+ } else {
+ for (Medication m : meds) {
+ canvas.drawText("- " + m.name + " (" + m.dosage + ") - Horários: " + m.time, 70, y, paint);
+ y += 20;
+ if (m.notes != null && !m.notes.isEmpty()) {
+ paint.setColor(Color.GRAY);
+ canvas.drawText(" Notas: " + m.notes, 70, y, paint);
+ paint.setColor(Color.BLACK);
+ y += 20;
+ }
+ if (y > 750) break; // Simple page break check
+ }
+ }
+
+ y += 20;
+ paint.setFakeBoldText(true);
+ paint.setTextSize(18);
+ canvas.drawText("Consultas Agendadas:", 50, y, paint);
+ y += 25;
+ paint.setFakeBoldText(false);
+ paint.setTextSize(12);
+
+ if (appts.isEmpty()) {
+ canvas.drawText("Nenhuma consulta registada.", 70, y, paint);
+ } else {
+ for (Appointment a : appts) {
+ canvas.drawText("- " + a.type + " em " + a.date + " às " + a.time, 70, y, paint);
+ y += 20;
+ canvas.drawText(" Motivo: " + a.reason, 70, y, paint);
+ y += 25;
+ if (y > 800) break;
+ }
+ }
+
+ document.finishPage(page);
+
+ File file = new File(requireContext().getCacheDir(), "Relatorio_Saude_Cuida.pdf");
+ try {
+ document.writeTo(new FileOutputStream(file));
+ document.close();
+ shareFile(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ Toast.makeText(getContext(), "Erro ao gerar PDF", Toast.LENGTH_SHORT).show();
+ document.close();
+ }
+ }
+
+ private void shareFile(File file) {
+ android.net.Uri uri = FileProvider.getUriForFile(requireContext(),
+ requireContext().getPackageName() + ".fileprovider", file);
+
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("application/pdf");
+ intent.putExtra(Intent.EXTRA_SUBJECT, "Relatório de Saúde Cuida+");
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+ startActivity(Intent.createChooser(intent, "Partilhar Relatório"));
}
}
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
index e29b28e..c089292 100644
--- a/app/src/main/java/com/example/cuida/ui/schedule/ScheduleViewModel.java
+++ b/app/src/main/java/com/example/cuida/ui/schedule/ScheduleViewModel.java
@@ -199,20 +199,25 @@ public class ScheduleViewModel extends AndroidViewModel {
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
- Calendar calendar = Calendar.getInstance();
- calendar.set(year, month, day, hour, minute, 0);
- // 1 hour before
- calendar.add(Calendar.HOUR_OF_DAY, -1);
+ Calendar baseCal = Calendar.getInstance();
+ baseCal.set(year, month, day, hour, minute, 0);
- if (calendar.getTimeInMillis() > System.currentTimeMillis()) {
- String title = "Lembrete de Consulta";
- String msg = "A sua consulta é daqui a 1 hora (" + time + ").";
- AlarmScheduler.scheduleAlarm(
- getApplication(),
- calendar.getTimeInMillis(),
- title,
- msg,
- (date + time).hashCode());
+ // 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();
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
index 45dd434..60af1ea 100644
--- a/app/src/main/java/com/example/cuida/ui/sns24/Sns24Fragment.java
+++ b/app/src/main/java/com/example/cuida/ui/sns24/Sns24Fragment.java
@@ -13,6 +13,10 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.example.cuida.databinding.FragmentSns24Binding;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.FirebaseFirestore;
+import java.util.HashMap;
+import java.util.Map;
public class Sns24Fragment extends Fragment {
@@ -85,6 +89,9 @@ public class Sns24Fragment extends Fragment {
startActivity(mapIntent);
});
}
+
+ // Guardar no Histórico do Firestore
+ saveTriageToHistory(symptoms, displayResult);
});
}
}
@@ -101,6 +108,26 @@ public class Sns24Fragment extends Fragment {
});
}
+ private void saveTriageToHistory(String symptoms, String result) {
+ FirebaseAuth auth = FirebaseAuth.getInstance();
+ if (auth.getCurrentUser() == null) return;
+
+ Map triage = new HashMap<>();
+ triage.put("userId", auth.getCurrentUser().getUid());
+ triage.put("symptoms", symptoms);
+ triage.put("result", result);
+ triage.put("timestamp", com.google.firebase.Timestamp.now());
+
+ FirebaseFirestore.getInstance().collection("triagens")
+ .add(triage)
+ .addOnSuccessListener(documentReference -> {
+ // Histórico guardado com sucesso
+ })
+ .addOnFailureListener(e -> {
+ // Falha ao guardar histórico
+ });
+ }
+
@Override
public void onDestroyView() {
super.onDestroyView();
diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml
index 0dbc2e7..e0c0ec7 100644
--- a/app/src/main/res/layout/activity_login.xml
+++ b/app/src/main/res/layout/activity_login.xml
@@ -86,7 +86,18 @@
android:text="@string/login_button"
android:textSize="16sp"
android:textStyle="bold"
- android:layout_marginBottom="16dp"/>
+ android:layout_marginBottom="8dp"/>
+
+
+
-
-
-
-
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_marginBottom="4dp">
+
+
-
-
+
+
+
+
-
+
+
+
+
+
+
+
diff --git a/build/reports/problems/problems-report.html b/build/reports/problems/problems-report.html
index 0d7235d..5073103 100644
--- a/build/reports/problems/problems-report.html
+++ b/build/reports/problems/problems-report.html
@@ -646,7 +646,7 @@ code + .copy-button {
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/documentacao_projecto/plano_melhorias_futuras.md b/documentacao_projecto/plano_melhorias_futuras.md
new file mode 100644
index 0000000..1a64dc5
--- /dev/null
+++ b/documentacao_projecto/plano_melhorias_futuras.md
@@ -0,0 +1,32 @@
+# Plano de Próximas Melhorias - Cuida+ (Estado: Concluído ✅)
+
+Todas as melhorias planeadas foram implementadas com sucesso!
+
+## 1. Notificações de Consultas 🔔 [CONCLUÍDO]
+- **Estado:** Implementado no `ScheduleViewModel`.
+- **Funcionalidade:** Agora a app agenda automaticamente dois alarmes para cada consulta: um 24 horas antes e outro 30 minutos antes da hora marcada.
+
+## 2. Histórico de Triagens 🤖 [CONCLUÍDO]
+- **Estado:** Implementado no `Sns24Fragment`.
+- **Funcionalidade:** Cada análise de sintomas feita com o Gemini IA é agora guardada na coleção `triagens` no Firestore, incluindo o diagnóstico e a data.
+
+## 3. Modo Offline 📶 [CONCLUÍDO]
+- **Estado:** Ativado no `MainActivity`.
+- **Funcionalidade:** A persistência offline do Firestore foi ativada. A app agora guarda em cache local os medicamentos e consultas, permitindo visualizá-los mesmo sem internet.
+
+## 4. Partilha de Relatório 📊 [CONCLUÍDO]
+- **Estado:** Implementado no `ProfileFragment`.
+- **Funcionalidade:** Adicionado botão "Exportar Relatório Mensal" que gera um PDF com o resumo de toda a medicação e consultas, permitindo partilhar via Email ou WhatsApp com o médico.
+
+## 5. Login Biométrico 🎨 [CONCLUÍDO]
+- **Estado:** Implementado no `LoginActivity`.
+- **Funcionalidade:** Suporte para impressões digitais e reconhecimento facial adicionado. O utilizador pode entrar na conta instantaneamente sem digitar a password (desde que tenha feito login com sucesso anteriormente).
+
+---
+### Novas Ideias para o Futuro (V2):
+1. **Gráficos de Aderência:** Visualizar estatísticas de quantos remédios foram tomados vs. esquecidos.
+2. **Integração com Google Calendar:** Sincronizar as consultas da app com o calendário do telemóvel.
+3. **Modo Familiar:** Permitir que um cuidador veja os dados de um idoso (partilha de dados).
+
+---
+*Plano de Evolução - Cuida+ (Atualizado em Abril 2026)*