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'
|
implementation 'androidx.navigation:navigation-ui:2.7.7'
|
||||||
|
|
||||||
// Adiciona a biblioteca para Auth se for do Google ID (credentials)
|
// 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:1.5.0'
|
||||||
implementation 'androidx.credentials:credentials-play-services-auth:1.5.0'
|
implementation 'androidx.credentials:credentials-play-services-auth:1.5.0'
|
||||||
//noinspection UseIdentifyId
|
//noinspection UseIdentifyId
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".CuidaApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
@@ -58,6 +59,16 @@
|
|||||||
|
|
||||||
<receiver android:name=".services.AlarmReceiver" android:exported="false" />
|
<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>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
@@ -13,8 +13,10 @@ import com.example.cuida.ui.auth.LoginActivity;
|
|||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.example.cuida.utils.NotificationHelper;
|
import com.example.cuida.utils.NotificationHelper;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
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.FirebaseAuth;
|
||||||
import com.google.firebase.auth.FirebaseUser;
|
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 {
|
public class LoginActivity extends AppCompatActivity {
|
||||||
// gvjhbk
|
// gvjhbk
|
||||||
@@ -56,6 +60,69 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
binding.forgotPasswordLink.setOnClickListener(v -> {
|
binding.forgotPasswordLink.setOnClickListener(v -> {
|
||||||
startActivity(new Intent(this, ForgotPasswordActivity.class));
|
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() {
|
private void login() {
|
||||||
@@ -109,6 +176,12 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
prefs.edit().putBoolean("is_logged_in", true).apply();
|
prefs.edit().putBoolean("is_logged_in", true).apply();
|
||||||
prefs.edit().putBoolean("remember_me", rememberMe).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
|
if (fetchTask.isSuccessful() && fetchTask.getResult() != null
|
||||||
&& fetchTask.getResult().exists()) {
|
&& fetchTask.getResult().exists()) {
|
||||||
com.google.firebase.firestore.DocumentSnapshot documentSnapshot = fetchTask
|
com.google.firebase.firestore.DocumentSnapshot documentSnapshot = fetchTask
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ import android.text.TextWatcher;
|
|||||||
import com.example.cuida.data.model.Comprimido;
|
import com.example.cuida.data.model.Comprimido;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.Toast;
|
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 {
|
public class MedicationDialog extends DialogFragment {
|
||||||
|
|
||||||
@@ -43,7 +47,8 @@ public class MedicationDialog extends DialogFragment {
|
|||||||
private EditText editNotes;
|
private EditText editNotes;
|
||||||
private android.widget.RadioButton radioOral, radioTopical, radioInhalatory;
|
private android.widget.RadioButton radioOral, radioTopical, radioInhalatory;
|
||||||
private android.widget.RadioGroup radioGroupRoute;
|
private android.widget.RadioGroup radioGroupRoute;
|
||||||
private TextView textTime;
|
private ChipGroup chipGroupTimes;
|
||||||
|
private List<String> selectedTimes = new ArrayList<>();
|
||||||
private Medication medicationToEdit;
|
private Medication medicationToEdit;
|
||||||
private OnMedicationSaveListener listener;
|
private OnMedicationSaveListener listener;
|
||||||
private OnMedicationDeleteListener deleteListener;
|
private OnMedicationDeleteListener deleteListener;
|
||||||
@@ -78,7 +83,8 @@ public class MedicationDialog extends DialogFragment {
|
|||||||
editName = view.findViewById(R.id.edit_med_name);
|
editName = view.findViewById(R.id.edit_med_name);
|
||||||
recyclerResults = view.findViewById(R.id.recycler_search_results);
|
recyclerResults = view.findViewById(R.id.recycler_search_results);
|
||||||
editNotes = view.findViewById(R.id.edit_med_notes);
|
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);
|
radioGroupRoute = view.findViewById(R.id.radio_group_route);
|
||||||
radioOral = view.findViewById(R.id.radio_oral);
|
radioOral = view.findViewById(R.id.radio_oral);
|
||||||
@@ -124,12 +130,18 @@ public class MedicationDialog extends DialogFragment {
|
|||||||
radioInhalatory = view.findViewById(R.id.radio_inhalatory);
|
radioInhalatory = view.findViewById(R.id.radio_inhalatory);
|
||||||
|
|
||||||
// Set up TimePicker
|
// Set up TimePicker
|
||||||
textTime.setOnClickListener(v -> showTimePicker());
|
btnAddTime.setOnClickListener(v -> showTimePicker());
|
||||||
|
|
||||||
if (medicationToEdit != null) {
|
if (medicationToEdit != null) {
|
||||||
editName.setText(medicationToEdit.name);
|
editName.setText(medicationToEdit.name);
|
||||||
editNotes.setText(medicationToEdit.notes);
|
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;
|
String dosage = medicationToEdit.dosage;
|
||||||
if (dosage != null) {
|
if (dosage != null) {
|
||||||
@@ -146,14 +158,23 @@ public class MedicationDialog extends DialogFragment {
|
|||||||
builder.setTitle("Adicionar Medicamento");
|
builder.setTitle("Adicionar Medicamento");
|
||||||
// Default time to current time
|
// Default time to current time
|
||||||
Calendar cal = Calendar.getInstance();
|
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)
|
builder.setView(view)
|
||||||
.setPositiveButton("Guardar", (dialog, id) -> {
|
.setPositiveButton("Guardar", (dialog, id) -> {
|
||||||
String name = editName.getText().toString();
|
String name = editName.getText().toString();
|
||||||
String notes = editNotes.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();
|
int selectedId = radioGroupRoute.getCheckedRadioButtonId();
|
||||||
String dosage = "Via não especificada";
|
String dosage = "Via não especificada";
|
||||||
@@ -227,25 +248,32 @@ public class MedicationDialog extends DialogFragment {
|
|||||||
int hour = cal.get(Calendar.HOUR_OF_DAY);
|
int hour = cal.get(Calendar.HOUR_OF_DAY);
|
||||||
int minute = cal.get(Calendar.MINUTE);
|
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(),
|
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);
|
hour, minute, true);
|
||||||
timePickerDialog.show();
|
timePickerDialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTimeLabel(int hourOfDay, int minute) {
|
private void refreshTimeChips() {
|
||||||
String time = String.format(Locale.getDefault(), "%02d:%02d", hourOfDay, minute);
|
if (chipGroupTimes == null) return;
|
||||||
textTime.setText(time);
|
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() {
|
private void fetchAllMedsOnce() {
|
||||||
|
|||||||
@@ -58,36 +58,55 @@ public class MedicationFragment extends Fragment {
|
|||||||
MedicationDialog dialog = new MedicationDialog();
|
MedicationDialog dialog = new MedicationDialog();
|
||||||
dialog.setMedicationToEdit(medication);
|
dialog.setMedicationToEdit(medication);
|
||||||
dialog.setListener(medicationToSave -> {
|
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) {
|
if (medication == null) {
|
||||||
medicationViewModel.insert(medicationToSave);
|
medicationViewModel.insert(medicationToSave);
|
||||||
} else {
|
} else {
|
||||||
medicationViewModel.update(medicationToSave);
|
medicationViewModel.update(medicationToSave);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
String[] times = medicationToSave.time.split(",\\s*");
|
||||||
String[] timeParts = medicationToSave.time.split(":");
|
for (String t : times) {
|
||||||
int hour = Integer.parseInt(timeParts[0]);
|
if (t.isEmpty()) continue;
|
||||||
int minute = Integer.parseInt(timeParts[1]);
|
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();
|
java.util.Calendar calendar = java.util.Calendar.getInstance();
|
||||||
calendar.set(java.util.Calendar.HOUR_OF_DAY, hour);
|
calendar.set(java.util.Calendar.HOUR_OF_DAY, hour);
|
||||||
calendar.set(java.util.Calendar.MINUTE, minute);
|
calendar.set(java.util.Calendar.MINUTE, minute);
|
||||||
calendar.set(java.util.Calendar.SECOND, 0);
|
calendar.set(java.util.Calendar.SECOND, 0);
|
||||||
|
|
||||||
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
|
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
|
||||||
calendar.add(java.util.Calendar.DAY_OF_YEAR, 1);
|
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();
|
dialog.dismiss();
|
||||||
@@ -96,13 +115,18 @@ public class MedicationFragment extends Fragment {
|
|||||||
dialog.setDeleteListener(medicationToDelete -> {
|
dialog.setDeleteListener(medicationToDelete -> {
|
||||||
medicationViewModel.delete(medicationToDelete);
|
medicationViewModel.delete(medicationToDelete);
|
||||||
|
|
||||||
// Cancel alarm if configured
|
// Cancel all alarms for this medication
|
||||||
try {
|
if (medicationToDelete.time != null) {
|
||||||
com.example.cuida.utils.AlarmScheduler.cancelAlarm(
|
String[] times = medicationToDelete.time.split(",\\s*");
|
||||||
requireContext(),
|
for (String t : times) {
|
||||||
medicationToDelete.name.hashCode());
|
if (t.isEmpty()) continue;
|
||||||
} catch (Exception e) {
|
try {
|
||||||
e.printStackTrace();
|
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.view.ViewGroup;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.Toast;
|
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.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
@@ -47,6 +59,8 @@ public class ProfileFragment extends Fragment {
|
|||||||
getActivity().finish();
|
getActivity().finish();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
binding.buttonExportReport.setOnClickListener(v -> exportMonthlyReport());
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,9 +299,119 @@ public class ProfileFragment extends Fragment {
|
|||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void exportMonthlyReport() {
|
||||||
public void onDestroyView() {
|
if (currentUser == null || auth.getCurrentUser() == null) return;
|
||||||
super.onDestroyView();
|
String userId = auth.getCurrentUser().getUid();
|
||||||
binding = null;
|
|
||||||
|
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 hour = Integer.parseInt(timeParts[0]);
|
||||||
int minute = Integer.parseInt(timeParts[1]);
|
int minute = Integer.parseInt(timeParts[1]);
|
||||||
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
Calendar baseCal = Calendar.getInstance();
|
||||||
calendar.set(year, month, day, hour, minute, 0);
|
baseCal.set(year, month, day, hour, minute, 0);
|
||||||
// 1 hour before
|
|
||||||
calendar.add(Calendar.HOUR_OF_DAY, -1);
|
|
||||||
|
|
||||||
if (calendar.getTimeInMillis() > System.currentTimeMillis()) {
|
// Schedule 24 hours before
|
||||||
String title = "Lembrete de Consulta";
|
Calendar cal24h = (Calendar) baseCal.clone();
|
||||||
String msg = "A sua consulta é daqui a 1 hora (" + time + ").";
|
cal24h.add(Calendar.DAY_OF_YEAR, -1);
|
||||||
AlarmScheduler.scheduleAlarm(
|
if (cal24h.getTimeInMillis() > System.currentTimeMillis()) {
|
||||||
getApplication(),
|
AlarmScheduler.scheduleAlarm(getApplication(), cal24h.getTimeInMillis(),
|
||||||
calendar.getTimeInMillis(),
|
"Lembrete de Consulta", "A sua consulta é amanhã às " + time,
|
||||||
title,
|
(date + time + "24h").hashCode());
|
||||||
msg,
|
}
|
||||||
(date + time).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) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
import com.example.cuida.databinding.FragmentSns24Binding;
|
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 {
|
public class Sns24Fragment extends Fragment {
|
||||||
|
|
||||||
@@ -85,6 +89,9 @@ public class Sns24Fragment extends Fragment {
|
|||||||
startActivity(mapIntent);
|
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
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -86,7 +86,18 @@
|
|||||||
android:text="@string/login_button"
|
android:text="@string/login_button"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold"
|
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
|
<TextView
|
||||||
android:id="@+id/forgot_password_link"
|
android:id="@+id/forgot_password_link"
|
||||||
|
|||||||
@@ -29,36 +29,38 @@
|
|||||||
android:inputType="textCapWords" />
|
android:inputType="textCapWords" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:text="Horário"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="14sp"
|
android:orientation="horizontal"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginBottom="4dp">
|
||||||
|
|
||||||
<!-- O resto do conteúdo do diálogo continua aqui em baixo -->
|
<TextView
|
||||||
</LinearLayout>
|
android:text="Horários"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/recycler_search_results"
|
android:id="@+id/btn_add_time"
|
||||||
android:layout_width="match_parent"
|
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||||
android:layout_height="200dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_marginTop="60dp"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="gone"
|
android:text="Adicionar"
|
||||||
android:elevation="8dp"
|
android:padding="0dp"
|
||||||
android:background="@drawable/bg_search_results" />
|
android:minWidth="0dp"
|
||||||
</FrameLayout>
|
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
|
<TextView
|
||||||
android:text="Via de Administração"
|
android:text="Via de Administração"
|
||||||
|
|||||||
@@ -115,6 +115,17 @@
|
|||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:backgroundTint="@color/secondary_color"/>
|
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
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/button_logout"
|
android:id="@+id/button_logout"
|
||||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
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">
|
<script type="text/javascript">
|
||||||
function configurationCacheProblems() { return (
|
function configurationCacheProblems() { return (
|
||||||
// begin-report-data
|
// 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
|
// end-report-data
|
||||||
);}
|
);}
|
||||||
</script>
|
</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