From b942073b67c7767fd96f3aec17dff9435db58e61 Mon Sep 17 00:00:00 2001 From: 230408 <230408@epvc.pt> Date: Wed, 6 May 2026 12:42:32 +0100 Subject: [PATCH] Merge remote-tracking branch 'origin/main' # Conflicts: # .idea/deploymentTargetSelector.xml # app/src/main/java/com/example/pap_findu/LocationService.java # app/src/main/java/com/example/pap_findu/login_activity.java # app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java --- .../example/pap_findu/LocationService.java | 178 ++++++++++++++---- .../pap_findu/ui/alerts/AlertsFragment.java | 156 ++++++++------- .../pap_findu/ui/history/HistoryFragment.java | 47 ++--- .../example/pap_findu/ui/map/MapFragment.java | 10 +- .../pap_findu/ui/zones/ZonesFragment.java | 147 ++------------- app/src/main/res/layout/fragment_zones.xml | 32 ++-- 6 files changed, 282 insertions(+), 288 deletions(-) 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 b9a3139..bbed931 100644 --- a/app/src/main/java/com/example/pap_findu/LocationService.java +++ b/app/src/main/java/com/example/pap_findu/LocationService.java @@ -31,8 +31,13 @@ import com.google.android.gms.location.LocationServices; import com.google.android.gms.location.Priority; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FieldValue; +import com.google.firebase.firestore.FirebaseFirestore; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; public class LocationService extends Service { @@ -40,23 +45,39 @@ public class LocationService extends Service { private FusedLocationProviderClient fusedLocationClient; private LocationCallback locationCallback; private DatabaseReference databaseReference; - private GeofenceManager geofenceManager; + + private FirebaseFirestore db; + private String currentChildCode; + private List activeSafeZones = new ArrayList<>(); + private HashMap isInsideZoneMap = new HashMap<>(); + + // VARIÁVEIS DE MOVIMENTO INTELIGENTE + private Location lastCheckLocation = null; + private long lastCheckTime = 0; + private boolean isCurrentlyMoving = false; + private long movementStartTime = 0; @Override public void onCreate() { super.onCreate(); fusedLocationClient = LocationServices.getFusedLocationProviderClient(this); + db = FirebaseFirestore.getInstance(); - String childCode = getSharedPreferences("FindU_Prefs", MODE_PRIVATE) + currentChildCode = getSharedPreferences("FindU_Prefs", MODE_PRIVATE) .getString("child_access_code", null); - if (childCode != null) { - // Caminho direto na raiz para o monitoramento em tempo real - databaseReference = FirebaseDatabase.getInstance().getReference(childCode).child("live_location"); + if (currentChildCode != null) { + databaseReference = FirebaseDatabase.getInstance().getReference(currentChildCode).child("live_location"); + loadSafeZones(); } + } - geofenceManager = new GeofenceManager(this); - geofenceManager.setupGeofences(); + private void loadSafeZones() { + db.collection("SafeZones") + .whereEqualTo("childCode", currentChildCode) + .addSnapshotListener((value, error) -> { + if (value != null) activeSafeZones = value.getDocuments(); + }); } @Override @@ -64,7 +85,7 @@ public class LocationService extends Service { createNotificationChannel(); Notification notification = new NotificationCompat.Builder(this, "LocationChannel") .setContentTitle("FindU Ativo") - .setContentText("A partilhar localização e bateria em tempo real...") + .setContentText("Monitoramento de segurança em tempo real...") .setSmallIcon(R.mipmap.ic_launcher) .setPriority(NotificationCompat.PRIORITY_LOW) .build(); @@ -84,71 +105,148 @@ public class LocationService extends Service { private void requestLocationUpdates() { LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000) - .setMinUpdateDistanceMeters(0) + .setMinUpdateIntervalMillis(2000) .build(); locationCallback = new LocationCallback() { @Override public void onLocationResult(@NonNull LocationResult locationResult) { for (Location location : locationResult.getLocations()) { - // Filtro para ignorar a localização padrão do emulador (Califórnia) - if (Math.abs(location.getLatitude() - 37.4219) > 0.001) { - updateFirebase(location); - } + updateFirebase(location); + checkGeofenceMath(location); + checkMovementSmartHistory(location); } } }; try { fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper()); - } catch (SecurityException e) { - Log.e("LocationService", "Erro de permissão GPS"); + } catch (SecurityException e) { e.printStackTrace(); } + } + + // LÓGICA DE MOVIMENTO APERFEIÇOADA (SEM SPAM) + private void checkMovementSmartHistory(Location currentLocation) { + long currentTime = System.currentTimeMillis(); + + if (lastCheckLocation == null) { + lastCheckLocation = currentLocation; + lastCheckTime = currentTime; + return; } + + long elapsedMillis = currentTime - lastCheckTime; + + // VERIFICAÇÃO A CADA 10 MINUTOS (Mude para 1 para testar no emulador) + if (elapsedMillis >= 10 * 60 * 1000L) { + float distanceMoved = currentLocation.distanceTo(lastCheckLocation); + + // Se ele se moveu mais de 500 metros desde a última vez + if (distanceMoved > 500.0f) { + if (!isCurrentlyMoving) { + // ELE COMEÇOU A ANDAR AGORA + isCurrentlyMoving = true; + movementStartTime = lastCheckTime; // O movimento começou no último checkpoint + sendHistoryOnlyEvent("Início de Deslocação", "O filho começou a movimentar-se."); + } + // Se já estava a andar, não fazemos nada (para não encher o histórico) + Log.d("FindU_Move", "Filho continua em movimento..."); + } + else { + // ELE ESTÁ PARADO (Moveu menos de 500m) + if (isCurrentlyMoving) { + // ELE ESTAVA A ANDAR E PAROU AGORA + isCurrentlyMoving = false; + long totalDurationMillis = currentTime - movementStartTime; + int totalMinutes = (int) (totalDurationMillis / 60000); + + sendHistoryOnlyEvent("Fim de Deslocação", "O filho parou. Esteve em movimento por aproximadamente " + totalMinutes + " minutos."); + } else { + // Já estava parado e continua parado... enviamos uma nota apenas se for a primeira vez + // Ou podemos simplesmente não enviar nada para manter o histórico limpo + Log.d("FindU_Move", "Filho continua parado na mesma zona."); + } + } + + // Atualiza o ponto de referência para os próximos 10 minutos + lastCheckLocation = currentLocation; + lastCheckTime = currentTime; + } + } + + private void checkGeofenceMath(Location currentLocation) { + if (activeSafeZones == null || currentChildCode == null) return; + for (DocumentSnapshot zoneDoc : activeSafeZones) { + String zoneId = zoneDoc.getId(); + Number latNum = (Number) zoneDoc.get("latitude"); + Number lngNum = (Number) zoneDoc.get("longitude"); + Number radNum = (Number) zoneDoc.get("radius"); + + if (latNum != null && lngNum != null && radNum != null) { + Location zoneLoc = new Location(""); + zoneLoc.setLatitude(latNum.doubleValue()); + zoneLoc.setLongitude(lngNum.doubleValue()); + float dist = currentLocation.distanceTo(zoneLoc); + boolean isInside = dist <= radNum.floatValue(); + + if (!isInsideZoneMap.containsKey(zoneId)) { + isInsideZoneMap.put(zoneId, isInside); + continue; + } + if (Boolean.TRUE.equals(isInsideZoneMap.get(zoneId)) && !isInside) { + isInsideZoneMap.put(zoneId, false); + sendAlert("Saída de Zona", "O filho saiu de uma zona segura."); + } else if (Boolean.FALSE.equals(isInsideZoneMap.get(zoneId)) && isInside) { + isInsideZoneMap.put(zoneId, true); + sendAlert("Entrada na Zona", "O filho entrou numa zona segura."); + } + } + } + } + + private void sendAlert(String title, String message) { + Map data = new HashMap<>(); + data.put("childCode", currentChildCode); + data.put("title", title); + data.put("message", message); + data.put("timestamp", FieldValue.serverTimestamp()); + data.put("status", "PENDING"); + db.collection("Alerts").add(data); + db.collection("History").add(data); + } + + private void sendHistoryOnlyEvent(String title, String message) { + Map data = new HashMap<>(); + data.put("childCode", currentChildCode); + data.put("title", title); + data.put("message", message); + data.put("timestamp", FieldValue.serverTimestamp()); + data.put("type", "INFO"); + db.collection("History").add(data); } private void updateFirebase(Location location) { if (databaseReference != null) { - // --- LÓGICA DA BATERIA --- IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); Intent batteryStatus = registerReceiver(null, ifilter); - int level = -1; if (batteryStatus != null) { - int rawLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1); - int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1); - level = (int) ((rawLevel / (float) scale) * 100); + level = (int) ((batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) / (float) batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1)) * 100); } - // ------------------------- - Map data = new HashMap<>(); data.put("latitude", location.getLatitude()); data.put("longitude", location.getLongitude()); - data.put("bateria", level + "%"); // Envia ex: "85%" + data.put("bateria", level + "%"); data.put("last_updated", System.currentTimeMillis()); - - databaseReference.setValue(data).addOnFailureListener(e -> { - Log.e("LocationService", "Erro ao enviar para Firebase: " + e.getMessage()); - }); + databaseReference.setValue(data); } } - @Override - public void onDestroy() { - if (fusedLocationClient != null && locationCallback != null) { - fusedLocationClient.removeLocationUpdates(locationCallback); - } - super.onDestroy(); - } - + @Override public void onDestroy() { 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", - NotificationManager.IMPORTANCE_LOW - ); + NotificationChannel channel = new NotificationChannel("LocationChannel", "Monitoramento", NotificationManager.IMPORTANCE_LOW); NotificationManager manager = getSystemService(NotificationManager.class); if (manager != null) manager.createNotificationChannel(channel); } diff --git a/app/src/main/java/com/example/pap_findu/ui/alerts/AlertsFragment.java b/app/src/main/java/com/example/pap_findu/ui/alerts/AlertsFragment.java index 9496239..ce06d78 100644 --- a/app/src/main/java/com/example/pap_findu/ui/alerts/AlertsFragment.java +++ b/app/src/main/java/com/example/pap_findu/ui/alerts/AlertsFragment.java @@ -38,7 +38,7 @@ public class AlertsFragment extends Fragment { db = FirebaseFirestore.getInstance(); adapter = new AlertsAdapter(requireContext()); - + binding.rvAlerts.setLayoutManager(new LinearLayoutManager(getContext())); binding.rvAlerts.setAdapter(adapter); @@ -77,43 +77,94 @@ public class AlertsFragment extends Fragment { } private void loadAlerts() { - // Isolamento de dados: só carregar alertas deste filho String childCode = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE) .getString("child_access_code", null); - android.util.Log.d("FindU_Debug", "AlertsFragment -> childCode obtido: [" + childCode + "]"); - - if (childCode == null) { - android.util.Log.e("FindU_Debug", "ERRO FATAL: childCode é nulo no AlertsFragment! Verifique o registo do filho."); - if (getContext() != null) { - android.widget.Toast.makeText(getContext(), "Erro: Código do filho não encontrado.", android.widget.Toast.LENGTH_LONG).show(); - } + if (childCode == null || childCode.isEmpty()) { + android.util.Log.e("FindU_Debug", "ERRO FATAL: childCode é nulo/vazio no AlertsFragment!"); return; } db.collection("Alerts") - .whereEqualTo("childCode", childCode) - .orderBy("timestamp", Query.Direction.DESCENDING) - .addSnapshotListener((value, error) -> { - if (error != null) { - // IMPORTANTE: O erro do Firestore contém o link para criar o índice no Logcat - android.util.Log.e("FindU_Debug", "Erro na Query: " + error.getMessage()); - if (getActivity() != null) { - android.widget.Toast.makeText(getActivity(), "Erro de Base de Dados. Verifique o Logcat para o link do Índice.", android.widget.Toast.LENGTH_LONG).show(); - } - return; - } - if (value != null) { - android.util.Log.d("FirebaseApp", "AlertsFragment -> Documentos recebidos: " + value.size()); - allAlertsList.clear(); - for (QueryDocumentSnapshot doc : value) { - AlertMessage alert = doc.toObject(AlertMessage.class); - allAlertsList.add(alert); - } - filterAdapterList(); - updateAlertsSummary(); - } - }); + .whereEqualTo("childCode", childCode) + .orderBy("timestamp", Query.Direction.DESCENDING) + .addSnapshotListener((value, error) -> { + if (error != null) { + android.util.Log.e("FindU_Debug", "Erro na Query: " + error.getMessage()); + return; + } + if (value != null) { + allAlertsList.clear(); + for (QueryDocumentSnapshot doc : value) { + AlertMessage alert = doc.toObject(AlertMessage.class); + allAlertsList.add(alert); + } + updateAlertsSummary(); + filterAdapterList(); + } + }); + } + + private void filterAdapterList() { + List filtered = new ArrayList<>(); + + if (currentFilter.equals("ALL")) { + filtered = new ArrayList<>(allAlertsList); + } else { + for (AlertMessage msg : allAlertsList) { + // AQUI ESTÁ A CORREÇÃO MÁGICA: equalsIgnoreCase e verificação de nulo + if (msg.getStatus() != null && msg.getStatus().equalsIgnoreCase(currentFilter)) { + filtered.add(msg); + } + } + } + + adapter.setAlertsList(filtered); + + View root = binding.getRoot(); + com.google.android.material.card.MaterialCardView bannerCard = root.findViewById(R.id.new_alert_banner); + + if (filtered.isEmpty()) { + binding.rvAlerts.setVisibility(View.GONE); + if (bannerCard != null) { + bannerCard.setVisibility(View.VISIBLE); + + android.widget.TextView titleView = root.findViewById(R.id.new_alert_title); + android.widget.TextView subtitleView = root.findViewById(R.id.new_alert_subtitle); + android.widget.ImageView iconView = root.findViewById(R.id.new_alert_icon); + + bannerCard.setCardBackgroundColor(android.graphics.Color.parseColor("#E8F5E9")); + titleView.setText("Tudo tranquilo"); + titleView.setTextColor(android.graphics.Color.parseColor("#2E7D32")); + + if (currentFilter.equals("PENDING")) { + subtitleView.setText("Não há alertas pendentes no momento."); + } else if (currentFilter.equals("RESOLVED")) { + subtitleView.setText("Ainda não há alertas resolvidos."); + } else { + subtitleView.setText("Nenhum alerta registado até agora."); + } + + iconView.setImageResource(android.R.drawable.ic_dialog_info); + iconView.clearColorFilter(); + iconView.setColorFilter(android.graphics.Color.parseColor("#4CAF50"), android.graphics.PorterDuff.Mode.SRC_IN); + } + } else { + binding.rvAlerts.setVisibility(View.VISIBLE); + + int pendingCount = 0; + for (AlertMessage msg : allAlertsList) { + if (msg.getStatus() != null && msg.getStatus().equalsIgnoreCase("PENDING")) { + pendingCount++; + } + } + + if (pendingCount == 0 && bannerCard != null) { + bannerCard.setVisibility(View.GONE); + } else if (bannerCard != null) { + bannerCard.setVisibility(View.VISIBLE); + } + } } private void updateAlertsSummary() { @@ -123,7 +174,7 @@ public class AlertsFragment extends Fragment { AlertMessage latestPending = null; for (AlertMessage msg : allAlertsList) { - if ("PENDING".equals(msg.getStatus())) { + if (msg.getStatus() != null && msg.getStatus().equalsIgnoreCase("PENDING")) { pendingCount++; if (latestPending == null || (msg.getTimestamp() != null && latestPending.getTimestamp() != null && msg.getTimestamp().after(latestPending.getTimestamp()))) { latestPending = msg; @@ -131,22 +182,15 @@ public class AlertsFragment extends Fragment { } } - // Referências diretas aos NOVOS IDs criados do zero View root = binding.getRoot(); com.google.android.material.card.MaterialCardView bannerCard = root.findViewById(R.id.new_alert_banner); android.widget.ImageView iconView = root.findViewById(R.id.new_alert_icon); android.widget.TextView titleView = root.findViewById(R.id.new_alert_title); android.widget.TextView subtitleView = root.findViewById(R.id.new_alert_subtitle); - if (bannerCard == null || iconView == null || titleView == null || subtitleView == null) { - android.util.Log.e("AlertsFragment", "ERRO: Não foi possível encontrar os novos IDs do banner!"); - return; - } - - bannerCard.setVisibility(View.VISIBLE); + if (bannerCard == null || iconView == null || titleView == null || subtitleView == null) return; if (pendingCount > 0) { - // --- ESTADO DE ATENÇÃO (Laranja) --- bannerCard.setCardBackgroundColor(android.graphics.Color.parseColor("#FFF3E0")); titleView.setText(pendingCount + " alerta(s) aguardando revisão"); titleView.setTextColor(android.graphics.Color.parseColor("#E65100")); @@ -156,21 +200,10 @@ public class AlertsFragment extends Fragment { } else { subtitleView.setText("Verifique os detalhes abaixo."); } - + iconView.setImageResource(R.drawable.ic_alert_warning); iconView.clearColorFilter(); iconView.setColorFilter(android.graphics.Color.parseColor("#E65100"), android.graphics.PorterDuff.Mode.SRC_IN); - - } else { - // --- ESTADO SEGURO (Verde) --- - bannerCard.setCardBackgroundColor(android.graphics.Color.parseColor("#E8F5E9")); - titleView.setText("Tudo tranquilo"); - titleView.setTextColor(android.graphics.Color.parseColor("#2E7D32")); - subtitleView.setText("Nenhum alerta pendente. Todos os eventos foram revistos."); - - iconView.setImageResource(android.R.drawable.ic_dialog_info); - iconView.clearColorFilter(); - iconView.setColorFilter(android.graphics.Color.parseColor("#4CAF50"), android.graphics.PorterDuff.Mode.SRC_IN); } } @@ -179,31 +212,16 @@ public class AlertsFragment extends Fragment { long time = date.getTime(); long now = System.currentTimeMillis(); long diff = now - time; - + if (diff < 60000) return "Agora mesmo"; else if (diff < 3600000) return "Há " + (diff / 60000) + " minuto(s)"; else if (diff < 86400000) return "Há " + (diff / 3600000) + " hora(s)"; else return "Há " + (diff / 86400000) + " dia(s)"; } - private void filterAdapterList() { - if (currentFilter.equals("ALL")) { - adapter.setAlertsList(new ArrayList<>(allAlertsList)); - return; - } - - List filtered = new ArrayList<>(); - for (AlertMessage msg : allAlertsList) { - if (currentFilter.equals(msg.getStatus())) { - filtered.add(msg); - } - } - adapter.setAlertsList(filtered); - } - @Override public void onDestroyView() { super.onDestroyView(); binding = null; } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap_findu/ui/history/HistoryFragment.java b/app/src/main/java/com/example/pap_findu/ui/history/HistoryFragment.java index 7e46c50..e61f415 100644 --- a/app/src/main/java/com/example/pap_findu/ui/history/HistoryFragment.java +++ b/app/src/main/java/com/example/pap_findu/ui/history/HistoryFragment.java @@ -40,7 +40,9 @@ public class HistoryFragment extends Fragment { RecyclerView rvHistory = binding.rvHistory; rvHistory.setLayoutManager(new LinearLayoutManager(getContext())); - adapter = new HistoryAdapter(); + + // Inicializa o adapter + adapter = new HistoryAdapter(requireContext()); rvHistory.setAdapter(adapter); loadHistory(); @@ -49,35 +51,26 @@ public class HistoryFragment extends Fragment { } private void loadHistory() { - // Isolamento de dados: só carregar histórico deste filho + // Recuperar o código do filho para isolamento de dados String childCode = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE) .getString("child_access_code", null); - Log.d("FindU_Debug", "HistoryFragment -> childCode obtido: [" + childCode + "]"); - - if (childCode == null) { - Log.e("FindU_Debug", "ERRO FATAL: childCode é nulo no HistoryFragment! Verifique o registo do filho."); - if (getContext() != null) { - android.widget.Toast.makeText(getContext(), "Erro: Código do filho não encontrado.", android.widget.Toast.LENGTH_LONG).show(); - } + if (childCode == null || childCode.isEmpty()) { + Log.e("FindU_Debug", "ERRO: childCode não encontrado no HistoryFragment."); return; } - db.collection("Alerts") + // MUDANÇA CRUCIAL: Agora buscamos na coleção "History" + db.collection("History") .whereEqualTo("childCode", childCode) .orderBy("timestamp", Query.Direction.DESCENDING) .addSnapshotListener((value, error) -> { if (error != null) { - // IMPORTANTE: O erro do Firestore contém o link para criar o índice no Logcat - Log.e("FindU_Debug", "Erro na Query: " + error.getMessage()); - if (getActivity() != null) { - android.widget.Toast.makeText(getActivity(), "Erro de Base de Dados. Verifique o Logcat para o link do Índice.", android.widget.Toast.LENGTH_LONG).show(); - } + Log.e("FindU_Debug", "Erro na Query History: " + error.getMessage()); return; } if (value != null) { - Log.d("FirebaseApp", "HistoryFragment -> Documentos recebidos: " + value.size()); List timelineList = new ArrayList<>(); String lastDateHeader = ""; @@ -95,6 +88,7 @@ public class HistoryFragment extends Fragment { timelineList.add(alert); } + // Envia a lista processada (com datas e eventos) para o adapter adapter.setItems(timelineList); } }); @@ -105,22 +99,21 @@ public class HistoryFragment extends Fragment { targetCal.setTime(date); Calendar todayCal = Calendar.getInstance(); - + Calendar yesterdayCal = Calendar.getInstance(); yesterdayCal.add(Calendar.DAY_OF_YEAR, -1); + SimpleDateFormat sdfBase = new SimpleDateFormat("dd MMMM", new Locale("pt", "PT")); + if (targetCal.get(Calendar.YEAR) == todayCal.get(Calendar.YEAR) && - targetCal.get(Calendar.DAY_OF_YEAR) == todayCal.get(Calendar.DAY_OF_YEAR)) { - SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM", new Locale("pt", "PT")); - return "Hoje, " + sdf.format(date); + targetCal.get(Calendar.DAY_OF_YEAR) == todayCal.get(Calendar.DAY_OF_YEAR)) { + return "Hoje, " + sdfBase.format(date); } else if (targetCal.get(Calendar.YEAR) == yesterdayCal.get(Calendar.YEAR) && - targetCal.get(Calendar.DAY_OF_YEAR) == yesterdayCal.get(Calendar.DAY_OF_YEAR)) { - SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM", new Locale("pt", "PT")); - return "Ontem, " + sdf.format(date); + targetCal.get(Calendar.DAY_OF_YEAR) == yesterdayCal.get(Calendar.DAY_OF_YEAR)) { + return "Ontem, " + sdfBase.format(date); } else { - SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy", new Locale("pt", "PT")); - // Capitalizar primeira letra no caso do MMMM ser retornado em minúsculo localmente - String f = sdf.format(date); + SimpleDateFormat sdfFull = new SimpleDateFormat("dd MMMM yyyy", new Locale("pt", "PT")); + String f = sdfFull.format(date); return f.substring(0, 1).toUpperCase() + f.substring(1); } } @@ -130,4 +123,4 @@ public class HistoryFragment extends Fragment { super.onDestroyView(); binding = null; } -} +} \ 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 d57868b..e2d83c0 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 @@ -303,7 +303,9 @@ public class MapFragment extends Fragment implements OnMapReadyCallback { } } } - @Override public void onCancelled(@NonNull DatabaseError error) {} + @Override public void onCancelled(@NonNull DatabaseError error) { + Log.e("FindU_Map", "Erro ao ler GPS: " + error.getMessage()); + } }; locationRef.addValueEventListener(locationListener); @@ -315,7 +317,11 @@ public class MapFragment extends Fragment implements OnMapReadyCallback { safeZonesListener = db.collection("SafeZones") .whereEqualTo("childCode", childCode) .addSnapshotListener((value, error) -> { - if (error != null || value == null || mMap == null) return; + if (error != null) { + Log.e("FindU_Map", "Erro ao ler SafeZones: " + error.getMessage()); + return; + } + if (value == null || mMap == null) return; // Limpa circulos velhos for (com.google.android.gms.maps.model.Circle circle : mapCircles) { diff --git a/app/src/main/java/com/example/pap_findu/ui/zones/ZonesFragment.java b/app/src/main/java/com/example/pap_findu/ui/zones/ZonesFragment.java index 2074915..9045e41 100644 --- a/app/src/main/java/com/example/pap_findu/ui/zones/ZonesFragment.java +++ b/app/src/main/java/com/example/pap_findu/ui/zones/ZonesFragment.java @@ -1,14 +1,10 @@ package com.example.pap_findu.ui.zones; -import android.graphics.Color; -import android.graphics.drawable.GradientDrawable; -import android.location.Location; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.NonNull; @@ -20,11 +16,6 @@ import com.example.pap_findu.adapters.ZonesAdapter; import com.example.pap_findu.databinding.FragmentZonesBinding; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseUser; -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 com.google.firebase.firestore.DocumentSnapshot; import com.google.firebase.firestore.FirebaseFirestore; @@ -36,14 +27,8 @@ public class ZonesFragment extends Fragment { private FragmentZonesBinding binding; private FirebaseAuth auth; private FirebaseFirestore db; - private String currentChildId = null; - private String currentChildName = "Filho"; - private DatabaseReference locationRef; - private ValueEventListener locationListener; private com.google.firebase.firestore.ListenerRegistration safeZonesListener; - - private Location childLocation = null; private List safeZonesList = new ArrayList<>(); @Override @@ -87,133 +72,31 @@ public class ZonesFragment extends Fragment { if (value != null) { safeZonesList = value.getDocuments(); adapter.setZonesList(safeZonesList); - checkSafetyStatus(); + + // Atualizar contador dinâmico de zonas + if (isAdded() && getActivity() != null && binding != null) { + int count = value.size(); + TextView tvZoneCount = binding.getRoot().findViewById(R.id.tvDynamicZoneCount); + if (tvZoneCount != null) { + if (count == 0) { + tvZoneCount.setText("Nenhuma zona segura criada"); + } else if (count == 1) { + tvZoneCount.setText("1 zona monitorizada"); + } else { + tvZoneCount.setText(count + " zonas monitorizadas"); + } + } + } } }); } - checkUserChildren(); - return root; } - private void checkUserChildren() { - FirebaseUser user = auth.getCurrentUser(); - if (user == null) return; - - if (user.isAnonymous()) { - currentChildId = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE) - .getString("child_access_code", null); - currentChildName = "Você"; - if (currentChildId != null) { - listenToChildLocation(); - } - return; - } - - db.collection("children") - .whereEqualTo("parentId", user.getUid()) - .limit(1) - .get() - .addOnCompleteListener(task -> { - if (task.isSuccessful() && task.getResult() != null && !task.getResult().isEmpty()) { - DocumentSnapshot document = task.getResult().getDocuments().get(0); - currentChildId = document.getString("accessCode"); - - if (document.contains("name")) { - currentChildName = document.getString("name"); - } else if (document.contains("nome")) { - currentChildName = document.getString("nome"); - } - - if (currentChildId != null) { - listenToChildLocation(); - } - } - }); - } - - private void listenToChildLocation() { - locationRef = FirebaseDatabase.getInstance().getReference(currentChildId).child("live_location"); - locationListener = new ValueEventListener() { - @Override - public void onDataChange(@NonNull DataSnapshot snapshot) { - if (snapshot.exists()) { - Double lat = snapshot.child("latitude").getValue(Double.class); - Double lng = snapshot.child("longitude").getValue(Double.class); - if (lat != null && lng != null) { - childLocation = new Location(""); - childLocation.setLatitude(lat); - childLocation.setLongitude(lng); - checkSafetyStatus(); - } - } - } - - @Override - public void onCancelled(@NonNull DatabaseError error) {} - }; - locationRef.addValueEventListener(locationListener); - } - - private void checkSafetyStatus() { - if (childLocation == null || binding == null || safeZonesList == null) return; - - boolean isSafe = false; - - for (DocumentSnapshot zoneDoc : safeZonesList) { - Double lat = zoneDoc.getDouble("latitude"); - Double lng = zoneDoc.getDouble("longitude"); - if (lat != null && lng != null) { - Location zoneLocation = new Location(""); - zoneLocation.setLatitude(lat); - zoneLocation.setLongitude(lng); - - if (childLocation.distanceTo(zoneLocation) <= 200.0) { - isSafe = true; - break; - } - } - } - - updateUIBanner(isSafe); - } - - private void updateUIBanner(boolean isSafe) { - View root = binding.getRoot(); - LinearLayout bannerLayout = root.findViewById(R.id.bannerZoneLayout); - TextView title = root.findViewById(R.id.bannerZoneTitle); - TextView subtitle = root.findViewById(R.id.bannerZoneSubtitle); - - if (bannerLayout == null || title == null || subtitle == null) return; - - GradientDrawable background = (GradientDrawable) bannerLayout.getBackground().mutate(); - - if (isSafe) { - // VERDE -> Seguro - background.setColor(Color.parseColor("#EAF4D4")); // Fundo verde clarinho - title.setTextColor(Color.parseColor("#0B6635")); - subtitle.setTextColor(Color.parseColor("#0B6635")); - - title.setText("O " + currentChildName + " está numa zona segura"); - subtitle.setText("A rotina tem estado estável."); - } else { - // LARANJA -> Fora da zona - background.setColor(Color.parseColor("#FFE0B2")); // Fundo laranja clarinho - title.setTextColor(Color.parseColor("#E65100")); - subtitle.setTextColor(Color.parseColor("#E65100")); - - title.setText("Atenção: Fora de zona segura"); - subtitle.setText("O " + currentChildName + " movimentou-se para fora de contexto."); - } - } - @Override public void onDestroyView() { super.onDestroyView(); - if (locationRef != null && locationListener != null) { - locationRef.removeEventListener(locationListener); - } if (safeZonesListener != null) { safeZonesListener.remove(); } diff --git a/app/src/main/res/layout/fragment_zones.xml b/app/src/main/res/layout/fragment_zones.xml index 240f322..036e564 100644 --- a/app/src/main/res/layout/fragment_zones.xml +++ b/app/src/main/res/layout/fragment_zones.xml @@ -46,33 +46,29 @@ - + app:cardCornerRadius="16dp" + app:cardElevation="4dp" + app:cardBackgroundColor="#FFFFFF"> - - +