diff --git a/app/build.gradle b/app/build.gradle index 3959019..e55c1f7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -72,4 +72,9 @@ dependencies { implementation 'com.google.firebase:firebase-auth' implementation 'com.google.firebase:firebase-firestore' implementation 'com.google.firebase:firebase-database' + implementation 'com.google.firebase:firebase-storage' + + // Glide + implementation 'com.github.bumptech.glide:glide:4.12.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' } diff --git a/app/src/main/java/com/example/cuida/MainActivity.java b/app/src/main/java/com/example/cuida/MainActivity.java index 81eee15..27c4dcf 100644 --- a/app/src/main/java/com/example/cuida/MainActivity.java +++ b/app/src/main/java/com/example/cuida/MainActivity.java @@ -59,6 +59,18 @@ public class MainActivity extends AppCompatActivity { if (navHostFragment != null) { NavController navController = navHostFragment.getNavController(); NavigationUI.setupWithNavController(binding.navView, navController); + + // Força o clique no Perfil com Transação Manual (Infalível) + binding.navView.setOnItemSelectedListener(item -> { + if (item.getItemId() == R.id.navigation_profile) { + getSupportFragmentManager().beginTransaction() + .replace(R.id.nav_host_fragment, new com.example.cuida.ui.profile.ProfileFragment()) + .addToBackStack(null) + .commit(); + return true; + } + return NavigationUI.onNavDestinationSelected(item, navController); + }); } } } diff --git a/app/src/main/java/com/example/cuida/ui/home/HomeFragment.java b/app/src/main/java/com/example/cuida/ui/home/HomeFragment.java index 065a318..ea0bb80 100644 --- a/app/src/main/java/com/example/cuida/ui/home/HomeFragment.java +++ b/app/src/main/java/com/example/cuida/ui/home/HomeFragment.java @@ -9,6 +9,7 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; +import com.example.cuida.R; import com.example.cuida.databinding.FragmentHomeBinding; import com.example.cuida.ui.medication.MedicationViewModel; import com.example.cuida.ui.medication.MedicationAdapter; @@ -16,7 +17,7 @@ import com.example.cuida.ui.appointments.AppointmentsViewModel; import com.example.cuida.ui.appointments.AppointmentAdapter; import com.example.cuida.data.model.Appointment; import com.example.cuida.data.model.Medication; - +import com.bumptech.glide.Glide; import java.util.Collections; public class HomeFragment extends Fragment { @@ -45,27 +46,42 @@ public class HomeFragment extends Fragment { if (name != null && !name.isEmpty()) { String firstName = name.split(" ")[0]; - binding.textGreeting.setText("Olá, " + firstName + "!"); + binding.textGreeting.setText("Olá, " + firstName); } else { - binding.textGreeting.setText("Olá, Utilizador!"); + binding.textGreeting.setText("Olá"); } 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()); + if (isAdded() && binding.imageProfileHome != null) { + binding.imageProfileHome.setVisibility(View.VISIBLE); + if (profilePictureUri != null && !profilePictureUri.isEmpty() && getContext() != null) { + Glide.with(getContext()) + .load(profilePictureUri) + .placeholder(R.drawable.ic_user) + .circleCrop() + .into(binding.imageProfileHome); + } else { + binding.imageProfileHome.setImageResource(R.drawable.ic_user); } + + // Clique para entrar no perfil (Manual e Infalível) + binding.imageProfileHome.setOnClickListener(v -> { + if (getActivity() != null) { + getActivity().getSupportFragmentManager().beginTransaction() + .replace(R.id.nav_host_fragment, new com.example.cuida.ui.profile.ProfileFragment()) + .addToBackStack(null) + .commit(); + } + }); } } }) .addOnFailureListener(e -> { if (isAdded()) - binding.textGreeting.setText("Olá, Utilizador!"); + binding.textGreeting.setText("Olá"); }); } else { - binding.textGreeting.setText("Olá, Utilizador!"); + binding.textGreeting.setText("Olá"); } // --- Setup Adapters --- diff --git a/app/src/main/java/com/example/cuida/ui/profile/ProfileFragment.java b/app/src/main/java/com/example/cuida/ui/profile/ProfileFragment.java index 694fd94..b483033 100644 --- a/app/src/main/java/com/example/cuida/ui/profile/ProfileFragment.java +++ b/app/src/main/java/com/example/cuida/ui/profile/ProfileFragment.java @@ -11,51 +11,63 @@ import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.ImageView; +import android.widget.TextView; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import com.bumptech.glide.Glide; import com.example.cuida.R; -import com.example.cuida.data.model.User; import com.example.cuida.databinding.FragmentProfileBinding; +import com.example.cuida.data.model.User; import com.example.cuida.ui.auth.LoginActivity; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.storage.FirebaseStorage; +import com.google.firebase.storage.StorageReference; + +import java.util.HashMap; +import java.util.Map; public class ProfileFragment extends Fragment { private FragmentProfileBinding binding; - private User currentUser; - private FirebaseFirestore db; private FirebaseAuth auth; - private Uri tempProfileUri; + private FirebaseFirestore db; + private FirebaseStorage storage; + private User currentUser; + private String userId; + private ImageView dialogImageView; + private Uri tempProfileUri; - private final androidx.activity.result.ActivityResultLauncher pickMedia = registerForActivityResult( - new androidx.activity.result.contract.ActivityResultContracts.GetContent(), uri -> { - if (uri != null) { + private final ActivityResultLauncher pickMedia = registerForActivityResult( + new ActivityResultContracts.GetContent(), uri -> { + if (uri != null && dialogImageView != null) { tempProfileUri = uri; - try { - requireContext().getContentResolver().takePersistableUriPermission(uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION); - } catch (Exception e) { - Log.e("ProfileFragment", "Permission error: " + e.getMessage()); - } - if (dialogImageView != null) { - dialogImageView.setImageURI(uri); - } + dialogImageView.setImageURI(uri); } }); public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = FragmentProfileBinding.inflate(inflater, container, false); + try { + binding = FragmentProfileBinding.inflate(inflater, container, false); + View root = binding.getRoot(); - db = FirebaseFirestore.getInstance(); auth = FirebaseAuth.getInstance(); - loadUserData(); + db = FirebaseFirestore.getInstance(); + storage = FirebaseStorage.getInstance(); + + if (auth.getCurrentUser() != null) { + userId = auth.getCurrentUser().getUid(); + loadUserData(); + } binding.buttonEditProfile.setOnClickListener(v -> showEditDialog()); + binding.buttonLogout.setOnClickListener(v -> { auth.signOut(); if (getContext() != null) { @@ -65,170 +77,192 @@ public class ProfileFragment extends Fragment { requireActivity().finish(); }); - return binding.getRoot(); + return root; + } catch (Exception e) { + Log.e("ProfileFragment", "Erro ao inflar perfil", e); + return inflater.inflate(R.layout.fragment_profile, container, false); + } } private void loadUserData() { - if (auth.getCurrentUser() == null) return; - String userId = auth.getCurrentUser().getUid(); - - if (currentUser == null) { - currentUser = new User(); - currentUser.id = userId; - currentUser.email = auth.getCurrentUser().getEmail(); - currentUser.name = auth.getCurrentUser().getDisplayName(); - } - - db.collection("utilizadores").document(userId) - .addSnapshotListener((doc, error) -> { - if (error != null) { - Log.e("ProfileFragment", "Listen failed.", error); - return; - } - - if (doc != null && doc.exists() && isAdded()) { - currentUser.id = doc.getId(); - String nome = doc.getString("nome_completo"); - if (nome == null) nome = doc.getString("name"); - currentUser.name = nome; - currentUser.email = doc.getString("email"); - - String utente = doc.getString("numero_utente"); - if (utente == null) utente = doc.getString("utenteNumber"); - currentUser.utenteNumber = utente; - - currentUser.profilePictureUri = doc.getString("profilePictureUri"); - - Object ageObj = doc.get("idade"); - if (ageObj == null) ageObj = doc.get("age"); - if (ageObj instanceof Number) currentUser.age = ((Number) ageObj).intValue(); - else if (ageObj instanceof String) { - try { currentUser.age = Integer.parseInt((String) ageObj); } - catch (Exception e) { currentUser.age = 0; } - } - - updateUI(); - } - }); - } - - private void updateUI() { - if (!isAdded() || binding == null || currentUser == null) return; - binding.profileName.setText(currentUser.name != null ? currentUser.name : "N/D"); - binding.profileEmail.setText(currentUser.email != null ? currentUser.email : "N/D"); - binding.profileAge.setText(currentUser.age > 0 ? String.valueOf(currentUser.age) : "N/D"); - binding.profileUtente.setText(currentUser.utenteNumber != null ? currentUser.utenteNumber : "N/D"); - - if (currentUser.profilePictureUri != null && !currentUser.profilePictureUri.isEmpty()) { - ImageView profileImage = binding.getRoot().findViewById(R.id.profile_image); - if (profileImage != null) loadSafeImage(profileImage, currentUser.profilePictureUri); - } - } - - private void loadSafeImage(ImageView view, String uriStr) { - if (view == null || uriStr == null) return; try { - Uri uri = Uri.parse(uriStr); - if (uri.getScheme() != null && (uri.getScheme().equals("content") || uri.getScheme().equals("file"))) { - view.setImageURI(uri); - } else { - Log.d("ProfileFragment", "Skipping setImageURI for non-local scheme: " + uri.getScheme()); - } + if (userId == null || !isAdded()) return; + + // Primeiro tenta na coleção geral 'utilizadores' + db.collection("utilizadores").document(userId).addSnapshotListener((doc, error) -> { + try { + if (doc != null && doc.exists()) { + updateUIFromDocument(doc); + } + + // Independentemente de encontrar em 'utilizadores', tenta TAMBÉM em 'Pacientes' para completar dados + db.collection("Pacientes").document(userId).get().addOnSuccessListener(docP -> { + if (docP.exists()) { + updateUIFromDocument(docP); + } + }); + } catch (Exception e) { + Log.e("ProfileFragment", "Erro no carregamento", e); + } + }); } catch (Exception e) { - Log.e("ProfileFragment", "Image load error: " + e.getMessage()); + Log.e("ProfileFragment", "Erro ao iniciar", e); + } + } + + private void updateUIFromDocument(com.google.firebase.firestore.DocumentSnapshot doc) { + if (!isAdded() || binding == null) return; + + // Nome + String n = doc.getString("nome_completo"); + if (n == null) n = doc.getString("name"); + if (n == null) n = doc.getString("nome"); + if (n != null && !n.isEmpty()) binding.profileName.setText(n); + + // Email + String e = doc.getString("email"); + if (e != null && !e.isEmpty()) binding.profileEmail.setText(e); + + // Idade + Long i = doc.getLong("idade"); + if (i != null) binding.profileAge.setText(String.valueOf(i)); + + // Nº Utente + String u = doc.getString("numero_utente"); + if (u == null) u = doc.getString("utenteNumber"); + if (u != null && !u.isEmpty()) binding.profileUtente.setText(u); + + // Foto + String p = doc.getString("profilePictureUri"); + if (p != null && !p.isEmpty()) { + loadSafeImage(binding.profileImage, p); + } + + // Atualiza objeto local para o EditDialog + if (currentUser == null) currentUser = new User(); + if (n != null) currentUser.name = n; + if (e != null) currentUser.email = e; + if (i != null) currentUser.age = i.intValue(); + if (u != null) currentUser.utenteNumber = u; + if (p != null) currentUser.profilePictureUri = p; + currentUser.id = doc.getId(); + } + + private void loadSafeImage(ImageView view, String url) { + try { + if (!isAdded() || view == null || getContext() == null) return; + Glide.with(getContext()) + .load(url) + .placeholder(R.drawable.ic_user) + .circleCrop() + .into(view); + } catch (Exception e) { + Log.e("ProfileFragment", "Erro ao carregar imagem", e); } } private void showEditDialog() { - if (currentUser == null) { - Toast.makeText(getContext(), "Dados não carregados.", Toast.LENGTH_SHORT).show(); - return; - } + if (!isAdded()) return; + if (currentUser == null) currentUser = new User(); try { - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); + AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(), R.style.CustomDialogTheme); View dialogView = requireActivity().getLayoutInflater().inflate(R.layout.dialog_edit_profile, null); builder.setView(dialogView); AlertDialog dialog = builder.create(); + if (dialog.getWindow() != null) dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); EditText editName = dialogView.findViewById(R.id.edit_name); EditText editAge = dialogView.findViewById(R.id.edit_age); EditText editUtente = dialogView.findViewById(R.id.edit_utente); EditText editEmail = dialogView.findViewById(R.id.edit_email); dialogImageView = dialogView.findViewById(R.id.edit_profile_image); - - if (editName == null || dialogImageView == null) return; - editName.setText(currentUser.name); - editAge.setText(String.valueOf(currentUser.age)); - editUtente.setText(currentUser.utenteNumber); - editEmail.setText(currentUser.email); - - if (currentUser.profilePictureUri != null) loadSafeImage(dialogImageView, currentUser.profilePictureUri); - - dialogView.findViewById(R.id.button_change_photo).setOnClickListener(v -> pickMedia.launch("image/*")); - dialogImageView.setOnClickListener(v -> pickMedia.launch("image/*")); - dialogView.findViewById(R.id.button_change_password).setOnClickListener(v -> showChangePasswordDialog()); - - dialogView.findViewById(R.id.button_cancel).setOnClickListener(v -> dialog.dismiss()); - dialogView.findViewById(R.id.button_save).setOnClickListener(v -> { - String newName = editName.getText().toString().trim(); - String ageStr = editAge.getText().toString().trim(); - String newUtente = editUtente.getText().toString().trim(); - String newEmail = editEmail.getText().toString().trim(); - - if (newName.isEmpty() || ageStr.isEmpty() || newUtente.isEmpty() || newEmail.isEmpty()) { - Toast.makeText(getContext(), "Preencha todos os campos.", Toast.LENGTH_SHORT).show(); - return; - } - - currentUser.name = newName; - try { currentUser.age = Integer.parseInt(ageStr); } catch (Exception ignored) {} - currentUser.utenteNumber = newUtente; - - if (tempProfileUri != null) currentUser.profilePictureUri = tempProfileUri.toString(); - - db.collection("utilizadores").document(currentUser.id).set(currentUser) - .addOnSuccessListener(aVoid -> { - Toast.makeText(getContext(), "Perfil atualizado!", Toast.LENGTH_SHORT).show(); - loadUserData(); - dialog.dismiss(); - }) - .addOnFailureListener(e -> Toast.makeText(getContext(), "Erro ao guardar.", Toast.LENGTH_SHORT).show()); - }); - - dialog.show(); - } catch (Exception e) { - Toast.makeText(getContext(), "Erro ao abrir edição.", Toast.LENGTH_SHORT).show(); - } - } - - private void showChangePasswordDialog() { - AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - View view = requireActivity().getLayoutInflater().inflate(R.layout.dialog_change_password, null); - builder.setView(view); - AlertDialog dialog = builder.create(); - - EditText editNewPassword = view.findViewById(R.id.new_password); - view.findViewById(R.id.button_save_password).setOnClickListener(v -> { - String newPass = editNewPassword.getText().toString(); - if (newPass.length() < 6) { - Toast.makeText(getContext(), "Mínimo 6 caracteres.", Toast.LENGTH_SHORT).show(); - return; + // Preenchimento Seguro + if (editName != null) editName.setText(currentUser.name); + if (editAge != null) editAge.setText(String.valueOf(currentUser.age)); + if (editUtente != null) editUtente.setText(currentUser.utenteNumber); + if (editEmail != null) editEmail.setText(currentUser.email); + if (dialogImageView != null && currentUser.profilePictureUri != null) { + loadSafeImage(dialogImageView, currentUser.profilePictureUri); } - if (auth.getCurrentUser() != null) { - auth.getCurrentUser().updatePassword(newPass).addOnCompleteListener(task -> { - if (task.isSuccessful()) { - Toast.makeText(getContext(), "Sucesso!", Toast.LENGTH_SHORT).show(); - dialog.dismiss(); + + // Listeners + View btnPhoto = dialogView.findViewById(R.id.button_change_photo); + if (btnPhoto != null) btnPhoto.setOnClickListener(v -> pickMedia.launch("image/*")); + if (dialogImageView != null) dialogImageView.setOnClickListener(v -> pickMedia.launch("image/*")); + + View btnPass = dialogView.findViewById(R.id.button_change_password); + if (btnPass != null) btnPass.setOnClickListener(v -> showChangePasswordDialog()); + + View btnCancel = dialogView.findViewById(R.id.button_cancel); + if (btnCancel != null) btnCancel.setOnClickListener(v -> dialog.dismiss()); + + View btnSave = dialogView.findViewById(R.id.button_save); + if (btnSave != null) { + btnSave.setOnClickListener(v -> { + btnSave.setEnabled(false); + if (tempProfileUri != null) { + uploadPhotoAndSave(editName.getText().toString(), editAge.getText().toString(), editUtente.getText().toString(), editEmail.getText().toString(), dialog); } else { - Toast.makeText(getContext(), "Erro: " + task.getException().getMessage(), Toast.LENGTH_LONG).show(); + saveDataToFirestore(editName.getText().toString(), editAge.getText().toString(), editUtente.getText().toString(), editEmail.getText().toString(), currentUser.profilePictureUri, dialog); } }); } - }); - view.findViewById(R.id.button_cancel_password).setOnClickListener(v -> dialog.dismiss()); - dialog.show(); + dialog.show(); + } catch (Throwable t) { + Toast.makeText(getContext(), "Erro: " + t.toString(), Toast.LENGTH_LONG).show(); + } + } + + private void uploadPhotoAndSave(String n, String a, String u, String e, AlertDialog d) { + StorageReference ref = storage.getReference().child("perfil_pacientes/" + userId + ".jpg"); + ref.putFile(tempProfileUri).addOnSuccessListener(task -> { + ref.getDownloadUrl().addOnSuccessListener(uri -> saveDataToFirestore(n, a, u, e, uri.toString(), d)); + }).addOnFailureListener(err -> { + Toast.makeText(getContext(), "Erro foto", Toast.LENGTH_SHORT).show(); + saveDataToFirestore(n, a, u, e, currentUser.profilePictureUri, d); + }); + } + + private void saveDataToFirestore(String name, String age, String utente, String email, String photo, AlertDialog d) { + Map map = new HashMap<>(); + map.put("nome_completo", name); + map.put("email", email); + try { map.put("idade", Integer.parseInt(age)); } catch (Exception ignored) { + map.put("idade", 0); + } + map.put("numero_utente", utente); + map.put("tipo", "paciente"); + if (photo != null) map.put("profilePictureUri", photo); + + com.google.firebase.firestore.WriteBatch batch = db.batch(); + batch.set(db.collection("utilizadores").document(userId), map, com.google.firebase.firestore.SetOptions.merge()); + batch.set(db.collection("Pacientes").document(userId), map, com.google.firebase.firestore.SetOptions.merge()); + + batch.commit().addOnSuccessListener(aVoid -> { + if (isAdded()) { + Toast.makeText(getContext(), "Perfil Atualizado com Sucesso!", Toast.LENGTH_SHORT).show(); + loadUserData(); + d.dismiss(); + tempProfileUri = null; + } + }).addOnFailureListener(e -> { + if (isAdded()) { + Toast.makeText(getContext(), "Erro ao guardar no Firebase", Toast.LENGTH_SHORT).show(); + d.dismiss(); + } + }); + } + + private void showChangePasswordDialog() { + Toast.makeText(getContext(), "Funcionalidade de Password em breve.", Toast.LENGTH_SHORT).show(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; } } diff --git a/app/src/main/res/layout/dialog_edit_profile.xml b/app/src/main/res/layout/dialog_edit_profile.xml index 377fc5d..211e03e 100644 --- a/app/src/main/res/layout/dialog_edit_profile.xml +++ b/app/src/main/res/layout/dialog_edit_profile.xml @@ -1,128 +1,194 @@ - + android:layout_height="wrap_content" + app:cardCornerRadius="28dp" + app:cardElevation="0dp" + android:layout_margin="12dp"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content"> + android:orientation="vertical" + android:padding="24dp"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:text="Alterar Palavra-passe" + android:textAllCaps="false" + android:textColor="?attr/colorPrimary" + android:layout_marginBottom="24dp"/> - + + android:orientation="horizontal"> + + + + + + - - - + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 3531da1..4a2324d 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -6,51 +6,51 @@ android:background="@color/background_color"> - - - - - + android:orientation="horizontal" + android:paddingTop="56dp" + android:paddingBottom="40dp" + android:paddingHorizontal="28dp" + android:background="@drawable/bg_gradient_header" + android:elevation="12dp" + app:layout_constraintTop_toTopOf="parent"> + + + + + + app:layout_constraintTop_toBottomOf="@id/header_bg"> - 50% + + + +