From f3835fa65b8ca58c0b5fb5fef0f8ae55ea88cc2c Mon Sep 17 00:00:00 2001 From: 230408 <230408@epvc.pt> Date: Wed, 6 May 2026 12:43:54 +0100 Subject: [PATCH] app --- .../pap_findu/GeofenceBroadcastReceiver.java | 122 ++++++++++++++++ .../example/pap_findu/GeofenceManager.java | 123 ++++++++++++++++ .../pap_findu/adapters/AlertsAdapter.java | 137 ++++++++++++++++++ .../pap_findu/adapters/HistoryAdapter.java | 110 ++++++++++++++ .../pap_findu/adapters/ZonesAdapter.java | 105 ++++++++++++++ .../pap_findu/models/AlertMessage.java | 86 +++++++++++ app/src/main/res/layout/item_alert.xml | 128 ++++++++++++++++ .../main/res/layout/item_history_event.xml | 71 +++++++++ .../main/res/layout/item_history_header.xml | 20 +++ app/src/main/res/layout/item_zone.xml | 105 ++++++++++++++ 10 files changed, 1007 insertions(+) create mode 100644 app/src/main/java/com/example/pap_findu/GeofenceBroadcastReceiver.java create mode 100644 app/src/main/java/com/example/pap_findu/GeofenceManager.java create mode 100644 app/src/main/java/com/example/pap_findu/adapters/AlertsAdapter.java create mode 100644 app/src/main/java/com/example/pap_findu/adapters/HistoryAdapter.java create mode 100644 app/src/main/java/com/example/pap_findu/adapters/ZonesAdapter.java create mode 100644 app/src/main/java/com/example/pap_findu/models/AlertMessage.java create mode 100644 app/src/main/res/layout/item_alert.xml create mode 100644 app/src/main/res/layout/item_history_event.xml create mode 100644 app/src/main/res/layout/item_history_header.xml create mode 100644 app/src/main/res/layout/item_zone.xml diff --git a/app/src/main/java/com/example/pap_findu/GeofenceBroadcastReceiver.java b/app/src/main/java/com/example/pap_findu/GeofenceBroadcastReceiver.java new file mode 100644 index 0000000..dd9b077 --- /dev/null +++ b/app/src/main/java/com/example/pap_findu/GeofenceBroadcastReceiver.java @@ -0,0 +1,122 @@ +package com.example.pap_findu; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import android.widget.Toast; + +import com.example.pap_findu.models.AlertMessage; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.firestore.FirebaseFirestore; + +import com.google.android.gms.location.Geofence; +import com.google.android.gms.location.GeofencingEvent; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GeofenceBroadcastReceiver extends BroadcastReceiver { + + private static final String TAG = "GeofenceReceiver"; + + @Override + public void onReceive(Context context, Intent intent) { + GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent); + if (geofencingEvent.hasError()) { + Log.e(TAG, "Geofencing error: " + geofencingEvent.getErrorCode()); + return; + } + + // Get the transition type + int geofenceTransition = geofencingEvent.getGeofenceTransition(); + + // Test that the reported transition was of interest + if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT || geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) { + + // Get the geofences that were triggered + List triggeringGeofences = geofencingEvent.getTriggeringGeofences(); + + if (triggeringGeofences != null && !triggeringGeofences.isEmpty()) { + String requestId = triggeringGeofences.get(0).getRequestId(); + + FirebaseFirestore db = FirebaseFirestore.getInstance(); + FirebaseAuth auth = FirebaseAuth.getInstance(); + FirebaseUser user = auth.getCurrentUser(); + + db.collection("SafeZones").document(requestId).get() + .addOnSuccessListener(documentSnapshot -> { + String zoneName = "Zona Desconhecida"; + if (documentSnapshot.exists() && documentSnapshot.getString("name") != null) { + zoneName = documentSnapshot.getString("name"); + } + salvarAlerta(context, db, user, geofenceTransition, zoneName); + }) + .addOnFailureListener(e -> { + salvarAlerta(context, db, user, geofenceTransition, "Zona Desconhecida"); + }); + } + } else { + Log.e(TAG, "GEOFENCE_TRANSITION INVALID TYPE: " + geofenceTransition); + } + } + + private void salvarAlerta(Context context, FirebaseFirestore db, FirebaseUser user, int geofenceTransition, String zoneName) { + // TAREFA 1: Recuperar childCode das SharedPreferences com a mesma chave de isolamento + String childCode = context.getSharedPreferences("FindU_Prefs", Context.MODE_PRIVATE) + .getString("child_access_code", null); + + Log.d(TAG, "salvarAlerta -> childCode obtido: [" + childCode + "]"); + Log.d("FindU_Flow", "Gravando alerta para o código: " + childCode); + + if (childCode == null || childCode.isEmpty() || childCode.equals("Unknown")) { + Log.e(TAG, "ERRO: O filho tentou gerar um alerta mas o childCode é nulo/vazio! Alerta NÃO gravado."); + return; // Não grava o alerta órfão + } + + String docId = db.collection("Alerts").document().getId(); + + String type; + String status; + String title; + String message; + + if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) { + type = "URGENT"; + status = "PENDING"; + title = "Saída da Zona Segura"; + message = "O filho saiu da zona: " + zoneName; + } else { + type = "INFO"; + status = "RESOLVED"; + title = "Entrada na Zona Segura"; + message = "O filho entrou na zona: " + zoneName; + } + + // OBRIGATÓRIO: Usar HashMap para garantir que childCode está presente no documento + Map alertData = new HashMap<>(); + alertData.put("id", docId); + alertData.put("title", title); + alertData.put("message", message); + alertData.put("timestamp", new Date()); + alertData.put("type", type); + alertData.put("status", status); + alertData.put("childCode", childCode); // OBRIGATÓRIO: campo de isolamento + + db.collection("Alerts").document(docId).set(alertData) + .addOnSuccessListener(aVoid -> { + Log.d(TAG, "✅ Alerta gravado com SUCESSO. docId=" + docId + ", childCode=" + childCode); + Log.d("FindU_Flow", "Alerta gravado: docId=" + docId + " | childCode=" + childCode + " | type=" + type); + }) + .addOnFailureListener(e -> { + Log.e(TAG, "❌ ERRO ao gravar alerta: " + e.getMessage(), e); + Log.e("FindU_Flow", "Falha ao gravar alerta para childCode=" + childCode + ": " + e.getMessage()); + }); + + Log.w(TAG, message); + Toast.makeText(context, message, Toast.LENGTH_LONG).show(); + } +} diff --git a/app/src/main/java/com/example/pap_findu/GeofenceManager.java b/app/src/main/java/com/example/pap_findu/GeofenceManager.java new file mode 100644 index 0000000..ce2d8ed --- /dev/null +++ b/app/src/main/java/com/example/pap_findu/GeofenceManager.java @@ -0,0 +1,123 @@ +package com.example.pap_findu; + +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Build; +import android.util.Log; + +import androidx.annotation.NonNull; + +import com.google.android.gms.location.Geofence; +import com.google.android.gms.location.GeofencingClient; +import com.google.android.gms.location.GeofencingRequest; +import com.google.android.gms.location.LocationServices; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; + +import java.util.ArrayList; +import java.util.List; + +public class GeofenceManager { + + private static final String TAG = "GeofenceManager"; + private final Context context; + private final GeofencingClient geofencingClient; + private final FirebaseFirestore db; + private PendingIntent geofencePendingIntent; + + public GeofenceManager(Context context) { + this.context = context; + this.geofencingClient = LocationServices.getGeofencingClient(context); + this.db = FirebaseFirestore.getInstance(); + } + + public void setupGeofences() { + // Isolamento de dados: só escutar zonas deste filho + String childCode = context.getSharedPreferences("FindU_Prefs", Context.MODE_PRIVATE) + .getString("child_access_code", null); + if (childCode == null) { + Log.w(TAG, "childCode nulo, geofences não serão configuradas."); + return; + } + + // Escuta a coleção "SafeZones" filtrada por childCode em tempo real + db.collection("SafeZones") + .whereEqualTo("childCode", childCode) + .addSnapshotListener((value, error) -> { + if (error != null) { + Log.e(TAG, "Listen failed: " + error.getMessage()); + return; + } + + if (value != null) { + List geofenceList = new ArrayList<>(); + for (DocumentSnapshot doc : value.getDocuments()) { + String id = doc.getId(); + Double lat = doc.getDouble("latitude"); + Double lng = doc.getDouble("longitude"); + Double radius = doc.getDouble("radius"); + + if (radius == null) radius = 200.0; + + if (lat != null && lng != null) { + Geofence geofence = new Geofence.Builder() + .setRequestId(id) + .setCircularRegion(lat, lng, radius.floatValue()) + .setExpirationDuration(Geofence.NEVER_EXPIRE) + // Detetar entrada E saída da zona segura. + .setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT) + .build(); + + geofenceList.add(geofence); + } + } + + if (!geofenceList.isEmpty()) { + registerGeofences(geofenceList); + } else { + removeGeofences(); + } + } + }); + } + + private void registerGeofences(List geofenceList) { + GeofencingRequest geofencingRequest = new GeofencingRequest.Builder() + .setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER | GeofencingRequest.INITIAL_TRIGGER_EXIT) + .addGeofences(geofenceList) + .build(); + + try { + geofencingClient.addGeofences(geofencingRequest, getGeofencePendingIntent()) + .addOnSuccessListener(aVoid -> Log.d(TAG, "Geofences adicionadas com sucesso!")) + .addOnFailureListener(e -> Log.e(TAG, "Erro ao adicionar geofences: " + e.getMessage())); + } catch (SecurityException securityException) { + Log.e(TAG, "Falta permissão para Geofences: " + securityException.getMessage()); + } + } + + private void removeGeofences() { + if (geofencePendingIntent != null) { + geofencingClient.removeGeofences(geofencePendingIntent) + .addOnSuccessListener(aVoid -> Log.d(TAG, "Geofences removidas com sucesso")) + .addOnFailureListener(e -> Log.e(TAG, "Erro ao remover geofences: " + e.getMessage())); + } + } + + private PendingIntent getGeofencePendingIntent() { + if (geofencePendingIntent != null) { + return geofencePendingIntent; + } + Intent intent = new Intent(context, GeofenceBroadcastReceiver.class); + + int flags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + flags |= PendingIntent.FLAG_MUTABLE; + } + + geofencePendingIntent = PendingIntent.getBroadcast(context, 0, intent, flags); + return geofencePendingIntent; + } +} diff --git a/app/src/main/java/com/example/pap_findu/adapters/AlertsAdapter.java b/app/src/main/java/com/example/pap_findu/adapters/AlertsAdapter.java new file mode 100644 index 0000000..ac78e06 --- /dev/null +++ b/app/src/main/java/com/example/pap_findu/adapters/AlertsAdapter.java @@ -0,0 +1,137 @@ +package com.example.pap_findu.adapters; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.pap_findu.R; +import com.example.pap_findu.models.AlertMessage; +import com.google.firebase.auth.FirebaseAuth; // ADICIONADO +import com.google.firebase.auth.FirebaseUser; // ADICIONADO +import com.google.firebase.firestore.FirebaseFirestore; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class AlertsAdapter extends RecyclerView.Adapter { + + private List alertsList = new ArrayList<>(); + private Context context; + private SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm", Locale.getDefault()); + + public AlertsAdapter(Context context) { + this.context = context; + } + + public void setAlertsList(List alertsList) { + this.alertsList = alertsList; + notifyDataSetChanged(); + } + + @NonNull + @Override + public AlertViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(context).inflate(R.layout.item_alert, parent, false); + return new AlertViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull AlertViewHolder holder, int position) { + AlertMessage alert = alertsList.get(position); + + holder.tvAlertTitle.setText(alert.getTitle() != null ? alert.getTitle() : ""); + holder.tvAlertMessage.setText(alert.getMessage() != null ? alert.getMessage() : ""); + + if (alert.getTimestamp() != null) { + holder.tvAlertDate.setText(sdf.format(alert.getTimestamp())); + } else { + holder.tvAlertDate.setText(""); + } + + // VERIFICAÇÃO DE QUEM ESTÁ LOGADO + FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); + boolean isParent = (user != null && !user.isAnonymous()); + + if ("RESOLVED".equalsIgnoreCase(alert.getStatus())) { + holder.flAlertIconBg.setBackgroundResource(R.drawable.bg_alert_icon_info); + holder.ivAlertIcon.setImageResource(android.R.drawable.ic_dialog_info); + holder.ivAlertIcon.setColorFilter(Color.parseColor("#1F6AEF")); + + holder.tvAlertStatus.setBackgroundResource(R.drawable.bg_chip_success); + holder.tvAlertStatus.setText("Resolvido"); + holder.tvAlertStatus.setTextColor(Color.parseColor("#0F7A45")); + + holder.llAlertActions.setVisibility(View.GONE); + } else { + holder.flAlertIconBg.setBackgroundResource(R.drawable.bg_alert_icon_warning); + holder.ivAlertIcon.setImageResource(R.drawable.ic_alert_warning); + holder.ivAlertIcon.setColorFilter(null); + + holder.tvAlertStatus.setBackgroundResource(R.drawable.bg_chip_warning); + holder.tvAlertStatus.setText("Urgente"); + holder.tvAlertStatus.setTextColor(Color.parseColor("#B45309")); + + // LOGICA DE SEGURANÇA: Só mostra os botões de ação se for o PAI + if (isParent) { + holder.llAlertActions.setVisibility(View.VISIBLE); + } else { + holder.llAlertActions.setVisibility(View.GONE); + } + } + + holder.btnContactar.setOnClickListener(v -> { + Intent intent = new Intent(Intent.ACTION_DIAL); + v.getContext().startActivity(intent); + }); + + holder.btnResolver.setOnClickListener(v -> { + // DUPLA SEGURANÇA: Verifica novamente no clique + if (isParent && alert.getId() != null) { + FirebaseFirestore.getInstance().collection("Alerts").document(alert.getId()) + .update("status", "RESOLVED") + .addOnSuccessListener(aVoid -> Toast.makeText(context, "Alerta resolvido!", Toast.LENGTH_SHORT).show()); + } else if (!isParent) { + Toast.makeText(context, "Apenas os pais podem resolver alertas.", Toast.LENGTH_SHORT).show(); + } + }); + } + + @Override + public int getItemCount() { + return alertsList.size(); + } + + static class AlertViewHolder extends RecyclerView.ViewHolder { + FrameLayout flAlertIconBg; + ImageView ivAlertIcon; + TextView tvAlertTitle, tvAlertMessage, tvAlertStatus, tvAlertDate; + LinearLayout llAlertActions; + TextView btnContactar, btnResolver; + + public AlertViewHolder(@NonNull View itemView) { + super(itemView); + flAlertIconBg = itemView.findViewById(R.id.flAlertIconBg); + ivAlertIcon = itemView.findViewById(R.id.ivAlertIcon); + tvAlertTitle = itemView.findViewById(R.id.tvAlertTitle); + tvAlertMessage = itemView.findViewById(R.id.tvAlertMessage); + tvAlertStatus = itemView.findViewById(R.id.tvAlertStatus); + tvAlertDate = itemView.findViewById(R.id.tvAlertDate); + llAlertActions = itemView.findViewById(R.id.llAlertActions); + btnContactar = itemView.findViewById(R.id.btnContactar); + btnResolver = itemView.findViewById(R.id.btnResolver); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap_findu/adapters/HistoryAdapter.java b/app/src/main/java/com/example/pap_findu/adapters/HistoryAdapter.java new file mode 100644 index 0000000..7fb9703 --- /dev/null +++ b/app/src/main/java/com/example/pap_findu/adapters/HistoryAdapter.java @@ -0,0 +1,110 @@ +package com.example.pap_findu.adapters; + +import android.content.Context; +import android.graphics.Color; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.TextView; +import com.example.pap_findu.R; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import com.example.pap_findu.models.AlertMessage; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class HistoryAdapter extends RecyclerView.Adapter { + + private static final int TYPE_HEADER = 0; + private static final int TYPE_EVENT = 1; + private List items = new ArrayList<>(); + private Context context; + + // Construtor atualizado para receber o contexto + public HistoryAdapter(Context context) { + this.context = context; + } + + public void setItems(List items) { + this.items = items; + notifyDataSetChanged(); + } + + @Override + public int getItemViewType(int position) { + if (items.get(position) instanceof String) return TYPE_HEADER; + return TYPE_EVENT; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == TYPE_HEADER) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_history_header, parent, false); + return new HeaderViewHolder(view); + } else { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_history_event, parent, false); + return new EventViewHolder(view); + } + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + if (getItemViewType(position) == TYPE_HEADER) { + ((HeaderViewHolder) holder).tvHeaderDate.setText((String) items.get(position)); + } else { + AlertMessage alert = (AlertMessage) items.get(position); + EventViewHolder ev = (EventViewHolder) holder; + + ev.tvEventTitle.setText(alert.getTitle() != null ? alert.getTitle() : "Evento"); + ev.tvEventDesc.setText(alert.getMessage() != null ? alert.getMessage() : ""); + + if (alert.getTimestamp() != null) { + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm", Locale.getDefault()); + ev.tvEventTime.setText(sdf.format(alert.getTimestamp())); + } + + String title = alert.getTitle() != null ? alert.getTitle().toLowerCase() : ""; + if (title.contains("entrada")) { + ev.tvEventIconText.setText("🏠"); + ev.flEventIconBg.setBackgroundColor(Color.parseColor("#E8F5E9")); + } else if (title.contains("saída")) { + ev.tvEventIconText.setText("🚪"); + ev.flEventIconBg.setBackgroundColor(Color.parseColor("#FFF3E0")); + } else if (title.contains("movimento") || title.contains("deslocação")) { + ev.tvEventIconText.setText("🚶"); + ev.flEventIconBg.setBackgroundColor(Color.parseColor("#E0E7FF")); + } else if (title.contains("paragem")) { + ev.tvEventIconText.setText("🛑"); + ev.flEventIconBg.setBackgroundColor(Color.parseColor("#FEE2E2")); + } else { + ev.tvEventIconText.setText("📌"); + ev.flEventIconBg.setBackgroundColor(Color.parseColor("#F3F4F6")); + } + } + } + + @Override + public int getItemCount() { return items.size(); } + + static class HeaderViewHolder extends RecyclerView.ViewHolder { + TextView tvHeaderDate; + HeaderViewHolder(View itemView) { super(itemView); tvHeaderDate = itemView.findViewById(R.id.tvHeaderDate); } + } + + static class EventViewHolder extends RecyclerView.ViewHolder { + FrameLayout flEventIconBg; + TextView tvEventIconText, tvEventTitle, tvEventDesc, tvEventTime; + EventViewHolder(View itemView) { + super(itemView); + flEventIconBg = itemView.findViewById(R.id.flEventIconBg); + tvEventIconText = itemView.findViewById(R.id.tvEventIconText); + tvEventTitle = itemView.findViewById(R.id.tvEventTitle); + tvEventDesc = itemView.findViewById(R.id.tvEventDesc); + tvEventTime = itemView.findViewById(R.id.tvEventTime); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap_findu/adapters/ZonesAdapter.java b/app/src/main/java/com/example/pap_findu/adapters/ZonesAdapter.java new file mode 100644 index 0000000..f97886d --- /dev/null +++ b/app/src/main/java/com/example/pap_findu/adapters/ZonesAdapter.java @@ -0,0 +1,105 @@ +package com.example.pap_findu.adapters; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.pap_findu.R; +import com.google.firebase.firestore.DocumentSnapshot; + +import com.google.firebase.firestore.FirebaseFirestore; +import android.app.AlertDialog; +import android.widget.ImageView; +import android.widget.Toast; + +import java.util.ArrayList; +import java.util.List; + +public class ZonesAdapter extends RecyclerView.Adapter { + + private List zonesList = new ArrayList<>(); + private boolean isChild = false; + + public void setChildMode(boolean isChild) { + this.isChild = isChild; + notifyDataSetChanged(); + } + + public void setZonesList(List zonesList) { + this.zonesList = zonesList; + notifyDataSetChanged(); + } + + @NonNull + @Override + public ZoneViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_zone, parent, false); + return new ZoneViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ZoneViewHolder holder, int position) { + DocumentSnapshot document = zonesList.get(position); + String name = document.getString("name"); + String address = document.getString("address"); + Double radius = document.getDouble("radius"); + + if (name != null) holder.tvZoneName.setText(name); + if (address != null) holder.tvZoneAddress.setText(address); + if (radius != null) { + holder.tvZoneRadius.setText(radius + "m"); + } else { + holder.tvZoneRadius.setText("200m"); + } + + // Let's set an icon dynamically or statically for now + holder.tvZoneIcon.setText("\uD83D\uDCCD"); // Pin icon + + if (isChild) { + holder.ivDeleteZone.setVisibility(View.GONE); + } else { + holder.ivDeleteZone.setVisibility(View.VISIBLE); + holder.ivDeleteZone.setOnClickListener(v -> { + new AlertDialog.Builder(v.getContext()) + .setTitle("Remover Zona") + .setMessage("Tem a certeza que deseja eliminar esta zona segura?") + .setPositiveButton("Eliminar", (dialog, which) -> { + String zoneId = document.getId(); + FirebaseFirestore.getInstance().collection("SafeZones").document(zoneId) + .delete() + .addOnSuccessListener(aVoid -> { + Toast.makeText(v.getContext(), "Zona removida com sucesso.", Toast.LENGTH_SHORT).show(); + }) + .addOnFailureListener(e -> { + Toast.makeText(v.getContext(), "Erro ao remover zona.", Toast.LENGTH_SHORT).show(); + }); + }) + .setNegativeButton("Cancelar", null) + .show(); + }); + } + } + + @Override + public int getItemCount() { + return zonesList.size(); + } + + static class ZoneViewHolder extends RecyclerView.ViewHolder { + TextView tvZoneName, tvZoneAddress, tvZoneRadius, tvZoneIcon; + ImageView ivDeleteZone; + + public ZoneViewHolder(@NonNull View itemView) { + super(itemView); + tvZoneName = itemView.findViewById(R.id.tvZoneName); + tvZoneAddress = itemView.findViewById(R.id.tvZoneAddress); + tvZoneRadius = itemView.findViewById(R.id.tvZoneRadius); + tvZoneIcon = itemView.findViewById(R.id.tvZoneIcon); + ivDeleteZone = itemView.findViewById(R.id.ivDeleteZone); + } + } +} diff --git a/app/src/main/java/com/example/pap_findu/models/AlertMessage.java b/app/src/main/java/com/example/pap_findu/models/AlertMessage.java new file mode 100644 index 0000000..a7d5014 --- /dev/null +++ b/app/src/main/java/com/example/pap_findu/models/AlertMessage.java @@ -0,0 +1,86 @@ +package com.example.pap_findu.models; + +import com.google.firebase.firestore.DocumentId; +import java.util.Date; + +public class AlertMessage { + + @DocumentId + private String id; + + private String title; + private String message; + private Date timestamp; + private String type; // "URGENT" ou "INFO" + private String status; // "PENDING" ou "RESOLVED" + private String childCode; // Código do filho para isolamento de dados + + // Required empty constructor for Firestore + public AlertMessage() { + } + + public AlertMessage(String id, String title, String message, Date timestamp, String type, String status) { + this.id = id; + this.title = title; + this.message = message; + this.timestamp = timestamp; + this.type = type; + this.status = status; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public Date getTimestamp() { + return timestamp; + } + + public void setTimestamp(Date timestamp) { + this.timestamp = timestamp; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getChildCode() { + return childCode; + } + + public void setChildCode(String childCode) { + this.childCode = childCode; + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_alert.xml b/app/src/main/res/layout/item_alert.xml new file mode 100644 index 0000000..7ac38d6 --- /dev/null +++ b/app/src/main/res/layout/item_alert.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_history_event.xml b/app/src/main/res/layout/item_history_event.xml new file mode 100644 index 0000000..2ae9e46 --- /dev/null +++ b/app/src/main/res/layout/item_history_event.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_history_header.xml b/app/src/main/res/layout/item_history_header.xml new file mode 100644 index 0000000..c531375 --- /dev/null +++ b/app/src/main/res/layout/item_history_header.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/layout/item_zone.xml b/app/src/main/res/layout/item_zone.xml new file mode 100644 index 0000000..68ec32b --- /dev/null +++ b/app/src/main/res/layout/item_zone.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +