Merge remote-tracking branch 'origin/main'
# Conflicts: # .idea/deploymentTargetSelector.xml # app/src/main/java/com/example/pap_findu/LocationService.java # app/src/main/java/com/example/pap_findu/login_activity.java # app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java
This commit is contained in:
@@ -31,8 +31,13 @@ import com.google.android.gms.location.LocationServices;
|
|||||||
import com.google.android.gms.location.Priority;
|
import com.google.android.gms.location.Priority;
|
||||||
import com.google.firebase.database.DatabaseReference;
|
import com.google.firebase.database.DatabaseReference;
|
||||||
import com.google.firebase.database.FirebaseDatabase;
|
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.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class LocationService extends Service {
|
public class LocationService extends Service {
|
||||||
@@ -40,23 +45,39 @@ public class LocationService extends Service {
|
|||||||
private FusedLocationProviderClient fusedLocationClient;
|
private FusedLocationProviderClient fusedLocationClient;
|
||||||
private LocationCallback locationCallback;
|
private LocationCallback locationCallback;
|
||||||
private DatabaseReference databaseReference;
|
private DatabaseReference databaseReference;
|
||||||
private GeofenceManager geofenceManager;
|
|
||||||
|
private FirebaseFirestore db;
|
||||||
|
private String currentChildCode;
|
||||||
|
private List<DocumentSnapshot> activeSafeZones = new ArrayList<>();
|
||||||
|
private HashMap<String, Boolean> 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
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
|
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);
|
.getString("child_access_code", null);
|
||||||
|
|
||||||
if (childCode != null) {
|
if (currentChildCode != null) {
|
||||||
// Caminho direto na raiz para o monitoramento em tempo real
|
databaseReference = FirebaseDatabase.getInstance().getReference(currentChildCode).child("live_location");
|
||||||
databaseReference = FirebaseDatabase.getInstance().getReference(childCode).child("live_location");
|
loadSafeZones();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
geofenceManager = new GeofenceManager(this);
|
private void loadSafeZones() {
|
||||||
geofenceManager.setupGeofences();
|
db.collection("SafeZones")
|
||||||
|
.whereEqualTo("childCode", currentChildCode)
|
||||||
|
.addSnapshotListener((value, error) -> {
|
||||||
|
if (value != null) activeSafeZones = value.getDocuments();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -64,7 +85,7 @@ public class LocationService extends Service {
|
|||||||
createNotificationChannel();
|
createNotificationChannel();
|
||||||
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
|
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
|
||||||
.setContentTitle("FindU Ativo")
|
.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)
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.build();
|
.build();
|
||||||
@@ -84,71 +105,148 @@ public class LocationService extends Service {
|
|||||||
|
|
||||||
private void requestLocationUpdates() {
|
private void requestLocationUpdates() {
|
||||||
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
|
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
|
||||||
.setMinUpdateDistanceMeters(0)
|
.setMinUpdateIntervalMillis(2000)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
locationCallback = new LocationCallback() {
|
locationCallback = new LocationCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onLocationResult(@NonNull LocationResult locationResult) {
|
public void onLocationResult(@NonNull LocationResult locationResult) {
|
||||||
for (Location location : locationResult.getLocations()) {
|
for (Location location : locationResult.getLocations()) {
|
||||||
// Filtro para ignorar a localização padrão do emulador (Califórnia)
|
updateFirebase(location);
|
||||||
if (Math.abs(location.getLatitude() - 37.4219) > 0.001) {
|
checkGeofenceMath(location);
|
||||||
updateFirebase(location);
|
checkMovementSmartHistory(location);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
|
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) { e.printStackTrace(); }
|
||||||
Log.e("LocationService", "Erro de permissão GPS");
|
}
|
||||||
|
|
||||||
|
// 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<String, Object> 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<String, Object> 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) {
|
private void updateFirebase(Location location) {
|
||||||
if (databaseReference != null) {
|
if (databaseReference != null) {
|
||||||
// --- LÓGICA DA BATERIA ---
|
|
||||||
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
|
||||||
Intent batteryStatus = registerReceiver(null, ifilter);
|
Intent batteryStatus = registerReceiver(null, ifilter);
|
||||||
|
|
||||||
int level = -1;
|
int level = -1;
|
||||||
if (batteryStatus != null) {
|
if (batteryStatus != null) {
|
||||||
int rawLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
level = (int) ((batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) / (float) batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1)) * 100);
|
||||||
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
|
|
||||||
level = (int) ((rawLevel / (float) scale) * 100);
|
|
||||||
}
|
}
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
data.put("latitude", location.getLatitude());
|
data.put("latitude", location.getLatitude());
|
||||||
data.put("longitude", location.getLongitude());
|
data.put("longitude", location.getLongitude());
|
||||||
data.put("bateria", level + "%"); // Envia ex: "85%"
|
data.put("bateria", level + "%");
|
||||||
data.put("last_updated", System.currentTimeMillis());
|
data.put("last_updated", System.currentTimeMillis());
|
||||||
|
databaseReference.setValue(data);
|
||||||
databaseReference.setValue(data).addOnFailureListener(e -> {
|
|
||||||
Log.e("LocationService", "Erro ao enviar para Firebase: " + e.getMessage());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public void onDestroy() { super.onDestroy(); }
|
||||||
public void onDestroy() {
|
|
||||||
if (fusedLocationClient != null && locationCallback != null) {
|
|
||||||
fusedLocationClient.removeLocationUpdates(locationCallback);
|
|
||||||
}
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable @Override public IBinder onBind(Intent intent) { return null; }
|
@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", "Monitoramento", NotificationManager.IMPORTANCE_LOW);
|
||||||
"LocationChannel",
|
|
||||||
"Monitoramento",
|
|
||||||
NotificationManager.IMPORTANCE_LOW
|
|
||||||
);
|
|
||||||
NotificationManager manager = getSystemService(NotificationManager.class);
|
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||||
if (manager != null) manager.createNotificationChannel(channel);
|
if (manager != null) manager.createNotificationChannel(channel);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class AlertsFragment extends Fragment {
|
|||||||
|
|
||||||
db = FirebaseFirestore.getInstance();
|
db = FirebaseFirestore.getInstance();
|
||||||
adapter = new AlertsAdapter(requireContext());
|
adapter = new AlertsAdapter(requireContext());
|
||||||
|
|
||||||
binding.rvAlerts.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.rvAlerts.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
binding.rvAlerts.setAdapter(adapter);
|
binding.rvAlerts.setAdapter(adapter);
|
||||||
|
|
||||||
@@ -77,43 +77,94 @@ public class AlertsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadAlerts() {
|
private void loadAlerts() {
|
||||||
// Isolamento de dados: só carregar alertas deste filho
|
|
||||||
String childCode = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE)
|
String childCode = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE)
|
||||||
.getString("child_access_code", null);
|
.getString("child_access_code", null);
|
||||||
|
|
||||||
android.util.Log.d("FindU_Debug", "AlertsFragment -> childCode obtido: [" + childCode + "]");
|
if (childCode == null || childCode.isEmpty()) {
|
||||||
|
android.util.Log.e("FindU_Debug", "ERRO FATAL: childCode é nulo/vazio no AlertsFragment!");
|
||||||
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();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
db.collection("Alerts")
|
db.collection("Alerts")
|
||||||
.whereEqualTo("childCode", childCode)
|
.whereEqualTo("childCode", childCode)
|
||||||
.orderBy("timestamp", Query.Direction.DESCENDING)
|
.orderBy("timestamp", Query.Direction.DESCENDING)
|
||||||
.addSnapshotListener((value, error) -> {
|
.addSnapshotListener((value, error) -> {
|
||||||
if (error != null) {
|
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());
|
||||||
android.util.Log.e("FindU_Debug", "Erro na Query: " + error.getMessage());
|
return;
|
||||||
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();
|
if (value != null) {
|
||||||
}
|
allAlertsList.clear();
|
||||||
return;
|
for (QueryDocumentSnapshot doc : value) {
|
||||||
}
|
AlertMessage alert = doc.toObject(AlertMessage.class);
|
||||||
if (value != null) {
|
allAlertsList.add(alert);
|
||||||
android.util.Log.d("FirebaseApp", "AlertsFragment -> Documentos recebidos: " + value.size());
|
}
|
||||||
allAlertsList.clear();
|
updateAlertsSummary();
|
||||||
for (QueryDocumentSnapshot doc : value) {
|
filterAdapterList();
|
||||||
AlertMessage alert = doc.toObject(AlertMessage.class);
|
}
|
||||||
allAlertsList.add(alert);
|
});
|
||||||
}
|
}
|
||||||
filterAdapterList();
|
|
||||||
updateAlertsSummary();
|
private void filterAdapterList() {
|
||||||
}
|
List<AlertMessage> 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() {
|
private void updateAlertsSummary() {
|
||||||
@@ -123,7 +174,7 @@ public class AlertsFragment extends Fragment {
|
|||||||
AlertMessage latestPending = null;
|
AlertMessage latestPending = null;
|
||||||
|
|
||||||
for (AlertMessage msg : allAlertsList) {
|
for (AlertMessage msg : allAlertsList) {
|
||||||
if ("PENDING".equals(msg.getStatus())) {
|
if (msg.getStatus() != null && msg.getStatus().equalsIgnoreCase("PENDING")) {
|
||||||
pendingCount++;
|
pendingCount++;
|
||||||
if (latestPending == null || (msg.getTimestamp() != null && latestPending.getTimestamp() != null && msg.getTimestamp().after(latestPending.getTimestamp()))) {
|
if (latestPending == null || (msg.getTimestamp() != null && latestPending.getTimestamp() != null && msg.getTimestamp().after(latestPending.getTimestamp()))) {
|
||||||
latestPending = msg;
|
latestPending = msg;
|
||||||
@@ -131,22 +182,15 @@ public class AlertsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Referências diretas aos NOVOS IDs criados do zero
|
|
||||||
View root = binding.getRoot();
|
View root = binding.getRoot();
|
||||||
com.google.android.material.card.MaterialCardView bannerCard = root.findViewById(R.id.new_alert_banner);
|
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.ImageView iconView = root.findViewById(R.id.new_alert_icon);
|
||||||
android.widget.TextView titleView = root.findViewById(R.id.new_alert_title);
|
android.widget.TextView titleView = root.findViewById(R.id.new_alert_title);
|
||||||
android.widget.TextView subtitleView = root.findViewById(R.id.new_alert_subtitle);
|
android.widget.TextView subtitleView = root.findViewById(R.id.new_alert_subtitle);
|
||||||
|
|
||||||
if (bannerCard == null || iconView == null || titleView == null || subtitleView == null) {
|
if (bannerCard == null || iconView == null || titleView == null || subtitleView == null) return;
|
||||||
android.util.Log.e("AlertsFragment", "ERRO: Não foi possível encontrar os novos IDs do banner!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bannerCard.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
if (pendingCount > 0) {
|
if (pendingCount > 0) {
|
||||||
// --- ESTADO DE ATENÇÃO (Laranja) ---
|
|
||||||
bannerCard.setCardBackgroundColor(android.graphics.Color.parseColor("#FFF3E0"));
|
bannerCard.setCardBackgroundColor(android.graphics.Color.parseColor("#FFF3E0"));
|
||||||
titleView.setText(pendingCount + " alerta(s) aguardando revisão");
|
titleView.setText(pendingCount + " alerta(s) aguardando revisão");
|
||||||
titleView.setTextColor(android.graphics.Color.parseColor("#E65100"));
|
titleView.setTextColor(android.graphics.Color.parseColor("#E65100"));
|
||||||
@@ -156,21 +200,10 @@ public class AlertsFragment extends Fragment {
|
|||||||
} else {
|
} else {
|
||||||
subtitleView.setText("Verifique os detalhes abaixo.");
|
subtitleView.setText("Verifique os detalhes abaixo.");
|
||||||
}
|
}
|
||||||
|
|
||||||
iconView.setImageResource(R.drawable.ic_alert_warning);
|
iconView.setImageResource(R.drawable.ic_alert_warning);
|
||||||
iconView.clearColorFilter();
|
iconView.clearColorFilter();
|
||||||
iconView.setColorFilter(android.graphics.Color.parseColor("#E65100"), android.graphics.PorterDuff.Mode.SRC_IN);
|
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 time = date.getTime();
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long diff = now - time;
|
long diff = now - time;
|
||||||
|
|
||||||
if (diff < 60000) return "Agora mesmo";
|
if (diff < 60000) return "Agora mesmo";
|
||||||
else if (diff < 3600000) return "Há " + (diff / 60000) + " minuto(s)";
|
else if (diff < 3600000) return "Há " + (diff / 60000) + " minuto(s)";
|
||||||
else if (diff < 86400000) return "Há " + (diff / 3600000) + " hora(s)";
|
else if (diff < 86400000) return "Há " + (diff / 3600000) + " hora(s)";
|
||||||
else return "Há " + (diff / 86400000) + " dia(s)";
|
else return "Há " + (diff / 86400000) + " dia(s)";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void filterAdapterList() {
|
|
||||||
if (currentFilter.equals("ALL")) {
|
|
||||||
adapter.setAlertsList(new ArrayList<>(allAlertsList));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<AlertMessage> filtered = new ArrayList<>();
|
|
||||||
for (AlertMessage msg : allAlertsList) {
|
|
||||||
if (currentFilter.equals(msg.getStatus())) {
|
|
||||||
filtered.add(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adapter.setAlertsList(filtered);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,9 @@ public class HistoryFragment extends Fragment {
|
|||||||
|
|
||||||
RecyclerView rvHistory = binding.rvHistory;
|
RecyclerView rvHistory = binding.rvHistory;
|
||||||
rvHistory.setLayoutManager(new LinearLayoutManager(getContext()));
|
rvHistory.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
adapter = new HistoryAdapter();
|
|
||||||
|
// Inicializa o adapter
|
||||||
|
adapter = new HistoryAdapter(requireContext());
|
||||||
rvHistory.setAdapter(adapter);
|
rvHistory.setAdapter(adapter);
|
||||||
|
|
||||||
loadHistory();
|
loadHistory();
|
||||||
@@ -49,35 +51,26 @@ public class HistoryFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadHistory() {
|
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)
|
String childCode = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE)
|
||||||
.getString("child_access_code", null);
|
.getString("child_access_code", null);
|
||||||
|
|
||||||
Log.d("FindU_Debug", "HistoryFragment -> childCode obtido: [" + childCode + "]");
|
if (childCode == null || childCode.isEmpty()) {
|
||||||
|
Log.e("FindU_Debug", "ERRO: childCode não encontrado no HistoryFragment.");
|
||||||
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();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
db.collection("Alerts")
|
// MUDANÇA CRUCIAL: Agora buscamos na coleção "History"
|
||||||
|
db.collection("History")
|
||||||
.whereEqualTo("childCode", childCode)
|
.whereEqualTo("childCode", childCode)
|
||||||
.orderBy("timestamp", Query.Direction.DESCENDING)
|
.orderBy("timestamp", Query.Direction.DESCENDING)
|
||||||
.addSnapshotListener((value, error) -> {
|
.addSnapshotListener((value, error) -> {
|
||||||
if (error != null) {
|
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 History: " + error.getMessage());
|
||||||
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
Log.d("FirebaseApp", "HistoryFragment -> Documentos recebidos: " + value.size());
|
|
||||||
List<Object> timelineList = new ArrayList<>();
|
List<Object> timelineList = new ArrayList<>();
|
||||||
String lastDateHeader = "";
|
String lastDateHeader = "";
|
||||||
|
|
||||||
@@ -95,6 +88,7 @@ public class HistoryFragment extends Fragment {
|
|||||||
timelineList.add(alert);
|
timelineList.add(alert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Envia a lista processada (com datas e eventos) para o adapter
|
||||||
adapter.setItems(timelineList);
|
adapter.setItems(timelineList);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -105,22 +99,21 @@ public class HistoryFragment extends Fragment {
|
|||||||
targetCal.setTime(date);
|
targetCal.setTime(date);
|
||||||
|
|
||||||
Calendar todayCal = Calendar.getInstance();
|
Calendar todayCal = Calendar.getInstance();
|
||||||
|
|
||||||
Calendar yesterdayCal = Calendar.getInstance();
|
Calendar yesterdayCal = Calendar.getInstance();
|
||||||
yesterdayCal.add(Calendar.DAY_OF_YEAR, -1);
|
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) &&
|
if (targetCal.get(Calendar.YEAR) == todayCal.get(Calendar.YEAR) &&
|
||||||
targetCal.get(Calendar.DAY_OF_YEAR) == todayCal.get(Calendar.DAY_OF_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, " + sdfBase.format(date);
|
||||||
return "Hoje, " + sdf.format(date);
|
|
||||||
} else if (targetCal.get(Calendar.YEAR) == yesterdayCal.get(Calendar.YEAR) &&
|
} else if (targetCal.get(Calendar.YEAR) == yesterdayCal.get(Calendar.YEAR) &&
|
||||||
targetCal.get(Calendar.DAY_OF_YEAR) == yesterdayCal.get(Calendar.DAY_OF_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, " + sdfBase.format(date);
|
||||||
return "Ontem, " + sdf.format(date);
|
|
||||||
} else {
|
} else {
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy", new Locale("pt", "PT"));
|
SimpleDateFormat sdfFull = new SimpleDateFormat("dd MMMM yyyy", new Locale("pt", "PT"));
|
||||||
// Capitalizar primeira letra no caso do MMMM ser retornado em minúsculo localmente
|
String f = sdfFull.format(date);
|
||||||
String f = sdf.format(date);
|
|
||||||
return f.substring(0, 1).toUpperCase() + f.substring(1);
|
return f.substring(0, 1).toUpperCase() + f.substring(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,4 +123,4 @@ public class HistoryFragment extends Fragment {
|
|||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
locationRef.addValueEventListener(locationListener);
|
||||||
|
|
||||||
@@ -315,7 +317,11 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
safeZonesListener = db.collection("SafeZones")
|
safeZonesListener = db.collection("SafeZones")
|
||||||
.whereEqualTo("childCode", childCode)
|
.whereEqualTo("childCode", childCode)
|
||||||
.addSnapshotListener((value, error) -> {
|
.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
|
// Limpa circulos velhos
|
||||||
for (com.google.android.gms.maps.model.Circle circle : mapCircles) {
|
for (com.google.android.gms.maps.model.Circle circle : mapCircles) {
|
||||||
|
|||||||
@@ -1,14 +1,10 @@
|
|||||||
package com.example.pap_findu.ui.zones;
|
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.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -20,11 +16,6 @@ import com.example.pap_findu.adapters.ZonesAdapter;
|
|||||||
import com.example.pap_findu.databinding.FragmentZonesBinding;
|
import com.example.pap_findu.databinding.FragmentZonesBinding;
|
||||||
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.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.DocumentSnapshot;
|
||||||
import com.google.firebase.firestore.FirebaseFirestore;
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
|
||||||
@@ -36,14 +27,8 @@ public class ZonesFragment extends Fragment {
|
|||||||
private FragmentZonesBinding binding;
|
private FragmentZonesBinding binding;
|
||||||
private FirebaseAuth auth;
|
private FirebaseAuth auth;
|
||||||
private FirebaseFirestore db;
|
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 com.google.firebase.firestore.ListenerRegistration safeZonesListener;
|
||||||
|
|
||||||
private Location childLocation = null;
|
|
||||||
private List<DocumentSnapshot> safeZonesList = new ArrayList<>();
|
private List<DocumentSnapshot> safeZonesList = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -87,133 +72,31 @@ public class ZonesFragment extends Fragment {
|
|||||||
if (value != null) {
|
if (value != null) {
|
||||||
safeZonesList = value.getDocuments();
|
safeZonesList = value.getDocuments();
|
||||||
adapter.setZonesList(safeZonesList);
|
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;
|
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
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
if (locationRef != null && locationListener != null) {
|
|
||||||
locationRef.removeEventListener(locationListener);
|
|
||||||
}
|
|
||||||
if (safeZonesListener != null) {
|
if (safeZonesListener != null) {
|
||||||
safeZonesListener.remove();
|
safeZonesListener.remove();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,33 +46,29 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:id="@+id/bannerZoneLayout"
|
android:id="@+id/cardZoneCount"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_marginTop="-40dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:background="@drawable/bg_safe_banner"
|
app:cardCornerRadius="16dp"
|
||||||
android:orientation="vertical"
|
app:cardElevation="4dp"
|
||||||
android:padding="18dp">
|
app:cardBackgroundColor="#FFFFFF">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/bannerZoneTitle"
|
android:id="@+id/tvDynamicZoneCount"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/zones_summary_title"
|
android:gravity="center"
|
||||||
android:textColor="#0B6635"
|
android:padding="16dp"
|
||||||
|
android:text="A carregar zonas…"
|
||||||
|
android:textColor="#37474F"
|
||||||
|
android:textSize="15sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
</com.google.android.material.card.MaterialCardView>
|
||||||
android:id="@+id/bannerZoneSubtitle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:text="@string/zones_summary_desc"
|
|
||||||
android:textColor="#0B6635" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/addZoneButton"
|
android:id="@+id/addZoneButton"
|
||||||
|
|||||||
Reference in New Issue
Block a user