From ae62000ff3f45e02c50b78386a898c15af8442fe Mon Sep 17 00:00:00 2001 From: 230421 <230421@epvc.pt> Date: Tue, 20 Jan 2026 15:49:42 +0000 Subject: [PATCH] refactor: Update settings UI to use color resources and refine MaterialCardView styling. --- .../vdcscore/ui/definicoes/ContaActivity.java | 207 +++++++------ app/src/main/res/layout/activity_conta.xml | 292 ++++++++---------- .../main/res/layout/fragment_definicoes.xml | 60 ++-- 3 files changed, 268 insertions(+), 291 deletions(-) diff --git a/app/src/main/java/com/example/vdcscore/ui/definicoes/ContaActivity.java b/app/src/main/java/com/example/vdcscore/ui/definicoes/ContaActivity.java index 22b35b8..3cf664a 100644 --- a/app/src/main/java/com/example/vdcscore/ui/definicoes/ContaActivity.java +++ b/app/src/main/java/com/example/vdcscore/ui/definicoes/ContaActivity.java @@ -55,17 +55,17 @@ public class ContaActivity extends AppCompatActivity { private Button btnLogout; private View btnChangePhoto; private View cardImageContainer; - + private FirebaseAuth mAuth; private FirebaseStorage mStorage; private StorageReference storageRef; private ProgressDialog progressDialog; - + private ActivityResultLauncher imagePickerLauncher; private ActivityResultLauncher permissionLauncher; private Uri selectedImageUri; private String imageUrlFromGoogle; - + private static final String PREFS_NAME = "LoginPrefs"; @Override @@ -73,7 +73,7 @@ public class ContaActivity extends AppCompatActivity { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_conta); - + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); @@ -99,18 +99,31 @@ public class ContaActivity extends AppCompatActivity { imageProfile = findViewById(R.id.imageProfile); editName = findViewById(R.id.editName); editImageUrl = findViewById(R.id.editImageUrl); + // textEmail removed from class fields if not used, or kept if used. + // In XML it is present as @+id/textEmail inside cardSecurity. textEmail = findViewById(R.id.textEmail); + btnSaveAll = findViewById(R.id.btnSaveAll); btnLoadImageUrl = findViewById(R.id.btnLoadImageUrl); btnChangeEmail = findViewById(R.id.btnChangeEmail); btnChangePassword = findViewById(R.id.btnChangePassword); btnLogout = findViewById(R.id.btnLogout); - btnChangePhoto = findViewById(R.id.btnChangePhoto); + + // New FAB for editing photo + btnChangePhoto = findViewById(R.id.fabEditPhoto); + // Also allow clicking the text + View btnChangePhotoText = findViewById(R.id.btnChangePhoto); + cardImageContainer = findViewById(R.id.cardImageContainer); - + progressDialog = new ProgressDialog(this); progressDialog.setMessage("A processar..."); progressDialog.setCancelable(false); + + // Setup listeners here or in setupListeners() + if (btnChangePhotoText != null) { + btnChangePhotoText.setOnClickListener(v -> openImagePicker()); + } } private void initFirebase() { @@ -121,43 +134,42 @@ public class ContaActivity extends AppCompatActivity { private void setupImagePicker() { imagePickerLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == RESULT_OK && result.getData() != null) { - selectedImageUri = result.getData().getData(); - if (selectedImageUri != null) { - try { - InputStream inputStream = getContentResolver().openInputStream(selectedImageUri); - if (inputStream != null) { - Bitmap bitmap = BitmapFactory.decodeStream(inputStream); - inputStream.close(); - if (bitmap != null) { - imageProfile.setImageBitmap(bitmap); - uploadProfileImage(); - } else { - Toast.makeText(this, "Erro ao carregar imagem", Toast.LENGTH_SHORT).show(); + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK && result.getData() != null) { + selectedImageUri = result.getData().getData(); + if (selectedImageUri != null) { + try { + InputStream inputStream = getContentResolver().openInputStream(selectedImageUri); + if (inputStream != null) { + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + inputStream.close(); + if (bitmap != null) { + imageProfile.setImageBitmap(bitmap); + uploadProfileImage(); + } else { + Toast.makeText(this, "Erro ao carregar imagem", Toast.LENGTH_SHORT).show(); + } } + } catch (Exception e) { + Toast.makeText(this, "Erro ao carregar imagem: " + e.getMessage(), Toast.LENGTH_SHORT) + .show(); } - } catch (Exception e) { - Toast.makeText(this, "Erro ao carregar imagem: " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } - } - } - ); + }); } private void setupPermissionLauncher() { permissionLauncher = registerForActivityResult( - new ActivityResultContracts.RequestPermission(), - isGranted -> { - if (isGranted) { - openImagePicker(); - } else { - Toast.makeText(this, "Permissão necessária para selecionar imagem", Toast.LENGTH_SHORT).show(); - } - } - ); + new ActivityResultContracts.RequestPermission(), + isGranted -> { + if (isGranted) { + openImagePicker(); + } else { + Toast.makeText(this, "Permissão necessária para selecionar imagem", Toast.LENGTH_SHORT).show(); + } + }); } private void loadUserData() { @@ -167,12 +179,12 @@ public class ContaActivity extends AppCompatActivity { if (textEmail != null) { textEmail.setText(user.getEmail() != null ? user.getEmail() : ""); } - + // Carregar nome if (editName != null && user.getDisplayName() != null && !user.getDisplayName().isEmpty()) { editName.setText(user.getDisplayName()); } - + // Carregar foto de perfil if (user.getPhotoUrl() != null) { loadProfileImage(user.getPhotoUrl().toString()); @@ -217,8 +229,9 @@ public class ContaActivity extends AppCompatActivity { private void loadProfileImageFromStorage() { FirebaseUser user = mAuth.getCurrentUser(); - if (user == null) return; - + if (user == null) + return; + StorageReference profileImageRef = storageRef.child("profile_images/" + user.getUid() + ".jpg"); profileImageRef.getDownloadUrl().addOnSuccessListener(uri -> { loadProfileImage(uri.toString()); @@ -267,22 +280,23 @@ public class ContaActivity extends AppCompatActivity { } private void loadImageFromUrl() { - if (editImageUrl == null) return; - + if (editImageUrl == null) + return; + String imageUrl = editImageUrl.getText().toString().trim(); - + if (imageUrl.isEmpty()) { Toast.makeText(this, "Por favor, insira uma URL válida", Toast.LENGTH_SHORT).show(); return; } - + if (!imageUrl.startsWith("http://") && !imageUrl.startsWith("https://")) { Toast.makeText(this, "URL inválida. Deve começar com http:// ou https://", Toast.LENGTH_SHORT).show(); return; } - + progressDialog.show(); - + // Carregar imagem da URL new Thread(() -> { try { @@ -292,7 +306,7 @@ public class ContaActivity extends AppCompatActivity { connection.connect(); InputStream input = connection.getInputStream(); Bitmap bitmap = BitmapFactory.decodeStream(input); - + runOnUiThread(() -> { progressDialog.dismiss(); if (bitmap != null && imageProfile != null) { @@ -305,7 +319,7 @@ public class ContaActivity extends AppCompatActivity { Toast.makeText(this, "Erro ao carregar imagem da URL", Toast.LENGTH_SHORT).show(); } }); - + if (input != null) { input.close(); } @@ -317,19 +331,20 @@ public class ContaActivity extends AppCompatActivity { } }).start(); } - + private void updateProfileWithImageUrl(String imageUrl) { FirebaseUser user = mAuth.getCurrentUser(); - if (user == null) return; - + if (user == null) + return; + progressDialog.show(); - + try { Uri imageUri = Uri.parse(imageUrl); UserProfileChangeRequest profileUpdates = new UserProfileChangeRequest.Builder() .setPhotoUri(imageUri) .build(); - + user.updateProfile(profileUpdates).addOnCompleteListener(task -> { progressDialog.dismiss(); if (task.isSuccessful()) { @@ -337,8 +352,8 @@ public class ContaActivity extends AppCompatActivity { editImageUrl.setText(""); loadProfileImage(imageUrl); } else { - Toast.makeText(this, "Erro ao atualizar foto: " + - (task.getException() != null ? task.getException().getMessage() : "Erro desconhecido"), + Toast.makeText(this, "Erro ao atualizar foto: " + + (task.getException() != null ? task.getException().getMessage() : "Erro desconhecido"), Toast.LENGTH_SHORT).show(); } }); @@ -349,13 +364,15 @@ public class ContaActivity extends AppCompatActivity { } private void uploadProfileImage() { - if (selectedImageUri == null) return; - + if (selectedImageUri == null) + return; + FirebaseUser user = mAuth.getCurrentUser(); - if (user == null) return; - + if (user == null) + return; + progressDialog.show(); - + InputStream inputStream = null; try { inputStream = getContentResolver().openInputStream(selectedImageUri); @@ -364,14 +381,14 @@ public class ContaActivity extends AppCompatActivity { Toast.makeText(this, "Erro ao abrir imagem", Toast.LENGTH_SHORT).show(); return; } - + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); if (bitmap == null) { progressDialog.dismiss(); Toast.makeText(this, "Erro ao processar imagem", Toast.LENGTH_SHORT).show(); return; } - + // Redimensionar se muito grande int maxSize = 1024; if (bitmap.getWidth() > maxSize || bitmap.getHeight() > maxSize) { @@ -380,29 +397,30 @@ public class ContaActivity extends AppCompatActivity { int newHeight = Math.round(bitmap.getHeight() * scale); bitmap = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); } - + ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos); byte[] imageData = baos.toByteArray(); - + // Upload para Firebase Storage StorageReference profileImageRef = storageRef.child("profile_images/" + user.getUid() + ".jpg"); UploadTask uploadTask = profileImageRef.putBytes(imageData); - + uploadTask.addOnSuccessListener(taskSnapshot -> { profileImageRef.getDownloadUrl().addOnSuccessListener(uri -> { UserProfileChangeRequest profileUpdates = new UserProfileChangeRequest.Builder() .setPhotoUri(uri) .build(); - + user.updateProfile(profileUpdates).addOnCompleteListener(task -> { progressDialog.dismiss(); if (task.isSuccessful()) { Toast.makeText(this, "Foto de perfil atualizada com sucesso!", Toast.LENGTH_SHORT).show(); loadProfileImage(uri.toString()); } else { - Toast.makeText(this, "Erro ao atualizar foto: " + - (task.getException() != null ? task.getException().getMessage() : "Erro desconhecido"), + Toast.makeText(this, "Erro ao atualizar foto: " + + (task.getException() != null ? task.getException().getMessage() + : "Erro desconhecido"), Toast.LENGTH_SHORT).show(); } }); @@ -414,7 +432,7 @@ public class ContaActivity extends AppCompatActivity { progressDialog.dismiss(); Toast.makeText(this, "Erro ao fazer upload: " + e.getMessage(), Toast.LENGTH_SHORT).show(); }); - + } catch (Exception e) { progressDialog.dismiss(); Toast.makeText(this, "Erro ao processar imagem: " + e.getMessage(), Toast.LENGTH_SHORT).show(); @@ -430,53 +448,54 @@ public class ContaActivity extends AppCompatActivity { } private void saveAllChanges() { - if (editName == null) return; - + if (editName == null) + return; + String newName = editName.getText().toString().trim(); - + if (newName.isEmpty()) { Toast.makeText(this, "Por favor, insira um nome", Toast.LENGTH_SHORT).show(); return; } - + FirebaseUser user = mAuth.getCurrentUser(); if (user == null) { Toast.makeText(this, "Utilizador não autenticado", Toast.LENGTH_SHORT).show(); return; } - + progressDialog.show(); - + UserProfileChangeRequest profileUpdates = new UserProfileChangeRequest.Builder() .setDisplayName(newName) .build(); - + user.updateProfile(profileUpdates).addOnCompleteListener(task -> { progressDialog.dismiss(); if (task.isSuccessful()) { Toast.makeText(this, "Alterações guardadas com sucesso!", Toast.LENGTH_SHORT).show(); } else { - Toast.makeText(this, "Erro ao guardar alterações: " + - (task.getException() != null ? task.getException().getMessage() : "Erro desconhecido"), + Toast.makeText(this, "Erro ao guardar alterações: " + + (task.getException() != null ? task.getException().getMessage() : "Erro desconhecido"), Toast.LENGTH_SHORT).show(); } }); } - + private void sendPasswordResetEmail() { FirebaseUser user = mAuth.getCurrentUser(); if (user == null || user.getEmail() == null) { Toast.makeText(this, "Utilizador não autenticado", Toast.LENGTH_SHORT).show(); return; } - + progressDialog.show(); - + mAuth.sendPasswordResetEmail(user.getEmail()) .addOnCompleteListener(task -> { progressDialog.dismiss(); if (task.isSuccessful()) { - Toast.makeText(this, + Toast.makeText(this, "Email de alteração de password enviado! Verifique a sua caixa de entrada.", Toast.LENGTH_LONG).show(); } else { @@ -488,36 +507,38 @@ public class ContaActivity extends AppCompatActivity { } }); } - + private void sendEmailVerificationForEmailChange() { FirebaseUser user = mAuth.getCurrentUser(); if (user == null || user.getEmail() == null) { Toast.makeText(this, "Utilizador não autenticado", Toast.LENGTH_SHORT).show(); return; } - + progressDialog.show(); - + // Enviar email de verificação para alterar email user.sendEmailVerification() .addOnCompleteListener(task -> { progressDialog.dismiss(); if (task.isSuccessful()) { - Toast.makeText(this, + Toast.makeText(this, "Email de verificação enviado! Verifique a sua caixa de entrada para alterar o email.", Toast.LENGTH_LONG).show(); } else { - // Se não conseguir enviar email de verificação, enviar email de reset de password + // Se não conseguir enviar email de verificação, enviar email de reset de + // password // que pode ser usado para alterar o email através do Firebase Console mAuth.sendPasswordResetEmail(user.getEmail()) .addOnCompleteListener(resetTask -> { if (resetTask.isSuccessful()) { - Toast.makeText(this, + Toast.makeText(this, "Email enviado! Verifique a sua caixa de entrada para instruções de alteração.", Toast.LENGTH_LONG).show(); } else { String errorMessage = "Erro ao enviar email!"; - if (resetTask.getException() != null && resetTask.getException().getMessage() != null) { + if (resetTask.getException() != null + && resetTask.getException().getMessage() != null) { errorMessage = "Erro: " + resetTask.getException().getMessage(); } Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show(); @@ -526,7 +547,7 @@ public class ContaActivity extends AppCompatActivity { } }); } - + private void showLogoutConfirmation() { new AlertDialog.Builder(this) .setTitle("Terminar Sessão") @@ -535,19 +556,19 @@ public class ContaActivity extends AppCompatActivity { .setNegativeButton("Cancelar", null) .show(); } - + private void logoutUser() { // Limpar credenciais guardadas SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.clear(); editor.apply(); - + // Fazer logout do Firebase mAuth.signOut(); - + Toast.makeText(this, "Sessão terminada", Toast.LENGTH_SHORT).show(); - + // Voltar para o login Intent intent = new Intent(ContaActivity.this, LoginActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); diff --git a/app/src/main/res/layout/activity_conta.xml b/app/src/main/res/layout/activity_conta.xml index ebc72d3..d858ac8 100644 --- a/app/src/main/res/layout/activity_conta.xml +++ b/app/src/main/res/layout/activity_conta.xml @@ -6,7 +6,7 @@ android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="#F5F5F5" + android:background="@color/background_light" tools:context=".ui.definicoes.ContaActivity"> + app:navigationIconTint="@color/white" /> @@ -45,10 +45,10 @@ android:id="@+id/cardProfilePhoto" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="24dp" - app:cardCornerRadius="20dp" - app:cardElevation="4dp" - app:cardBackgroundColor="#FFFFFF" + android:layout_marginBottom="20dp" + app:cardCornerRadius="16dp" + app:cardElevation="2dp" + app:cardBackgroundColor="@color/surface_card" app:strokeWidth="0dp"> + android:textColor="@color/primary_color" + android:layout_marginBottom="24dp" /> - + + + - + + + - - - - + + + @@ -127,9 +132,9 @@ app:boxCornerRadiusBottomStart="12dp" app:boxCornerRadiusBottomEnd="12dp" app:boxStrokeWidth="1dp" - app:boxStrokeColor="#BDBDBD" + app:boxStrokeColor="@color/divider" app:startIconDrawable="@android:drawable/ic_menu_view" - app:startIconTint="#616161" + app:startIconTint="@color/text_secondary" app:boxBackgroundMode="outline" style="@style/Widget.Material3.TextInputLayout.OutlinedBox"> @@ -138,8 +143,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:inputType="textUri" - android:textColor="#212121" - android:textColorHint="#9E9E9E" + android:textColor="@color/text_primary" + android:textColorHint="@color/text_secondary" android:textSize="14sp" android:padding="14dp" android:background="@android:color/transparent" @@ -150,15 +155,13 @@