ja acabei a app acho?

This commit is contained in:
2026-02-27 15:36:03 +00:00
parent 91de8f46bf
commit 014432157d
744 changed files with 16486 additions and 16772 deletions

View File

@@ -9,9 +9,7 @@ import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.cuida.databinding.ActivityMainBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.example.cuida.ui.auth.LoginActivity;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
@@ -53,21 +51,11 @@ public class MainActivity extends AppCompatActivity {
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
BottomNavigationView navView = binding.navView;
// Find Navigation Host Fragment and setup Bottom Navigation
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
if (navHostFragment != null) {
NavController navController = navHostFragment.getNavController();
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_appointments, R.id.navigation_medication,
R.id.navigation_sns24, R.id.navigation_profile)
.build();
// NavigationUI.setupActionBarWithNavController(this, navController,
// appBarConfiguration);
NavigationUI.setupWithNavController(binding.navView, navController);
}
}

View File

@@ -1,7 +0,0 @@
package com.example.cuida.config;
public class SupabaseConfig {
public static final String URL = "https://iyngjivmpkbsxcetjcey.supabase.co";
public static final String AUTH_URL = "sb_publishable_fngoScKM-mFFYGL_VpdIQw_amS_MyYy";
public static final String API_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Iml5bmdqaXZtcGtic3hjZXRqY2V5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njk2ODA5MTYsImV4cCI6MjA4NTI1NjkxNn0.zZtwcjNQmaABgJMlqteqH2mrJKcMwQR-OgpfR0h3WYA";
}

View File

@@ -1,65 +0,0 @@
package com.example.cuida.data;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.sqlite.db.SupportSQLiteDatabase;
import androidx.annotation.NonNull;
import com.example.cuida.data.dao.UserDao;
import com.example.cuida.data.dao.AppointmentDao;
import com.example.cuida.data.dao.MedicationDao;
import com.example.cuida.data.model.User;
import com.example.cuida.data.model.Appointment;
import com.example.cuida.data.model.Medication;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Database(entities = { User.class, Appointment.class, Medication.class }, version = 4, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
public abstract AppointmentDao appointmentDao();
public abstract MedicationDao medicationDao();
private static volatile AppDatabase INSTANCE;
private static final int NUMBER_OF_THREADS = 4;
public static final ExecutorService databaseWriteExecutor = Executors.newFixedThreadPool(NUMBER_OF_THREADS);
public static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "cuida_database")
.fallbackToDestructiveMigration()
.addCallback(sRoomDatabaseCallback)
.build();
}
}
}
return INSTANCE;
}
private static RoomDatabase.Callback sRoomDatabaseCallback = new RoomDatabase.Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
databaseWriteExecutor.execute(() -> {
// Populate the database in the background.
MedicationDao medDao = INSTANCE.medicationDao();
medDao.insert(new Medication("Paracetamol", "08:00", "1 comp", "Tomar com água"));
medDao.insert(new Medication("Ibuprofeno", "14:00", "1 comp", "Após refeição"));
medDao.insert(new Medication("Vitamina C", "20:00", "1 comp", "Antes de dormir"));
AppointmentDao apptDao = INSTANCE.appointmentDao();
apptDao.insert(new Appointment("Medicina Geral", "25/01/2026", "10:00", "Check-up anual", false));
apptDao.insert(new Appointment("Cardiologia", "02/02/2026", "15:30", "Dor no peito", false));
apptDao.insert(new Appointment("Oftalmologia", "10/01/2025", "09:00", "Renovação óculos", true));
});
}
};
}

View File

@@ -1,23 +0,0 @@
package com.example.cuida.data.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import com.example.cuida.data.model.Appointment;
import java.util.List;
@Dao
public interface AppointmentDao {
@Insert
void insert(Appointment appointment);
@Query("SELECT * FROM appointments WHERE isPast = 0 ORDER BY date ASC, time ASC")
LiveData<List<Appointment>> getFutureAppointments();
@Query("SELECT * FROM appointments WHERE isPast = 1 ORDER BY date DESC, time DESC")
LiveData<List<Appointment>> getPastAppointments();
@Query("SELECT time FROM appointments WHERE date = :date")
List<String> getBookedTimesForDate(String date);
}

View File

