diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 4b5858a..3bfd88b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -46,6 +46,7 @@ dependencies { implementation(libs.credentials.play.services.auth) implementation(libs.googleid) implementation(libs.firebase.database) + implementation(libs.firebase.storage) implementation(libs.lifecycle.livedata.ktx) implementation(libs.lifecycle.viewmodel.ktx) implementation(libs.navigation.fragment) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 087c54b..279b3a2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,10 @@ + + + + imagePickerLauncher; + private Uri selectedImageUri; + @Override protected void onCreate(Bundle savedInstanceState) { 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); return insets; }); + + // Inicializar componentes + initViews(); + initFirebase(); + setupImagePicker(); + loadUserData(); + setupListeners(); } -} \ No newline at end of file + + private void initViews() { + MaterialToolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + toolbar.setNavigationOnClickListener(v -> finish()); + + imageProfile = findViewById(R.id.imageProfile); + editName = findViewById(R.id.editName); + textEmail = findViewById(R.id.textEmail); + btnSaveName = findViewById(R.id.btnSaveName); + btnChangePhoto = findViewById(R.id.btnChangePhoto); + cardImageContainer = findViewById(R.id.cardImageContainer); + + progressDialog = new ProgressDialog(this); + progressDialog.setMessage("A processar..."); + progressDialog.setCancelable(false); + } + + private void initFirebase() { + mAuth = FirebaseAuth.getInstance(); + mStorage = FirebaseStorage.getInstance(); + storageRef = mStorage.getReference(); + } + + 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); + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + imageProfile.setImageBitmap(bitmap); + uploadProfileImage(); + } catch (Exception e) { + Toast.makeText(this, "Erro ao carregar imagem: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + } + } + ); + } + + private void loadUserData() { + FirebaseUser user = mAuth.getCurrentUser(); + if (user != null) { + // Carregar email + textEmail.setText(user.getEmail()); + + // Carregar nome + if (user.getDisplayName() != null && !user.getDisplayName().isEmpty()) { + editName.setText(user.getDisplayName()); + } + + // Carregar foto de perfil + if (user.getPhotoUrl() != null) { + loadProfileImage(user.getPhotoUrl().toString()); + } else { + // Tentar carregar do Storage + loadProfileImageFromStorage(); + } + } else { + Toast.makeText(this, "Utilizador não autenticado", Toast.LENGTH_SHORT).show(); + finish(); + } + } + + private void loadProfileImage(String imageUrl) { + // Usar uma biblioteca de imagens seria ideal aqui (Glide/Picasso) + // Por agora, vamos usar uma abordagem simples com Thread + new Thread(() -> { + try { + java.net.URL url = new java.net.URL(imageUrl); + java.net.HttpURLConnection connection = (java.net.HttpURLConnection) url.openConnection(); + connection.setDoInput(true); + connection.connect(); + InputStream input = connection.getInputStream(); + Bitmap bitmap = BitmapFactory.decodeStream(input); + runOnUiThread(() -> { + if (bitmap != null) { + imageProfile.setImageBitmap(bitmap); + } + }); + } catch (Exception e) { + // Se falhar, tentar carregar do Storage + runOnUiThread(() -> loadProfileImageFromStorage()); + } + }).start(); + } + + private void loadProfileImageFromStorage() { + FirebaseUser user = mAuth.getCurrentUser(); + if (user == null) return; + + StorageReference profileImageRef = storageRef.child("profile_images/" + user.getUid() + ".jpg"); + profileImageRef.getDownloadUrl().addOnSuccessListener(uri -> { + loadProfileImage(uri.toString()); + }).addOnFailureListener(e -> { + // Foto não encontrada, manter imagem padrão + }); + } + + private void setupListeners() { + btnChangePhoto.setOnClickListener(v -> openImagePicker()); + cardImageContainer.setOnClickListener(v -> openImagePicker()); + + btnSaveName.setOnClickListener(v -> saveUserName()); + } + + private void openImagePicker() { + if (checkPermissions()) { + Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + imagePickerLauncher.launch(intent); + } else { + requestPermissions(); + } + } + + private boolean checkPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) + == PackageManager.PERMISSION_GRANTED; + } else { + return ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + == PackageManager.PERMISSION_GRANTED; + } + } + + private void requestPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.READ_MEDIA_IMAGES}, + PERMISSION_REQUEST_CODE); + } else { + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, + PERMISSION_REQUEST_CODE); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == PERMISSION_REQUEST_CODE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + openImagePicker(); + } else { + Toast.makeText(this, "Permissão necessária para selecionar imagem", Toast.LENGTH_SHORT).show(); + } + } + } + + private void uploadProfileImage() { + if (selectedImageUri == null) return; + + FirebaseUser user = mAuth.getCurrentUser(); + if (user == null) return; + + progressDialog.show(); + + try { + // Comprimir imagem + InputStream inputStream = getContentResolver().openInputStream(selectedImageUri); + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + 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 -> { + // Obter URL da imagem + profileImageRef.getDownloadUrl().addOnSuccessListener(uri -> { + // Atualizar perfil do utilizador com a URL da foto + 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.LENGTH_SHORT).show(); + } + }); + }).addOnFailureListener(e -> { + progressDialog.dismiss(); + Toast.makeText(this, "Erro ao obter URL da imagem: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + }).addOnFailureListener(e -> { + 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(); + } + } + + private void saveUserName() { + 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, "Nome atualizado com sucesso!", Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(this, "Erro ao atualizar nome: " + + (task.getException() != null ? task.getException().getMessage() : "Erro desconhecido"), + Toast.LENGTH_SHORT).show(); + } + }); + } +} diff --git a/app/src/main/res/drawable/circle_decoration.xml b/app/src/main/res/drawable/circle_decoration.xml new file mode 100644 index 0000000..9f3c01d --- /dev/null +++ b/app/src/main/res/drawable/circle_decoration.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/circle_edit_background.xml b/app/src/main/res/drawable/circle_edit_background.xml new file mode 100644 index 0000000..0d3130f --- /dev/null +++ b/app/src/main/res/drawable/circle_edit_background.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/main/res/layout/activity_conta.xml b/app/src/main/res/layout/activity_conta.xml index 0b38e5a..753af84 100644 --- a/app/src/main/res/layout/activity_conta.xml +++ b/app/src/main/res/layout/activity_conta.xml @@ -1,10 +1,243 @@ - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +