ja meti o droplist nos medicamentos
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
1610
.idea/caches/deviceStreaming.xml
generated
1610
.idea/caches/deviceStreaming.xml
generated
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||
|
||||
<application
|
||||
android:name=".CuidaApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
@@ -58,6 +59,16 @@
|
||||
|
||||
<receiver android:name=".services.AlarmReceiver" android:exported="false" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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<String> 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() {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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<Medication> 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<Appointment> appts = new ArrayList<>();
|
||||
for (com.google.firebase.firestore.DocumentSnapshot doc : apptSnapshots) {
|
||||
appts.add(doc.toObject(Appointment.class));
|
||||
}
|
||||
|
||||
generateAndSharePDF(meds, appts);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void generateAndSharePDF(List<Medication> meds, List<Appointment> 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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<String, Object> 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();
|
||||
|
||||
@@ -86,7 +86,18 @@
|
||||
android:text="@string/login_button"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/biometric_button"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Login com Biometria"
|
||||
android:icon="@android:drawable/ic_dialog_info"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/forgot_password_link"
|
||||
|
||||
@@ -29,36 +29,38 @@
|
||||
android:inputType="textCapWords" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<TextView
|
||||
android:text="Horário"
|
||||
android:layout_width="wrap_content"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<!-- O resto do conteúdo do diálogo continua aqui em baixo -->
|
||||
</LinearLayout>
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="4dp">
|
||||
|
||||
<TextView
|
||||
android:text="Horários"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_search_results"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginTop="60dp"
|
||||
android:visibility="gone"
|
||||
android:elevation="8dp"
|
||||
android:background="@drawable/bg_search_results" />
|
||||
</FrameLayout>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_add_time"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Adicionar"
|
||||
android:padding="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/chip_group_times"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:padding="4dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_med_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="08:00"
|
||||
android:textSize="18sp"
|
||||
android:padding="12dp"
|
||||
android:background="#E0E0E0"
|
||||
android:gravity="center"
|
||||
android:layout_marginBottom="16dp"/>
|
||||
|
||||
<TextView
|
||||
android:text="Via de Administração"
|
||||
|
||||
@@ -115,6 +115,17 @@
|
||||
android:layout_marginBottom="16dp"
|
||||
android:backgroundTint="@color/secondary_color"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_export_report"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:text="Exportar Relatório Mensal"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:strokeColor="@color/secondary_color"
|
||||
android:textColor="@color/secondary_color"/>
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_logout"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
|
||||
5
app/src/main/res/xml/file_paths.xml
Normal file
5
app/src/main/res/xml/file_paths.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path name="my_cache" path="." />
|
||||
<external-path name="my_external" path="." />
|
||||
</paths>
|
||||
@@ -646,7 +646,7 @@ code + .copy-button {
|
||||
<script type="text/javascript">
|
||||
function configurationCacheProblems() { return (
|
||||
// begin-report-data
|
||||
{"diagnostics":[{"locations":[{"path":"/Users/230405/Desktop/papcuida/app/build.gradle","line":7}],"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.3.1/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('namespace = <value>') instead."}]]},{"locations":[{"path":"/Users/230405/Desktop/papcuida/app/build.gradle","line":34}],"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.3.1/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('viewBinding = <value>') instead."}]]}],"problemsReport":{"totalProblemCount":2,"buildName":"Cuida","requestedTasks":":app:assembleDebug","documentationLink":"https://docs.gradle.org/9.3.1/userguide/reporting_problems.html","documentationLinkCaption":"Problem report","summaries":[]}}
|
||||
{"diagnostics":[{"locations":[{"path":"/Users/230405/Desktop/papcuida/app/build.gradle","line":7}],"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.3.1/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('namespace = <value>') instead."}]]},{"locations":[{"path":"/Users/230405/Desktop/papcuida/app/build.gradle","line":34}],"problem":[{"text":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"severity":"WARNING","problemDetails":[{"text":"This is scheduled to be removed in Gradle 10."}],"contextualLabel":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated.","documentationLink":"https://docs.gradle.org/9.3.1/userguide/upgrading_version_8.html#groovy_space_assignment_syntax","problemId":[{"name":"deprecation","displayName":"Deprecation"},{"name":"properties-should-be-assigned-using-the-propname-value-syntax-setting-a-property-via-the-gradle-generated-propname-value-or-propname-value-syntax-in-groovy-dsl","displayName":"Properties should be assigned using the 'propName = value' syntax. Setting a property via the Gradle-generated 'propName value' or 'propName(value)' syntax in Groovy DSL has been deprecated."}],"solutions":[[{"text":"Use assignment ('viewBinding = <value>') instead."}]]}],"problemsReport":{"totalProblemCount":2,"buildName":"Cuida","requestedTasks":"","documentationLink":"https://docs.gradle.org/9.3.1/userguide/reporting_problems.html","documentationLinkCaption":"Problem report","summaries":[]}}
|
||||
// end-report-data
|
||||
);}
|
||||
</script>
|
||||
|
||||
92
documentacao_projecto/backups_codigo/HomeFragment.java
Normal file
92
documentacao_projecto/backups_codigo/HomeFragment.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
85
documentacao_projecto/backups_codigo/MedicationAdapter.java
Normal file
85
documentacao_projecto/backups_codigo/MedicationAdapter.java
Normal file
@@ -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<MedicationAdapter.MedicationViewHolder> {
|
||||
|
||||
private List<Medication> 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<Medication> 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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
368
documentacao_projecto/backups_codigo/MedicationDialog.java
Normal file
368
documentacao_projecto/backups_codigo/MedicationDialog.java
Normal file
@@ -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<Comprimido> searchResults = new ArrayList<>();
|
||||
private List<Comprimido> 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<String> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
141
documentacao_projecto/backups_codigo/MedicationFragment.java
Normal file
141
documentacao_projecto/backups_codigo/MedicationFragment.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
118
documentacao_projecto/backups_codigo/MedicationViewModel.java
Normal file
118
documentacao_projecto/backups_codigo/MedicationViewModel.java
Normal file
@@ -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<List<Medication>> allMedications = new MutableLiveData<>(new ArrayList<>());
|
||||
private final MutableLiveData<Medication> 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<Medication> 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<List<Medication>> getAllMedications() {
|
||||
return allMedications;
|
||||
}
|
||||
|
||||
public LiveData<Medication> 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));
|
||||
}
|
||||
}
|
||||
111
documentacao_projecto/backups_codigo/dialog_add_medication.xml
Normal file
111
documentacao_projecto/backups_codigo/dialog_add_medication.xml
Normal file
@@ -0,0 +1,111 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="24dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/layout_med_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:hint="Nome do Medicamento"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edit_med_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textCapWords" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="4dp">
|
||||
|
||||
<TextView
|
||||
android:text="Horários"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_add_time"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Adicionar"
|
||||
android:padding="0dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/chip_group_times"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:padding="4dp"/>
|
||||
|
||||
|
||||
<TextView
|
||||
android:text="Via de Administração"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginBottom="4dp"/>
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/radio_group_route"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio_oral"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Via Oral (Pela boca)" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio_topical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Via Tópica (Na pele)" />
|
||||
|
||||
<RadioButton
|
||||
android:id="@+id/radio_inhalatory"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Via Inalatória (Pelo nariz/boca)" />
|
||||
</RadioGroup>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Notas (Opcional)"
|
||||
android:layout_marginBottom="16dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/edit_med_notes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textMultiLine"
|
||||
android:minLines="2"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</LinearLayout>
|
||||
47
documentacao_projecto/base_de_dados_firebase.md
Normal file
47
documentacao_projecto/base_de_dados_firebase.md
Normal file
@@ -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+*
|
||||
34
documentacao_projecto/detalhes_medicamentos_multiplos.md
Normal file
34
documentacao_projecto/detalhes_medicamentos_multiplos.md
Normal file
@@ -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<String> 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+*
|
||||
54
documentacao_projecto/guia_arquitetura_app.md
Normal file
54
documentacao_projecto/guia_arquitetura_app.md
Normal file
@@ -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.*
|
||||
31
documentacao_projecto/guia_utilizacao_app.md
Normal file
31
documentacao_projecto/guia_utilizacao_app.md
Normal file
@@ -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+*
|
||||
61
documentacao_projecto/historico_alteracoes.md
Normal file
61
documentacao_projecto/historico_alteracoes.md
Normal file
@@ -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.*
|
||||
33
documentacao_projecto/manual_tecnico_setup.md
Normal file
33
documentacao_projecto/manual_tecnico_setup.md
Normal file
@@ -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+*
|
||||
53
documentacao_projecto/mapa_ficheiros_completo.md
Normal file
53
documentacao_projecto/mapa_ficheiros_completo.md
Normal file
@@ -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)*
|
||||
32
documentacao_projecto/plano_melhorias_futuras.md
Normal file
32
documentacao_projecto/plano_melhorias_futuras.md
Normal file
@@ -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)*
|
||||
Reference in New Issue
Block a user