@@ -1,23 +0,0 @@
package com.example.cuida.data.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import com.example.cuida.data.model.Medication;
import java.util.List;
@Dao
public interface MedicationDao {
@Insert
void insert(Medication medication);
@androidx.room.Update
void update(Medication medication);
@Query("SELECT * FROM medications ORDER BY time ASC")
LiveData<List<Medication>> getAllMedications();
@Query("SELECT * FROM medications ORDER BY time ASC LIMIT 1")
LiveData<Medication> getNextMedication();
}

View File

@@ -1,26 +0,0 @@
package com.example.cuida.data.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.Update;
import androidx.room.OnConflictStrategy;
import com.example.cuida.data.model.User;
@Dao
public interface UserDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(User user);
@Update
void update(User user);
@Query("SELECT * FROM users WHERE email = :email AND password = :password LIMIT 1")
User login(String email, String password);
@Query("SELECT * FROM users WHERE email = :email LIMIT 1")
User checkUser(String email);
@Query("delete from users")
void deleteAll();
}

View File

@@ -1,12 +1,11 @@
package com.example.cuida.data.model;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.google.firebase.firestore.DocumentId;
@Entity(tableName = "appointments")
public class Appointment {
@PrimaryKey(autoGenerate = true)
public int id;
@DocumentId
public String id;
public String type; // e.g. "Medicina Geral", "Cardiologia"
public String date; // dd/MM/yyyy
@@ -14,6 +13,10 @@ public class Appointment {
public String reason;
public boolean isPast;
// Required empty constructor for Firestore deserialization
public Appointment() {
}
public Appointment(String type, String date, String time, String reason, boolean isPast) {
this.type = type;
this.date = date;

View File

@@ -1,12 +1,11 @@
package com.example.cuida.data.model;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.google.firebase.firestore.DocumentId;
@Entity(tableName = "medications")
public class Medication {
@PrimaryKey(autoGenerate = true)
public int id;
@DocumentId
public String id;
public String name;
public String time; // HH:mm
@@ -14,6 +13,10 @@ public class Medication {
public String notes;
public boolean isTaken;
// Required empty constructor for Firestore deserialization
public Medication() {
}
public Medication(String name, String time, String dosage, String notes) {
this.name = name;
this.time = time;

View File

@@ -1,12 +1,11 @@
package com.example.cuida.data.model;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.google.firebase.firestore.DocumentId;
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
public int uid;
@DocumentId
public String id;
public String name;
public String email;
@@ -15,6 +14,10 @@ public class User {
public String utenteNumber;
public String profilePictureUri;
// Required empty constructor for Firestore deserialization
public User() {
}
public User(String name, String email, String password, int age, String utenteNumber) {
this.name = name;
this.email = email;

View File

@@ -12,7 +12,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class Gemini {
private final GenerativeModelFutures modelo;
public Gemini() {

View File

@@ -1,30 +1,79 @@
package com.example.cuida.ui.appointments;
import android.app.Application;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.example.cuida.data.AppDatabase;
import com.example.cuida.data.dao.AppointmentDao;
import androidx.lifecycle.MutableLiveData;
import com.example.cuida.data.model.Appointment;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class AppointmentsViewModel extends AndroidViewModel {
private final AppointmentDao appointmentDao;
private final LiveData<List<Appointment>> futureAppointments;
private final LiveData<List<Appointment>> pastAppointments;
private final ExecutorService executorService;
private final MutableLiveData<List<Appointment>> futureAppointments = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<List<Appointment>> pastAppointments = new MutableLiveData<>(new ArrayList<>());
private final FirebaseFirestore db;
private final FirebaseAuth auth;
public AppointmentsViewModel(@NonNull Application application) {
super(application);
AppDatabase db = AppDatabase.getDatabase(application);
appointmentDao = db.appointmentDao();
futureAppointments = appointmentDao.getFutureAppointments();
pastAppointments = appointmentDao.getPastAppointments();
executorService = Executors.newSingleThreadExecutor();
db = FirebaseFirestore.getInstance();
auth = FirebaseAuth.getInstance();
fetchAppointments();
}
private void fetchAppointments() {
if (auth.getCurrentUser() == null)
return;
String userId = auth.getCurrentUser().getUid();
// 1. Fetch Future Appointments
db.collection("users").document(userId).collection("appointments")
.whereEqualTo("isPast", false)
.orderBy("date", Query.Direction.ASCENDING)
.orderBy("time", Query.Direction.ASCENDING)
.addSnapshotListener((value, error) -> {
if (error != null) {
Log.e("AppointmentsVM", "Listen failed for future.", error);
return;
}
List<Appointment> apps = new ArrayList<>();
if (value != null) {
for (QueryDocumentSnapshot doc : value) {
apps.add(doc.toObject(Appointment.class));
}
}
futureAppointments.setValue(apps);
});
// 2. Fetch Past Appointments
db.collection("users").document(userId).collection("appointments")
.whereEqualTo("isPast", true)
.orderBy("date", Query.Direction.DESCENDING)
.orderBy("time", Query.Direction.DESCENDING)
.addSnapshotListener((value, error) -> {
if (error != null) {
Log.e("AppointmentsVM", "Listen failed for past.", error);
return;
}
List<Appointment> apps = new ArrayList<>();
if (value != null) {
for (QueryDocumentSnapshot doc : value) {
apps.add(doc.toObject(Appointment.class));
}
}
pastAppointments.setValue(apps);
});
}
public LiveData<List<Appointment>> getFutureAppointments() {
@@ -36,6 +85,13 @@ public class AppointmentsViewModel extends AndroidViewModel {
}
public void insert(Appointment appointment) {
executorService.execute(() -> appointmentDao.insert(appointment));
if (auth.getCurrentUser() == null)
return;
String userId = auth.getCurrentUser().getUid();
db.collection("users").document(userId).collection("appointments")
.add(appointment)
.addOnSuccessListener(documentReference -> Log.d("AppointmentsVM", "Appointment added"))
.addOnFailureListener(e -> Log.w("AppointmentsVM", "Error adding appointment", e));
}
}

View File

@@ -6,8 +6,6 @@ import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.cuida.MainActivity;
import com.example.cuida.data.AppDatabase;
import com.example.cuida.data.dao.UserDao;
import com.example.cuida.data.model.User;
import com.example.cuida.databinding.ActivityLoginBinding;
import com.example.cuida.R;
@@ -65,16 +63,7 @@ public class LoginActivity extends AppCompatActivity {
prefs.edit().putString("user_email", adminEmail).apply();
prefs.edit().putString("user_name", "Administrador").apply();
// Ensure admin exists in DB
UserDao userDao = AppDatabase.getDatabase(this).userDao();
AppDatabase.databaseWriteExecutor.execute(() -> {
if (userDao.checkUser(adminEmail) == null) {
User adminUser = new User("Administrador", adminEmail, "123", 99, "000000000");
// Set empty profile picture URI if needed to avoid null issues later, though
// it's optional
userDao.insert(adminUser);
}
});
// Remove local Room admin creation since Room is gone
Toast.makeText(this, "Login de Administrador", Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, MainActivity.class));

View File

@@ -4,18 +4,9 @@ import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.cuida.data.AppDatabase;
import com.example.cuida.data.dao.UserDao;
import com.example.cuida.data.model.User;
import com.example.cuida.databinding.ActivityRegisterBinding;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.HashMap;
import java.util.Map;
public class RegisterActivity extends AppCompatActivity {
private ActivityRegisterBinding binding;
@@ -50,28 +41,29 @@ public class RegisterActivity extends AppCompatActivity {
binding.registerButton.setEnabled(false);
binding.registerButton.setText("A registar...");
FirebaseAuth mAuth = FirebaseAuth.getInstance();
FirebaseFirestore db = FirebaseFirestore.getInstance();
com.google.firebase.auth.FirebaseAuth mAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
com.google.firebase.firestore.FirebaseFirestore db = com.google.firebase.firestore.FirebaseFirestore
.getInstance();
mAuth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(this, task -> {
if (task.isSuccessful()) {
// Registration success, save additional info to Firestore
FirebaseUser firebaseUser = mAuth.getCurrentUser();
com.google.firebase.auth.FirebaseUser firebaseUser = mAuth.getCurrentUser();
if (firebaseUser != null) {
String userId = firebaseUser.getUid();
Map<String, Object> userMap = new HashMap<>();
userMap.put("nome_completo", name);
userMap.put("idade", ageStr);
userMap.put("numero_utente", utenteStr);
java.util.Map<String, Object> userMap = new java.util.HashMap<>();
userMap.put("uid", userId);
userMap.put("name", name);
userMap.put("email", email);
userMap.put("age", age);
userMap.put("utenteNumber", utenteStr);
userMap.put("profilePictureUri", ""); // Init empty
db.collection("utilizadores").document(userId)
db.collection("users").document(userId)
.set(userMap)
.addOnSuccessListener(aVoid -> {
// Optional: Also save to local Room DB for offline cache if desired,
// but for now we focus on Firebase as requested.
Toast.makeText(RegisterActivity.this, "Conta criada com sucesso!",
Toast.LENGTH_SHORT).show();

View File

@@ -1,25 +1,63 @@
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 com.example.cuida.data.AppDatabase;
import com.example.cuida.data.dao.MedicationDao;
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 LiveData<List<Medication>> allMedications;
private final LiveData<Medication> nextMedication;
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);
AppDatabase db = AppDatabase.getDatabase(application);
MedicationDao medicationDao = db.medicationDao();
allMedications = medicationDao.getAllMedications();
nextMedication = medicationDao.getNextMedication();
db = FirebaseFirestore.getInstance();
auth = FirebaseAuth.getInstance();
fetchMedications();
}
private void fetchMedications() {
if (auth.getCurrentUser() == null)
return;
String userId = auth.getCurrentUser().getUid();
db.collection("users").document(userId).collection("medications")
.orderBy("time", Query.Direction.ASCENDING)
.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);
meds.add(med);
}
}
allMedications.setValue(meds);
if (!meds.isEmpty()) {
nextMedication.setValue(meds.get(0));
} else {
nextMedication.setValue(null);
}
});
}
public LiveData<List<Medication>> getAllMedications() {
@@ -31,14 +69,25 @@ public class MedicationViewModel extends AndroidViewModel {
}
public void insert(Medication medication) {
AppDatabase.databaseWriteExecutor.execute(() -> {
AppDatabase.getDatabase(getApplication()).medicationDao().insert(medication);
});
if (auth.getCurrentUser() == null)
return;
String userId = auth.getCurrentUser().getUid();
db.collection("users").document(userId).collection("medications")
.add(medication)
.addOnSuccessListener(documentReference -> Log.d("MedicationViewModel", "Medication added"))
.addOnFailureListener(e -> Log.w("MedicationViewModel", "Error adding medication", e));
}
public void update(Medication medication) {
AppDatabase.databaseWriteExecutor.execute(() -> {
AppDatabase.getDatabase(getApplication()).medicationDao().update(medication);
});
if (auth.getCurrentUser() == null || medication.id == null)
return;
String userId = auth.getCurrentUser().getUid();
db.collection("users").document(userId).collection("medications")
.document(medication.id)
.set(medication)
.addOnSuccessListener(aVoid -> Log.d("MedicationViewModel", "Medication updated"))
.addOnFailureListener(e -> Log.w("MedicationViewModel", "Error updating medication", e));
}
}

View File

@@ -5,39 +5,42 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.example.cuida.data.AppDatabase;
import com.example.cuida.data.dao.UserDao;
import com.example.cuida.data.model.User;
import com.example.cuida.R;
import com.example.cuida.data.model.User;
import com.example.cuida.databinding.FragmentProfileBinding;
import com.example.cuida.ui.auth.LoginActivity;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
public class ProfileFragment extends Fragment {
private FragmentProfileBinding binding;
private User currentUser;
private UserDao userDao;
private FirebaseFirestore db;
private FirebaseAuth auth;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentProfileBinding.inflate(inflater, container, false);
userDao = AppDatabase.getDatabase(requireContext()).userDao();
db = FirebaseFirestore.getInstance();
auth = FirebaseAuth.getInstance();
loadUserData();
binding.buttonEditProfile.setOnClickListener(v -> showEditDialog());
binding.buttonLogout.setOnClickListener(v -> {
auth.signOut();
getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE).edit().clear().apply();
Intent intent = new Intent(getContext(), LoginActivity.class);
startActivity(intent);
@@ -48,30 +51,40 @@ public class ProfileFragment extends Fragment {
}
private void loadUserData() {
SharedPreferences prefs = requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
String email = prefs.getString("user_email", "");
if (auth.getCurrentUser() == null)
return;
String userId = auth.getCurrentUser().getUid();
AppDatabase.databaseWriteExecutor.execute(() -> {
currentUser = userDao.checkUser(email);
if (currentUser != null) {
getActivity().runOnUiThread(() -> {
binding.profileName.setText(currentUser.name);
binding.profileEmail.setText(currentUser.email);
binding.profileAge.setText(String.valueOf(currentUser.age));
binding.profileUtente.setText(currentUser.utenteNumber != null ? currentUser.utenteNumber : "N/A");
if (currentUser.profilePictureUri != null) {
android.widget.ImageView profileImage = binding.getRoot().findViewById(R.id.profile_image);
if (profileImage != null) {
try {
profileImage.setImageURI(android.net.Uri.parse(currentUser.profilePictureUri));
} catch (Exception e) {
e.printStackTrace();
db.collection("users").document(userId).get()
.addOnSuccessListener(documentSnapshot -> {
if (documentSnapshot.exists()) {
currentUser = documentSnapshot.toObject(User.class);
if (currentUser != null && isAdded()) {
binding.profileName.setText(currentUser.name);
binding.profileEmail.setText(currentUser.email);
binding.profileAge.setText(String.valueOf(currentUser.age));
binding.profileUtente
.setText(currentUser.utenteNumber != null ? currentUser.utenteNumber : "N/A");
if (currentUser.profilePictureUri != null && !currentUser.profilePictureUri.isEmpty()) {
android.widget.ImageView profileImage = binding.getRoot()
.findViewById(R.id.profile_image);
if (profileImage != null) {
try {
profileImage.setImageURI(android.net.Uri.parse(currentUser.profilePictureUri));
} catch (Exception e) {
Log.e("ProfileFragment", "Error loading profile pic view: " + e.getMessage());
}
}
}
}
}
})
.addOnFailureListener(e -> {
if (isAdded()) {
Toast.makeText(getContext(), "Failed to load profile data", Toast.LENGTH_SHORT).show();
}
});
}
});
}
private android.net.Uri tempProfileUri;
@@ -99,23 +112,18 @@ public class ProfileFragment extends Fragment {
return;
}
// Reset temp uri
tempProfileUri = null;
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
// Inflate custom layout
LayoutInflater inflater = requireActivity().getLayoutInflater();
View dialogView = inflater.inflate(R.layout.dialog_edit_profile, null);
builder.setView(dialogView);
AlertDialog dialog = builder.create();
// Bind Views
EditText editName = dialogView.findViewById(R.id.edit_name);
EditText editAge = dialogView.findViewById(R.id.edit_age);
EditText editUtente = dialogView.findViewById(R.id.edit_utente);
EditText editEmail = dialogView.findViewById(R.id.edit_email);
dialogImageView = dialogView.findViewById(R.id.edit_profile_image);
View btnChangePhoto = dialogView.findViewById(R.id.button_change_photo);
@@ -123,13 +131,12 @@ public class ProfileFragment extends Fragment {
View btnSave = dialogView.findViewById(R.id.button_save);
View btnCancel = dialogView.findViewById(R.id.button_cancel);
// Pre-fill data
editName.setText(currentUser.name);
editAge.setText(String.valueOf(currentUser.age));
editUtente.setText(currentUser.utenteNumber);
editEmail.setText(currentUser.email);
if (currentUser.profilePictureUri != null) {
if (currentUser.profilePictureUri != null && !currentUser.profilePictureUri.isEmpty()) {
dialogImageView.setImageURI(android.net.Uri.parse(currentUser.profilePictureUri));
}
@@ -157,31 +164,39 @@ public class ProfileFragment extends Fragment {
currentUser.name = newName;
currentUser.age = newAge;
currentUser.utenteNumber = newUtente;
currentUser.email = newEmail;
// Password not updated here
if (tempProfileUri != null) {
currentUser.profilePictureUri = tempProfileUri.toString();
}
AppDatabase.databaseWriteExecutor.execute(() -> {
userDao.insert(currentUser);
// Warning: We update the email field in Firestore, but FirebaseAuth updateEmail
// requires re-authentication usually.
// Assuming simple data sync for now.
currentUser.email = newEmail;
// Update SharedPreferences if email changed (key for login persistence)
if (emailChanged) {
getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE)
.edit()
.putString("user_email", newEmail)
.apply();
}
String userId = auth.getCurrentUser().getUid();
db.collection("users").document(userId)
.set(currentUser)
.addOnSuccessListener(aVoid -> {
if (emailChanged) {
getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE)
.edit()
.putString("user_email", newEmail)
.apply();
}
getActivity().runOnUiThread(() -> {
// UI update
loadUserData(); // Reload to show new image and data
Toast.makeText(getContext(), "Dados atualizados com sucesso!", Toast.LENGTH_SHORT).show();
dialog.dismiss();
dialogImageView = null; // Clear reference
});
});
if (isAdded()) {
loadUserData(); // Reload to show new image and data
Toast.makeText(getContext(), "Dados atualizados com sucesso!", Toast.LENGTH_SHORT).show();
dialog.dismiss();
dialogImageView = null;
}
})
.addOnFailureListener(e -> {
if (isAdded()) {
Toast.makeText(getContext(), "Erro a guardar perfil.", Toast.LENGTH_SHORT).show();
}
});
});
btnCancel.setOnClickListener(v -> {
@@ -213,13 +228,17 @@ public class ProfileFragment extends Fragment {
return;
}
currentUser.password = newPass;
AppDatabase.databaseWriteExecutor.execute(() -> {
userDao.insert(currentUser);
});
Toast.makeText(getContext(), "Palavra-passe alterada!", Toast.LENGTH_SHORT).show();
dialog.dismiss();
if (auth.getCurrentUser() != null) {
auth.getCurrentUser().updatePassword(newPass).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Toast.makeText(getContext(), "Palavra-passe alterada!", Toast.LENGTH_SHORT).show();
dialog.dismiss();
} else {
Toast.makeText(getContext(), "Erro a alterar: " + task.getException().getMessage(),
Toast.LENGTH_LONG).show();
}
});
}
});
btnCancel.setOnClickListener(v -> dialog.dismiss());

View File

@@ -1,22 +1,26 @@
package com.example.cuida.ui.schedule;
import android.app.Application;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.example.cuida.data.AppDatabase;
import com.example.cuida.data.dao.AppointmentDao;
import com.example.cuida.data.model.Appointment;
import com.example.cuida.utils.AlarmScheduler;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ScheduleViewModel extends AndroidViewModel {
private final AppointmentDao appointmentDao;
private final ExecutorService executorService;
private final FirebaseFirestore db;
private final FirebaseAuth auth;
private final MutableLiveData<String> selectedDate = new MutableLiveData<>();
private final MutableLiveData<String> selectedTime = new MutableLiveData<>();
@@ -25,14 +29,11 @@ public class ScheduleViewModel extends AndroidViewModel {
public ScheduleViewModel(@NonNull Application application) {
super(application);
AppDatabase db = AppDatabase.getDatabase(application);
appointmentDao = db.appointmentDao();
executorService = Executors.newSingleThreadExecutor();
// Initial empty state or default date
db = FirebaseFirestore.getInstance();
auth = FirebaseAuth.getInstance();
}
public void setDate(int year, int month, int dayOfMonth) {
// Format to dd/MM/yyyy
String date = String.format("%02d/%02d/%04d", dayOfMonth, month + 1, year);
selectedDate.setValue(date);
loadTimeSlots(date);
@@ -44,8 +45,6 @@ public class ScheduleViewModel extends AndroidViewModel {
public void setTime(String time) {
selectedTime.setValue(time);
// Update selection state in the list
List<TimeSlot> currentSlots = timeSlots.getValue();
if (currentSlots != null) {
for (TimeSlot slot : currentSlots) {
@@ -68,11 +67,30 @@ public class ScheduleViewModel extends AndroidViewModel {
}
private void loadTimeSlots(String date) {
executorService.execute(() -> {
List<String> bookedTimes = appointmentDao.getBookedTimesForDate(date);
List<TimeSlot> slots = generateTimeSlots(bookedTimes);
timeSlots.postValue(slots);
});
if (auth.getCurrentUser() == null)
return;
String userId = auth.getCurrentUser().getUid();
db.collection("users").document(userId).collection("appointments")
.whereEqualTo("date", date)
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
List<String> bookedTimes = new ArrayList<>();
for (QueryDocumentSnapshot document : task.getResult()) {
Appointment appt = document.toObject(Appointment.class);
if (appt.time != null) {
bookedTimes.add(appt.time);
}
}
List<TimeSlot> slots = generateTimeSlots(bookedTimes);
timeSlots.setValue(slots);
} else {
Log.e("ScheduleViewModel", "Error getting booked slots", task.getException());
List<TimeSlot> slots = generateTimeSlots(new ArrayList<>());
timeSlots.setValue(slots);
}
});
}
private List<TimeSlot> generateTimeSlots(List<String> bookedTimes) {
@@ -89,8 +107,6 @@ public class ScheduleViewModel extends AndroidViewModel {
private void addSlot(List<TimeSlot> slots, String time, List<String> bookedTimes) {
boolean isBooked = bookedTimes.contains(time);
// If current selected time is now booked (e.g. concurrent), deselect it?
// For simplicity, just check booked state.
boolean isSelected = time.equals(selectedTime.getValue());
slots.add(new TimeSlot(time, isBooked, isSelected));
}
@@ -99,42 +115,47 @@ public class ScheduleViewModel extends AndroidViewModel {
String date = selectedDate.getValue();
String time = selectedTime.getValue();
if (auth.getCurrentUser() == null)
return;
String userId = auth.getCurrentUser().getUid();
if (date != null && time != null) {
Appointment appointment = new Appointment("Consulta Geral", date, time, reason, false);
executorService.execute(() -> {
appointmentDao.insert(appointment);
try {
String[] dateParts = date.split("/");
int day = Integer.parseInt(dateParts[0]);
int month = Integer.parseInt(dateParts[1]) - 1; // 0-based
int year = Integer.parseInt(dateParts[2]);
db.collection("users").document(userId).collection("appointments")
.add(appointment)
.addOnSuccessListener(documentReference -> {
try {
String[] dateParts = date.split("/");
int day = Integer.parseInt(dateParts[0]);
int month = Integer.parseInt(dateParts[1]) - 1; // 0-based
int year = Integer.parseInt(dateParts[2]);
String[] timeParts = time.split(":");
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
String[] timeParts = time.split(":");
int hour = Integer.parseInt(timeParts[0]);
int minute = Integer.parseInt(timeParts[1]);
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.set(year, month, day, hour, minute, 0);
// 1 hour before
calendar.add(java.util.Calendar.HOUR_OF_DAY, -1);
Calendar calendar = Calendar.getInstance();
calendar.set(year, month, day, hour, minute, 0);
// 1 hour before
calendar.add(Calendar.HOUR_OF_DAY, -1);
if (calendar.getTimeInMillis() > System.currentTimeMillis()) {
String title = "Lembrete de Consulta";
String msg = "A sua consulta é daqui a 1 hora (" + time + ").";
com.example.cuida.utils.AlarmScheduler.scheduleAlarm(
getApplication(),
calendar.getTimeInMillis(),
title,
msg,
(date + time).hashCode());
}
} catch (Exception e) {
e.printStackTrace();
}
saveSuccess.postValue(true);
});
if (calendar.getTimeInMillis() > System.currentTimeMillis()) {
String title = "Lembrete de Consulta";
String msg = "A sua consulta é daqui a 1 hora (" + time + ").";
AlarmScheduler.scheduleAlarm(
getApplication(),
calendar.getTimeInMillis(),
title,
msg,
(date + time).hashCode());
}
} catch (Exception e) {
e.printStackTrace();
}
saveSuccess.postValue(true);
})
.addOnFailureListener(e -> Log.e("ScheduleViewModel", "Failed to confirm appt", e));
}
}
}

View File

@@ -12,30 +12,18 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.google.ai.client.generativeai.GenerativeModel;
import com.google.ai.client.generativeai.java.GenerativeModelFutures;
import com.google.ai.client.generativeai.type.Content;
import com.google.ai.client.generativeai.type.GenerateContentResponse;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import com.example.cuida.databinding.FragmentSns24Binding;
public class Sns24Fragment extends Fragment {
private FragmentSns24Binding binding;
private GenerativeModelFutures model;
private static final String API_KEY = "AIzaSyBmLgn-SHaTDvAeDWsw2iTZRR9gahhOu7k";
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = FragmentSns24Binding.inflate(inflater, container, false);
View view = inflater.inflate(com.example.cuida.R.layout.fragment_sns24, container, false);
binding = FragmentSns24Binding.bind(view);
return binding.getRoot();
}
@@ -43,11 +31,6 @@ public class Sns24Fragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 1. Inicializar o Modelo Gemini
// NOTA: Substitua "SUA_API_KEY" pela sua chave do Google AI Studio
GenerativeModel gm = new GenerativeModel("gemini-2.5-flash", API_KEY);
model = GenerativeModelFutures.from(gm);
// 2. Botão Ligar SNS 24
binding.buttonCallSns.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_DIAL);
@@ -70,56 +53,49 @@ public class Sns24Fragment extends Fragment {
// Feedback visual de carregamento
binding.buttonAiTriage.setEnabled(false);
binding.textAiResult.setVisibility(View.VISIBLE);
binding.textAiResult.setText("A analisar sintomas com IA...");
binding.textAiResult.setText("A contactar a Inteligência Artificial...");
binding.buttonFindHospital.setVisibility(View.GONE);
// Criar o prompt
Content content = new Content.Builder()
.addText("Atua como um assistente de triagem de saúde. O utilizador diz: \"" + symptoms + "\". " +
"Se a situação exigir avaliação médica imediata ou urgência, começa a resposta com 'URGENTE: HOSPITAL'. "
+
"Caso contrário, dá apenas o conselho habitual. " +
"Dá uma resposta curta (máximo 4 linhas). " +
"Aviso: Isto não substitui aconselhamento médico.")
.build();
com.example.cuida.services.Gemini gemini = new com.example.cuida.services.Gemini();
String prompt = "Atua como um assistente de triagem médica (em português europeu). " +
"Analisa os seguintes sintomas de um paciente e dá uma resposta curta (1 ou 2 parágrafos no máximo) " +
"avaliando a urgência e com uma recomendação clara (ir às urgências, ligar ao SNS 24 ou marcar consulta local). "
+
"Sintomas: " + symptoms;
// Executar chamada assíncrona (usando Guava Futures para Java)
ListenableFuture<GenerateContentResponse> response = model.generateContent(content);
// Executor para rodar a resposta na UI Thread
// Creating a new single thread executor for the background work if needed,
// but the callback needs to run on main thread or handle UI updates on main
// thread.
// We use ContextCompat.getMainExecutor to be safe on API < 28 (minSdk is 24)
Futures.addCallback(response, new FutureCallback<GenerateContentResponse>() {
gemini.fazerPergunta(prompt, new com.example.cuida.services.Gemini.GeminiCallback() {
@Override
public void onSuccess(GenerateContentResponse result) {
if (getActivity() != null) {
String text = result.getText();
binding.textAiResult.setText(text);
binding.buttonAiTriage.setEnabled(true);
public void onSuccess(String result) {
if (getActivity() != null && binding != null) {
getActivity().runOnUiThread(() -> {
binding.textAiResult.setText(result);
binding.buttonAiTriage.setEnabled(true);
if (text != null && text.contains("URGENTE: HOSPITAL")) {
binding.buttonFindHospital.setVisibility(View.VISIBLE);
binding.buttonFindHospital.setOnClickListener(v -> {
Uri gmmIntentUri = Uri.parse("geo:0,0?q=hospital");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
mapIntent.setPackage("com.google.android.apps.maps");
startActivity(mapIntent);
});
}
String resultLower = result.toLowerCase();
if (resultLower.contains("urgência") || resultLower.contains("hospital")
|| resultLower.contains("112")) {
binding.buttonFindHospital.setVisibility(View.VISIBLE);
binding.buttonFindHospital.setOnClickListener(v -> {
Uri gmmIntentUri = Uri.parse("geo:0,0?q=hospital");
Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
mapIntent.setPackage("com.google.android.apps.maps");
startActivity(mapIntent);
});
}
});
}
}
@Override
public void onFailure(Throwable t) {
if (getActivity() != null) {
binding.textAiResult.setText("Erro ao contactar a IA: " + t.getMessage());
binding.buttonAiTriage.setEnabled(true);
public void onError(Throwable t) {
if (getActivity() != null && binding != null) {
getActivity().runOnUiThread(() -> {
binding.textAiResult.setText("Falha na comunicação com a IA: " + t.getMessage());
binding.buttonAiTriage.setEnabled(true);
});
}
}
}, androidx.core.content.ContextCompat.getMainExecutor(requireContext()));
});
}
@Override