Merge remote-tracking branch 'origin/main'
# Conflicts: # .idea/deploymentTargetSelector.xml # app/src/main/java/com/example/pap_findu/LocationService.java # app/src/main/java/com/example/pap_findu/login_activity.java # app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java
This commit is contained in:
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -7,10 +7,10 @@
|
||||
</SelectionState>
|
||||
<SelectionState runConfigName="login_activity">
|
||||
<option name="selectionMode" value="DIALOG" />
|
||||
<DropdownSelection timestamp="2026-03-12T15:46:37.841197Z">
|
||||
<DropdownSelection timestamp="2026-03-17T14:22:15.472961Z">
|
||||
<Target type="DEFAULT_BOOT">
|
||||
<handle>
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Medium_Phone_2.avd" />
|
||||
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Pixel_7.avd" />
|
||||
</handle>
|
||||
</Target>
|
||||
</DropdownSelection>
|
||||
|
||||
@@ -2,46 +2,55 @@ package com.example.pap_findu;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.example.pap_findu.models.User;
|
||||
import com.google.firebase.database.DatabaseReference;
|
||||
import com.google.firebase.database.FirebaseDatabase;
|
||||
|
||||
import androidx.activity.EdgeToEdge;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.example.pap_findu.models.User;
|
||||
import com.google.android.gms.tasks.OnCompleteListener;
|
||||
import com.google.android.gms.tasks.Task;
|
||||
import com.google.firebase.auth.AuthResult;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
import com.google.firebase.database.DatabaseReference;
|
||||
import com.google.firebase.database.FirebaseDatabase;
|
||||
|
||||
public class CriarConta extends AppCompatActivity {
|
||||
|
||||
private EditText inputFullName;
|
||||
private EditText emailEditText;
|
||||
private EditText passwordEditText;
|
||||
private EditText inputConfirmPassword;
|
||||
private EditText inputFullName, emailEditText, passwordEditText, inputConfirmPassword;
|
||||
private CheckBox checkTerms;
|
||||
private Button btnCreateAccount;
|
||||
private TextView loginLink;
|
||||
private com.google.firebase.auth.FirebaseAuth mAuth;
|
||||
private FirebaseAuth mAuth;
|
||||
private DatabaseReference mDatabase;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
EdgeToEdge.enable(this);
|
||||
setContentView(R.layout.activity_criar_conta);
|
||||
|
||||
// Ajuste de Padding para EdgeToEdge
|
||||
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;
|
||||
});
|
||||
|
||||
// Initialize views
|
||||
// 1. Inicializar Firebase
|
||||
mAuth = FirebaseAuth.getInstance();
|
||||
mDatabase = FirebaseDatabase.getInstance().getReference("users");
|
||||
|
||||
// 2. Inicializar Views
|
||||
inputFullName = findViewById(R.id.inputFullName);
|
||||
emailEditText = findViewById(R.id.emailEditText);
|
||||
passwordEditText = findViewById(R.id.passwordEditText);
|
||||
@@ -50,78 +59,69 @@ public class CriarConta extends AppCompatActivity {
|
||||
btnCreateAccount = findViewById(R.id.btnCreateAccount);
|
||||
loginLink = findViewById(R.id.loginLink);
|
||||
|
||||
mAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
|
||||
// 3. Botão Criar Conta
|
||||
btnCreateAccount.setOnClickListener(v -> validarECriar());
|
||||
|
||||
// Set click listener for the create account button
|
||||
btnCreateAccount.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
String fullName = inputFullName.getText().toString();
|
||||
String email = emailEditText.getText().toString();
|
||||
String password = passwordEditText.getText().toString();
|
||||
String confirmPassword = inputConfirmPassword.getText().toString();
|
||||
// 4. Link para Login
|
||||
loginLink.setOnClickListener(v -> {
|
||||
startActivity(new Intent(CriarConta.this, login_activity.class));
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
||||
private void validarECriar() {
|
||||
String fullName = inputFullName.getText().toString().trim();
|
||||
String email = emailEditText.getText().toString().trim();
|
||||
String password = passwordEditText.getText().toString().trim();
|
||||
String confirmPassword = inputConfirmPassword.getText().toString().trim();
|
||||
|
||||
// Validações Básicas
|
||||
if (fullName.isEmpty() || email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()) {
|
||||
Toast.makeText(CriarConta.this, "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show();
|
||||
} else if (!password.equals(confirmPassword)) {
|
||||
Toast.makeText(CriarConta.this, "As palavras-passe não coincidem.", Toast.LENGTH_SHORT).show();
|
||||
} else if (!checkTerms.isChecked()) {
|
||||
Toast.makeText(CriarConta.this, "Você deve concordar com os Termos de Serviço.", Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
} else {
|
||||
Toast.makeText(this, "Preencha todos os campos!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!password.equals(confirmPassword)) {
|
||||
Toast.makeText(this, "As passwords não coincidem!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkTerms.isChecked()) {
|
||||
Toast.makeText(this, "Aceite os termos de serviço!", Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
|
||||
// Criar no FirebaseAuth
|
||||
mAuth.createUserWithEmailAndPassword(email, password)
|
||||
.addOnCompleteListener(CriarConta.this,
|
||||
new com.google.android.gms.tasks.OnCompleteListener<com.google.firebase.auth.AuthResult>() {
|
||||
@Override
|
||||
public void onComplete(
|
||||
@androidx.annotation.NonNull com.google.android.gms.tasks.Task<com.google.firebase.auth.AuthResult> task) {
|
||||
.addOnCompleteListener(this, task -> {
|
||||
if (task.isSuccessful()) {
|
||||
// Sign in success, update UI with the signed-in user's information
|
||||
Toast.makeText(CriarConta.this, "Conta criada com sucesso!",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
com.google.firebase.auth.FirebaseUser firebaseUser = mAuth
|
||||
.getCurrentUser();
|
||||
|
||||
// Save user data to Realtime Database
|
||||
if (firebaseUser != null) {
|
||||
String userId = firebaseUser.getUid();
|
||||
DatabaseReference mDatabase = FirebaseDatabase.getInstance()
|
||||
.getReference("users");
|
||||
User user = new User(fullName, email);
|
||||
|
||||
mDatabase.child(userId).setValue(user)
|
||||
.addOnCompleteListener(task1 -> {
|
||||
if (!task1.isSuccessful()) {
|
||||
Toast.makeText(CriarConta.this,
|
||||
"Falha ao salvar dados do perfil.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
salvarDadosNoPerfil(fullName, email);
|
||||
} else {
|
||||
Toast.makeText(CriarConta.this, "Erro: " + task.getException().getMessage(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void salvarDadosNoPerfil(String fullName, String email) {
|
||||
FirebaseUser firebaseUser = mAuth.getCurrentUser();
|
||||
if (firebaseUser != null) {
|
||||
String userId = firebaseUser.getUid();
|
||||
|
||||
// Usamos o teu modelo User (Mapping: fullName -> name)
|
||||
User user = new User(fullName, email);
|
||||
|
||||
mDatabase.child(userId).setValue(user)
|
||||
.addOnCompleteListener(task -> {
|
||||
if (task.isSuccessful()) {
|
||||
Toast.makeText(CriarConta.this, "Conta configurada com sucesso!", Toast.LENGTH_SHORT).show();
|
||||
// Só avança se os dados foram salvos
|
||||
Intent intent = new Intent(CriarConta.this, MainActivity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
} else {
|
||||
// If sign in fails, display a message to the user.
|
||||
Toast.makeText(CriarConta.this,
|
||||
"Falha ao criar conta: " + task.getException().getMessage(),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
Toast.makeText(CriarConta.this, "Erro ao salvar perfil no banco de dados.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Set click listener for the login link text
|
||||
loginLink.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Navigate back to the login activity
|
||||
Intent intent = new Intent(CriarConta.this, login_activity.class);
|
||||
startActivity(intent);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
package com.example.pap_findu;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Base64;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -15,9 +18,6 @@ import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.example.pap_findu.models.User;
|
||||
import com.example.pap_findu.ui.profile.ProfileFragment;
|
||||
import com.google.android.gms.tasks.OnFailureListener;
|
||||
import com.google.android.gms.tasks.OnSuccessListener;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
@@ -27,25 +27,27 @@ 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 com.google.firebase.storage.FirebaseStorage;
|
||||
import com.google.firebase.storage.StorageReference;
|
||||
import com.google.firebase.storage.UploadTask;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
public class EditProfileActivity extends AppCompatActivity {
|
||||
|
||||
private ImageView btnBack;
|
||||
private ImageView editProfileImage;
|
||||
private View btnChangePhoto;
|
||||
private TextView btnChangePhoto;
|
||||
private TextInputEditText editName;
|
||||
private TextInputEditText editEmail;
|
||||
private TextInputEditText editPhone;
|
||||
|
||||
// Botoes
|
||||
private MaterialButton btnSaveProfile;
|
||||
private MaterialButton btnChangePassword;
|
||||
|
||||
private FirebaseAuth mAuth;
|
||||
private DatabaseReference mDatabase;
|
||||
private StorageReference mStorageRef;
|
||||
private FirebaseUser currentUser;
|
||||
private Uri selectedImageUri;
|
||||
|
||||
// NOVO: Variável para guardar o texto gigante da imagem (Base64) em vez do Uri
|
||||
private String base64ImageString = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -55,7 +57,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||
mAuth = FirebaseAuth.getInstance();
|
||||
currentUser = mAuth.getCurrentUser();
|
||||
mDatabase = FirebaseDatabase.getInstance().getReference("users");
|
||||
mStorageRef = FirebaseStorage.getInstance().getReference("profile_images");
|
||||
// O StorageReference foi removido pois já não precisamos dele!
|
||||
|
||||
if (currentUser == null) {
|
||||
finish();
|
||||
@@ -73,8 +75,8 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||
btnChangePhoto = findViewById(R.id.btnChangePhoto);
|
||||
editName = findViewById(R.id.editName);
|
||||
editEmail = findViewById(R.id.editEmail);
|
||||
editPhone = findViewById(R.id.editPhone);
|
||||
btnSaveProfile = findViewById(R.id.btnSaveProfile);
|
||||
btnChangePassword = findViewById(R.id.btnChangePassword);
|
||||
}
|
||||
|
||||
private void loadUserData() {
|
||||
@@ -85,14 +87,23 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||
if (user != null) {
|
||||
editName.setText(user.getName());
|
||||
editEmail.setText(user.getEmail());
|
||||
editPhone.setText(user.getPhone());
|
||||
|
||||
if (user.getProfileImageUrl() != null && !user.getProfileImageUrl().isEmpty()) {
|
||||
try {
|
||||
// Tenta descodificar a imagem de formato Base64 (Texto para Imagem)
|
||||
byte[] decodedString = Base64.decode(user.getProfileImageUrl(), Base64.DEFAULT);
|
||||
Glide.with(EditProfileActivity.this)
|
||||
.load(decodedString)
|
||||
.placeholder(R.drawable.logo)
|
||||
.into(editProfileImage);
|
||||
} catch (Exception e) {
|
||||
// Se der erro (ex: se na DB estiver um link antigo em vez de Base64), tenta carregar normalmente
|
||||
Glide.with(EditProfileActivity.this)
|
||||
.load(user.getProfileImageUrl())
|
||||
.placeholder(R.drawable.logo)
|
||||
.into(editProfileImage);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback to auth data if DB is empty
|
||||
editName.setText(currentUser.getDisplayName());
|
||||
@@ -110,13 +121,26 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||
private void setupListeners() {
|
||||
btnBack.setOnClickListener(v -> finish());
|
||||
|
||||
// Lógica de mudar a senha
|
||||
btnChangePassword.setOnClickListener(v -> {
|
||||
String emailAddress = editEmail.getText().toString().trim();
|
||||
if (!emailAddress.isEmpty()) {
|
||||
enviarEmailRecuperacao(emailAddress);
|
||||
} else {
|
||||
Toast.makeText(this, "Erro: Email não encontrado.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
|
||||
// Image Picker Launcher
|
||||
ActivityResultLauncher<Intent> imagePickerLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
||||
selectedImageUri = result.getData().getData();
|
||||
Uri selectedImageUri = result.getData().getData();
|
||||
editProfileImage.setImageURI(selectedImageUri);
|
||||
|
||||
// NOVO: Faz a conversão mágica da imagem para texto!
|
||||
converterImagemParaTexto(selectedImageUri);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -128,10 +152,41 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||
btnSaveProfile.setOnClickListener(v -> saveProfile());
|
||||
}
|
||||
|
||||
// NOVO: Função que pega na foto, encolhe-a e transforma num texto gigante
|
||||
private void converterImagemParaTexto(Uri imageUri) {
|
||||
try {
|
||||
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);
|
||||
// Encolher para 300x300 pixeis para não exceder o limite da Realtime Database
|
||||
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, 300, 300, true);
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, baos); // Qualidade 70%
|
||||
byte[] imageBytes = baos.toByteArray();
|
||||
|
||||
// Guarda a imagem final como uma string
|
||||
base64ImageString = Base64.encodeToString(imageBytes, Base64.DEFAULT);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Toast.makeText(this, "Erro ao processar imagem", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
// Função para pedir ao Firebase para enviar email de reset
|
||||
private void enviarEmailRecuperacao(String email) {
|
||||
btnChangePassword.setEnabled(false);
|
||||
mAuth.sendPasswordResetEmail(email)
|
||||
.addOnCompleteListener(task -> {
|
||||
btnChangePassword.setEnabled(true);
|
||||
if (task.isSuccessful()) {
|
||||
Toast.makeText(EditProfileActivity.this, "Email enviado! Verifique a sua caixa de entrada.", Toast.LENGTH_LONG).show();
|
||||
} else {
|
||||
Toast.makeText(EditProfileActivity.this, "Erro ao enviar email.", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void saveProfile() {
|
||||
String name = editName.getText().toString().trim();
|
||||
String email = editEmail.getText().toString().trim();
|
||||
String phone = editPhone.getText().toString().trim();
|
||||
|
||||
if (name.isEmpty() || email.isEmpty()) {
|
||||
Toast.makeText(this, "Nome e Email são obrigatórios", Toast.LENGTH_SHORT).show();
|
||||
@@ -139,32 +194,13 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
btnSaveProfile.setEnabled(false);
|
||||
btnSaveProfile.setText("Salvando...");
|
||||
btnSaveProfile.setText("A Salvar...");
|
||||
|
||||
if (selectedImageUri != null) {
|
||||
uploadImageAndSaveUser(name, email, phone);
|
||||
} else {
|
||||
saveUserToDb(name, email, phone, null);
|
||||
}
|
||||
// Guardamos o perfil passando a String Base64 (se houver), em vez de fazer upload para o Storage
|
||||
saveUserToDb(name, email, base64ImageString);
|
||||
}
|
||||
|
||||
private void uploadImageAndSaveUser(String name, String email, String phone) {
|
||||
final StorageReference fileRef = mStorageRef.child(currentUser.getUid() + ".jpg");
|
||||
|
||||
fileRef.putFile(selectedImageUri)
|
||||
.addOnSuccessListener(taskSnapshot -> fileRef.getDownloadUrl().addOnSuccessListener(uri -> {
|
||||
String imageUrl = uri.toString();
|
||||
saveUserToDb(name, email, phone, imageUrl);
|
||||
}))
|
||||
.addOnFailureListener(e -> {
|
||||
Toast.makeText(EditProfileActivity.this, "Erro ao enviar imagem: " + e.getMessage(),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
btnSaveProfile.setEnabled(true);
|
||||
btnSaveProfile.setText("Salvar Alterações");
|
||||
});
|
||||
}
|
||||
|
||||
private void saveUserToDb(String name, String email, String phone, String imageUrl) {
|
||||
private void saveUserToDb(String name, String email, String imageUrlBase64) {
|
||||
mDatabase.child(currentUser.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
|
||||
@Override
|
||||
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
||||
@@ -174,9 +210,10 @@ public class EditProfileActivity extends AppCompatActivity {
|
||||
|
||||
user.setName(name);
|
||||
user.setEmail(email);
|
||||
user.setPhone(phone);
|
||||
if (imageUrl != null) {
|
||||
user.setProfileImageUrl(imageUrl);
|
||||
|
||||
// Se houver uma imagem convertida em Base64, guardamos na base de dados
|
||||
if (imageUrlBase64 != null) {
|
||||
user.setProfileImageUrl(imageUrlBase64);
|
||||
}
|
||||
|
||||
mDatabase.child(currentUser.getUid()).setValue(user)
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
package com.example.pap_findu;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.location.Location;
|
||||
import android.os.BatteryManager;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.Service;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.google.android.gms.location.FusedLocationProviderClient;
|
||||
import com.google.android.gms.location.LocationCallback;
|
||||
@@ -46,7 +50,7 @@ public class LocationService extends Service {
|
||||
.getString("child_access_code", null);
|
||||
|
||||
if (childCode != null) {
|
||||
// CAMINHO EXATO: Direto na raiz, como na tua print
|
||||
// Caminho direto na raiz para o monitoramento em tempo real
|
||||
databaseReference = FirebaseDatabase.getInstance().getReference(childCode).child("live_location");
|
||||
}
|
||||
}
|
||||
@@ -56,8 +60,9 @@ public class LocationService extends Service {
|
||||
createNotificationChannel();
|
||||
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
|
||||
.setContentTitle("FindU Ativo")
|
||||
.setContentText("A partilhar localização real...")
|
||||
.setContentText("A partilhar localização e bateria em tempo real...")
|
||||
.setSmallIcon(R.mipmap.ic_launcher)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build();
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
@@ -74,8 +79,7 @@ public class LocationService extends Service {
|
||||
}
|
||||
|
||||
private void requestLocationUpdates() {
|
||||
// Pedimos atualizações de 2 em 2 segundos
|
||||
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 2000)
|
||||
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
|
||||
.setMinUpdateDistanceMeters(0)
|
||||
.build();
|
||||
|
||||
@@ -83,11 +87,9 @@ public class LocationService extends Service {
|
||||
@Override
|
||||
public void onLocationResult(@NonNull LocationResult locationResult) {
|
||||
for (Location location : locationResult.getLocations()) {
|
||||
// FILTRO CRÍTICO: Se for a latitude da Google (37.42...), IGNORA.
|
||||
// Filtro para ignorar a localização padrão do emulador (Califórnia)
|
||||
if (Math.abs(location.getLatitude() - 37.4219) > 0.001) {
|
||||
updateFirebase(location);
|
||||
} else {
|
||||
Log.d("LocationService", "Ignorada localização falsa da Califórnia");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,19 +98,33 @@ public class LocationService extends Service {
|
||||
try {
|
||||
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
|
||||
} catch (SecurityException e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LocationService", "Erro de permissão GPS");
|
||||
}
|
||||
}
|
||||
|
||||
private void updateFirebase(Location location) {
|
||||
if (databaseReference != null) {
|
||||
// --- LÓGICA DA BATERIA ---
|
||||
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||
Intent batteryStatus = registerReceiver(null, ifilter);
|
||||
|
||||
int level = -1;
|
||||
if (batteryStatus != null) {
|
||||
int rawLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
||||
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
|
||||
level = (int) ((rawLevel / (float) scale) * 100);
|
||||
}
|
||||
// -------------------------
|
||||
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("latitude", location.getLatitude());
|
||||
data.put("longitude", location.getLongitude());
|
||||
data.put("bateria", level + "%"); // Envia ex: "85%"
|
||||
data.put("last_updated", System.currentTimeMillis());
|
||||
|
||||
databaseReference.setValue(data);
|
||||
Log.d("LocationService", "Enviada localização REAL: " + location.getLatitude());
|
||||
databaseReference.setValue(data).addOnFailureListener(e -> {
|
||||
Log.e("LocationService", "Erro ao enviar para Firebase: " + e.getMessage());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,8 +140,13 @@ public class LocationService extends Service {
|
||||
|
||||
private void createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel("LocationChannel", "GPS", NotificationManager.IMPORTANCE_LOW);
|
||||
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||
NotificationChannel channel = new NotificationChannel(
|
||||
"LocationChannel",
|
||||
"Monitoramento",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
);
|
||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||
if (manager != null) manager.createNotificationChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -18,6 +19,8 @@ import androidx.navigation.ui.NavigationUI;
|
||||
|
||||
import com.example.pap_findu.databinding.ActivityMainBinding;
|
||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -25,24 +28,16 @@ import java.util.List;
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private ActivityMainBinding binding;
|
||||
private FirebaseAuth mAuth;
|
||||
|
||||
// ========================================================
|
||||
// 1. GESTOR DE PERMISSÕES (O "Contrato" com o Android)
|
||||
// ========================================================
|
||||
private final ActivityResultLauncher<String[]> requestPermissionLauncher =
|
||||
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissions -> {
|
||||
Boolean fineLocationGranted = permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
|
||||
Boolean coarseLocationGranted = permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false);
|
||||
|
||||
if (fineLocationGranted != null && fineLocationGranted) {
|
||||
// Permissão precisa concedida, podemos iniciar o serviço!
|
||||
startLocationService();
|
||||
} else if (coarseLocationGranted != null && coarseLocationGranted) {
|
||||
// Permissão aproximada concedida, podemos iniciar o serviço!
|
||||
startLocationService();
|
||||
decidirInicioDeServico();
|
||||
} else {
|
||||
// O utilizador recusou as permissões. A app não pode iniciar o serviço.
|
||||
Toast.makeText(this, "Permissão de localização negada. O rastreamento não funcionará.", Toast.LENGTH_LONG).show();
|
||||
Toast.makeText(this, "Sem permissão, o rastreamento não funcionará.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -53,8 +48,17 @@ public class MainActivity extends AppCompatActivity {
|
||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
mAuth = FirebaseAuth.getInstance();
|
||||
|
||||
// Configuração da Navegação (Bottom Bar)
|
||||
setupNavigation();
|
||||
|
||||
// Verifica permissões antes de tudo
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
|
||||
private void setupNavigation() {
|
||||
BottomNavigationView navView = binding.navView;
|
||||
// Configurações da barra de navegação
|
||||
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
|
||||
R.id.navigation_map, R.id.navigation_zones, R.id.navigation_alerts,
|
||||
R.id.navigation_history, R.id.navigation_profile)
|
||||
@@ -66,25 +70,16 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (navHostFragment != null) {
|
||||
NavController navController = navHostFragment.getNavController();
|
||||
NavigationUI.setupWithNavController(binding.navView, navController);
|
||||
//teste
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// 2. PEDE PERMISSÕES ANTES DE LIGAR O MOTOR
|
||||
// ========================================================
|
||||
checkAndRequestPermissions();
|
||||
}
|
||||
|
||||
private void checkAndRequestPermissions() {
|
||||
List<String> permissionsNeeded = new ArrayList<>();
|
||||
|
||||
// Verifica a permissão de localização
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||
permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
||||
permissionsNeeded.add(Manifest.permission.ACCESS_COARSE_LOCATION);
|
||||
}
|
||||
|
||||
// Se for Android 13 ou superior, verifica a permissão de notificações (Obrigatório para Foreground Services)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||
permissionsNeeded.add(Manifest.permission.POST_NOTIFICATIONS);
|
||||
@@ -92,17 +87,32 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
if (!permissionsNeeded.isEmpty()) {
|
||||
// Se faltam permissões, pede-as ao utilizador! (Isto vai mostrar a janelinha)
|
||||
requestPermissionLauncher.launch(permissionsNeeded.toArray(new String[0]));
|
||||
} else {
|
||||
// Se já tem todas as permissões, arranca logo com o serviço de localização!
|
||||
decidirInicioDeServico();
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
// LÓGICA DE DECISÃO: PAI VS FILHO
|
||||
// ========================================================
|
||||
private void decidirInicioDeServico() {
|
||||
FirebaseUser user = mAuth.getCurrentUser();
|
||||
|
||||
if (user != null) {
|
||||
if (user.isAnonymous()) {
|
||||
// É O FILHO: Precisamos de enviar a localização e bateria
|
||||
startLocationService();
|
||||
Log.d("MainActivity", "Modo Filho: Serviço de GPS iniciado.");
|
||||
} else {
|
||||
// É O PAI: Apenas carregamos a interface (o MapFragment tratará do resto)
|
||||
Log.d("MainActivity", "Modo Pai: GPS desligado para poupar bateria.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startLocationService() {
|
||||
Intent serviceIntent = new Intent(this, LocationService.class);
|
||||
// O ContextCompat resolve automaticamente os problemas de compatibilidade de versões do Android!
|
||||
ContextCompat.startForegroundService(this, serviceIntent);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,17 @@
|
||||
package com.example.pap_findu.ui.map;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -28,6 +29,7 @@ import com.google.android.gms.maps.model.LatLng;
|
||||
import com.google.android.gms.maps.model.Marker;
|
||||
import com.google.android.gms.maps.model.MarkerOptions;
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
import com.google.android.material.card.MaterialCardView;
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
import com.google.firebase.auth.FirebaseUser;
|
||||
@@ -36,6 +38,7 @@ 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 com.google.firebase.firestore.DocumentSnapshot;
|
||||
import com.google.firebase.firestore.FirebaseFirestore;
|
||||
|
||||
public class MapFragment extends Fragment implements OnMapReadyCallback {
|
||||
@@ -49,6 +52,16 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
||||
private FloatingActionButton btnAbrirChat;
|
||||
private MaterialButton btnSOS;
|
||||
|
||||
// --- VARIÁVEIS PARA O CARTÃO DE INFORMAÇÃO PREMIUM ---
|
||||
private View cardChildInfo;
|
||||
private TextView txtChildName;
|
||||
private TextView txtChildBattery;
|
||||
|
||||
// --- VARIÁVEIS PARA A ZONA DE STATUS (VERDE/LARANJA) ---
|
||||
private MaterialCardView cardZoneStatus, cardZoneIconBG;
|
||||
private ImageView iconZone;
|
||||
private TextView txtZoneTitle, txtZoneSubtitle;
|
||||
|
||||
private GoogleMap mMap;
|
||||
private Marker childMarker;
|
||||
private DatabaseReference locationRef;
|
||||
@@ -56,6 +69,9 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
||||
|
||||
private String currentChildId = null;
|
||||
|
||||
// NOVA VARIÁVEL: Guarda o nome real do filho
|
||||
private String currentChildName = "A carregar...";
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
@@ -70,8 +86,21 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
||||
btnAbrirChat = root.findViewById(R.id.btnAbrirChat);
|
||||
btnSOS = root.findViewById(R.id.btnSOS);
|
||||
|
||||
// --- INICIALIZAR AS VIEWS DO CARTÃO ---
|
||||
cardChildInfo = root.findViewById(R.id.cardChildInfo);
|
||||
txtChildName = root.findViewById(R.id.txtChildName);
|
||||
txtChildBattery = root.findViewById(R.id.txtChildBattery);
|
||||
|
||||
// --- INICIALIZAR AS VIEWS DA ZONA ---
|
||||
cardZoneStatus = root.findViewById(R.id.cardZoneStatus);
|
||||
cardZoneIconBG = root.findViewById(R.id.cardZoneIconBG);
|
||||
iconZone = root.findViewById(R.id.iconZone);
|
||||
txtZoneTitle = root.findViewById(R.id.txtZoneTitle);
|
||||
txtZoneSubtitle = root.findViewById(R.id.txtZoneSubtitle);
|
||||
|
||||
if (btnAbrirChat != null) btnAbrirChat.setVisibility(View.GONE);
|
||||
if (btnSOS != null) btnSOS.setVisibility(View.GONE);
|
||||
if (cardChildInfo != null) cardChildInfo.setVisibility(View.GONE); // Começa escondido
|
||||
|
||||
if (btnAddChild != null) {
|
||||
btnAddChild.setOnClickListener(v -> openAddChildScreen());
|
||||
@@ -104,7 +133,20 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
||||
.get()
|
||||
.addOnCompleteListener(task -> {
|
||||
if (task.isSuccessful() && task.getResult() != null && !task.getResult().isEmpty()) {
|
||||
currentChildId = task.getResult().getDocuments().get(0).getString("accessCode");
|
||||
|
||||
// Extraímos o documento todo
|
||||
DocumentSnapshot document = task.getResult().getDocuments().get(0);
|
||||
currentChildId = document.getString("accessCode");
|
||||
|
||||
// LER O NOME REAL DA BASE DE DADOS FIRESTORE
|
||||
if (document.contains("name")) {
|
||||
currentChildName = document.getString("name");
|
||||
} else if (document.contains("nome")) {
|
||||
currentChildName = document.getString("nome");
|
||||
} else {
|
||||
currentChildName = "Filho";
|
||||
}
|
||||
|
||||
if (currentChildId != null) {
|
||||
showMapState();
|
||||
setupChildButtons();
|
||||
@@ -166,9 +208,19 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
||||
private void startListeningToChildLocation() {
|
||||
if (currentChildId == null || mMap == null) return;
|
||||
|
||||
// CORREÇÃO: Caminho direto na raiz como aparece no teu Firebase
|
||||
locationRef = FirebaseDatabase.getInstance().getReference(currentChildId).child("live_location");
|
||||
FirebaseUser user = auth.getCurrentUser();
|
||||
|
||||
// SE FOR O PAI, MOSTRAMOS O CARTÃO DO FILHO
|
||||
if (user != null && !user.isAnonymous()) {
|
||||
if (cardChildInfo != null) cardChildInfo.setVisibility(View.VISIBLE);
|
||||
|
||||
// CORREÇÃO: Agora usa estritamente o nome lido da base de dados (Ex: "Jorge")
|
||||
if (txtChildName != null) {
|
||||
txtChildName.setText(currentChildName);
|
||||
}
|
||||
}
|
||||
|
||||
locationRef = FirebaseDatabase.getInstance().getReference(currentChildId).child("live_location");
|
||||
if (locationListener != null) locationRef.removeEventListener(locationListener);
|
||||
|
||||
locationListener = new ValueEventListener() {
|
||||
@@ -178,36 +230,67 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
||||
Double lat = snapshot.child("latitude").getValue(Double.class);
|
||||
Double lng = snapshot.child("longitude").getValue(Double.class);
|
||||
|
||||
// LÊ A BATERIA DO FIREBASE
|
||||
String bateria = snapshot.child("bateria").getValue(String.class);
|
||||
|
||||
// ATUALIZA O TEXTO DA BATERIA
|
||||
if (bateria != null && txtChildBattery != null) {
|
||||
txtChildBattery.setText(bateria);
|
||||
}
|
||||
|
||||
if (lat != null && lng != null) {
|
||||
LatLng childPos = new LatLng(lat, lng);
|
||||
|
||||
// FILTRO: Só atualiza o mapa se a localização NÃO for a da Google HQ (Califórnia)
|
||||
if (Math.abs(lat - 37.4219) > 0.001) {
|
||||
if (childMarker == null) {
|
||||
childMarker = mMap.addMarker(new MarkerOptions()
|
||||
.position(childPos)
|
||||
.title("Filho")
|
||||
.title(currentChildName) // O pino no mapa também vai dizer o nome do filho
|
||||
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
|
||||
} else {
|
||||
childMarker.setPosition(childPos);
|
||||
childMarker.setTitle(currentChildName);
|
||||
}
|
||||
|
||||
// Move a câmara automaticamente para Vila do Conde
|
||||
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(childPos, 17f));
|
||||
Log.d("MapFragment", "Pai recebeu localização real: " + lat);
|
||||
} else {
|
||||
Log.d("MapFragment", "Pai ignorou localização de teste (Califórnia)");
|
||||
|
||||
// TESTE DE DESIGN
|
||||
atualizarStatusZona(true, "A atualizar localização...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override public void onCancelled(@NonNull DatabaseError error) {
|
||||
Log.e("MapFragment", "Erro ao ler Firebase: " + error.getMessage());
|
||||
}
|
||||
@Override public void onCancelled(@NonNull DatabaseError error) {}
|
||||
};
|
||||
locationRef.addValueEventListener(locationListener);
|
||||
}
|
||||
|
||||
// --- FUNÇÃO PARA MUDAR AS CORES DA ZONA (VERDE/LARANJA) ---
|
||||
private void atualizarStatusZona(boolean naZonaSegura, String subtitulo) {
|
||||
if (cardZoneStatus == null) return;
|
||||
|
||||
if (naZonaSegura) {
|
||||
// MODO VERDE: Dentro de Zona Segura
|
||||
cardZoneStatus.setCardBackgroundColor(Color.parseColor("#9DCA43"));
|
||||
cardZoneIconBG.setCardBackgroundColor(Color.parseColor("#EAF4D4"));
|
||||
iconZone.setColorFilter(Color.parseColor("#5F8B1A"));
|
||||
|
||||
txtZoneTitle.setText("Dentro de Zona Segura");
|
||||
txtZoneTitle.setTextColor(Color.parseColor("#2D4608"));
|
||||
txtZoneSubtitle.setText(subtitulo);
|
||||
txtZoneSubtitle.setTextColor(Color.parseColor("#486B11"));
|
||||
} else {
|
||||
// MODO LARANJA: Em Movimento / Fora de Zonas
|
||||
cardZoneStatus.setCardBackgroundColor(Color.parseColor("#FFA726"));
|
||||
cardZoneIconBG.setCardBackgroundColor(Color.parseColor("#FFE0B2"));
|
||||
iconZone.setColorFilter(Color.parseColor("#E65100"));
|
||||
|
||||
txtZoneTitle.setText("A movimentar-se");
|
||||
txtZoneTitle.setTextColor(Color.parseColor("#822B00"));
|
||||
txtZoneSubtitle.setText("Fora das zonas seguras");
|
||||
txtZoneSubtitle.setTextColor(Color.parseColor("#A63A00"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.example.pap_findu.ui.profile;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Base64;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -13,7 +14,6 @@ import androidx.fragment.app.Fragment;
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.example.pap_findu.EditProfileActivity;
|
||||
import com.example.pap_findu.R;
|
||||
import com.example.pap_findu.SecurityActivity;
|
||||
import com.example.pap_findu.databinding.FragmentProfileBinding;
|
||||
import com.example.pap_findu.models.User;
|
||||
import com.google.firebase.auth.FirebaseAuth;
|
||||
@@ -28,10 +28,10 @@ public class ProfileFragment extends Fragment {
|
||||
|
||||
private FragmentProfileBinding binding;
|
||||
private FirebaseAuth mAuth;
|
||||
private DatabaseReference mDatabase;
|
||||
private ValueEventListener mUserListener;
|
||||
private DatabaseReference mUserRef;
|
||||
private ValueEventListener mUserListener;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||
ViewGroup container, Bundle savedInstanceState) {
|
||||
binding = FragmentProfileBinding.inflate(inflater, container, false);
|
||||
@@ -41,40 +41,58 @@ public class ProfileFragment extends Fragment {
|
||||
FirebaseUser currentUser = mAuth.getCurrentUser();
|
||||
|
||||
if (currentUser == null) {
|
||||
// Should prompt login or handle error
|
||||
return root;
|
||||
}
|
||||
|
||||
mDatabase = FirebaseDatabase.getInstance().getReference("users");
|
||||
mUserRef = mDatabase.child(currentUser.getUid());
|
||||
|
||||
setupListeners();
|
||||
|
||||
// Listen for user data changes
|
||||
// Verifica se é o Filho (Anónimo)
|
||||
if (currentUser.isAnonymous()) {
|
||||
binding.profileName.setText("Conta de Criança");
|
||||
binding.profileEmail.setText("Modo de Monitorização Ativo");
|
||||
binding.profileImage.setImageResource(R.drawable.logo);
|
||||
|
||||
// Oculta opções que não fazem sentido para o Filho
|
||||
binding.layoutEditProfile.setVisibility(View.GONE);
|
||||
|
||||
} else {
|
||||
// É O PAI: Carrega os dados reais do Firebase
|
||||
mUserRef = FirebaseDatabase.getInstance().getReference("users").child(currentUser.getUid());
|
||||
|
||||
mUserListener = new ValueEventListener() {
|
||||
@Override
|
||||
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
||||
if (getContext() == null)
|
||||
return;
|
||||
if (getContext() == null || binding == null) return;
|
||||
|
||||
if (snapshot.exists()) {
|
||||
User user = snapshot.getValue(User.class);
|
||||
if (user != null) {
|
||||
binding.profileName.setText(user.getName());
|
||||
binding.profileEmail.setText(user.getEmail());
|
||||
|
||||
// MAGIA: LER A FOTO EM FORMATO DE TEXTO (BASE64)
|
||||
if (user.getProfileImageUrl() != null && !user.getProfileImageUrl().isEmpty()) {
|
||||
try {
|
||||
byte[] decodedString = Base64.decode(user.getProfileImageUrl(), Base64.DEFAULT);
|
||||
Glide.with(ProfileFragment.this)
|
||||
.load(decodedString)
|
||||
.placeholder(R.drawable.logo)
|
||||
.into(binding.profileImage);
|
||||
} catch (Exception e) {
|
||||
// Fallback caso a imagem seja um link antigo e não Base64
|
||||
try {
|
||||
Glide.with(ProfileFragment.this)
|
||||
.load(user.getProfileImageUrl())
|
||||
.placeholder(R.drawable.logo) // Make sure logo exists or use R.mipmap.ic_launcher
|
||||
.placeholder(R.drawable.logo)
|
||||
.into(binding.profileImage);
|
||||
} catch (Exception ex) {
|
||||
binding.profileImage.setImageResource(R.drawable.logo);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
binding.profileImage.setImageResource(R.drawable.logo);
|
||||
}
|
||||
} else {
|
||||
// Fallback to Auth data if DB is empty
|
||||
binding.profileName.setText(
|
||||
currentUser.getDisplayName() != null ? currentUser.getDisplayName() : "Utilizador");
|
||||
binding.profileEmail.setText(currentUser.getEmail());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +102,7 @@ public class ProfileFragment extends Fragment {
|
||||
Toast.makeText(getContext(), "Erro ao carregar perfil", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
@@ -93,10 +112,6 @@ public class ProfileFragment extends Fragment {
|
||||
startActivity(new Intent(getActivity(), EditProfileActivity.class));
|
||||
});
|
||||
|
||||
binding.layoutSecurity.setOnClickListener(v -> {
|
||||
startActivity(new Intent(getActivity(), SecurityActivity.class));
|
||||
});
|
||||
|
||||
binding.btnLogout.setOnClickListener(v -> {
|
||||
mAuth.signOut();
|
||||
Intent intent = new Intent(getActivity(), com.example.pap_findu.login_activity.class);
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
android:background="#F6F7FB"
|
||||
tools:context=".EditProfileActivity">
|
||||
|
||||
<!-- Header -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -48,7 +47,6 @@
|
||||
android:padding="24dp"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<!-- Profile Image -->
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
@@ -62,32 +60,35 @@
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.MaterialComponents.MediumComponent"
|
||||
android:src="@drawable/logo" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:background="@drawable/bg_circle_button"
|
||||
android:padding="6dp"
|
||||
android:layout_marginEnd="-8dp"
|
||||
android:layout_marginBottom="-8dp"
|
||||
android:src="@android:drawable/ic_menu_camera"
|
||||
app:tint="#FFFFFF" />
|
||||
app:backgroundTint="#3B82F6"
|
||||
app:fabSize="mini"
|
||||
app:tint="#FFFFFF"
|
||||
android:clickable="false" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btnChangePhoto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="Alterar Foto"
|
||||
android:textColor="#3B82F6"
|
||||
android:textStyle="bold"
|
||||
android:clickable="true"
|
||||
android:padding="8dp"
|
||||
android:background="?attr/selectableItemBackground" />
|
||||
|
||||
<!-- Form -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:hint="Nome Completo"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
@@ -102,35 +103,34 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="Email"
|
||||
android:hint="Email (Não editável)"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editEmail"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="textEmailAddress" />
|
||||
android:inputType="textEmailAddress"
|
||||
android:enabled="false"
|
||||
android:textColor="#9CA3AF"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnChangePassword"
|
||||
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:hint="Telefone (Opcional)"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editPhone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="phone" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="MUDAR PALAVRA-PASSE"
|
||||
android:textColor="#3B82F6"
|
||||
app:strokeColor="#3B82F6"
|
||||
app:cornerRadius="12dp" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSaveProfile"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:text="Salvar Alterações"
|
||||
app:cornerRadius="12dp" />
|
||||
|
||||
|
||||
@@ -39,6 +39,199 @@
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/cardChildInfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="20dp"
|
||||
android:layout_marginTop="32dp"
|
||||
app:cardCornerRadius="24dp"
|
||||
app:cardElevation="8dp"
|
||||
app:cardBackgroundColor="#FFFFFF"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
app:cardCornerRadius="28dp"
|
||||
app:cardBackgroundColor="#F0F4FF"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="12dp"
|
||||
android:src="@android:drawable/ic_menu_myplaces"
|
||||
app:tint="#A0AABF"/>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtChildName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Nome do Filho"
|
||||
android:textColor="#1F2937"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="2dp">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="10dp"
|
||||
app:cardCornerRadius="5dp"
|
||||
app:cardBackgroundColor="#4CAF50"
|
||||
app:cardElevation="0dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Online"
|
||||
android:textColor="#4CAF50"
|
||||
android:textStyle="bold"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginStart="6dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="end">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@android:drawable/ic_lock_idle_charging"
|
||||
app:tint="#4CAF50"/>
|
||||
<TextView
|
||||
android:id="@+id/txtChildBattery"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="--%"
|
||||
android:textColor="#1F2937"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginStart="4dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginTop="6dp">
|
||||
<ImageView
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:src="@android:drawable/ic_menu_mylocation"
|
||||
app:tint="#3B82F6"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="GPS"
|
||||
android:textColor="#1F2937"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginStart="4dp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/cardZoneStatus"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:cardCornerRadius="12dp"
|
||||
app:cardBackgroundColor="#9DCA43"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:padding="12dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/cardZoneIconBG"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
app:cardCornerRadius="18dp"
|
||||
app:cardBackgroundColor="#EAF4D4"
|
||||
app:cardElevation="0dp">
|
||||
<ImageView
|
||||
android:id="@+id/iconZone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"
|
||||
android:src="@android:drawable/ic_menu_mylocation"
|
||||
app:tint="#5F8B1A"/>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="12dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtZoneTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Dentro de Zona Segura"
|
||||
android:textColor="#2D4608"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtZoneSubtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="A carregar..."
|
||||
android:textColor="#486B11"
|
||||
android:textSize="13sp"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/btnAbrirChat"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -49,8 +242,7 @@
|
||||
app:tint="@android:color/white"
|
||||
android:src="@android:drawable/ic_dialog_email"
|
||||
app:layout_constraintBottom_toTopOf="@+id/btnSOS"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:contentDescription="Abrir Chat" />
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btnSOS"
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
<!-- Account Settings Section -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -88,7 +87,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Item 1 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutEditProfile"
|
||||
android:layout_width="match_parent"
|
||||
@@ -121,47 +119,9 @@
|
||||
android:rotation="0" />
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="#F3F4F6" />
|
||||
|
||||
<!-- Item 2 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/layoutSecurity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:src="@android:drawable/ic_lock_idle_lock"
|
||||
app:tint="#4B5563" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_weight="1"
|
||||
android:text="Segurança e Senha"
|
||||
android:textColor="#1F2937"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@android:drawable/ic_media_play"
|
||||
app:tint="#9CA3AF"
|
||||
android:rotation="0" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
<!-- App Settings Section -->
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -189,7 +149,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<!-- Item 1 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -224,7 +183,6 @@
|
||||
android:layout_height="1dp"
|
||||
android:background="#F3F4F6" />
|
||||
|
||||
<!-- Item 2 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
@@ -274,7 +232,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center"
|
||||
android:text="FindU Inc. © 2024"
|
||||
android:text="FindU Inc. © 2026"
|
||||
android:textColor="#D1D5DB"
|
||||
android:textSize="12sp" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user