Compare commits
4 Commits
86da99ee13
...
f3835fa65b
| Author | SHA1 | Date | |
|---|---|---|---|
| f3835fa65b | |||
| b942073b67 | |||
| 17a7cd7aae | |||
| 9441cf992d |
@@ -58,20 +58,28 @@ dependencies {
|
|||||||
implementation(libs.navigation.fragment)
|
implementation(libs.navigation.fragment)
|
||||||
implementation(libs.navigation.ui)
|
implementation(libs.navigation.ui)
|
||||||
implementation(libs.activity)
|
implementation(libs.activity)
|
||||||
implementation(libs.firebase.database)
|
|
||||||
implementation(libs.firebase.auth)
|
// Firebase BOM - Import the Firebase BoM
|
||||||
|
implementation(platform("com.google.firebase:firebase-bom:32.8.0"))
|
||||||
|
|
||||||
|
// Firebase SDKs - Let BOM handle the versions
|
||||||
|
implementation("com.google.firebase:firebase-database")
|
||||||
|
implementation("com.google.firebase:firebase-auth")
|
||||||
|
implementation("com.google.firebase:firebase-firestore")
|
||||||
|
implementation("com.google.firebase:firebase-storage")
|
||||||
|
|
||||||
implementation(libs.credentials)
|
implementation(libs.credentials)
|
||||||
implementation(libs.credentials.play.services.auth)
|
implementation(libs.credentials.play.services.auth)
|
||||||
implementation(libs.googleid)
|
implementation(libs.googleid)
|
||||||
|
|
||||||
|
// Google Play Services
|
||||||
implementation("com.google.android.gms:play-services-maps:18.2.0")
|
implementation("com.google.android.gms:play-services-maps:18.2.0")
|
||||||
implementation("com.google.android.gms:play-services-location:21.0.1")
|
implementation("com.google.android.gms:play-services-location:21.2.0")
|
||||||
implementation(libs.firebase.firestore)
|
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.ext.junit)
|
androidTestImplementation(libs.ext.junit)
|
||||||
androidTestImplementation(libs.espresso.core)
|
androidTestImplementation(libs.espresso.core)
|
||||||
|
|
||||||
implementation("com.github.bumptech.glide:glide:4.16.0")
|
implementation("com.github.bumptech.glide:glide:4.16.0")
|
||||||
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
|
annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
|
||||||
implementation("com.google.firebase:firebase-storage:21.0.1")
|
|
||||||
implementation("com.google.android.gms:play-services-location:21.0.1")
|
|
||||||
implementation("com.google.firebase:firebase-database-ktx:20.3.0")
|
|
||||||
}
|
}
|
||||||
@@ -84,6 +84,10 @@
|
|||||||
android:name=".SecurityActivity"
|
android:name=".SecurityActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".GeofenceBroadcastReceiver"
|
||||||
|
android:exported="true" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,47 +1,186 @@
|
|||||||
package com.example.pap_findu;
|
package com.example.pap_findu;
|
||||||
|
|
||||||
|
import android.location.Address;
|
||||||
|
import android.location.Geocoder;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
public class AddZoneActivity extends AppCompatActivity {
|
import com.google.android.gms.maps.CameraUpdateFactory;
|
||||||
|
import com.google.android.gms.maps.GoogleMap;
|
||||||
|
import com.google.android.gms.maps.OnMapReadyCallback;
|
||||||
|
import com.google.android.gms.maps.SupportMapFragment;
|
||||||
|
import com.google.android.gms.maps.model.CircleOptions;
|
||||||
|
import com.google.android.gms.maps.model.LatLng;
|
||||||
|
import com.google.android.gms.maps.model.MarkerOptions;
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class AddZoneActivity extends AppCompatActivity implements OnMapReadyCallback {
|
||||||
|
|
||||||
|
private double zoneLat = 0.0;
|
||||||
|
private double zoneLng = 0.0;
|
||||||
|
private String zoneAddressStr = "";
|
||||||
|
|
||||||
|
private FirebaseFirestore db;
|
||||||
|
private GoogleMap mMap;
|
||||||
|
|
||||||
|
private EditText editName;
|
||||||
|
private EditText editSearchAddress;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_add_zone);
|
setContentView(R.layout.activity_add_zone);
|
||||||
|
|
||||||
|
db = FirebaseFirestore.getInstance();
|
||||||
|
|
||||||
ImageButton btnBack = findViewById(R.id.btnBack);
|
ImageButton btnBack = findViewById(R.id.btnBack);
|
||||||
Button btnSave = findViewById(R.id.btnSave);
|
Button btnSave = findViewById(R.id.btnSave);
|
||||||
EditText editName = findViewById(R.id.editZoneName);
|
editName = findViewById(R.id.editZoneName);
|
||||||
EditText editAddress = findViewById(R.id.editZoneAddress);
|
editSearchAddress = findViewById(R.id.editSearchAddress);
|
||||||
|
ImageButton btnSearch = findViewById(R.id.btnSearch);
|
||||||
|
|
||||||
// Check if opened from Map with coordinates
|
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.mapFragment);
|
||||||
if (getIntent().hasExtra("lat") && getIntent().hasExtra("lng")) {
|
if (mapFragment != null) {
|
||||||
double lat = getIntent().getDoubleExtra("lat", 0);
|
mapFragment.getMapAsync(this);
|
||||||
double lng = getIntent().getDoubleExtra("lng", 0);
|
|
||||||
editAddress.setText(String.format(java.util.Locale.getDefault(), "Lat: %.5f, Lng: %.5f", lat, lng));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
btnBack.setOnClickListener(v -> finish());
|
btnBack.setOnClickListener(v -> finish());
|
||||||
|
|
||||||
btnSave.setOnClickListener(v -> {
|
btnSearch.setOnClickListener(v -> searchAddress());
|
||||||
String name = editName.getText().toString();
|
|
||||||
String address = editAddress.getText().toString();
|
|
||||||
|
|
||||||
if (name.isEmpty() || address.isEmpty()) {
|
editSearchAddress.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
Toast.makeText(this, "Preencha todos os campos", Toast.LENGTH_SHORT).show();
|
if (actionId == EditorInfo.IME_ACTION_SEARCH ||
|
||||||
|
(event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
|
||||||
|
searchAddress();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
btnSave.setOnClickListener(v -> saveZone(btnSave));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMapReady(@NonNull GoogleMap googleMap) {
|
||||||
|
mMap = googleMap;
|
||||||
|
|
||||||
|
// Verifica se abriu do Mapa da main com as coordenadas
|
||||||
|
if (getIntent().hasExtra("lat") && getIntent().hasExtra("lng")) {
|
||||||
|
zoneLat = getIntent().getDoubleExtra("lat", 0);
|
||||||
|
zoneLng = getIntent().getDoubleExtra("lng", 0);
|
||||||
|
LatLng startLocation = new LatLng(zoneLat, zoneLng);
|
||||||
|
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(startLocation, 14f));
|
||||||
|
updateMapMarker(startLocation);
|
||||||
|
} else {
|
||||||
|
// Posiciona inicial aprox em PT (Opcional, ou deixa o padrao do maps com o mundo)
|
||||||
|
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(39.3999, -8.2245), 6f));
|
||||||
|
}
|
||||||
|
|
||||||
|
mMap.setOnMapClickListener(latLng -> {
|
||||||
|
zoneLat = latLng.latitude;
|
||||||
|
zoneLng = latLng.longitude;
|
||||||
|
zoneAddressStr = "Local selecionado no mapa";
|
||||||
|
|
||||||
|
updateMapMarker(latLng);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void searchAddress() {
|
||||||
|
String location = editSearchAddress.getText().toString();
|
||||||
|
if (location.isEmpty()) {
|
||||||
|
Toast.makeText(this, "Digite um endereço para pesquisar", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we would save to Firebase/Database
|
Geocoder geocoder = new Geocoder(this, Locale.getDefault());
|
||||||
|
try {
|
||||||
|
List<Address> addresses = geocoder.getFromLocationName(location, 1);
|
||||||
|
if (addresses != null && !addresses.isEmpty()) {
|
||||||
|
Address address = addresses.get(0);
|
||||||
|
LatLng latLng = new LatLng(address.getLatitude(), address.getLongitude());
|
||||||
|
|
||||||
|
zoneLat = latLng.latitude;
|
||||||
|
zoneLng = latLng.longitude;
|
||||||
|
|
||||||
|
StringBuilder addressBuilder = new StringBuilder();
|
||||||
|
for (int i = 0; i <= address.getMaxAddressLineIndex(); i++) {
|
||||||
|
addressBuilder.append(address.getAddressLine(i)).append(" ");
|
||||||
|
}
|
||||||
|
zoneAddressStr = addressBuilder.toString().trim();
|
||||||
|
|
||||||
|
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(latLng, 14f));
|
||||||
|
updateMapMarker(latLng);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Endereço não encontrado", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Toast.makeText(this, "Erro ao buscar endereço", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateMapMarker(LatLng latLng) {
|
||||||
|
if (mMap != null) {
|
||||||
|
mMap.clear(); // Limpa marcadores e circulos antigos
|
||||||
|
mMap.addMarker(new MarkerOptions().position(latLng).title("Centro da Zona Segura"));
|
||||||
|
|
||||||
|
mMap.addCircle(new CircleOptions()
|
||||||
|
.center(latLng)
|
||||||
|
.radius(200.0) // 200m
|
||||||
|
.strokeColor(0xFF006400) // Verde escuro
|
||||||
|
.fillColor(0x3300FF00) // Verde semi-transparente
|
||||||
|
.strokeWidth(5f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveZone(Button btnSave) {
|
||||||
|
String name = editName.getText().toString();
|
||||||
|
|
||||||
|
if (name.isEmpty() || zoneLat == 0.0 || zoneLng == 0.0) {
|
||||||
|
Toast.makeText(this, "Preencha o nome e selecione um local no mapa", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Isolamento de dados: obter o childCode do filho associado
|
||||||
|
String childCode = getSharedPreferences("FindU_Prefs", MODE_PRIVATE).getString("child_access_code", null);
|
||||||
|
if (childCode == null) {
|
||||||
|
Toast.makeText(this, "Erro: Nenhum filho associado. Adicione um filho primeiro.", Toast.LENGTH_LONG).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btnSave.setEnabled(false);
|
||||||
|
Toast.makeText(this, "Salvando zona...", Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
|
Map<String, Object> safeZone = new HashMap<>();
|
||||||
|
safeZone.put("name", name);
|
||||||
|
safeZone.put("address", zoneAddressStr.isEmpty() ? "Coordenadas: " + zoneLat + ", " + zoneLng : zoneAddressStr);
|
||||||
|
safeZone.put("latitude", zoneLat);
|
||||||
|
safeZone.put("longitude", zoneLng);
|
||||||
|
safeZone.put("radius", 200.0);
|
||||||
|
safeZone.put("childCode", childCode);
|
||||||
|
|
||||||
|
db.collection("SafeZones")
|
||||||
|
.add(safeZone)
|
||||||
|
.addOnSuccessListener(documentReference -> {
|
||||||
Toast.makeText(this, "Zona '" + name + "' adicionada com sucesso!", Toast.LENGTH_LONG).show();
|
Toast.makeText(this, "Zona '" + name + "' adicionada com sucesso!", Toast.LENGTH_LONG).show();
|
||||||
finish();
|
finish();
|
||||||
|
})
|
||||||
|
.addOnFailureListener(e -> {
|
||||||
|
Toast.makeText(this, "Erro ao salvar: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
btnSave.setEnabled(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,6 @@ import java.io.ByteArrayOutputStream;
|
|||||||
|
|
||||||
public class EditProfileActivity extends AppCompatActivity {
|
public class EditProfileActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private ImageView btnBack;
|
|
||||||
private ImageView editProfileImage;
|
private ImageView editProfileImage;
|
||||||
private TextView btnChangePhoto;
|
private TextView btnChangePhoto;
|
||||||
private TextInputEditText editName;
|
private TextInputEditText editName;
|
||||||
@@ -41,6 +40,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||||||
// Botoes
|
// Botoes
|
||||||
private MaterialButton btnSaveProfile;
|
private MaterialButton btnSaveProfile;
|
||||||
private MaterialButton btnChangePassword;
|
private MaterialButton btnChangePassword;
|
||||||
|
private MaterialButton btnCancelProfile;
|
||||||
|
|
||||||
private FirebaseAuth mAuth;
|
private FirebaseAuth mAuth;
|
||||||
private DatabaseReference mDatabase;
|
private DatabaseReference mDatabase;
|
||||||
@@ -70,13 +70,13 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initViews() {
|
private void initViews() {
|
||||||
btnBack = findViewById(R.id.btnBack);
|
|
||||||
editProfileImage = findViewById(R.id.editProfileImage);
|
editProfileImage = findViewById(R.id.editProfileImage);
|
||||||
btnChangePhoto = findViewById(R.id.btnChangePhoto);
|
btnChangePhoto = findViewById(R.id.btnChangePhoto);
|
||||||
editName = findViewById(R.id.editName);
|
editName = findViewById(R.id.editName);
|
||||||
editEmail = findViewById(R.id.editEmail);
|
editEmail = findViewById(R.id.editEmail);
|
||||||
btnSaveProfile = findViewById(R.id.btnSaveProfile);
|
btnSaveProfile = findViewById(R.id.btnSaveProfile);
|
||||||
btnChangePassword = findViewById(R.id.btnChangePassword);
|
btnChangePassword = findViewById(R.id.btnChangePassword);
|
||||||
|
btnCancelProfile = findViewById(R.id.btnCancelProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadUserData() {
|
private void loadUserData() {
|
||||||
@@ -119,7 +119,10 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupListeners() {
|
private void setupListeners() {
|
||||||
btnBack.setOnClickListener(v -> finish());
|
// Botão CANCELAR (vermelho) fecha o ecrã
|
||||||
|
if (btnCancelProfile != null) {
|
||||||
|
btnCancelProfile.setOnClickListener(v -> finish());
|
||||||
|
}
|
||||||
|
|
||||||
// Lógica de mudar a senha
|
// Lógica de mudar a senha
|
||||||
btnChangePassword.setOnClickListener(v -> {
|
btnChangePassword.setOnClickListener(v -> {
|
||||||
|
|||||||
@@ -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<Geofence> 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<String, Object> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
123
app/src/main/java/com/example/pap_findu/GeofenceManager.java
Normal file
123
app/src/main/java/com/example/pap_findu/GeofenceManager.java
Normal file
@@ -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<Geofence> 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<Geofence> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
@@ -41,26 +46,46 @@ public class LocationService extends Service {
|
|||||||
private LocationCallback locationCallback;
|
private LocationCallback locationCallback;
|
||||||
private DatabaseReference databaseReference;
|
private DatabaseReference databaseReference;
|
||||||
|
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadSafeZones() {
|
||||||
|
db.collection("SafeZones")
|
||||||
|
.whereEqualTo("childCode", currentChildCode)
|
||||||
|
.addSnapshotListener((value, error) -> {
|
||||||
|
if (value != null) activeSafeZones = value.getDocuments();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
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();
|
||||||
@@ -80,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)
|
|
||||||
if (Math.abs(location.getLatitude() - 37.4219) > 0.001) {
|
|
||||||
updateFirebase(location);
|
updateFirebase(location);
|
||||||
}
|
checkGeofenceMath(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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import android.widget.Toast;
|
|||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
@@ -22,7 +23,17 @@ import com.google.android.material.bottomnavigation.BottomNavigationView;
|
|||||||
import com.google.firebase.auth.FirebaseAuth;
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
import com.google.firebase.auth.FirebaseUser;
|
import com.google.firebase.auth.FirebaseUser;
|
||||||
|
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
import com.google.firebase.firestore.DocumentChange;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.app.NotificationManagerCompat;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
import com.google.firebase.firestore.Query;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity {
|
public class MainActivity extends AppCompatActivity {
|
||||||
@@ -43,6 +54,8 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
// TAREFA 3: Forçar Modo Claro para evitar distorção de cores
|
||||||
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
binding = ActivityMainBinding.inflate(getLayoutInflater());
|
||||||
@@ -107,10 +120,69 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
} else {
|
} else {
|
||||||
// É O PAI: Apenas carregamos a interface (o MapFragment tratará do resto)
|
// É O PAI: Apenas carregamos a interface (o MapFragment tratará do resto)
|
||||||
Log.d("MainActivity", "Modo Pai: GPS desligado para poupar bateria.");
|
Log.d("MainActivity", "Modo Pai: GPS desligado para poupar bateria.");
|
||||||
|
listenForAlerts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void listenForAlerts() {
|
||||||
|
FirebaseFirestore db = FirebaseFirestore.getInstance();
|
||||||
|
Date startupTime = new Date();
|
||||||
|
|
||||||
|
// Isolamento de dados: só escutar alertas deste filho
|
||||||
|
String childCode = getSharedPreferences("FindU_Prefs", MODE_PRIVATE)
|
||||||
|
.getString("child_access_code", null);
|
||||||
|
if (childCode == null) return;
|
||||||
|
|
||||||
|
db.collection("Alerts")
|
||||||
|
.whereEqualTo("childCode", childCode)
|
||||||
|
.whereGreaterThan("timestamp", startupTime)
|
||||||
|
.orderBy("timestamp", Query.Direction.ASCENDING)
|
||||||
|
.addSnapshotListener((snapshots, e) -> {
|
||||||
|
if (e != null || snapshots == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (DocumentChange dc : snapshots.getDocumentChanges()) {
|
||||||
|
if (dc.getType() == DocumentChange.Type.ADDED) {
|
||||||
|
String status = dc.getDocument().getString("status");
|
||||||
|
String title = dc.getDocument().getString("title");
|
||||||
|
String message = dc.getDocument().getString("message");
|
||||||
|
|
||||||
|
if ("PENDING".equals(status)) {
|
||||||
|
sendPushNotification(title, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendPushNotification(String title, String message) {
|
||||||
|
String channelId = "alerts_channel";
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
CharSequence name = "Alertas Recentes";
|
||||||
|
String description = "Notificações de Alertas de Zona";
|
||||||
|
int importance = NotificationManager.IMPORTANCE_HIGH;
|
||||||
|
NotificationChannel channel = new NotificationChannel(channelId, name, importance);
|
||||||
|
channel.setDescription(description);
|
||||||
|
NotificationManager notificationManager = getSystemService(NotificationManager.class);
|
||||||
|
if (notificationManager != null) {
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
|
||||||
|
.setSmallIcon(android.R.drawable.ic_dialog_alert)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(message)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setAutoCancel(true);
|
||||||
|
|
||||||
|
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED || Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
NotificationManagerCompat.from(this).notify((int) System.currentTimeMillis(), builder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void startLocationService() {
|
private void startLocationService() {
|
||||||
Intent serviceIntent = new Intent(this, LocationService.class);
|
Intent serviceIntent = new Intent(this, LocationService.class);
|
||||||
ContextCompat.startForegroundService(this, serviceIntent);
|
ContextCompat.startForegroundService(this, serviceIntent);
|
||||||
|
|||||||
@@ -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<AlertsAdapter.AlertViewHolder> {
|
||||||
|
|
||||||
|
private List<AlertMessage> 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<AlertMessage> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<RecyclerView.ViewHolder> {
|
||||||
|
|
||||||
|
private static final int TYPE_HEADER = 0;
|
||||||
|
private static final int TYPE_EVENT = 1;
|
||||||
|
private List<Object> items = new ArrayList<>();
|
||||||
|
private Context context;
|
||||||
|
|
||||||
|
// Construtor atualizado para receber o contexto
|
||||||
|
public HistoryAdapter(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setItems(List<Object> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<ZonesAdapter.ZoneViewHolder> {
|
||||||
|
|
||||||
|
private List<DocumentSnapshot> zonesList = new ArrayList<>();
|
||||||
|
private boolean isChild = false;
|
||||||
|
|
||||||
|
public void setChildMode(boolean isChild) {
|
||||||
|
this.isChild = isChild;
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setZonesList(List<DocumentSnapshot> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,18 +7,216 @@ import android.view.ViewGroup;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import android.widget.TextView;
|
||||||
|
import com.example.pap_findu.R;
|
||||||
|
import com.example.pap_findu.adapters.AlertsAdapter;
|
||||||
import com.example.pap_findu.databinding.FragmentAlertsBinding;
|
import com.example.pap_findu.databinding.FragmentAlertsBinding;
|
||||||
|
import com.example.pap_findu.models.AlertMessage;
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
import com.google.firebase.firestore.Query;
|
||||||
|
import com.google.firebase.firestore.QueryDocumentSnapshot;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class AlertsFragment extends Fragment {
|
public class AlertsFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentAlertsBinding binding;
|
private FragmentAlertsBinding binding;
|
||||||
|
private AlertsAdapter adapter;
|
||||||
|
private FirebaseFirestore db;
|
||||||
|
private List<AlertMessage> allAlertsList = new ArrayList<>();
|
||||||
|
private String currentFilter = "ALL";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
binding = FragmentAlertsBinding.inflate(inflater, container, false);
|
binding = FragmentAlertsBinding.inflate(inflater, container, false);
|
||||||
return binding.getRoot();
|
View root = binding.getRoot();
|
||||||
|
|
||||||
|
db = FirebaseFirestore.getInstance();
|
||||||
|
adapter = new AlertsAdapter(requireContext());
|
||||||
|
|
||||||
|
binding.rvAlerts.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
binding.rvAlerts.setAdapter(adapter);
|
||||||
|
|
||||||
|
setupFilters();
|
||||||
|
loadAlerts();
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupFilters() {
|
||||||
|
binding.filterAll.setOnClickListener(v -> applyFilter("ALL", binding.filterAll));
|
||||||
|
binding.filterPending.setOnClickListener(v -> applyFilter("PENDING", binding.filterPending));
|
||||||
|
binding.filterResolved.setOnClickListener(v -> applyFilter("RESOLVED", binding.filterResolved));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyFilter(String filterType, TextView activeTextView) {
|
||||||
|
currentFilter = filterType;
|
||||||
|
|
||||||
|
// Reset visual state
|
||||||
|
resetFilterStyles();
|
||||||
|
|
||||||
|
// Set active style
|
||||||
|
activeTextView.setBackgroundResource(R.drawable.bg_filter_chip_active);
|
||||||
|
activeTextView.setTextColor(android.graphics.Color.parseColor("#1C6CFF"));
|
||||||
|
|
||||||
|
filterAdapterList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resetFilterStyles() {
|
||||||
|
binding.filterAll.setBackgroundResource(R.drawable.bg_filter_chip_neutral);
|
||||||
|
binding.filterAll.setTextColor(android.graphics.Color.parseColor("#64748B"));
|
||||||
|
binding.filterPending.setBackgroundResource(R.drawable.bg_filter_chip_neutral);
|
||||||
|
binding.filterPending.setTextColor(android.graphics.Color.parseColor("#64748B"));
|
||||||
|
binding.filterResolved.setBackgroundResource(R.drawable.bg_filter_chip_neutral);
|
||||||
|
binding.filterResolved.setTextColor(android.graphics.Color.parseColor("#64748B"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAlerts() {
|
||||||
|
String childCode = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE)
|
||||||
|
.getString("child_access_code", null);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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<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() {
|
||||||
|
if (binding == null) return;
|
||||||
|
|
||||||
|
int pendingCount = 0;
|
||||||
|
AlertMessage latestPending = null;
|
||||||
|
|
||||||
|
for (AlertMessage msg : allAlertsList) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) return;
|
||||||
|
|
||||||
|
if (pendingCount > 0) {
|
||||||
|
bannerCard.setCardBackgroundColor(android.graphics.Color.parseColor("#FFF3E0"));
|
||||||
|
titleView.setText(pendingCount + " alerta(s) aguardando revisão");
|
||||||
|
titleView.setTextColor(android.graphics.Color.parseColor("#E65100"));
|
||||||
|
|
||||||
|
if (latestPending != null && latestPending.getTimestamp() != null) {
|
||||||
|
subtitleView.setText("Último evento: " + getTimeAgo(latestPending.getTimestamp()));
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTimeAgo(Date date) {
|
||||||
|
if (date == null) return "Desconhecido";
|
||||||
|
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)";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,28 +1,123 @@
|
|||||||
package com.example.pap_findu.ui.history;
|
package com.example.pap_findu.ui.history;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
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.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.example.pap_findu.adapters.HistoryAdapter;
|
||||||
import com.example.pap_findu.databinding.FragmentHistoryBinding;
|
import com.example.pap_findu.databinding.FragmentHistoryBinding;
|
||||||
|
import com.example.pap_findu.models.AlertMessage;
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
import com.google.firebase.firestore.Query;
|
||||||
|
import com.google.firebase.firestore.QueryDocumentSnapshot;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public class HistoryFragment extends Fragment {
|
public class HistoryFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentHistoryBinding binding;
|
private FragmentHistoryBinding binding;
|
||||||
|
private FirebaseFirestore db;
|
||||||
|
private HistoryAdapter adapter;
|
||||||
|
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
ViewGroup container, Bundle savedInstanceState) {
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
binding = FragmentHistoryBinding.inflate(inflater, container, false);
|
binding = FragmentHistoryBinding.inflate(inflater, container, false);
|
||||||
View root = binding.getRoot();
|
View root = binding.getRoot();
|
||||||
|
|
||||||
|
db = FirebaseFirestore.getInstance();
|
||||||
|
|
||||||
|
RecyclerView rvHistory = binding.rvHistory;
|
||||||
|
rvHistory.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
|
||||||
|
// Inicializa o adapter
|
||||||
|
adapter = new HistoryAdapter(requireContext());
|
||||||
|
rvHistory.setAdapter(adapter);
|
||||||
|
|
||||||
|
loadHistory();
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadHistory() {
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
if (childCode == null || childCode.isEmpty()) {
|
||||||
|
Log.e("FindU_Debug", "ERRO: childCode não encontrado no HistoryFragment.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
Log.e("FindU_Debug", "Erro na Query History: " + error.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
List<Object> timelineList = new ArrayList<>();
|
||||||
|
String lastDateHeader = "";
|
||||||
|
|
||||||
|
for (QueryDocumentSnapshot doc : value) {
|
||||||
|
AlertMessage alert = doc.toObject(AlertMessage.class);
|
||||||
|
Date date = alert.getTimestamp();
|
||||||
|
|
||||||
|
if (date != null) {
|
||||||
|
String dateHeader = formatDateHeader(date);
|
||||||
|
if (!dateHeader.equals(lastDateHeader)) {
|
||||||
|
timelineList.add(dateHeader);
|
||||||
|
lastDateHeader = dateHeader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
timelineList.add(alert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envia a lista processada (com datas e eventos) para o adapter
|
||||||
|
adapter.setItems(timelineList);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private String formatDateHeader(Date date) {
|
||||||
|
Calendar targetCal = Calendar.getInstance();
|
||||||
|
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)) {
|
||||||
|
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)) {
|
||||||
|
return "Ontem, " + sdfBase.format(date);
|
||||||
|
} else {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.example.pap_findu.ui.map;
|
|||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -50,6 +51,7 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
private FirebaseAuth auth;
|
private FirebaseAuth auth;
|
||||||
|
|
||||||
private FloatingActionButton btnAbrirChat;
|
private FloatingActionButton btnAbrirChat;
|
||||||
|
private FloatingActionButton btnCenterMap;
|
||||||
private MaterialButton btnSOS;
|
private MaterialButton btnSOS;
|
||||||
|
|
||||||
// --- VARIÁVEIS PARA O CARTÃO DE INFORMAÇÃO PREMIUM ---
|
// --- VARIÁVEIS PARA O CARTÃO DE INFORMAÇÃO PREMIUM ---
|
||||||
@@ -71,6 +73,18 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
|
|
||||||
// NOVA VARIÁVEL: Guarda o nome real do filho
|
// NOVA VARIÁVEL: Guarda o nome real do filho
|
||||||
private String currentChildName = "A carregar...";
|
private String currentChildName = "A carregar...";
|
||||||
|
private LatLng lastChildLocation;
|
||||||
|
|
||||||
|
private com.google.firebase.firestore.ListenerRegistration safeZonesListener;
|
||||||
|
private java.util.List<com.google.android.gms.maps.model.Circle> mapCircles = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
private static class SafeZoneData {
|
||||||
|
String name;
|
||||||
|
double latitude;
|
||||||
|
double longitude;
|
||||||
|
double radius;
|
||||||
|
}
|
||||||
|
private java.util.List<SafeZoneData> safeZonesList = new java.util.ArrayList<>();
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
@@ -97,6 +111,11 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
iconZone = root.findViewById(R.id.iconZone);
|
iconZone = root.findViewById(R.id.iconZone);
|
||||||
txtZoneTitle = root.findViewById(R.id.txtZoneTitle);
|
txtZoneTitle = root.findViewById(R.id.txtZoneTitle);
|
||||||
txtZoneSubtitle = root.findViewById(R.id.txtZoneSubtitle);
|
txtZoneSubtitle = root.findViewById(R.id.txtZoneSubtitle);
|
||||||
|
btnCenterMap = root.findViewById(R.id.btnCenterMap);
|
||||||
|
|
||||||
|
if (btnCenterMap != null) {
|
||||||
|
btnCenterMap.setOnClickListener(v -> centerOnChildLocation());
|
||||||
|
}
|
||||||
|
|
||||||
if (btnAbrirChat != null) btnAbrirChat.setVisibility(View.GONE);
|
if (btnAbrirChat != null) btnAbrirChat.setVisibility(View.GONE);
|
||||||
if (btnSOS != null) btnSOS.setVisibility(View.GONE);
|
if (btnSOS != null) btnSOS.setVisibility(View.GONE);
|
||||||
@@ -148,6 +167,9 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentChildId != null) {
|
if (currentChildId != null) {
|
||||||
|
// Guardar childCode em SharedPreferences para uso em outros fragments
|
||||||
|
requireContext().getSharedPreferences("FindU_Prefs", Context.MODE_PRIVATE)
|
||||||
|
.edit().putString("child_access_code", currentChildId).apply();
|
||||||
showMapState();
|
showMapState();
|
||||||
setupChildButtons();
|
setupChildButtons();
|
||||||
if (mMap != null) {
|
if (mMap != null) {
|
||||||
@@ -176,7 +198,14 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (btnSOS != null) btnSOS.setVisibility(View.VISIBLE);
|
if (btnSOS != null) {
|
||||||
|
btnSOS.setVisibility(View.VISIBLE);
|
||||||
|
btnSOS.setOnClickListener(v -> {
|
||||||
|
Intent sosIntent = new Intent(Intent.ACTION_DIAL);
|
||||||
|
sosIntent.setData(Uri.parse("tel:112"));
|
||||||
|
startActivity(sosIntent);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showMapState() {
|
private void showMapState() {
|
||||||
@@ -192,10 +221,20 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
@Override
|
@Override
|
||||||
public void onMapReady(@NonNull GoogleMap googleMap) {
|
public void onMapReady(@NonNull GoogleMap googleMap) {
|
||||||
mMap = googleMap;
|
mMap = googleMap;
|
||||||
|
mMap.getUiSettings().setMyLocationButtonEnabled(false);
|
||||||
FirebaseUser user = auth.getCurrentUser();
|
FirebaseUser user = auth.getCurrentUser();
|
||||||
|
|
||||||
if (user != null && user.isAnonymous()) {
|
if (user != null && user.isAnonymous()) {
|
||||||
try { mMap.setMyLocationEnabled(true); } catch (SecurityException e) {}
|
if (androidx.core.content.ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
||||||
|
mMap.setMyLocationEnabled(true);
|
||||||
|
com.google.android.gms.location.FusedLocationProviderClient fusedLocationClient = com.google.android.gms.location.LocationServices.getFusedLocationProviderClient(requireActivity());
|
||||||
|
fusedLocationClient.getLastLocation().addOnSuccessListener(location -> {
|
||||||
|
if (location != null && mMap != null) {
|
||||||
|
mMap.moveCamera(com.google.android.gms.maps.CameraUpdateFactory.newLatLngZoom(
|
||||||
|
new com.google.android.gms.maps.model.LatLng(location.getLatitude(), location.getLongitude()), 16f));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (currentChildId != null) {
|
if (currentChildId != null) {
|
||||||
startListeningToChildLocation();
|
startListeningToChildLocation();
|
||||||
@@ -239,6 +278,7 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (lat != null && lng != null) {
|
if (lat != null && lng != null) {
|
||||||
|
lastChildLocation = new LatLng(lat, lng);
|
||||||
LatLng childPos = new LatLng(lat, lng);
|
LatLng childPos = new LatLng(lat, lng);
|
||||||
|
|
||||||
if (Math.abs(lat - 37.4219) > 0.001) {
|
if (Math.abs(lat - 37.4219) > 0.001) {
|
||||||
@@ -253,15 +293,97 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
}
|
}
|
||||||
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(childPos, 17f));
|
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(childPos, 17f));
|
||||||
|
|
||||||
// TESTE DE DESIGN
|
android.location.Location childLoc = new android.location.Location("child");
|
||||||
atualizarStatusZona(true, "A atualizar localização...");
|
childLoc.setLatitude(lat);
|
||||||
|
childLoc.setLongitude(lng);
|
||||||
|
if (getActivity() != null) {
|
||||||
|
getActivity().runOnUiThread(() -> updateSafetyStatusBanner(childLoc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@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);
|
||||||
|
|
||||||
|
// Listener de SafeZones filtrado por childCode
|
||||||
|
String childCode = requireContext().getSharedPreferences("FindU_Prefs", Context.MODE_PRIVATE)
|
||||||
|
.getString("child_access_code", null);
|
||||||
|
if (safeZonesListener != null) safeZonesListener.remove();
|
||||||
|
if (childCode == null) return;
|
||||||
|
safeZonesListener = db.collection("SafeZones")
|
||||||
|
.whereEqualTo("childCode", childCode)
|
||||||
|
.addSnapshotListener((value, error) -> {
|
||||||
|
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) {
|
||||||
|
if (circle != null) circle.remove();
|
||||||
|
}
|
||||||
|
mapCircles.clear();
|
||||||
|
safeZonesList.clear();
|
||||||
|
|
||||||
|
for (DocumentSnapshot doc : value.getDocuments()) {
|
||||||
|
Double lat = doc.getDouble("latitude");
|
||||||
|
Double lng = doc.getDouble("longitude");
|
||||||
|
Double radius = doc.getDouble("radius");
|
||||||
|
String name = doc.getString("name");
|
||||||
|
|
||||||
|
if (lat != null && lng != null) {
|
||||||
|
SafeZoneData sz = new SafeZoneData();
|
||||||
|
sz.name = name != null ? name : "Zona";
|
||||||
|
sz.latitude = lat;
|
||||||
|
sz.longitude = lng;
|
||||||
|
sz.radius = radius != null ? radius : 200.0;
|
||||||
|
safeZonesList.add(sz);
|
||||||
|
|
||||||
|
LatLng latLng = new LatLng(lat, lng);
|
||||||
|
com.google.android.gms.maps.model.Circle circle = mMap.addCircle(new com.google.android.gms.maps.model.CircleOptions()
|
||||||
|
.center(latLng)
|
||||||
|
.radius(sz.radius)
|
||||||
|
.strokeColor(0xFF006400) // Verde escuro
|
||||||
|
.fillColor(0x3300FF00) // Verde semi-transparente
|
||||||
|
.strokeWidth(5f));
|
||||||
|
mapCircles.add(circle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSafetyStatusBanner(android.location.Location childLocation) {
|
||||||
|
if (safeZonesList.isEmpty()) {
|
||||||
|
atualizarStatusZona(false, "Nenhuma zona segura definida");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean insideAnyZone = false;
|
||||||
|
String insideZoneName = "";
|
||||||
|
|
||||||
|
for (SafeZoneData zone : safeZonesList) {
|
||||||
|
android.location.Location zoneLoc = new android.location.Location("zone");
|
||||||
|
zoneLoc.setLatitude(zone.latitude);
|
||||||
|
zoneLoc.setLongitude(zone.longitude);
|
||||||
|
|
||||||
|
float distance = childLocation.distanceTo(zoneLoc);
|
||||||
|
if (distance <= zone.radius) {
|
||||||
|
insideAnyZone = true;
|
||||||
|
insideZoneName = zone.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (insideAnyZone) {
|
||||||
|
atualizarStatusZona(true, "Seguro em: " + insideZoneName);
|
||||||
|
} else {
|
||||||
|
atualizarStatusZona(false, "Longe das zonas seguras");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- FUNÇÃO PARA MUDAR AS CORES DA ZONA (VERDE/LARANJA) ---
|
// --- FUNÇÃO PARA MUDAR AS CORES DA ZONA (VERDE/LARANJA) ---
|
||||||
@@ -286,7 +408,7 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
|
|
||||||
txtZoneTitle.setText("A movimentar-se");
|
txtZoneTitle.setText("A movimentar-se");
|
||||||
txtZoneTitle.setTextColor(Color.parseColor("#822B00"));
|
txtZoneTitle.setTextColor(Color.parseColor("#822B00"));
|
||||||
txtZoneSubtitle.setText("Fora das zonas seguras");
|
txtZoneSubtitle.setText(subtitulo);
|
||||||
txtZoneSubtitle.setTextColor(Color.parseColor("#A63A00"));
|
txtZoneSubtitle.setTextColor(Color.parseColor("#A63A00"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,6 +417,10 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
if (locationRef != null && locationListener != null) locationRef.removeEventListener(locationListener);
|
if (locationRef != null && locationListener != null) locationRef.removeEventListener(locationListener);
|
||||||
|
if (safeZonesListener != null) {
|
||||||
|
safeZonesListener.remove();
|
||||||
|
safeZonesListener = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showEmptyState() {
|
private void showEmptyState() {
|
||||||
@@ -315,4 +441,30 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
.setPositiveButton("OK", (d, w) -> checkIfHasChildren())
|
.setPositiveButton("OK", (d, w) -> checkIfHasChildren())
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void centerOnChildLocation() {
|
||||||
|
FirebaseUser user = auth.getCurrentUser();
|
||||||
|
if (user != null && user.isAnonymous()) {
|
||||||
|
if (androidx.core.content.ContextCompat.checkSelfPermission(requireContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
||||||
|
com.google.android.gms.location.FusedLocationProviderClient fusedLocationClient = com.google.android.gms.location.LocationServices.getFusedLocationProviderClient(requireActivity());
|
||||||
|
fusedLocationClient.getLastLocation().addOnSuccessListener(location -> {
|
||||||
|
if (location != null && mMap != null) {
|
||||||
|
mMap.animateCamera(com.google.android.gms.maps.CameraUpdateFactory.newLatLngZoom(
|
||||||
|
new com.google.android.gms.maps.model.LatLng(location.getLatitude(), location.getLongitude()), 16f));
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), "A procurar localização atual...", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), "Sem permissão de localização.", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mMap != null && lastChildLocation != null) {
|
||||||
|
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(lastChildLocation, 16f));
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), "A aguardar localização do filho...", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,35 @@
|
|||||||
package com.example.pap_findu.ui.zones;
|
package com.example.pap_findu.ui.zones;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
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.TextView;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import com.example.pap_findu.R;
|
||||||
|
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.FirebaseUser;
|
||||||
|
import com.google.firebase.firestore.DocumentSnapshot;
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class ZonesFragment extends Fragment {
|
public class ZonesFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentZonesBinding binding;
|
private FragmentZonesBinding binding;
|
||||||
|
private FirebaseAuth auth;
|
||||||
|
private FirebaseFirestore db;
|
||||||
|
|
||||||
|
private com.google.firebase.firestore.ListenerRegistration safeZonesListener;
|
||||||
|
private List<DocumentSnapshot> safeZonesList = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -20,18 +37,69 @@ public class ZonesFragment extends Fragment {
|
|||||||
binding = FragmentZonesBinding.inflate(inflater, container, false);
|
binding = FragmentZonesBinding.inflate(inflater, container, false);
|
||||||
View root = binding.getRoot();
|
View root = binding.getRoot();
|
||||||
|
|
||||||
|
auth = FirebaseAuth.getInstance();
|
||||||
|
db = FirebaseFirestore.getInstance();
|
||||||
|
|
||||||
|
boolean isChild = false;
|
||||||
|
FirebaseUser user = auth.getCurrentUser();
|
||||||
|
if (user != null && user.isAnonymous()) {
|
||||||
|
isChild = true;
|
||||||
|
binding.addZoneButton.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
binding.addZoneButton.setOnClickListener(v -> {
|
binding.addZoneButton.setOnClickListener(v -> {
|
||||||
android.content.Intent intent = new android.content.Intent(getContext(),
|
android.content.Intent intent = new android.content.Intent(getContext(),
|
||||||
com.example.pap_findu.AddZoneActivity.class);
|
com.example.pap_findu.AddZoneActivity.class);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ZonesAdapter adapter = new ZonesAdapter();
|
||||||
|
adapter.setChildMode(isChild);
|
||||||
|
binding.recyclerViewZones.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
binding.recyclerViewZones.setAdapter(adapter);
|
||||||
|
|
||||||
|
// Isolamento de dados: só carregar zonas deste filho
|
||||||
|
String childCode = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE)
|
||||||
|
.getString("child_access_code", null);
|
||||||
|
if (childCode != null) {
|
||||||
|
safeZonesListener = db.collection("SafeZones")
|
||||||
|
.whereEqualTo("childCode", childCode)
|
||||||
|
.addSnapshotListener((value, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
Log.e("ZonesFragment", "Erro ao carregar zonas", error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
safeZonesList = value.getDocuments();
|
||||||
|
adapter.setZonesList(safeZonesList);
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
if (safeZonesListener != null) {
|
||||||
|
safeZonesListener.remove();
|
||||||
|
}
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,25 +59,57 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="20dp"
|
||||||
android:text="Endereço ou Localização"
|
android:text="Pesquisar Localização"
|
||||||
android:textColor="#1F2937"
|
android:textColor="#1F2937"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<EditText
|
<LinearLayout
|
||||||
android:id="@+id/editZoneAddress"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/editSearchAddress"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/edit_text_background"
|
android:background="@drawable/edit_text_background"
|
||||||
android:hint="Digite o endereço..."
|
android:hint="Digite o endereço..."
|
||||||
|
android:imeOptions="actionSearch"
|
||||||
|
android:inputType="textPostalAddress"
|
||||||
android:padding="16dp" />
|
android:padding="16dp" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnSearch"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:background="@drawable/edit_text_background"
|
||||||
|
android:src="@android:drawable/ic_menu_search"
|
||||||
|
android:tint="#1F2937" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/mapFragment"
|
||||||
|
android:name="com.google.android.gms.maps.SupportMapFragment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnSave"
|
android:id="@+id/btnSave"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="32dp"
|
|
||||||
android:backgroundTint="#1F6AEF"
|
android:backgroundTint="#1F6AEF"
|
||||||
android:text="Guardar Zona"
|
android:text="Guardar Zona"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
|
|||||||
@@ -15,21 +15,11 @@
|
|||||||
android:padding="16dp"
|
android:padding="16dp"
|
||||||
android:background="#FFFFFF"
|
android:background="#FFFFFF"
|
||||||
android:elevation="4dp"
|
android:elevation="4dp"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center">
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/btnBack"
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:src="@drawable/ic_arrow_back"
|
|
||||||
app:tint="#1F2937"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:clickable="true" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:text="Editar Perfil"
|
android:text="Editar Perfil"
|
||||||
android:textColor="#1F2937"
|
android:textColor="#1F2937"
|
||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
@@ -134,6 +124,20 @@
|
|||||||
android:text="Salvar Alterações"
|
android:text="Salvar Alterações"
|
||||||
app:cornerRadius="12dp" />
|
app:cornerRadius="12dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/btnCancelProfile"
|
||||||
|
style="@style/Widget.MaterialComponents.Button"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:text="CANCELAR"
|
||||||
|
android:textColor="#FFFFFF"
|
||||||
|
app:backgroundTint="#EF4444"
|
||||||
|
app:cornerRadius="12dp"
|
||||||
|
app:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||||
|
app:iconGravity="textStart"
|
||||||
|
app:iconTint="#FFFFFF" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -46,14 +46,17 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<androidx.cardview.widget.CardView
|
<!-- NOVO BANNER DE ALERTAS (IDs limpos) -->
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/new_alert_banner"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="20dp"
|
android:layout_marginStart="20dp"
|
||||||
android:layout_marginTop="-50dp"
|
android:layout_marginTop="-50dp"
|
||||||
android:layout_marginEnd="20dp"
|
android:layout_marginEnd="20dp"
|
||||||
app:cardCornerRadius="20dp"
|
app:cardCornerRadius="20dp"
|
||||||
app:cardElevation="4dp">
|
app:cardElevation="4dp"
|
||||||
|
app:cardBackgroundColor="#FFF3E0">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -63,6 +66,7 @@
|
|||||||
android:padding="18dp">
|
android:padding="18dp">
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
android:id="@+id/new_alert_icon"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
android:layout_height="48dp"
|
android:layout_height="48dp"
|
||||||
android:src="@drawable/ic_alert_warning" />
|
android:src="@drawable/ic_alert_warning" />
|
||||||
@@ -75,22 +79,24 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/new_alert_title"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/alerts_summary_title"
|
android:text="A carregar..."
|
||||||
android:textColor="#111827"
|
android:textColor="#111827"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/new_alert_subtitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="4dp"
|
android:layout_marginTop="4dp"
|
||||||
android:text="@string/alerts_summary_desc"
|
android:text=""
|
||||||
android:textColor="#6B7280" />
|
android:textColor="#6B7280" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/filterRow"
|
android:id="@+id/filterRow"
|
||||||
@@ -103,6 +109,7 @@
|
|||||||
android:paddingEnd="20dp">
|
android:paddingEnd="20dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/filterAll"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="@drawable/bg_filter_chip_active"
|
android:background="@drawable/bg_filter_chip_active"
|
||||||
@@ -111,6 +118,7 @@
|
|||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/filterPending"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
@@ -120,6 +128,7 @@
|
|||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/filterResolved"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_marginStart="12dp"
|
||||||
@@ -129,193 +138,16 @@
|
|||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/alertsList"
|
android:id="@+id/rvAlerts"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="20dp"
|
||||||
android:paddingEnd="20dp">
|
android:paddingEnd="20dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
<com.google.android.material.card.MaterialCardView
|
android:nestedScrollingEnabled="false"
|
||||||
android:layout_width="match_parent"
|
tools:listitem="@layout/item_alert" />
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
|
||||||
app:cardCornerRadius="22dp"
|
|
||||||
app:strokeColor="#E1E5EF"
|
|
||||||
app:strokeWidth="1dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="20dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="56dp"
|
|
||||||
android:layout_height="56dp"
|
|
||||||
android:background="@drawable/bg_alert_icon_warning">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="14dp"
|
|
||||||
android:src="@drawable/ic_alert_warning" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/alerts_school_title"
|
|
||||||
android:textColor="#111827"
|
|
||||||
android:textSize="17sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:text="@string/alerts_school_desc"
|
|
||||||
android:textColor="#6B7280" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/bg_chip_warning"
|
|
||||||
android:text="@string/alerts_status_attention"
|
|
||||||
android:textColor="#B45309"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="14dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/alerts_school_time"
|
|
||||||
android:textColor="#0F172A"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/alerts_school_action"
|
|
||||||
android:textColor="#1C6CFF"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
|
||||||
app:cardCornerRadius="22dp"
|
|
||||||
app:strokeColor="#E1E5EF"
|
|
||||||
app:strokeWidth="1dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="20dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="56dp"
|
|
||||||
android:layout_height="56dp"
|
|
||||||
android:background="@drawable/bg_alert_icon_info">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:padding="14dp"
|
|
||||||
android:src="@android:drawable/ic_dialog_info"
|
|
||||||
app:tint="#1F6AEF" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/alerts_geofence_title"
|
|
||||||
android:textColor="#111827"
|
|
||||||
android:textSize="17sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:text="@string/alerts_geofence_desc"
|
|
||||||
android:textColor="#6B7280" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/bg_chip_success"
|
|
||||||
android:text="@string/alerts_status_resolved"
|
|
||||||
android:textColor="#0F7A45"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="14dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/alerts_geofence_time"
|
|
||||||
android:textColor="#0F172A"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/alerts_geofence_action"
|
|
||||||
android:textColor="#9CA3AF" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|||||||
@@ -46,288 +46,15 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- Date Header -->
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
<TextView
|
android:id="@+id/rvHistory"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:text="Hoje, 12 Dezembro"
|
|
||||||
android:textColor="#6B7280"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<!-- Timeline Item 1 -->
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="20dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginTop="12dp"
|
android:clipToPadding="false"
|
||||||
android:layout_marginEnd="20dp"
|
android:nestedScrollingEnabled="false"
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
android:paddingBottom="24dp"
|
||||||
app:cardCornerRadius="20dp"
|
tools:listitem="@layout/item_history_event" />
|
||||||
app:strokeColor="#E1E5EF"
|
|
||||||
app:strokeWidth="1dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="@drawable/bg_zone_icon_primary">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="🏠"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Chegou a Casa"
|
|
||||||
android:textColor="#0F172A"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Localização segura confirmada"
|
|
||||||
android:textColor="#6B7280"
|
|
||||||
android:textSize="13sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="13:30"
|
|
||||||
android:textColor="#9CA3AF"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<!-- Timeline Item 2 -->
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
|
||||||
app:cardCornerRadius="20dp"
|
|
||||||
app:strokeColor="#E1E5EF"
|
|
||||||
app:strokeWidth="1dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="@drawable/bg_alert_icon_warning">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="⚠️"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Saiu da Area Escolar"
|
|
||||||
android:textColor="#0F172A"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Saída antes do horário previsto"
|
|
||||||
android:textColor="#B45309"
|
|
||||||
android:textSize="13sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="12:35"
|
|
||||||
android:textColor="#B45309"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<!-- Timeline Item 3 -->
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
|
||||||
app:cardCornerRadius="20dp"
|
|
||||||
app:strokeColor="#E1E5EF"
|
|
||||||
app:strokeWidth="1dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="@drawable/bg_zone_icon_secondary">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="🏫"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Chegou à Escola"
|
|
||||||
android:textColor="#0F172A"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Dentro do horário esperado"
|
|
||||||
android:textColor="#6B7280"
|
|
||||||
android:textSize="13sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="08:45"
|
|
||||||
android:textColor="#9CA3AF"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<!-- Date Header 2 -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="24dp"
|
|
||||||
android:layout_marginTop="24dp"
|
|
||||||
android:text="Ontem, 11 Dezembro"
|
|
||||||
android:textColor="#6B7280"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<!-- Timeline Item 4 -->
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="20dp"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
android:layout_marginEnd="20dp"
|
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
|
||||||
app:cardCornerRadius="20dp"
|
|
||||||
app:strokeColor="#E1E5EF"
|
|
||||||
app:strokeWidth="1dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:background="@drawable/bg_zone_icon_primary">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="🏠"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Chegou a Casa"
|
|
||||||
android:textColor="#0F172A"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Localização segura confirmada"
|
|
||||||
android:textColor="#6B7280"
|
|
||||||
android:textSize="13sp" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="18:15"
|
|
||||||
android:textColor="#9CA3AF"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|||||||
@@ -232,6 +232,18 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/btnCenterMap"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:backgroundTint="#FFFFFF"
|
||||||
|
app:tint="#3B82F6"
|
||||||
|
android:src="@android:drawable/ic_menu_mylocation"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/btnAbrirChat"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:id="@+id/btnAbrirChat"
|
android:id="@+id/btnAbrirChat"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|||||||
@@ -46,30 +46,29 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
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:layout_width="wrap_content"
|
android:id="@+id/tvDynamicZoneCount"
|
||||||
|
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: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"
|
||||||
@@ -88,195 +87,15 @@
|
|||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<LinearLayout
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/zoneList"
|
android:id="@+id/recyclerViewZones"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="20dp"
|
android:paddingStart="20dp"
|
||||||
android:paddingEnd="20dp">
|
android:paddingEnd="20dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
<com.google.android.material.card.MaterialCardView
|
tools:listitem="@layout/item_zone" />
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="12dp"
|
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
|
||||||
app:cardCornerRadius="24dp"
|
|
||||||
app:strokeColor="#E1E5EF"
|
|
||||||
app:strokeWidth="1dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="20dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="56dp"
|
|
||||||
android:layout_height="56dp"
|
|
||||||
android:background="@drawable/bg_zone_icon_primary">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/zone_school_icon"
|
|
||||||
android:textSize="24sp" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/zones_school_name"
|
|
||||||
android:textColor="#0F172A"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:text="@string/zones_school_desc"
|
|
||||||
android:textColor="#6B7280" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/bg_chip_success"
|
|
||||||
android:text="@string/zones_status_safe"
|
|
||||||
android:textColor="#0F7A45"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="14dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/bg_chip_muted"
|
|
||||||
android:text="@string/zones_chip_entry"
|
|
||||||
android:textColor="#4B5563" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/zones_last_update"
|
|
||||||
android:textColor="#9CA3AF" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
app:cardBackgroundColor="#FFFFFF"
|
|
||||||
app:cardCornerRadius="24dp"
|
|
||||||
app:strokeColor="#E1E5EF"
|
|
||||||
app:strokeWidth="1dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="20dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="56dp"
|
|
||||||
android:layout_height="56dp"
|
|
||||||
android:background="@drawable/bg_zone_icon_secondary">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:gravity="center"
|
|
||||||
android:text="@string/zone_home_icon"
|
|
||||||
android:textSize="24sp" />
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/zones_home_name"
|
|
||||||
android:textColor="#0F172A"
|
|
||||||
android:textSize="18sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="2dp"
|
|
||||||
android:text="@string/zones_home_desc"
|
|
||||||
android:textColor="#6B7280" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/bg_chip_warning"
|
|
||||||
android:text="@string/zones_status_attention"
|
|
||||||
android:textColor="#B45309"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="14dp"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/bg_chip_muted"
|
|
||||||
android:text="@string/zones_chip_exit"
|
|
||||||
android:textColor="#4B5563" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/zones_home_update"
|
|
||||||
android:textColor="#F97316"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|||||||
128
app/src/main/res/layout/item_alert.xml
Normal file
128
app/src/main/res/layout/item_alert.xml
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:cardBackgroundColor="#FFFFFF"
|
||||||
|
app:cardCornerRadius="22dp"
|
||||||
|
app:strokeColor="#E1E5EF"
|
||||||
|
app:strokeWidth="1dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/flAlertIconBg"
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@drawable/bg_alert_icon_warning">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivAlertIcon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="14dp"
|
||||||
|
android:src="@drawable/ic_alert_warning" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="12dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAlertTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Alert Title"
|
||||||
|
android:textColor="#111827"
|
||||||
|
android:textSize="17sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAlertMessage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:text="Alert Message"
|
||||||
|
android:textColor="#6B7280" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAlertStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_chip_warning"
|
||||||
|
android:paddingHorizontal="10dp"
|
||||||
|
android:paddingVertical="4dp"
|
||||||
|
android:text="Urgente"
|
||||||
|
android:textColor="#B45309"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAlertDate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Data e Hora"
|
||||||
|
android:textColor="#0F172A"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llAlertActions"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:gravity="end"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btnContactar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="Contactar"
|
||||||
|
android:textColor="#3B82F6"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:drawableStartCompat="@android:drawable/ic_menu_call"
|
||||||
|
app:drawableTint="#3B82F6"
|
||||||
|
android:drawablePadding="6dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/btnResolver"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:text="Marcar Resolvido"
|
||||||
|
android:textColor="#10B981"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:drawableStartCompat="@android:drawable/ic_menu_save"
|
||||||
|
app:drawableTint="#10B981"
|
||||||
|
android:drawablePadding="6dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
71
app/src/main/res/layout/item_history_event.xml
Normal file
71
app/src/main/res/layout/item_history_event.xml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
app:cardBackgroundColor="#FFFFFF"
|
||||||
|
app:cardCornerRadius="20dp"
|
||||||
|
app:strokeColor="#E1E5EF"
|
||||||
|
app:strokeWidth="1dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/flEventIconBg"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@drawable/bg_zone_icon_primary">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvEventIconText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="🏠"
|
||||||
|
android:textSize="20sp" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvEventTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Chegou a Casa"
|
||||||
|
android:textColor="#0F172A"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvEventDesc"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Localização segura confirmada"
|
||||||
|
android:textColor="#6B7280"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvEventTime"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="13:30"
|
||||||
|
android:textColor="#9CA3AF"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
20
app/src/main/res/layout/item_history_header.xml
Normal file
20
app/src/main/res/layout/item_history_header.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingEnd="24dp"
|
||||||
|
android:paddingTop="24dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvHeaderDate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hoje, 12 Dezembro"
|
||||||
|
android:textColor="#6B7280"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
105
app/src/main/res/layout/item_zone.xml
Normal file
105
app/src/main/res/layout/item_zone.xml
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
app:cardBackgroundColor="#FFFFFF"
|
||||||
|
app:cardCornerRadius="24dp"
|
||||||
|
app:strokeColor="#E1E5EF"
|
||||||
|
app:strokeWidth="1dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:background="@drawable/bg_zone_icon_primary">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvZoneIcon"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="📍"
|
||||||
|
android:textSize="24sp" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvZoneName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Zone Name"
|
||||||
|
android:textColor="#0F172A"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvZoneAddress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:text="Zone Address"
|
||||||
|
android:textColor="#6B7280" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvZoneStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_chip_success"
|
||||||
|
android:text="Safe"
|
||||||
|
android:textColor="#0F7A45"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivDeleteZone"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:src="@android:drawable/ic_menu_delete"
|
||||||
|
app:tint="#EF4444"
|
||||||
|
android:visibility="visible" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="14dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvZoneRadiusLabel"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@drawable/bg_chip_muted"
|
||||||
|
android:text="Radius"
|
||||||
|
android:textColor="#4B5563" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvZoneRadius"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="200m"
|
||||||
|
android:textColor="#9CA3AF" />
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,4 +1,4 @@
|
|||||||
#Thu Mar 12 10:10:15 WET 2026
|
#Mon Apr 20 14:11:21 WEST 2026
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||||
|
|||||||
Reference in New Issue
Block a user