a localizaçao esta funcionando + ou - certo
This commit is contained in:
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -18,12 +18,12 @@
|
|||||||
<targets>
|
<targets>
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=rogmvsfmmfus95e6" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Pixel_7.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Pixel_9_Pro.avd" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Pixel_7_filho.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</targets>
|
</targets>
|
||||||
|
|||||||
@@ -4,16 +4,23 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<package android:name="com.google.android.gms" />
|
||||||
|
<intent>
|
||||||
|
<action android:name="com.google.android.geo.API_KEY" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
@@ -22,12 +29,17 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.PAP_FindU">
|
android:theme="@style/Theme.PAP_FindU"
|
||||||
|
tools:targetApi="34">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.geo.API_KEY"
|
android:name="com.google.android.geo.API_KEY"
|
||||||
android:value="AIzaSyAxen212OKqkfpu1AbWajLGTCTSdRhJWlM" />
|
android:value="AIzaSyAxen212OKqkfpu1AbWajLGTCTSdRhJWlM" />
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.version"
|
||||||
|
android:value="@integer/google_play_services_version" />
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".LocationService"
|
android:name=".LocationService"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
@@ -37,9 +49,11 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".CriarConta"
|
android:name=".CriarConta"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".Recuperar_Passe"
|
android:name=".Recuperar_Passe"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ChatActivity"
|
android:name=".ChatActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
@@ -56,6 +70,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".AddZoneActivity"
|
android:name=".AddZoneActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -64,6 +79,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".EditProfileActivity"
|
android:name=".EditProfileActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".SecurityActivity"
|
android:name=".SecurityActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|||||||
@@ -2,16 +2,24 @@ package com.example.pap_findu;
|
|||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.view.View;
|
import android.util.Log;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.example.pap_findu.adapters.ChatAdapter;
|
import com.example.pap_findu.adapters.ChatAdapter;
|
||||||
import com.example.pap_findu.models.ChatMessage;
|
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.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -28,50 +36,101 @@ public class ChatActivity extends AppCompatActivity {
|
|||||||
private ChatAdapter adapter;
|
private ChatAdapter adapter;
|
||||||
private List<ChatMessage> messageList;
|
private List<ChatMessage> messageList;
|
||||||
|
|
||||||
|
private DatabaseReference chatRef;
|
||||||
|
private String currentUserId;
|
||||||
|
private String accessCode;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.chat_activity);
|
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);
|
recyclerChat = findViewById(R.id.recycler_chat);
|
||||||
editChatMessage = findViewById(R.id.edit_chat_message);
|
editChatMessage = findViewById(R.id.edit_chat_message);
|
||||||
btnSend = findViewById(R.id.btnSend);
|
btnSend = findViewById(R.id.btnSend);
|
||||||
btnBack = findViewById(R.id.btnBack);
|
btnBack = findViewById(R.id.btnBack);
|
||||||
|
|
||||||
// Initialize Message List with some dummy data
|
// 3. Configurar RecyclerView e Adapter
|
||||||
messageList = new ArrayList<>();
|
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);
|
adapter = new ChatAdapter(messageList);
|
||||||
recyclerChat.setLayoutManager(new LinearLayoutManager(this));
|
recyclerChat.setLayoutManager(new LinearLayoutManager(this));
|
||||||
recyclerChat.setAdapter(adapter);
|
recyclerChat.setAdapter(adapter);
|
||||||
|
|
||||||
// Scroll to bottom
|
// 4. Lógica do Botão Enviar
|
||||||
recyclerChat.scrollToPosition(messageList.size() - 1);
|
|
||||||
|
|
||||||
// Send Button Logic
|
|
||||||
btnSend.setOnClickListener(v -> {
|
btnSend.setOnClickListener(v -> {
|
||||||
String text = editChatMessage.getText().toString().trim();
|
String text = editChatMessage.getText().toString().trim();
|
||||||
if (!TextUtils.isEmpty(text)) {
|
if (!TextUtils.isEmpty(text)) {
|
||||||
sendMessage(text);
|
sendMessageToFirebase(text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Back Button Logic
|
// 5. Lógica do Botão Voltar
|
||||||
btnBack.setOnClickListener(v -> finish());
|
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());
|
String currentTime = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(new Date());
|
||||||
ChatMessage newMessage = new ChatMessage(text, true, currentTime);
|
|
||||||
|
|
||||||
messageList.add(newMessage);
|
// Criamos a mensagem com o ID de quem envia
|
||||||
adapter.notifyItemInserted(messageList.size() - 1);
|
ChatMessage newMessage = new ChatMessage(text, currentUserId, currentTime);
|
||||||
recyclerChat.scrollToPosition(messageList.size() - 1);
|
|
||||||
|
|
||||||
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());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,6 @@ import android.Manifest;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ServiceInfo;
|
import android.content.pm.ServiceInfo;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
@@ -14,6 +13,7 @@ import android.location.Location;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
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.LocationResult;
|
||||||
import com.google.android.gms.location.LocationServices;
|
import com.google.android.gms.location.LocationServices;
|
||||||
import com.google.android.gms.location.Priority;
|
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.DatabaseReference;
|
||||||
import com.google.firebase.database.FirebaseDatabase;
|
import com.google.firebase.database.FirebaseDatabase;
|
||||||
|
|
||||||
@@ -41,97 +37,62 @@ public class LocationService extends Service {
|
|||||||
private LocationCallback locationCallback;
|
private LocationCallback locationCallback;
|
||||||
private DatabaseReference databaseReference;
|
private DatabaseReference databaseReference;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public IBinder onBind(Intent intent) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
// Inicializa o cliente de GPS
|
|
||||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
|
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
|
||||||
|
|
||||||
// =================================================================
|
String childCode = getSharedPreferences("FindU_Prefs", MODE_PRIVATE)
|
||||||
// AUTOMAÇÃO: Vai buscar o ID do Filho que está logado neste telemóvel
|
.getString("child_access_code", null);
|
||||||
// =================================================================
|
|
||||||
FirebaseUser currentUser = FirebaseAuth.getInstance().getCurrentUser();
|
|
||||||
|
|
||||||
if (currentUser != null) {
|
if (childCode != null) {
|
||||||
String myUid = currentUser.getUid(); // Pega o ID único gerado pelo Firebase
|
// CAMINHO EXATO: Direto na raiz, como na tua print
|
||||||
|
databaseReference = FirebaseDatabase.getInstance().getReference(childCode).child("live_location");
|
||||||
// Cria a referência da base de dados usando este ID dinâmico
|
|
||||||
databaseReference = FirebaseDatabase.getInstance().getReference("users/" + myUid + "/live_location");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
// 1. Cria o Canal e a Notificação IMEDIATAMENTE
|
|
||||||
createNotificationChannel();
|
createNotificationChannel();
|
||||||
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
|
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
|
||||||
.setContentTitle("FindU Ativo")
|
.setContentTitle("FindU Ativo")
|
||||||
.setContentText("A monitorizar a localização em tempo real...")
|
.setContentText("A partilhar localização real...")
|
||||||
.setSmallIcon(R.mipmap.ic_launcher)
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 2. ENVOLVER TUDO NUM TRY-CATCH PARA EVITAR CRASHES NO ANDROID 14
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
try {
|
startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
|
||||||
// O Android Q (API 29) e superior permite/exige especificar o tipo de serviço no código
|
} else {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
startForeground(1, notification);
|
||||||
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) {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
|
requestLocationUpdates();
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return START_STICKY;
|
return START_STICKY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("MissingPermission")
|
|
||||||
private void requestLocationUpdates() {
|
private void requestLocationUpdates() {
|
||||||
// Configura a frequência do GPS (a cada 10 segundos, só se mover 5 metros)
|
// Pedimos atualizações de 2 em 2 segundos
|
||||||
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10000)
|
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 2000)
|
||||||
.setMinUpdateDistanceMeters(5.0f)
|
.setMinUpdateDistanceMeters(0)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
locationCallback = new LocationCallback() {
|
locationCallback = new LocationCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onLocationResult(@NonNull LocationResult locationResult) {
|
public void onLocationResult(@NonNull LocationResult locationResult) {
|
||||||
super.onLocationResult(locationResult);
|
|
||||||
for (Location location : locationResult.getLocations()) {
|
for (Location location : locationResult.getLocations()) {
|
||||||
// Prepara os dados
|
// FILTRO CRÍTICO: Se for a latitude da Google (37.42...), IGNORA.
|
||||||
Map<String, Object> locationData = new HashMap<>();
|
if (Math.abs(location.getLatitude() - 37.4219) > 0.001) {
|
||||||
locationData.put("latitude", location.getLatitude());
|
updateFirebase(location);
|
||||||
locationData.put("longitude", location.getLongitude());
|
} else {
|
||||||
locationData.put("last_updated", System.currentTimeMillis());
|
Log.d("LocationService", "Ignorada localização falsa da Califórnia");
|
||||||
|
|
||||||
// Verifica se o utilizador não é nulo antes de enviar para o Firebase
|
|
||||||
if (databaseReference != null) {
|
|
||||||
databaseReference.setValue(locationData);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Outro try-catch de segurança para a comunicação com os serviços do Google (GMS)
|
|
||||||
try {
|
try {
|
||||||
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
|
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
@@ -139,27 +100,32 @@ public class LocationService extends Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void updateFirebase(Location location) {
|
||||||
public void onDestroy() {
|
if (databaseReference != null) {
|
||||||
super.onDestroy();
|
Map<String, Object> data = new HashMap<>();
|
||||||
// Quando o serviço for desligado, para de usar o GPS para poupar bateria
|
data.put("latitude", location.getLatitude());
|
||||||
if (fusedLocationClient != null && locationCallback != null) {
|
data.put("longitude", location.getLongitude());
|
||||||
fusedLocationClient.removeLocationUpdates(locationCallback);
|
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() {
|
private void createNotificationChannel() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
NotificationChannel channel = new NotificationChannel(
|
NotificationChannel channel = new NotificationChannel("LocationChannel", "GPS", NotificationManager.IMPORTANCE_LOW);
|
||||||
"LocationChannel",
|
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
||||||
"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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.pap_findu;
|
package com.example.pap_findu;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@@ -25,10 +26,11 @@ import com.google.firebase.Timestamp;
|
|||||||
import com.google.firebase.auth.AuthResult;
|
import com.google.firebase.auth.AuthResult;
|
||||||
import com.google.firebase.auth.FirebaseAuth;
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
import com.google.firebase.auth.FirebaseUser;
|
import com.google.firebase.auth.FirebaseUser;
|
||||||
import com.google.firebase.firestore.DocumentSnapshot;
|
|
||||||
import com.google.firebase.firestore.FirebaseFirestore;
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class login_activity extends AppCompatActivity {
|
public class login_activity extends AppCompatActivity {
|
||||||
|
|
||||||
@@ -37,7 +39,6 @@ public class login_activity extends AppCompatActivity {
|
|||||||
private Button btnLogin;
|
private Button btnLogin;
|
||||||
private TextView criarContaTextView;
|
private TextView criarContaTextView;
|
||||||
|
|
||||||
// --- NOVAS VARIÁVEIS PARA O FILHO ---
|
|
||||||
private MaterialButton btnChildLogin;
|
private MaterialButton btnChildLogin;
|
||||||
private FirebaseFirestore db;
|
private FirebaseFirestore db;
|
||||||
private FirebaseAuth mAuth;
|
private FirebaseAuth mAuth;
|
||||||
@@ -54,68 +55,46 @@ public class login_activity extends AppCompatActivity {
|
|||||||
return insets;
|
return insets;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Inicializar Firebase Auth e Firestore
|
|
||||||
mAuth = FirebaseAuth.getInstance();
|
mAuth = FirebaseAuth.getInstance();
|
||||||
db = FirebaseFirestore.getInstance();
|
db = FirebaseFirestore.getInstance();
|
||||||
|
|
||||||
// Vincular componentes
|
|
||||||
emailEditText = findViewById(R.id.emailEditText);
|
emailEditText = findViewById(R.id.emailEditText);
|
||||||
passwordEditText = findViewById(R.id.passwordEditText);
|
passwordEditText = findViewById(R.id.passwordEditText);
|
||||||
btnLogin = findViewById(R.id.btnLogin);
|
btnLogin = findViewById(R.id.btnLogin);
|
||||||
criarContaTextView = findViewById(R.id.criarContaTextView);
|
criarContaTextView = findViewById(R.id.criarContaTextView);
|
||||||
|
|
||||||
// Novo botão do filho (Certifique-se que adicionou no XML com id btnChildLogin)
|
|
||||||
btnChildLogin = findViewById(R.id.btnChildLogin);
|
btnChildLogin = findViewById(R.id.btnChildLogin);
|
||||||
|
|
||||||
// --- LÓGICA 1: LOGIN DO PAI (Seu código original) ---
|
btnLogin.setOnClickListener(v -> {
|
||||||
btnLogin.setOnClickListener(new View.OnClickListener() {
|
String email = emailEditText.getText().toString();
|
||||||
@Override
|
String password = passwordEditText.getText().toString();
|
||||||
public void onClick(View v) {
|
|
||||||
String email = emailEditText.getText().toString();
|
|
||||||
String password = passwordEditText.getText().toString();
|
|
||||||
|
|
||||||
if (email.isEmpty() || password.isEmpty()) {
|
if (email.isEmpty() || password.isEmpty()) {
|
||||||
Toast.makeText(login_activity.this, "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show();
|
Toast.makeText(login_activity.this, "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
mAuth.signInWithEmailAndPassword(email, password)
|
|
||||||
.addOnCompleteListener(login_activity.this, new OnCompleteListener<AuthResult>() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(@NonNull Task<AuthResult> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(v -> showChildLoginDialog());
|
||||||
btnChildLogin.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
showChildLoginDialog();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// --- LÓGICA 3: CRIAR CONTA ---
|
criarContaTextView.setOnClickListener(v -> {
|
||||||
criarContaTextView.setOnClickListener(new View.OnClickListener() {
|
Intent intent = new Intent(login_activity.this, CriarConta.class);
|
||||||
@Override
|
startActivity(intent);
|
||||||
public void onClick(View v) {
|
|
||||||
Intent intent = new Intent(login_activity.this, CriarConta.class);
|
|
||||||
startActivity(intent);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
// Verifica se já está logado
|
|
||||||
FirebaseUser currentUser = mAuth.getCurrentUser();
|
FirebaseUser currentUser = mAuth.getCurrentUser();
|
||||||
if(currentUser != null){
|
if(currentUser != null){
|
||||||
goToMainActivity();
|
goToMainActivity();
|
||||||
@@ -124,28 +103,21 @@ public class login_activity extends AppCompatActivity {
|
|||||||
|
|
||||||
private void goToMainActivity() {
|
private void goToMainActivity() {
|
||||||
Intent intent = new Intent(login_activity.this, MainActivity.class);
|
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);
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==========================================================
|
|
||||||
// MÉTODOS DE LOGIN DO FILHO
|
|
||||||
// ==========================================================
|
|
||||||
|
|
||||||
private void showChildLoginDialog() {
|
private void showChildLoginDialog() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setTitle("Acesso do Filho");
|
builder.setTitle("Acesso do Filho");
|
||||||
builder.setMessage("Digite o código de 6 dígitos gerado pelo pai:");
|
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);
|
final EditText inputCode = new EditText(this);
|
||||||
inputCode.setInputType(InputType.TYPE_CLASS_NUMBER);
|
inputCode.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||||
inputCode.setHint("Ex: 123456");
|
inputCode.setHint("Ex: 123456");
|
||||||
builder.setView(inputCode);
|
builder.setView(inputCode);
|
||||||
|
|
||||||
// Botão Entrar do Alerta
|
|
||||||
builder.setPositiveButton("Entrar", (dialog, which) -> {
|
builder.setPositiveButton("Entrar", (dialog, which) -> {
|
||||||
String code = inputCode.getText().toString().trim();
|
String code = inputCode.getText().toString().trim();
|
||||||
if (!TextUtils.isEmpty(code)) {
|
if (!TextUtils.isEmpty(code)) {
|
||||||
@@ -160,14 +132,12 @@ public class login_activity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void verifyChildCode(String code) {
|
private void verifyChildCode(String code) {
|
||||||
// Verifica no Firestore se o código existe
|
|
||||||
db.collection("login_codes").document(code).get()
|
db.collection("login_codes").document(code).get()
|
||||||
.addOnSuccessListener(document -> {
|
.addOnSuccessListener(document -> {
|
||||||
if (document.exists()) {
|
if (document.exists()) {
|
||||||
boolean used = Boolean.TRUE.equals(document.getBoolean("used"));
|
boolean used = Boolean.TRUE.equals(document.getBoolean("used"));
|
||||||
Timestamp expiresAt = document.getTimestamp("expiresAt");
|
Timestamp expiresAt = document.getTimestamp("expiresAt");
|
||||||
|
|
||||||
// Validações
|
|
||||||
if (used) {
|
if (used) {
|
||||||
Toast.makeText(this, "Este código já foi usado.", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "Este código já foi usado.", Toast.LENGTH_LONG).show();
|
||||||
return;
|
return;
|
||||||
@@ -179,7 +149,20 @@ public class login_activity extends AppCompatActivity {
|
|||||||
|
|
||||||
// SUCESSO: O código é bom!
|
// SUCESSO: O código é bom!
|
||||||
String parentId = document.getString("parentId");
|
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<String, Object> 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 {
|
} else {
|
||||||
Toast.makeText(this, "Código inválido.", Toast.LENGTH_SHORT).show();
|
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) {
|
private void loginChildAnonymously(String code, String parentId) {
|
||||||
// Faz login anônimo (sem email)
|
|
||||||
mAuth.signInAnonymously()
|
mAuth.signInAnonymously()
|
||||||
.addOnCompleteListener(this, task -> {
|
.addOnCompleteListener(this, task -> {
|
||||||
if (task.isSuccessful()) {
|
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);
|
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();
|
Toast.makeText(this, "Conectado como Filho!", Toast.LENGTH_SHORT).show();
|
||||||
goToMainActivity();
|
goToMainActivity();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,24 +2,53 @@ package com.example.pap_findu.models;
|
|||||||
|
|
||||||
public class ChatMessage {
|
public class ChatMessage {
|
||||||
private String message;
|
private String message;
|
||||||
private boolean isSentByMe;
|
private String senderId;
|
||||||
private String timestamp;
|
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.message = message;
|
||||||
this.isSentByMe = isSentByMe;
|
this.senderId = senderId;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Getters e Setters ---
|
||||||
|
|
||||||
public String getMessage() {
|
public String getMessage() {
|
||||||
return message;
|
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() {
|
public boolean isSentByMe() {
|
||||||
return isSentByMe;
|
return isSentByMe;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getTimestamp() {
|
// Este setter é usado na ChatActivity dentro do loop listenForMessages
|
||||||
return timestamp;
|
public void setSentByMe(boolean sentByMe) {
|
||||||
|
isSentByMe = sentByMe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ import android.app.AlertDialog;
|
|||||||
import android.content.ClipData;
|
import android.content.ClipData;
|
||||||
import android.content.ClipboardManager;
|
import android.content.ClipboardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@@ -13,16 +14,21 @@ import android.widget.Button;
|
|||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import com.example.pap_findu.ChatActivity;
|
||||||
import com.example.pap_findu.R;
|
import com.example.pap_findu.R;
|
||||||
import com.google.android.gms.maps.CameraUpdateFactory;
|
import com.google.android.gms.maps.CameraUpdateFactory;
|
||||||
import com.google.android.gms.maps.GoogleMap;
|
import com.google.android.gms.maps.GoogleMap;
|
||||||
import com.google.android.gms.maps.OnMapReadyCallback;
|
import com.google.android.gms.maps.OnMapReadyCallback;
|
||||||
import com.google.android.gms.maps.SupportMapFragment;
|
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.LatLng;
|
||||||
import com.google.android.gms.maps.model.Marker;
|
import com.google.android.gms.maps.model.Marker;
|
||||||
import com.google.android.gms.maps.model.MarkerOptions;
|
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.FirebaseAuth;
|
||||||
import com.google.firebase.auth.FirebaseUser;
|
import com.google.firebase.auth.FirebaseUser;
|
||||||
import com.google.firebase.database.DataSnapshot;
|
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.FirebaseDatabase;
|
||||||
import com.google.firebase.database.ValueEventListener;
|
import com.google.firebase.database.ValueEventListener;
|
||||||
import com.google.firebase.firestore.FirebaseFirestore;
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
import com.google.firebase.firestore.QuerySnapshot;
|
|
||||||
|
|
||||||
public class MapFragment extends Fragment implements OnMapReadyCallback {
|
public class MapFragment extends Fragment implements OnMapReadyCallback {
|
||||||
|
|
||||||
@@ -41,18 +46,19 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
private FirebaseFirestore db;
|
private FirebaseFirestore db;
|
||||||
private FirebaseAuth auth;
|
private FirebaseAuth auth;
|
||||||
|
|
||||||
// Variáveis para o Mapa e Tempo Real
|
private FloatingActionButton btnAbrirChat;
|
||||||
|
private MaterialButton btnSOS;
|
||||||
|
|
||||||
private GoogleMap mMap;
|
private GoogleMap mMap;
|
||||||
private Marker childMarker;
|
private Marker childMarker;
|
||||||
private DatabaseReference locationRef;
|
private DatabaseReference locationRef;
|
||||||
private ValueEventListener locationListener;
|
private ValueEventListener locationListener;
|
||||||
|
|
||||||
// VARIÁVEL ADICIONADA PARA GUARDAR O ID DA CRIANÇA AUTOMATICAMENTE
|
|
||||||
private String currentChildId = null;
|
private String currentChildId = null;
|
||||||
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
@Nullable
|
||||||
ViewGroup container, Bundle savedInstanceState) {
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
View root = inflater.inflate(R.layout.fragment_map, container, false);
|
View root = inflater.inflate(R.layout.fragment_map, container, false);
|
||||||
|
|
||||||
db = FirebaseFirestore.getInstance();
|
db = FirebaseFirestore.getInstance();
|
||||||
@@ -61,16 +67,14 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
layoutEmptyState = root.findViewById(R.id.layoutEmptyState);
|
layoutEmptyState = root.findViewById(R.id.layoutEmptyState);
|
||||||
mapContainer = root.findViewById(R.id.mapContainer);
|
mapContainer = root.findViewById(R.id.mapContainer);
|
||||||
btnAddChild = root.findViewById(R.id.btnAddChild);
|
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) {
|
if (btnAddChild != null) {
|
||||||
btnAddChild.setOnClickListener(v -> {
|
btnAddChild.setOnClickListener(v -> openAddChildScreen());
|
||||||
FirebaseUser user = auth.getCurrentUser();
|
|
||||||
if (user != null) {
|
|
||||||
openAddChildScreen();
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getContext(), "Erro: Utilizador não autenticado", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkUserTypeAndShowScreen();
|
checkUserTypeAndShowScreen();
|
||||||
@@ -83,12 +87,11 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
if (user == null) return;
|
if (user == null) return;
|
||||||
|
|
||||||
if (user.isAnonymous()) {
|
if (user.isAnonymous()) {
|
||||||
if (btnAddChild != null) btnAddChild.setVisibility(View.GONE);
|
|
||||||
showMapState();
|
showMapState();
|
||||||
return;
|
setupChildButtons();
|
||||||
|
} else {
|
||||||
|
checkIfHasChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
checkIfHasChildren();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkIfHasChildren() {
|
private void checkIfHasChildren() {
|
||||||
@@ -101,19 +104,14 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
.get()
|
.get()
|
||||||
.addOnCompleteListener(task -> {
|
.addOnCompleteListener(task -> {
|
||||||
if (task.isSuccessful() && task.getResult() != null && !task.getResult().isEmpty()) {
|
if (task.isSuccessful() && task.getResult() != null && !task.getResult().isEmpty()) {
|
||||||
|
currentChildId = task.getResult().getDocuments().get(0).getString("accessCode");
|
||||||
// =================================================================
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
if (currentChildId != null) {
|
if (currentChildId != null) {
|
||||||
showMapState(); // Se encontrou o ID, carrega o mapa
|
showMapState();
|
||||||
|
setupChildButtons();
|
||||||
|
if (mMap != null) {
|
||||||
|
startListeningToChildLocation();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Erro: Criança encontrada, mas sem ID.", Toast.LENGTH_SHORT).show();
|
|
||||||
showEmptyState();
|
showEmptyState();
|
||||||
}
|
}
|
||||||
} else {
|
} 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() {
|
private void showMapState() {
|
||||||
if (layoutEmptyState != null) layoutEmptyState.setVisibility(View.GONE);
|
if (layoutEmptyState != null) layoutEmptyState.setVisibility(View.GONE);
|
||||||
if (mapContainer != null) mapContainer.setVisibility(View.VISIBLE);
|
if (mapContainer != null) mapContainer.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapContainer);
|
SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapContainer);
|
||||||
if (mapFragment == null) {
|
if (mapFragment != null) {
|
||||||
mapFragment = SupportMapFragment.newInstance();
|
mapFragment.getMapAsync(this);
|
||||||
getChildFragmentManager().beginTransaction()
|
|
||||||
.replace(R.id.mapContainer, mapFragment)
|
|
||||||
.commit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mapFragment.getMapAsync(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMapReady(@NonNull GoogleMap googleMap) {
|
public void onMapReady(@NonNull GoogleMap googleMap) {
|
||||||
mMap = googleMap;
|
mMap = googleMap;
|
||||||
|
FirebaseUser user = auth.getCurrentUser();
|
||||||
|
|
||||||
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(0, 0), 2f));
|
if (user != null && user.isAnonymous()) {
|
||||||
|
try { mMap.setMyLocationEnabled(true); } catch (SecurityException e) {}
|
||||||
// Inicia a escuta APENAS se tivermos encontrado um ID válido
|
|
||||||
if (currentChildId != null) {
|
|
||||||
startListeningToChildLocation();
|
|
||||||
} else {
|
} 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() {
|
private void startListeningToChildLocation() {
|
||||||
// =================================================================
|
if (currentChildId == null || mMap == null) return;
|
||||||
// AUTOMAÇÃO: Usa a variável currentChildId em vez de texto fixo
|
|
||||||
// =================================================================
|
// CORREÇÃO: Caminho direto na raiz como aparece no teu Firebase
|
||||||
locationRef = FirebaseDatabase.getInstance().getReference("users/" + currentChildId + "/live_location");
|
locationRef = FirebaseDatabase.getInstance().getReference(currentChildId).child("live_location");
|
||||||
|
|
||||||
|
if (locationListener != null) locationRef.removeEventListener(locationListener);
|
||||||
|
|
||||||
locationListener = new ValueEventListener() {
|
locationListener = new ValueEventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
||||||
if (snapshot.exists()) {
|
if (snapshot.exists() && mMap != null) {
|
||||||
Double lat = snapshot.child("latitude").getValue(Double.class);
|
Double lat = snapshot.child("latitude").getValue(Double.class);
|
||||||
Double lng = snapshot.child("longitude").getValue(Double.class);
|
Double lng = snapshot.child("longitude").getValue(Double.class);
|
||||||
|
|
||||||
if (lat != null && lng != null && mMap != null) {
|
if (lat != null && lng != null) {
|
||||||
LatLng childPosition = new LatLng(lat, lng);
|
LatLng childPos = new LatLng(lat, lng);
|
||||||
|
|
||||||
if (childMarker == null) {
|
// FILTRO: Só atualiza o mapa se a localização NÃO for a da Google HQ (Califórnia)
|
||||||
MarkerOptions markerOptions = new MarkerOptions()
|
if (Math.abs(lat - 37.4219) > 0.001) {
|
||||||
.position(childPosition)
|
if (childMarker == null) {
|
||||||
.title("Criança");
|
childMarker = mMap.addMarker(new MarkerOptions()
|
||||||
|
.position(childPos)
|
||||||
|
.title("Filho")
|
||||||
|
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
|
||||||
|
} else {
|
||||||
|
childMarker.setPosition(childPos);
|
||||||
|
}
|
||||||
|
|
||||||
childMarker = mMap.addMarker(markerOptions);
|
// Move a câmara automaticamente para Vila do Conde
|
||||||
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(childPosition, 16f));
|
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(childPos, 17f));
|
||||||
|
Log.d("MapFragment", "Pai recebeu localização real: " + lat);
|
||||||
} else {
|
} else {
|
||||||
childMarker.setPosition(childPosition);
|
Log.d("MapFragment", "Pai ignorou localização de teste (Califórnia)");
|
||||||
mMap.animateCamera(CameraUpdateFactory.newLatLng(childPosition));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Log.w("MapFragment", "Pasta de localização vazia ou inexistente para o ID: " + currentChildId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@Override public void onCancelled(@NonNull DatabaseError error) {
|
||||||
@Override
|
Log.e("MapFragment", "Erro ao ler Firebase: " + error.getMessage());
|
||||||
public void onCancelled(@NonNull DatabaseError error) {
|
|
||||||
Log.e("MapFragment", "Erro ao ler localização: " + error.getMessage());
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
locationRef.addValueEventListener(locationListener);
|
locationRef.addValueEventListener(locationListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
if (locationRef != null && locationListener != null) {
|
if (locationRef != null && locationListener != null) locationRef.removeEventListener(locationListener);
|
||||||
locationRef.removeEventListener(locationListener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showEmptyState() {
|
private void showEmptyState() {
|
||||||
@@ -213,19 +226,10 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showCodeDialog(String code) {
|
private void showCodeDialog(String code) {
|
||||||
if (getContext() == null) return;
|
new AlertDialog.Builder(requireContext())
|
||||||
|
|
||||||
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())
|
|
||||||
.setTitle("Filho Adicionado!")
|
.setTitle("Filho Adicionado!")
|
||||||
.setMessage("O código de acesso é: " + code + "\n\n(Já copiado automaticamente)")
|
.setMessage("Código: " + code)
|
||||||
.setCancelable(false)
|
.setPositiveButton("OK", (d, w) -> checkIfHasChildren())
|
||||||
.setPositiveButton("Ir para o Mapa", (dialog, which) -> checkIfHasChildren())
|
|
||||||
.setNegativeButton("Adicionar Outro", (dialog, which) -> dialog.dismiss())
|
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,128 +5,72 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#F3F6FB">
|
tools:context=".ui.map.MapFragment">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<LinearLayout
|
||||||
android:id="@+id/layoutEmptyState"
|
android:id="@+id/layoutEmptyState"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:visibility="visible">
|
android:gravity="center"
|
||||||
|
android:orientation="vertical"
|
||||||
<View
|
android:visibility="gone">
|
||||||
android:id="@+id/viewHeader"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="160dp"
|
|
||||||
android:background="@drawable/bg_header_rounded"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvAppTitle"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:text="Ainda não tem filhos associados."
|
||||||
android:text="FindU"
|
android:textSize="18sp"
|
||||||
android:textColor="@android:color/white"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:textSize="22sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<Button
|
||||||
android:id="@+id/cardEmptyState"
|
android:id="@+id/btnAddChild"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="0dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="24dp"
|
android:text="Adicionar Filho" />
|
||||||
android:layout_marginTop="30dp"
|
</LinearLayout>
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
app:cardCornerRadius="20dp"
|
|
||||||
app:cardElevation="4dp"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/btnSOS"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/tvAppTitle">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="24dp">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/bgIcon"
|
|
||||||
android:layout_width="100dp"
|
|
||||||
android:layout_height="100dp"
|
|
||||||
android:background="@drawable/bg_circle_light_blue"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/tvNoChildTitle"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintVertical_chainStyle="packed" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="50dp"
|
|
||||||
android:layout_height="50dp"
|
|
||||||
android:src="@android:drawable/ic_menu_add"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/bgIcon"
|
|
||||||
app:layout_constraintEnd_toEndOf="@+id/bgIcon"
|
|
||||||
app:layout_constraintStart_toStartOf="@+id/bgIcon"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/bgIcon"
|
|
||||||
app:tint="#3D6DFF" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvNoChildTitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:text="Nenhum filho adicionado"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/tvNoChildDesc"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/bgIcon" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvNoChildDesc"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="Adicione o seu primeiro filho para começar."
|
|
||||||
app:layout_constraintBottom_toTopOf="@+id/btnAddChild"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/tvNoChildTitle" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/btnAddChild"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="60dp"
|
|
||||||
android:text="Adicionar Filho"
|
|
||||||
android:textStyle="bold"
|
|
||||||
app:backgroundTint="#3D6DFF"
|
|
||||||
app:cornerRadius="12dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</androidx.cardview.widget.CardView>
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
|
||||||
android:id="@+id/btnSOS"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="65dp"
|
|
||||||
android:layout_marginHorizontal="24dp"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:text="SOS"
|
|
||||||
app:backgroundTint="#D32F2F"
|
|
||||||
app:cornerRadius="16dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
<androidx.fragment.app.FragmentContainerView
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/mapContainer"
|
android:id="@+id/mapContainer"
|
||||||
android:layout_width="match_parent"
|
android:name="com.google.android.gms.maps.SupportMapFragment"
|
||||||
android:layout_height="match_parent"
|
android:layout_width="0dp"
|
||||||
android:visibility="gone" />
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/btnAbrirChat"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:backgroundTint="#4CAF50"
|
||||||
|
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" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnSOS"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="60dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginBottom="24dp"
|
||||||
|
android:text="BOTÃO SOS DE EMERGÊNCIA"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:backgroundTint="#D32F2F"
|
||||||
|
app:cornerRadius="12dp"
|
||||||
|
app:icon="@android:drawable/ic_menu_call"
|
||||||
|
app:iconGravity="textEnd"
|
||||||
|
app:iconPadding="16dp"
|
||||||
|
app:iconTint="@android:color/white"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
Reference in New Issue
Block a user