From d6e9320b8089a5f33ace30145283b2bfdb1fe0ef Mon Sep 17 00:00:00 2001
From: 230408 <230408@epvc.pt>
Date: Thu, 12 Mar 2026 17:16:29 +0000
Subject: [PATCH] =?UTF-8?q?a=20localiza=C3=A7ao=20esta=20funcionando=20+?=
=?UTF-8?q?=20ou=20-=20certo?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/deploymentTargetSelector.xml | 4 +-
app/src/main/AndroidManifest.xml | 24 ++-
.../com/example/pap_findu/ChatActivity.java | 99 ++++++++---
.../example/pap_findu/LocationService.java | 120 +++++--------
.../com/example/pap_findu/login_activity.java | 100 +++++------
.../example/pap_findu/models/ChatMessage.java | 41 ++++-
.../example/pap_findu/ui/map/MapFragment.java | 162 ++++++++---------
app/src/main/res/layout/fragment_map.xml | 166 ++++++------------
8 files changed, 361 insertions(+), 355 deletions(-)
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index 3b4d6bd..944a55f 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -18,12 +18,12 @@
-
+
-
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9a9057d..dd316b9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -4,16 +4,23 @@
-
+
-
-
+
+
+
+
+
+
+
+
+
+ android:theme="@style/Theme.PAP_FindU"
+ tools:targetApi="34">
+
+
+
+
@@ -56,6 +70,7 @@
+
+
diff --git a/app/src/main/java/com/example/pap_findu/ChatActivity.java b/app/src/main/java/com/example/pap_findu/ChatActivity.java
index f9555a2..c971a4c 100644
--- a/app/src/main/java/com/example/pap_findu/ChatActivity.java
+++ b/app/src/main/java/com/example/pap_findu/ChatActivity.java
@@ -2,16 +2,24 @@ package com.example.pap_findu;
import android.os.Bundle;
import android.text.TextUtils;
-import android.view.View;
+import android.util.Log;
import android.widget.EditText;
import android.widget.ImageButton;
+import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_findu.adapters.ChatAdapter;
import com.example.pap_findu.models.ChatMessage;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.database.DataSnapshot;
+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 java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -28,50 +36,101 @@ public class ChatActivity extends AppCompatActivity {
private ChatAdapter adapter;
private List messageList;
+ private DatabaseReference chatRef;
+ private String currentUserId;
+ private String accessCode;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chat_activity);
+ // 1. Inicializar Firebase e User
+ currentUserId = FirebaseAuth.getInstance().getUid();
+
+ // Recuperamos o código da "sala" (partilhado entre pai e filho)
+ accessCode = getSharedPreferences("FindU_Prefs", MODE_PRIVATE)
+ .getString("child_access_code", null);
+
+ if (accessCode == null) {
+ Toast.makeText(this, "Erro: Sala de chat não encontrada", Toast.LENGTH_SHORT).show();
+ finish();
+ return;
+ }
+
+ // Caminho no Firebase: chats / 123456 / messages
+ chatRef = FirebaseDatabase.getInstance().getReference("chats")
+ .child(accessCode)
+ .child("messages");
+
+ // 2. Ligar Componentes do Teu Layout
recyclerChat = findViewById(R.id.recycler_chat);
editChatMessage = findViewById(R.id.edit_chat_message);
btnSend = findViewById(R.id.btnSend);
btnBack = findViewById(R.id.btnBack);
- // Initialize Message List with some dummy data
+ // 3. Configurar RecyclerView e Adapter
messageList = new ArrayList<>();
- messageList.add(new ChatMessage("Olá Miguel! Tudo bem?", true, "10:30"));
- messageList.add(new ChatMessage("Cheguei bem à escola.", false, "10:32"));
- messageList.add(new ChatMessage("Ainda bem! Qualquer coisa avisa.", true, "10:33"));
-
- // Setup Adapter
adapter = new ChatAdapter(messageList);
recyclerChat.setLayoutManager(new LinearLayoutManager(this));
recyclerChat.setAdapter(adapter);
- // Scroll to bottom
- recyclerChat.scrollToPosition(messageList.size() - 1);
-
- // Send Button Logic
+ // 4. Lógica do Botão Enviar
btnSend.setOnClickListener(v -> {
String text = editChatMessage.getText().toString().trim();
if (!TextUtils.isEmpty(text)) {
- sendMessage(text);
+ sendMessageToFirebase(text);
}
});
- // Back Button Logic
+ // 5. Lógica do Botão Voltar
btnBack.setOnClickListener(v -> finish());
+
+ // 6. Começar a ouvir mensagens em tempo real
+ listenForMessages();
}
- private void sendMessage(String text) {
+ private void sendMessageToFirebase(String text) {
String currentTime = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(new Date());
- ChatMessage newMessage = new ChatMessage(text, true, currentTime);
- messageList.add(newMessage);
- adapter.notifyItemInserted(messageList.size() - 1);
- recyclerChat.scrollToPosition(messageList.size() - 1);
+ // Criamos a mensagem com o ID de quem envia
+ ChatMessage newMessage = new ChatMessage(text, currentUserId, currentTime);
- editChatMessage.setText("");
+ // "Empurramos" para o Firebase (Gera um ID único automático)
+ chatRef.push().setValue(newMessage)
+ .addOnSuccessListener(aVoid -> {
+ editChatMessage.setText(""); // Limpa o campo se correu bem
+ })
+ .addOnFailureListener(e -> {
+ Toast.makeText(this, "Erro ao enviar", Toast.LENGTH_SHORT).show();
+ });
}
-}
+
+ private void listenForMessages() {
+ chatRef.addValueEventListener(new ValueEventListener() {
+ @Override
+ public void onDataChange(@NonNull DataSnapshot snapshot) {
+ messageList.clear();
+ for (DataSnapshot data : snapshot.getChildren()) {
+ ChatMessage msg = data.getValue(ChatMessage.class);
+ if (msg != null) {
+ // Se o senderId for o MEU, o adapter desenha à direita (azul)
+ msg.setSentByMe(msg.getSenderId().equals(currentUserId));
+ messageList.add(msg);
+ }
+ }
+ adapter.notifyDataSetChanged();
+
+ // Faz scroll automático para a última mensagem
+ if (messageList.size() > 0) {
+ recyclerChat.scrollToPosition(messageList.size() - 1);
+ }
+ }
+
+ @Override
+ public void onCancelled(@NonNull DatabaseError error) {
+ Log.e("ChatActivity", "Erro no Firebase: " + error.getMessage());
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap_findu/LocationService.java b/app/src/main/java/com/example/pap_findu/LocationService.java
index fb295b9..5927bb2 100644
--- a/app/src/main/java/com/example/pap_findu/LocationService.java
+++ b/app/src/main/java/com/example/pap_findu/LocationService.java
@@ -4,7 +4,6 @@ import android.Manifest;
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;
@@ -14,6 +13,7 @@ import android.location.Location;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -25,10 +25,6 @@ import com.google.android.gms.location.LocationRequest;
import com.google.android.gms.location.LocationResult;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.Priority;
-
-// IMPORTAÇÕES ADICIONADAS PARA A AUTENTICAÇÃO AUTOMÁTICA
-import com.google.firebase.auth.FirebaseAuth;
-import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
@@ -41,97 +37,62 @@ public class LocationService extends Service {
private LocationCallback locationCallback;
private DatabaseReference databaseReference;
- @Nullable
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
@Override
public void onCreate() {
super.onCreate();
-
- // Inicializa o cliente de GPS
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
- // =================================================================
- // AUTOMAÇÃO: Vai buscar o ID do Filho que está logado neste telemóvel
- // =================================================================
- FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
+ String childCode = getSharedPreferences("FindU_Prefs", MODE_PRIVATE)
+ .getString("child_access_code", null);
- if (currentUser != null) {
- String myUid = currentUser.getUid(); // Pega o ID único gerado pelo Firebase
-
- // Cria a referência da base de dados usando este ID dinâmico
- databaseReference = FirebaseDatabase.getInstance().getReference("users/" + myUid + "/live_location");
+ if (childCode != null) {
+ // CAMINHO EXATO: Direto na raiz, como na tua print
+ databaseReference = FirebaseDatabase.getInstance().getReference(childCode).child("live_location");
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- // 1. Cria o Canal e a Notificação IMEDIATAMENTE
createNotificationChannel();
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
.setContentTitle("FindU Ativo")
- .setContentText("A monitorizar a localização em tempo real...")
+ .setContentText("A partilhar localização real...")
.setSmallIcon(R.mipmap.ic_launcher)
.build();
- // 2. ENVOLVER TUDO NUM TRY-CATCH PARA EVITAR CRASHES NO ANDROID 14
- try {
- // O Android Q (API 29) e superior permite/exige especificar o tipo de serviço no código
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
- } else {
- startForeground(1, notification);
- }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
+ } else {
+ startForeground(1, notification);
+ }
- // 3. Verifica permissão ANTES de ligar o motor de GPS
- if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
- // Se tem permissão, arranca com a recolha de coordenadas!
- requestLocationUpdates();
- } else {
- // Se por acaso a permissão falhou, desliga-se silenciosamente
- stopForeground(true);
- stopSelf();
- }
-
- } catch (Exception e) {
- // Se o sistema operativo forçar uma paragem (muito comum em emuladores), apanhamos o erro aqui!
- e.printStackTrace();
- stopSelf();
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ requestLocationUpdates();
}
return START_STICKY;
}
- @SuppressWarnings("MissingPermission")
private void requestLocationUpdates() {
- // Configura a frequência do GPS (a cada 10 segundos, só se mover 5 metros)
- LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10000)
- .setMinUpdateDistanceMeters(5.0f)
+ // Pedimos atualizações de 2 em 2 segundos
+ LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 2000)
+ .setMinUpdateDistanceMeters(0)
.build();
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
- super.onLocationResult(locationResult);
for (Location location : locationResult.getLocations()) {
- // Prepara os dados
- Map locationData = new HashMap<>();
- locationData.put("latitude", location.getLatitude());
- locationData.put("longitude", location.getLongitude());
- locationData.put("last_updated", System.currentTimeMillis());
-
- // Verifica se o utilizador não é nulo antes de enviar para o Firebase
- if (databaseReference != null) {
- databaseReference.setValue(locationData);
+ // FILTRO CRÍTICO: Se for a latitude da Google (37.42...), IGNORA.
+ if (Math.abs(location.getLatitude() - 37.4219) > 0.001) {
+ updateFirebase(location);
+ } else {
+ Log.d("LocationService", "Ignorada localização falsa da Califórnia");
}
}
}
};
- // Outro try-catch de segurança para a comunicação com os serviços do Google (GMS)
try {
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
} catch (SecurityException e) {
@@ -139,27 +100,32 @@ public class LocationService extends Service {
}
}
- @Override
- public void onDestroy() {
- super.onDestroy();
- // Quando o serviço for desligado, para de usar o GPS para poupar bateria
- if (fusedLocationClient != null && locationCallback != null) {
- fusedLocationClient.removeLocationUpdates(locationCallback);
+ private void updateFirebase(Location location) {
+ if (databaseReference != null) {
+ Map data = new HashMap<>();
+ data.put("latitude", location.getLatitude());
+ data.put("longitude", location.getLongitude());
+ data.put("last_updated", System.currentTimeMillis());
+
+ databaseReference.setValue(data);
+ Log.d("LocationService", "Enviada localização REAL: " + location.getLatitude());
}
}
- // Cria o Canal de Notificação (obrigatório a partir do Android 8.0)
+ @Override
+ public void onDestroy() {
+ if (fusedLocationClient != null && locationCallback != null) {
+ fusedLocationClient.removeLocationUpdates(locationCallback);
+ }
+ super.onDestroy();
+ }
+
+ @Nullable @Override public IBinder onBind(Intent intent) { return null; }
+
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- NotificationChannel channel = new NotificationChannel(
- "LocationChannel",
- "Monitoramento de Localização",
- NotificationManager.IMPORTANCE_LOW // Low para não fazer barulho nem vibrar
- );
- NotificationManager manager = getSystemService(NotificationManager.class);
- if (manager != null) {
- manager.createNotificationChannel(channel);
- }
+ NotificationChannel channel = new NotificationChannel("LocationChannel", "GPS", NotificationManager.IMPORTANCE_LOW);
+ getSystemService(NotificationManager.class).createNotificationChannel(channel);
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap_findu/login_activity.java b/app/src/main/java/com/example/pap_findu/login_activity.java
index 114cfaf..0ee5771 100644
--- a/app/src/main/java/com/example/pap_findu/login_activity.java
+++ b/app/src/main/java/com/example/pap_findu/login_activity.java
@@ -1,6 +1,7 @@
package com.example.pap_findu;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.os.Bundle;
import android.text.InputType;
import android.text.TextUtils;
@@ -25,10 +26,11 @@ import com.google.firebase.Timestamp;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
-import com.google.firebase.firestore.DocumentSnapshot;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
public class login_activity extends AppCompatActivity {
@@ -37,7 +39,6 @@ public class login_activity extends AppCompatActivity {
private Button btnLogin;
private TextView criarContaTextView;
- // --- NOVAS VARIÁVEIS PARA O FILHO ---
private MaterialButton btnChildLogin;
private FirebaseFirestore db;
private FirebaseAuth mAuth;
@@ -54,68 +55,46 @@ public class login_activity extends AppCompatActivity {
return insets;
});
- // Inicializar Firebase Auth e Firestore
mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance();
- // Vincular componentes
emailEditText = findViewById(R.id.emailEditText);
passwordEditText = findViewById(R.id.passwordEditText);
btnLogin = findViewById(R.id.btnLogin);
criarContaTextView = findViewById(R.id.criarContaTextView);
-
- // Novo botão do filho (Certifique-se que adicionou no XML com id btnChildLogin)
btnChildLogin = findViewById(R.id.btnChildLogin);
- // --- LÓGICA 1: LOGIN DO PAI (Seu código original) ---
- btnLogin.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- String email = emailEditText.getText().toString();
- String password = passwordEditText.getText().toString();
+ btnLogin.setOnClickListener(v -> {
+ String email = emailEditText.getText().toString();
+ String password = passwordEditText.getText().toString();
- if (email.isEmpty() || password.isEmpty()) {
- Toast.makeText(login_activity.this, "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show();
- return;
- }
-
- mAuth.signInWithEmailAndPassword(email, password)
- .addOnCompleteListener(login_activity.this, new OnCompleteListener() {
- @Override
- public void onComplete(@NonNull Task task) {
- if (task.isSuccessful()) {
- Toast.makeText(login_activity.this, "Login com sucesso.", Toast.LENGTH_SHORT).show();
- goToMainActivity();
- } else {
- Toast.makeText(login_activity.this, "Falha na autenticação.", Toast.LENGTH_SHORT).show();
- }
- }
- });
+ if (email.isEmpty() || password.isEmpty()) {
+ Toast.makeText(login_activity.this, "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show();
+ return;
}
+
+ mAuth.signInWithEmailAndPassword(email, password)
+ .addOnCompleteListener(login_activity.this, task -> {
+ if (task.isSuccessful()) {
+ Toast.makeText(login_activity.this, "Login com sucesso.", Toast.LENGTH_SHORT).show();
+ goToMainActivity();
+ } else {
+ Toast.makeText(login_activity.this, "Falha na autenticação.", Toast.LENGTH_SHORT).show();
+ }
+ });
});
- // --- LÓGICA 2: LOGIN DO FILHO (Novo código) ---
- btnChildLogin.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- showChildLoginDialog();
- }
- });
+ btnChildLogin.setOnClickListener(v -> showChildLoginDialog());
- // --- LÓGICA 3: CRIAR CONTA ---
- criarContaTextView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(login_activity.this, CriarConta.class);
- startActivity(intent);
- }
+ criarContaTextView.setOnClickListener(v -> {
+ Intent intent = new Intent(login_activity.this, CriarConta.class);
+ startActivity(intent);
});
}
@Override
public void onStart() {
super.onStart();
- // Verifica se já está logado
FirebaseUser currentUser = mAuth.getCurrentUser();
if(currentUser != null){
goToMainActivity();
@@ -124,28 +103,21 @@ public class login_activity extends AppCompatActivity {
private void goToMainActivity() {
Intent intent = new Intent(login_activity.this, MainActivity.class);
- // Limpa a pilha para não voltar ao login com o botão voltar
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
- // ==========================================================
- // MÉTODOS DE LOGIN DO FILHO
- // ==========================================================
-
private void showChildLoginDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Acesso do Filho");
builder.setMessage("Digite o código de 6 dígitos gerado pelo pai:");
- // Cria uma caixa de texto dentro do alerta
final EditText inputCode = new EditText(this);
inputCode.setInputType(InputType.TYPE_CLASS_NUMBER);
inputCode.setHint("Ex: 123456");
builder.setView(inputCode);
- // Botão Entrar do Alerta
builder.setPositiveButton("Entrar", (dialog, which) -> {
String code = inputCode.getText().toString().trim();
if (!TextUtils.isEmpty(code)) {
@@ -160,14 +132,12 @@ public class login_activity extends AppCompatActivity {
}
private void verifyChildCode(String code) {
- // Verifica no Firestore se o código existe
db.collection("login_codes").document(code).get()
.addOnSuccessListener(document -> {
if (document.exists()) {
boolean used = Boolean.TRUE.equals(document.getBoolean("used"));
Timestamp expiresAt = document.getTimestamp("expiresAt");
- // Validações
if (used) {
Toast.makeText(this, "Este código já foi usado.", Toast.LENGTH_LONG).show();
return;
@@ -179,7 +149,20 @@ public class login_activity extends AppCompatActivity {
// SUCESSO: O código é bom!
String parentId = document.getString("parentId");
- loginChildAnonymously(code, parentId);
+ String childName = document.getString("childName");
+
+ // =======================================================
+ // ASSOCIAÇÃO AUTOMÁTICA: Cria o registo na coleção 'children'
+ // =======================================================
+ Map childData = new HashMap<>();
+ childData.put("parentId", parentId);
+ childData.put("accessCode", code);
+ childData.put("name", childName != null ? childName : "Filho");
+ childData.put("createdAt", new Timestamp(new Date()));
+
+ db.collection("children").document(code).set(childData)
+ .addOnSuccessListener(aVoid -> loginChildAnonymously(code, parentId))
+ .addOnFailureListener(e -> Toast.makeText(this, "Erro ao associar filho.", Toast.LENGTH_SHORT).show());
} else {
Toast.makeText(this, "Código inválido.", Toast.LENGTH_SHORT).show();
@@ -189,13 +172,18 @@ public class login_activity extends AppCompatActivity {
}
private void loginChildAnonymously(String code, String parentId) {
- // Faz login anônimo (sem email)
mAuth.signInAnonymously()
.addOnCompleteListener(this, task -> {
if (task.isSuccessful()) {
- // 1. Invalida o código para ninguém usar de novo
+ // Invalida o código no Firestore
db.collection("login_codes").document(code).update("used", true);
+ // Guarda o código para o LocationService usar
+ getSharedPreferences("FindU_Prefs", MODE_PRIVATE)
+ .edit()
+ .putString("child_access_code", code)
+ .apply();
+
Toast.makeText(this, "Conectado como Filho!", Toast.LENGTH_SHORT).show();
goToMainActivity();
} else {
diff --git a/app/src/main/java/com/example/pap_findu/models/ChatMessage.java b/app/src/main/java/com/example/pap_findu/models/ChatMessage.java
index 724a799..9d81cee 100644
--- a/app/src/main/java/com/example/pap_findu/models/ChatMessage.java
+++ b/app/src/main/java/com/example/pap_findu/models/ChatMessage.java
@@ -2,24 +2,53 @@ package com.example.pap_findu.models;
public class ChatMessage {
private String message;
- private boolean isSentByMe;
+ private String senderId;
private String timestamp;
+ private boolean isSentByMe;
- public ChatMessage(String message, boolean isSentByMe, String timestamp) {
+ // 1. Construtor Vazio (OBRIGATÓRIO para o Firebase conseguir ler os dados)
+ public ChatMessage() {
+ }
+
+ // 2. Construtor para enviar mensagens (usado na ChatActivity)
+ public ChatMessage(String message, String senderId, String timestamp) {
this.message = message;
- this.isSentByMe = isSentByMe;
+ this.senderId = senderId;
this.timestamp = timestamp;
}
+ // --- Getters e Setters ---
+
public String getMessage() {
return message;
}
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public String getSenderId() {
+ return senderId;
+ }
+
+ public void setSenderId(String senderId) {
+ this.senderId = senderId;
+ }
+
+ public String getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(String timestamp) {
+ this.timestamp = timestamp;
+ }
+
public boolean isSentByMe() {
return isSentByMe;
}
- public String getTimestamp() {
- return timestamp;
+ // Este setter é usado na ChatActivity dentro do loop listenForMessages
+ public void setSentByMe(boolean sentByMe) {
+ isSentByMe = sentByMe;
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java b/app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java
index 22a387f..d787c8d 100644
--- a/app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java
+++ b/app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java
@@ -4,6 +4,7 @@ import android.app.AlertDialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@@ -13,16 +14,21 @@ import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import com.example.pap_findu.ChatActivity;
import com.example.pap_findu.R;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
+import com.google.android.gms.maps.model.BitmapDescriptorFactory;
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.floatingactionbutton.FloatingActionButton;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot;
@@ -31,7 +37,6 @@ import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.firestore.FirebaseFirestore;
-import com.google.firebase.firestore.QuerySnapshot;
public class MapFragment extends Fragment implements OnMapReadyCallback {
@@ -41,18 +46,19 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
private FirebaseFirestore db;
private FirebaseAuth auth;
- // Variáveis para o Mapa e Tempo Real
+ private FloatingActionButton btnAbrirChat;
+ private MaterialButton btnSOS;
+
private GoogleMap mMap;
private Marker childMarker;
private DatabaseReference locationRef;
private ValueEventListener locationListener;
- // VARIÁVEL ADICIONADA PARA GUARDAR O ID DA CRIANÇA AUTOMATICAMENTE
private String currentChildId = null;
- public View onCreateView(@NonNull LayoutInflater inflater,
- ViewGroup container, Bundle savedInstanceState) {
-
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_map, container, false);
db = FirebaseFirestore.getInstance();
@@ -61,16 +67,14 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
layoutEmptyState = root.findViewById(R.id.layoutEmptyState);
mapContainer = root.findViewById(R.id.mapContainer);
btnAddChild = root.findViewById(R.id.btnAddChild);
+ btnAbrirChat = root.findViewById(R.id.btnAbrirChat);
+ btnSOS = root.findViewById(R.id.btnSOS);
+
+ if (btnAbrirChat != null) btnAbrirChat.setVisibility(View.GONE);
+ if (btnSOS != null) btnSOS.setVisibility(View.GONE);
if (btnAddChild != null) {
- btnAddChild.setOnClickListener(v -> {
- FirebaseUser user = auth.getCurrentUser();
- if (user != null) {
- openAddChildScreen();
- } else {
- Toast.makeText(getContext(), "Erro: Utilizador não autenticado", Toast.LENGTH_SHORT).show();
- }
- });
+ btnAddChild.setOnClickListener(v -> openAddChildScreen());
}
checkUserTypeAndShowScreen();
@@ -83,12 +87,11 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
if (user == null) return;
if (user.isAnonymous()) {
- if (btnAddChild != null) btnAddChild.setVisibility(View.GONE);
showMapState();
- return;
+ setupChildButtons();
+ } else {
+ checkIfHasChildren();
}
-
- checkIfHasChildren();
}
private void checkIfHasChildren() {
@@ -101,19 +104,14 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
.get()
.addOnCompleteListener(task -> {
if (task.isSuccessful() && task.getResult() != null && !task.getResult().isEmpty()) {
-
- // =================================================================
- // AUTOMAÇÃO: Encontra a criança e extrai o ID dela!
- // =================================================================
- var document = task.getResult().getDocuments().get(0);
-
- // MUITO IMPORTANTE: Mude "uid" para o nome do campo na sua base de dados que tem o ID do filho!
- currentChildId = document.getString("uid");
-
+ currentChildId = task.getResult().getDocuments().get(0).getString("accessCode");
if (currentChildId != null) {
- showMapState(); // Se encontrou o ID, carrega o mapa
+ showMapState();
+ setupChildButtons();
+ if (mMap != null) {
+ startListeningToChildLocation();
+ }
} else {
- Toast.makeText(getContext(), "Erro: Criança encontrada, mas sem ID.", Toast.LENGTH_SHORT).show();
showEmptyState();
}
} else {
@@ -122,83 +120,98 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
});
}
+ private void setupChildButtons() {
+ if (btnAbrirChat != null) {
+ btnAbrirChat.setVisibility(View.VISIBLE);
+ btnAbrirChat.setOnClickListener(v -> {
+ String roomCode = auth.getCurrentUser().isAnonymous() ?
+ requireContext().getSharedPreferences("FindU_Prefs", Context.MODE_PRIVATE).getString("child_access_code", null) :
+ currentChildId;
+
+ if (roomCode != null) {
+ requireContext().getSharedPreferences("FindU_Prefs", Context.MODE_PRIVATE).edit().putString("child_access_code", roomCode).apply();
+ startActivity(new Intent(getActivity(), ChatActivity.class));
+ }
+ });
+ }
+ if (btnSOS != null) btnSOS.setVisibility(View.VISIBLE);
+ }
+
private void showMapState() {
if (layoutEmptyState != null) layoutEmptyState.setVisibility(View.GONE);
if (mapContainer != null) mapContainer.setVisibility(View.VISIBLE);
SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapContainer);
- if (mapFragment == null) {
- mapFragment = SupportMapFragment.newInstance();
- getChildFragmentManager().beginTransaction()
- .replace(R.id.mapContainer, mapFragment)
- .commit();
+ if (mapFragment != null) {
+ mapFragment.getMapAsync(this);
}
-
- mapFragment.getMapAsync(this);
}
@Override
public void onMapReady(@NonNull GoogleMap googleMap) {
mMap = googleMap;
+ FirebaseUser user = auth.getCurrentUser();
- mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(0, 0), 2f));
-
- // Inicia a escuta APENAS se tivermos encontrado um ID válido
- if (currentChildId != null) {
- startListeningToChildLocation();
+ if (user != null && user.isAnonymous()) {
+ try { mMap.setMyLocationEnabled(true); } catch (SecurityException e) {}
} else {
- Toast.makeText(getContext(), "A aguardar ligação à criança...", Toast.LENGTH_SHORT).show();
+ if (currentChildId != null) {
+ startListeningToChildLocation();
+ } else {
+ mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(0, 0), 2f));
+ }
}
}
private void startListeningToChildLocation() {
- // =================================================================
- // AUTOMAÇÃO: Usa a variável currentChildId em vez de texto fixo
- // =================================================================
- locationRef = FirebaseDatabase.getInstance().getReference("users/" + currentChildId + "/live_location");
+ 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");
+
+ if (locationListener != null) locationRef.removeEventListener(locationListener);
locationListener = new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
- if (snapshot.exists()) {
+ if (snapshot.exists() && mMap != null) {
Double lat = snapshot.child("latitude").getValue(Double.class);
Double lng = snapshot.child("longitude").getValue(Double.class);
- if (lat != null && lng != null && mMap != null) {
- LatLng childPosition = new LatLng(lat, lng);
+ if (lat != null && lng != null) {
+ LatLng childPos = new LatLng(lat, lng);
- if (childMarker == null) {
- MarkerOptions markerOptions = new MarkerOptions()
- .position(childPosition)
- .title("Criança");
+ // 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")
+ .icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
+ } else {
+ childMarker.setPosition(childPos);
+ }
- childMarker = mMap.addMarker(markerOptions);
- mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(childPosition, 16f));
+ // 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 {
- childMarker.setPosition(childPosition);
- mMap.animateCamera(CameraUpdateFactory.newLatLng(childPosition));
+ Log.d("MapFragment", "Pai ignorou localização de teste (Califórnia)");
}
}
- } else {
- Log.w("MapFragment", "Pasta de localização vazia ou inexistente para o ID: " + currentChildId);
}
}
-
- @Override
- public void onCancelled(@NonNull DatabaseError error) {
- Log.e("MapFragment", "Erro ao ler localização: " + error.getMessage());
+ @Override public void onCancelled(@NonNull DatabaseError error) {
+ Log.e("MapFragment", "Erro ao ler Firebase: " + error.getMessage());
}
};
-
locationRef.addValueEventListener(locationListener);
}
@Override
public void onDestroyView() {
super.onDestroyView();
- if (locationRef != null && locationListener != null) {
- locationRef.removeEventListener(locationListener);
- }
+ if (locationRef != null && locationListener != null) locationRef.removeEventListener(locationListener);
}
private void showEmptyState() {
@@ -213,19 +226,10 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
}
private void showCodeDialog(String code) {
- if (getContext() == null) return;
-
- ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clip = ClipData.newPlainText("Código", code);
- clipboard.setPrimaryClip(clip);
- Toast.makeText(getContext(), "Código copiado!", Toast.LENGTH_SHORT).show();
-
- new AlertDialog.Builder(getContext())
+ new AlertDialog.Builder(requireContext())
.setTitle("Filho Adicionado!")
- .setMessage("O código de acesso é: " + code + "\n\n(Já copiado automaticamente)")
- .setCancelable(false)
- .setPositiveButton("Ir para o Mapa", (dialog, which) -> checkIfHasChildren())
- .setNegativeButton("Adicionar Outro", (dialog, which) -> dialog.dismiss())
+ .setMessage("Código: " + code)
+ .setPositiveButton("OK", (d, w) -> checkIfHasChildren())
.show();
}
}
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml
index 75bfdd8..d3b92bf 100644
--- a/app/src/main/res/layout/fragment_map.xml
+++ b/app/src/main/res/layout/fragment_map.xml
@@ -5,128 +5,72 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="#F3F6FB">
+ tools:context=".ui.map.MapFragment">
-
-
-
+ android:gravity="center"
+ android:orientation="vertical"
+ android:visibility="gone">
+ android:text="Ainda não tem filhos associados."
+ android:textSize="18sp"
+ android:layout_marginBottom="16dp"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+ android:name="com.google.android.gms.maps.SupportMapFragment"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+
+
+
\ No newline at end of file