Merge remote-tracking branch 'origin/main'
# Conflicts: # .idea/deploymentTargetSelector.xml # app/src/main/java/com/example/pap_findu/LocationService.java # app/src/main/java/com/example/pap_findu/login_activity.java # app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java
This commit is contained in:
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -7,10 +7,10 @@
|
|||||||
</SelectionState>
|
</SelectionState>
|
||||||
<SelectionState runConfigName="login_activity">
|
<SelectionState runConfigName="login_activity">
|
||||||
<option name="selectionMode" value="DIALOG" />
|
<option name="selectionMode" value="DIALOG" />
|
||||||
<DropdownSelection timestamp="2026-03-12T15:46:37.841197Z">
|
<DropdownSelection timestamp="2026-03-17T14:22:15.472961Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Medium_Phone_2.avd" />
|
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Pixel_7.avd" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
@@ -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 ||
|
||||||
return;
|
(event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) {
|
||||||
|
searchAddress();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
// Here we would save to Firebase/Database
|
btnSave.setOnClickListener(v -> saveZone(btnSave));
|
||||||
Toast.makeText(this, "Zona '" + name + "' adicionada com sucesso!", Toast.LENGTH_LONG).show();
|
}
|
||||||
finish();
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
finish();
|
||||||
|
})
|
||||||
|
.addOnFailureListener(e -> {
|
||||||
|
Toast.makeText(this, "Erro ao salvar: " + e.getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
btnSave.setEnabled(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,46 +2,55 @@ package com.example.pap_findu;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.example.pap_findu.models.User;
|
|
||||||
import com.google.firebase.database.DatabaseReference;
|
|
||||||
import com.google.firebase.database.FirebaseDatabase;
|
|
||||||
|
|
||||||
import androidx.activity.EdgeToEdge;
|
import androidx.activity.EdgeToEdge;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.graphics.Insets;
|
import androidx.core.graphics.Insets;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.core.view.WindowInsetsCompat;
|
import androidx.core.view.WindowInsetsCompat;
|
||||||
|
|
||||||
|
import com.example.pap_findu.models.User;
|
||||||
|
import com.google.android.gms.tasks.OnCompleteListener;
|
||||||
|
import com.google.android.gms.tasks.Task;
|
||||||
|
import com.google.firebase.auth.AuthResult;
|
||||||
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
|
import com.google.firebase.auth.FirebaseUser;
|
||||||
|
import com.google.firebase.database.DatabaseReference;
|
||||||
|
import com.google.firebase.database.FirebaseDatabase;
|
||||||
|
|
||||||
public class CriarConta extends AppCompatActivity {
|
public class CriarConta extends AppCompatActivity {
|
||||||
|
|
||||||
private EditText inputFullName;
|
private EditText inputFullName, emailEditText, passwordEditText, inputConfirmPassword;
|
||||||
private EditText emailEditText;
|
|
||||||
private EditText passwordEditText;
|
|
||||||
private EditText inputConfirmPassword;
|
|
||||||
private CheckBox checkTerms;
|
private CheckBox checkTerms;
|
||||||
private Button btnCreateAccount;
|
private Button btnCreateAccount;
|
||||||
private TextView loginLink;
|
private TextView loginLink;
|
||||||
private com.google.firebase.auth.FirebaseAuth mAuth;
|
private FirebaseAuth mAuth;
|
||||||
|
private DatabaseReference mDatabase;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
EdgeToEdge.enable(this);
|
EdgeToEdge.enable(this);
|
||||||
setContentView(R.layout.activity_criar_conta);
|
setContentView(R.layout.activity_criar_conta);
|
||||||
|
|
||||||
|
// Ajuste de Padding para EdgeToEdge
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
|
||||||
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
|
||||||
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
|
||||||
return insets;
|
return insets;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize views
|
// 1. Inicializar Firebase
|
||||||
|
mAuth = FirebaseAuth.getInstance();
|
||||||
|
mDatabase = FirebaseDatabase.getInstance().getReference("users");
|
||||||
|
|
||||||
|
// 2. Inicializar Views
|
||||||
inputFullName = findViewById(R.id.inputFullName);
|
inputFullName = findViewById(R.id.inputFullName);
|
||||||
emailEditText = findViewById(R.id.emailEditText);
|
emailEditText = findViewById(R.id.emailEditText);
|
||||||
passwordEditText = findViewById(R.id.passwordEditText);
|
passwordEditText = findViewById(R.id.passwordEditText);
|
||||||
@@ -50,78 +59,69 @@ public class CriarConta extends AppCompatActivity {
|
|||||||
btnCreateAccount = findViewById(R.id.btnCreateAccount);
|
btnCreateAccount = findViewById(R.id.btnCreateAccount);
|
||||||
loginLink = findViewById(R.id.loginLink);
|
loginLink = findViewById(R.id.loginLink);
|
||||||
|
|
||||||
mAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
|
// 3. Botão Criar Conta
|
||||||
|
btnCreateAccount.setOnClickListener(v -> validarECriar());
|
||||||
|
|
||||||
// Set click listener for the create account button
|
// 4. Link para Login
|
||||||
btnCreateAccount.setOnClickListener(new View.OnClickListener() {
|
loginLink.setOnClickListener(v -> {
|
||||||
@Override
|
startActivity(new Intent(CriarConta.this, login_activity.class));
|
||||||
public void onClick(View v) {
|
finish();
|
||||||
String fullName = inputFullName.getText().toString();
|
|
||||||
String email = emailEditText.getText().toString();
|
|
||||||
String password = passwordEditText.getText().toString();
|
|
||||||
String confirmPassword = inputConfirmPassword.getText().toString();
|
|
||||||
|
|
||||||
if (fullName.isEmpty() || email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()) {
|
|
||||||
Toast.makeText(CriarConta.this, "Por favor, preencha todos os campos.", Toast.LENGTH_SHORT).show();
|
|
||||||
} else if (!password.equals(confirmPassword)) {
|
|
||||||
Toast.makeText(CriarConta.this, "As palavras-passe não coincidem.", Toast.LENGTH_SHORT).show();
|
|
||||||
} else if (!checkTerms.isChecked()) {
|
|
||||||
Toast.makeText(CriarConta.this, "Você deve concordar com os Termos de Serviço.", Toast.LENGTH_SHORT)
|
|
||||||
.show();
|
|
||||||
} else {
|
|
||||||
mAuth.createUserWithEmailAndPassword(email, password)
|
|
||||||
.addOnCompleteListener(CriarConta.this,
|
|
||||||
new com.google.android.gms.tasks.OnCompleteListener<com.google.firebase.auth.AuthResult>() {
|
|
||||||
@Override
|
|
||||||
public void onComplete(
|
|
||||||
@androidx.annotation.NonNull com.google.android.gms.tasks.Task<com.google.firebase.auth.AuthResult> task) {
|
|
||||||
if (task.isSuccessful()) {
|
|
||||||
// Sign in success, update UI with the signed-in user's information
|
|
||||||
Toast.makeText(CriarConta.this, "Conta criada com sucesso!",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
com.google.firebase.auth.FirebaseUser firebaseUser = mAuth
|
|
||||||
.getCurrentUser();
|
|
||||||
|
|
||||||
// Save user data to Realtime Database
|
|
||||||
if (firebaseUser != null) {
|
|
||||||
String userId = firebaseUser.getUid();
|
|
||||||
DatabaseReference mDatabase = FirebaseDatabase.getInstance()
|
|
||||||
.getReference("users");
|
|
||||||
User user = new User(fullName, email);
|
|
||||||
|
|
||||||
mDatabase.child(userId).setValue(user)
|
|
||||||
.addOnCompleteListener(task1 -> {
|
|
||||||
if (!task1.isSuccessful()) {
|
|
||||||
Toast.makeText(CriarConta.this,
|
|
||||||
"Falha ao salvar dados do perfil.",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Intent intent = new Intent(CriarConta.this, MainActivity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
} else {
|
|
||||||
// If sign in fails, display a message to the user.
|
|
||||||
Toast.makeText(CriarConta.this,
|
|
||||||
"Falha ao criar conta: " + task.getException().getMessage(),
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set click listener for the login link text
|
|
||||||
loginLink.setOnClickListener(new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
// Navigate back to the login activity
|
|
||||||
Intent intent = new Intent(CriarConta.this, login_activity.class);
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private void validarECriar() {
|
||||||
|
String fullName = inputFullName.getText().toString().trim();
|
||||||
|
String email = emailEditText.getText().toString().trim();
|
||||||
|
String password = passwordEditText.getText().toString().trim();
|
||||||
|
String confirmPassword = inputConfirmPassword.getText().toString().trim();
|
||||||
|
|
||||||
|
// Validações Básicas
|
||||||
|
if (fullName.isEmpty() || email.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()) {
|
||||||
|
Toast.makeText(this, "Preencha todos os campos!", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password.equals(confirmPassword)) {
|
||||||
|
Toast.makeText(this, "As passwords não coincidem!", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!checkTerms.isChecked()) {
|
||||||
|
Toast.makeText(this, "Aceite os termos de serviço!", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Criar no FirebaseAuth
|
||||||
|
mAuth.createUserWithEmailAndPassword(email, password)
|
||||||
|
.addOnCompleteListener(this, task -> {
|
||||||
|
if (task.isSuccessful()) {
|
||||||
|
salvarDadosNoPerfil(fullName, email);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(CriarConta.this, "Erro: " + task.getException().getMessage(), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void salvarDadosNoPerfil(String fullName, String email) {
|
||||||
|
FirebaseUser firebaseUser = mAuth.getCurrentUser();
|
||||||
|
if (firebaseUser != null) {
|
||||||
|
String userId = firebaseUser.getUid();
|
||||||
|
|
||||||
|
// Usamos o teu modelo User (Mapping: fullName -> name)
|
||||||
|
User user = new User(fullName, email);
|
||||||
|
|
||||||
|
mDatabase.child(userId).setValue(user)
|
||||||
|
.addOnCompleteListener(task -> {
|
||||||
|
if (task.isSuccessful()) {
|
||||||
|
Toast.makeText(CriarConta.this, "Conta configurada com sucesso!", Toast.LENGTH_SHORT).show();
|
||||||
|
// Só avança se os dados foram salvos
|
||||||
|
Intent intent = new Intent(CriarConta.this, MainActivity.class);
|
||||||
|
startActivity(intent);
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(CriarConta.this, "Erro ao salvar perfil no banco de dados.", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,14 @@
|
|||||||
package com.example.pap_findu;
|
package com.example.pap_findu;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
|
import android.util.Base64;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
@@ -15,9 +18,6 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.example.pap_findu.models.User;
|
import com.example.pap_findu.models.User;
|
||||||
import com.example.pap_findu.ui.profile.ProfileFragment;
|
|
||||||
import com.google.android.gms.tasks.OnFailureListener;
|
|
||||||
import com.google.android.gms.tasks.OnSuccessListener;
|
|
||||||
import com.google.android.material.button.MaterialButton;
|
import com.google.android.material.button.MaterialButton;
|
||||||
import com.google.android.material.textfield.TextInputEditText;
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
import com.google.firebase.auth.FirebaseAuth;
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
@@ -27,25 +27,27 @@ import com.google.firebase.database.DatabaseError;
|
|||||||
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.database.ValueEventListener;
|
import com.google.firebase.database.ValueEventListener;
|
||||||
import com.google.firebase.storage.FirebaseStorage;
|
|
||||||
import com.google.firebase.storage.StorageReference;
|
import java.io.ByteArrayOutputStream;
|
||||||
import com.google.firebase.storage.UploadTask;
|
|
||||||
|
|
||||||
public class EditProfileActivity extends AppCompatActivity {
|
public class EditProfileActivity extends AppCompatActivity {
|
||||||
|
|
||||||
private ImageView btnBack;
|
|
||||||
private ImageView editProfileImage;
|
private ImageView editProfileImage;
|
||||||
private View btnChangePhoto;
|
private TextView btnChangePhoto;
|
||||||
private TextInputEditText editName;
|
private TextInputEditText editName;
|
||||||
private TextInputEditText editEmail;
|
private TextInputEditText editEmail;
|
||||||
private TextInputEditText editPhone;
|
|
||||||
|
// Botoes
|
||||||
private MaterialButton btnSaveProfile;
|
private MaterialButton btnSaveProfile;
|
||||||
|
private MaterialButton btnChangePassword;
|
||||||
|
private MaterialButton btnCancelProfile;
|
||||||
|
|
||||||
private FirebaseAuth mAuth;
|
private FirebaseAuth mAuth;
|
||||||
private DatabaseReference mDatabase;
|
private DatabaseReference mDatabase;
|
||||||
private StorageReference mStorageRef;
|
|
||||||
private FirebaseUser currentUser;
|
private FirebaseUser currentUser;
|
||||||
private Uri selectedImageUri;
|
|
||||||
|
// NOVO: Variável para guardar o texto gigante da imagem (Base64) em vez do Uri
|
||||||
|
private String base64ImageString = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -55,7 +57,7 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||||||
mAuth = FirebaseAuth.getInstance();
|
mAuth = FirebaseAuth.getInstance();
|
||||||
currentUser = mAuth.getCurrentUser();
|
currentUser = mAuth.getCurrentUser();
|
||||||
mDatabase = FirebaseDatabase.getInstance().getReference("users");
|
mDatabase = FirebaseDatabase.getInstance().getReference("users");
|
||||||
mStorageRef = FirebaseStorage.getInstance().getReference("profile_images");
|
// O StorageReference foi removido pois já não precisamos dele!
|
||||||
|
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
finish();
|
finish();
|
||||||
@@ -68,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);
|
||||||
editPhone = findViewById(R.id.editPhone);
|
|
||||||
btnSaveProfile = findViewById(R.id.btnSaveProfile);
|
btnSaveProfile = findViewById(R.id.btnSaveProfile);
|
||||||
|
btnChangePassword = findViewById(R.id.btnChangePassword);
|
||||||
|
btnCancelProfile = findViewById(R.id.btnCancelProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadUserData() {
|
private void loadUserData() {
|
||||||
@@ -85,13 +87,22 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||||||
if (user != null) {
|
if (user != null) {
|
||||||
editName.setText(user.getName());
|
editName.setText(user.getName());
|
||||||
editEmail.setText(user.getEmail());
|
editEmail.setText(user.getEmail());
|
||||||
editPhone.setText(user.getPhone());
|
|
||||||
|
|
||||||
if (user.getProfileImageUrl() != null && !user.getProfileImageUrl().isEmpty()) {
|
if (user.getProfileImageUrl() != null && !user.getProfileImageUrl().isEmpty()) {
|
||||||
Glide.with(EditProfileActivity.this)
|
try {
|
||||||
.load(user.getProfileImageUrl())
|
// Tenta descodificar a imagem de formato Base64 (Texto para Imagem)
|
||||||
.placeholder(R.drawable.logo)
|
byte[] decodedString = Base64.decode(user.getProfileImageUrl(), Base64.DEFAULT);
|
||||||
.into(editProfileImage);
|
Glide.with(EditProfileActivity.this)
|
||||||
|
.load(decodedString)
|
||||||
|
.placeholder(R.drawable.logo)
|
||||||
|
.into(editProfileImage);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Se der erro (ex: se na DB estiver um link antigo em vez de Base64), tenta carregar normalmente
|
||||||
|
Glide.with(EditProfileActivity.this)
|
||||||
|
.load(user.getProfileImageUrl())
|
||||||
|
.placeholder(R.drawable.logo)
|
||||||
|
.into(editProfileImage);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback to auth data if DB is empty
|
// Fallback to auth data if DB is empty
|
||||||
@@ -108,15 +119,31 @@ 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
|
||||||
|
btnChangePassword.setOnClickListener(v -> {
|
||||||
|
String emailAddress = editEmail.getText().toString().trim();
|
||||||
|
if (!emailAddress.isEmpty()) {
|
||||||
|
enviarEmailRecuperacao(emailAddress);
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Erro: Email não encontrado.", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Image Picker Launcher
|
// Image Picker Launcher
|
||||||
ActivityResultLauncher<Intent> imagePickerLauncher = registerForActivityResult(
|
ActivityResultLauncher<Intent> imagePickerLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.StartActivityForResult(),
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
result -> {
|
result -> {
|
||||||
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
|
||||||
selectedImageUri = result.getData().getData();
|
Uri selectedImageUri = result.getData().getData();
|
||||||
editProfileImage.setImageURI(selectedImageUri);
|
editProfileImage.setImageURI(selectedImageUri);
|
||||||
|
|
||||||
|
// NOVO: Faz a conversão mágica da imagem para texto!
|
||||||
|
converterImagemParaTexto(selectedImageUri);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -128,10 +155,41 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||||||
btnSaveProfile.setOnClickListener(v -> saveProfile());
|
btnSaveProfile.setOnClickListener(v -> saveProfile());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOVO: Função que pega na foto, encolhe-a e transforma num texto gigante
|
||||||
|
private void converterImagemParaTexto(Uri imageUri) {
|
||||||
|
try {
|
||||||
|
Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imageUri);
|
||||||
|
// Encolher para 300x300 pixeis para não exceder o limite da Realtime Database
|
||||||
|
Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, 300, 300, true);
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 70, baos); // Qualidade 70%
|
||||||
|
byte[] imageBytes = baos.toByteArray();
|
||||||
|
|
||||||
|
// Guarda a imagem final como uma string
|
||||||
|
base64ImageString = Base64.encodeToString(imageBytes, Base64.DEFAULT);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
Toast.makeText(this, "Erro ao processar imagem", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Função para pedir ao Firebase para enviar email de reset
|
||||||
|
private void enviarEmailRecuperacao(String email) {
|
||||||
|
btnChangePassword.setEnabled(false);
|
||||||
|
mAuth.sendPasswordResetEmail(email)
|
||||||
|
.addOnCompleteListener(task -> {
|
||||||
|
btnChangePassword.setEnabled(true);
|
||||||
|
if (task.isSuccessful()) {
|
||||||
|
Toast.makeText(EditProfileActivity.this, "Email enviado! Verifique a sua caixa de entrada.", Toast.LENGTH_LONG).show();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(EditProfileActivity.this, "Erro ao enviar email.", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void saveProfile() {
|
private void saveProfile() {
|
||||||
String name = editName.getText().toString().trim();
|
String name = editName.getText().toString().trim();
|
||||||
String email = editEmail.getText().toString().trim();
|
String email = editEmail.getText().toString().trim();
|
||||||
String phone = editPhone.getText().toString().trim();
|
|
||||||
|
|
||||||
if (name.isEmpty() || email.isEmpty()) {
|
if (name.isEmpty() || email.isEmpty()) {
|
||||||
Toast.makeText(this, "Nome e Email são obrigatórios", Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, "Nome e Email são obrigatórios", Toast.LENGTH_SHORT).show();
|
||||||
@@ -139,32 +197,13 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
btnSaveProfile.setEnabled(false);
|
btnSaveProfile.setEnabled(false);
|
||||||
btnSaveProfile.setText("Salvando...");
|
btnSaveProfile.setText("A Salvar...");
|
||||||
|
|
||||||
if (selectedImageUri != null) {
|
// Guardamos o perfil passando a String Base64 (se houver), em vez de fazer upload para o Storage
|
||||||
uploadImageAndSaveUser(name, email, phone);
|
saveUserToDb(name, email, base64ImageString);
|
||||||
} else {
|
|
||||||
saveUserToDb(name, email, phone, null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void uploadImageAndSaveUser(String name, String email, String phone) {
|
private void saveUserToDb(String name, String email, String imageUrlBase64) {
|
||||||
final StorageReference fileRef = mStorageRef.child(currentUser.getUid() + ".jpg");
|
|
||||||
|
|
||||||
fileRef.putFile(selectedImageUri)
|
|
||||||
.addOnSuccessListener(taskSnapshot -> fileRef.getDownloadUrl().addOnSuccessListener(uri -> {
|
|
||||||
String imageUrl = uri.toString();
|
|
||||||
saveUserToDb(name, email, phone, imageUrl);
|
|
||||||
}))
|
|
||||||
.addOnFailureListener(e -> {
|
|
||||||
Toast.makeText(EditProfileActivity.this, "Erro ao enviar imagem: " + e.getMessage(),
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
btnSaveProfile.setEnabled(true);
|
|
||||||
btnSaveProfile.setText("Salvar Alterações");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveUserToDb(String name, String email, String phone, String imageUrl) {
|
|
||||||
mDatabase.child(currentUser.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
|
mDatabase.child(currentUser.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
||||||
@@ -174,9 +213,10 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
user.setName(name);
|
user.setName(name);
|
||||||
user.setEmail(email);
|
user.setEmail(email);
|
||||||
user.setPhone(phone);
|
|
||||||
if (imageUrl != null) {
|
// Se houver uma imagem convertida em Base64, guardamos na base de dados
|
||||||
user.setProfileImageUrl(imageUrl);
|
if (imageUrlBase64 != null) {
|
||||||
|
user.setProfileImageUrl(imageUrlBase64);
|
||||||
}
|
}
|
||||||
|
|
||||||
mDatabase.child(currentUser.getUid()).setValue(user)
|
mDatabase.child(currentUser.getUid()).setValue(user)
|
||||||
@@ -198,4 +238,4 @@ public class EditProfileActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,27 @@
|
|||||||
package com.example.pap_findu;
|
package com.example.pap_findu;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ServiceInfo;
|
import android.content.pm.ServiceInfo;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import android.app.Notification;
|
|
||||||
import android.app.NotificationChannel;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.app.Service;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.os.BatteryManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import android.app.Notification;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.Service;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.google.android.gms.location.FusedLocationProviderClient;
|
import com.google.android.gms.location.FusedLocationProviderClient;
|
||||||
import com.google.android.gms.location.LocationCallback;
|
import com.google.android.gms.location.LocationCallback;
|
||||||
@@ -36,6 +40,7 @@ public class LocationService extends Service {
|
|||||||
private FusedLocationProviderClient fusedLocationClient;
|
private FusedLocationProviderClient fusedLocationClient;
|
||||||
private LocationCallback locationCallback;
|
private LocationCallback locationCallback;
|
||||||
private DatabaseReference databaseReference;
|
private DatabaseReference databaseReference;
|
||||||
|
private GeofenceManager geofenceManager;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
@@ -46,9 +51,12 @@ public class LocationService extends Service {
|
|||||||
.getString("child_access_code", null);
|
.getString("child_access_code", null);
|
||||||
|
|
||||||
if (childCode != null) {
|
if (childCode != null) {
|
||||||
// CAMINHO EXATO: Direto na raiz, como na tua print
|
// Caminho direto na raiz para o monitoramento em tempo real
|
||||||
databaseReference = FirebaseDatabase.getInstance().getReference(childCode).child("live_location");
|
databaseReference = FirebaseDatabase.getInstance().getReference(childCode).child("live_location");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
geofenceManager = new GeofenceManager(this);
|
||||||
|
geofenceManager.setupGeofences();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -56,8 +64,9 @@ public class LocationService extends Service {
|
|||||||
createNotificationChannel();
|
createNotificationChannel();
|
||||||
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
|
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
|
||||||
.setContentTitle("FindU Ativo")
|
.setContentTitle("FindU Ativo")
|
||||||
.setContentText("A partilhar localização real...")
|
.setContentText("A partilhar localização e bateria em tempo real...")
|
||||||
.setSmallIcon(R.mipmap.ic_launcher)
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
@@ -74,8 +83,7 @@ public class LocationService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void requestLocationUpdates() {
|
private void requestLocationUpdates() {
|
||||||
// Pedimos atualizações de 2 em 2 segundos
|
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
|
||||||
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 2000)
|
|
||||||
.setMinUpdateDistanceMeters(0)
|
.setMinUpdateDistanceMeters(0)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@@ -83,11 +91,9 @@ public class LocationService extends Service {
|
|||||||
@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 CRÍTICO: Se for a latitude da Google (37.42...), IGNORA.
|
// Filtro para ignorar a localização padrão do emulador (Califórnia)
|
||||||
if (Math.abs(location.getLatitude() - 37.4219) > 0.001) {
|
if (Math.abs(location.getLatitude() - 37.4219) > 0.001) {
|
||||||
updateFirebase(location);
|
updateFirebase(location);
|
||||||
} else {
|
|
||||||
Log.d("LocationService", "Ignorada localização falsa da Califórnia");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,19 +102,33 @@ public class LocationService extends Service {
|
|||||||
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
Intent batteryStatus = registerReceiver(null, ifilter);
|
||||||
|
|
||||||
|
int level = -1;
|
||||||
|
if (batteryStatus != null) {
|
||||||
|
int rawLevel = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
||||||
|
int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
|
||||||
|
level = (int) ((rawLevel / (float) scale) * 100);
|
||||||
|
}
|
||||||
|
// -------------------------
|
||||||
|
|
||||||
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("last_updated", System.currentTimeMillis());
|
data.put("last_updated", System.currentTimeMillis());
|
||||||
|
|
||||||
databaseReference.setValue(data);
|
databaseReference.setValue(data).addOnFailureListener(e -> {
|
||||||
Log.d("LocationService", "Enviada localização REAL: " + location.getLatitude());
|
Log.e("LocationService", "Erro ao enviar para Firebase: " + e.getMessage());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,8 +144,13 @@ public class LocationService extends Service {
|
|||||||
|
|
||||||
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("LocationChannel", "GPS", NotificationManager.IMPORTANCE_LOW);
|
NotificationChannel channel = new NotificationChannel(
|
||||||
getSystemService(NotificationManager.class).createNotificationChannel(channel);
|
"LocationChannel",
|
||||||
|
"Monitoramento",
|
||||||
|
NotificationManager.IMPORTANCE_LOW
|
||||||
|
);
|
||||||
|
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||||
|
if (manager != null) manager.createNotificationChannel(channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,13 @@ import android.content.Intent;
|
|||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
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;
|
||||||
@@ -18,43 +20,58 @@ import androidx.navigation.ui.NavigationUI;
|
|||||||
|
|
||||||
import com.example.pap_findu.databinding.ActivityMainBinding;
|
import com.example.pap_findu.databinding.ActivityMainBinding;
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
|
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 {
|
||||||
|
|
||||||
private ActivityMainBinding binding;
|
private ActivityMainBinding binding;
|
||||||
|
private FirebaseAuth mAuth;
|
||||||
|
|
||||||
// ========================================================
|
|
||||||
// 1. GESTOR DE PERMISSÕES (O "Contrato" com o Android)
|
|
||||||
// ========================================================
|
|
||||||
private final ActivityResultLauncher<String[]> requestPermissionLauncher =
|
private final ActivityResultLauncher<String[]> requestPermissionLauncher =
|
||||||
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissions -> {
|
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissions -> {
|
||||||
Boolean fineLocationGranted = permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
|
Boolean fineLocationGranted = permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
|
||||||
Boolean coarseLocationGranted = permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false);
|
|
||||||
|
|
||||||
if (fineLocationGranted != null && fineLocationGranted) {
|
if (fineLocationGranted != null && fineLocationGranted) {
|
||||||
// Permissão precisa concedida, podemos iniciar o serviço!
|
decidirInicioDeServico();
|
||||||
startLocationService();
|
|
||||||
} else if (coarseLocationGranted != null && coarseLocationGranted) {
|
|
||||||
// Permissão aproximada concedida, podemos iniciar o serviço!
|
|
||||||
startLocationService();
|
|
||||||
} else {
|
} else {
|
||||||
// O utilizador recusou as permissões. A app não pode iniciar o serviço.
|
Toast.makeText(this, "Sem permissão, o rastreamento não funcionará.", Toast.LENGTH_LONG).show();
|
||||||
Toast.makeText(this, "Permissão de localização negada. O rastreamento não funcionará.", Toast.LENGTH_LONG).show();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@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());
|
||||||
setContentView(binding.getRoot());
|
setContentView(binding.getRoot());
|
||||||
|
|
||||||
|
mAuth = FirebaseAuth.getInstance();
|
||||||
|
|
||||||
|
// Configuração da Navegação (Bottom Bar)
|
||||||
|
setupNavigation();
|
||||||
|
|
||||||
|
// Verifica permissões antes de tudo
|
||||||
|
checkAndRequestPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupNavigation() {
|
||||||
BottomNavigationView navView = binding.navView;
|
BottomNavigationView navView = binding.navView;
|
||||||
// Configurações da barra de navegação
|
|
||||||
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
|
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
|
||||||
R.id.navigation_map, R.id.navigation_zones, R.id.navigation_alerts,
|
R.id.navigation_map, R.id.navigation_zones, R.id.navigation_alerts,
|
||||||
R.id.navigation_history, R.id.navigation_profile)
|
R.id.navigation_history, R.id.navigation_profile)
|
||||||
@@ -66,25 +83,16 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
if (navHostFragment != null) {
|
if (navHostFragment != null) {
|
||||||
NavController navController = navHostFragment.getNavController();
|
NavController navController = navHostFragment.getNavController();
|
||||||
NavigationUI.setupWithNavController(binding.navView, navController);
|
NavigationUI.setupWithNavController(binding.navView, navController);
|
||||||
//teste
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================================
|
|
||||||
// 2. PEDE PERMISSÕES ANTES DE LIGAR O MOTOR
|
|
||||||
// ========================================================
|
|
||||||
checkAndRequestPermissions();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkAndRequestPermissions() {
|
private void checkAndRequestPermissions() {
|
||||||
List<String> permissionsNeeded = new ArrayList<>();
|
List<String> permissionsNeeded = new ArrayList<>();
|
||||||
|
|
||||||
// Verifica a permissão de localização
|
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
||||||
permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
|
||||||
permissionsNeeded.add(Manifest.permission.ACCESS_COARSE_LOCATION);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se for Android 13 ou superior, verifica a permissão de notificações (Obrigatório para Foreground Services)
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
permissionsNeeded.add(Manifest.permission.POST_NOTIFICATIONS);
|
permissionsNeeded.add(Manifest.permission.POST_NOTIFICATIONS);
|
||||||
@@ -92,17 +100,91 @@ public class MainActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!permissionsNeeded.isEmpty()) {
|
if (!permissionsNeeded.isEmpty()) {
|
||||||
// Se faltam permissões, pede-as ao utilizador! (Isto vai mostrar a janelinha)
|
|
||||||
requestPermissionLauncher.launch(permissionsNeeded.toArray(new String[0]));
|
requestPermissionLauncher.launch(permissionsNeeded.toArray(new String[0]));
|
||||||
} else {
|
} else {
|
||||||
// Se já tem todas as permissões, arranca logo com o serviço de localização!
|
decidirInicioDeServico();
|
||||||
startLocationService();
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
// LÓGICA DE DECISÃO: PAI VS FILHO
|
||||||
|
// ========================================================
|
||||||
|
private void decidirInicioDeServico() {
|
||||||
|
FirebaseUser user = mAuth.getCurrentUser();
|
||||||
|
|
||||||
|
if (user != null) {
|
||||||
|
if (user.isAnonymous()) {
|
||||||
|
// É O FILHO: Precisamos de enviar a localização e bateria
|
||||||
|
startLocationService();
|
||||||
|
Log.d("MainActivity", "Modo Filho: Serviço de GPS iniciado.");
|
||||||
|
} else {
|
||||||
|
// É O PAI: Apenas carregamos a interface (o MapFragment tratará do resto)
|
||||||
|
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);
|
||||||
// O ContextCompat resolve automaticamente os problemas de compatibilidade de versões do Android!
|
|
||||||
ContextCompat.startForegroundService(this, serviceIntent);
|
ContextCompat.startForegroundService(this, serviceIntent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,18 +7,198 @@ 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() {
|
||||||
|
// Isolamento de dados: só carregar alertas deste filho
|
||||||
|
String childCode = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE)
|
||||||
|
.getString("child_access_code", null);
|
||||||
|
|
||||||
|
android.util.Log.d("FindU_Debug", "AlertsFragment -> childCode obtido: [" + childCode + "]");
|
||||||
|
|
||||||
|
if (childCode == null) {
|
||||||
|
android.util.Log.e("FindU_Debug", "ERRO FATAL: childCode é nulo no AlertsFragment! Verifique o registo do filho.");
|
||||||
|
if (getContext() != null) {
|
||||||
|
android.widget.Toast.makeText(getContext(), "Erro: Código do filho não encontrado.", android.widget.Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.collection("Alerts")
|
||||||
|
.whereEqualTo("childCode", childCode)
|
||||||
|
.orderBy("timestamp", Query.Direction.DESCENDING)
|
||||||
|
.addSnapshotListener((value, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
// IMPORTANTE: O erro do Firestore contém o link para criar o índice no Logcat
|
||||||
|
android.util.Log.e("FindU_Debug", "Erro na Query: " + error.getMessage());
|
||||||
|
if (getActivity() != null) {
|
||||||
|
android.widget.Toast.makeText(getActivity(), "Erro de Base de Dados. Verifique o Logcat para o link do Índice.", android.widget.Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (value != null) {
|
||||||
|
android.util.Log.d("FirebaseApp", "AlertsFragment -> Documentos recebidos: " + value.size());
|
||||||
|
allAlertsList.clear();
|
||||||
|
for (QueryDocumentSnapshot doc : value) {
|
||||||
|
AlertMessage alert = doc.toObject(AlertMessage.class);
|
||||||
|
allAlertsList.add(alert);
|
||||||
|
}
|
||||||
|
filterAdapterList();
|
||||||
|
updateAlertsSummary();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateAlertsSummary() {
|
||||||
|
if (binding == null) return;
|
||||||
|
|
||||||
|
int pendingCount = 0;
|
||||||
|
AlertMessage latestPending = null;
|
||||||
|
|
||||||
|
for (AlertMessage msg : allAlertsList) {
|
||||||
|
if ("PENDING".equals(msg.getStatus())) {
|
||||||
|
pendingCount++;
|
||||||
|
if (latestPending == null || (msg.getTimestamp() != null && latestPending.getTimestamp() != null && msg.getTimestamp().after(latestPending.getTimestamp()))) {
|
||||||
|
latestPending = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Referências diretas aos NOVOS IDs criados do zero
|
||||||
|
View root = binding.getRoot();
|
||||||
|
com.google.android.material.card.MaterialCardView bannerCard = root.findViewById(R.id.new_alert_banner);
|
||||||
|
android.widget.ImageView iconView = root.findViewById(R.id.new_alert_icon);
|
||||||
|
android.widget.TextView titleView = root.findViewById(R.id.new_alert_title);
|
||||||
|
android.widget.TextView subtitleView = root.findViewById(R.id.new_alert_subtitle);
|
||||||
|
|
||||||
|
if (bannerCard == null || iconView == null || titleView == null || subtitleView == null) {
|
||||||
|
android.util.Log.e("AlertsFragment", "ERRO: Não foi possível encontrar os novos IDs do banner!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bannerCard.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
if (pendingCount > 0) {
|
||||||
|
// --- ESTADO DE ATENÇÃO (Laranja) ---
|
||||||
|
bannerCard.setCardBackgroundColor(android.graphics.Color.parseColor("#FFF3E0"));
|
||||||
|
titleView.setText(pendingCount + " alerta(s) aguardando revisão");
|
||||||
|
titleView.setTextColor(android.graphics.Color.parseColor("#E65100"));
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// --- ESTADO SEGURO (Verde) ---
|
||||||
|
bannerCard.setCardBackgroundColor(android.graphics.Color.parseColor("#E8F5E9"));
|
||||||
|
titleView.setText("Tudo tranquilo");
|
||||||
|
titleView.setTextColor(android.graphics.Color.parseColor("#2E7D32"));
|
||||||
|
subtitleView.setText("Nenhum alerta pendente. Todos os eventos foram revistos.");
|
||||||
|
|
||||||
|
iconView.setImageResource(android.R.drawable.ic_dialog_info);
|
||||||
|
iconView.clearColorFilter();
|
||||||
|
iconView.setColorFilter(android.graphics.Color.parseColor("#4CAF50"), android.graphics.PorterDuff.Mode.SRC_IN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filterAdapterList() {
|
||||||
|
if (currentFilter.equals("ALL")) {
|
||||||
|
adapter.setAlertsList(new ArrayList<>(allAlertsList));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<AlertMessage> filtered = new ArrayList<>();
|
||||||
|
for (AlertMessage msg : allAlertsList) {
|
||||||
|
if (currentFilter.equals(msg.getStatus())) {
|
||||||
|
filtered.add(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.setAlertsList(filtered);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,28 +1,130 @@
|
|||||||
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()));
|
||||||
|
adapter = new HistoryAdapter();
|
||||||
|
rvHistory.setAdapter(adapter);
|
||||||
|
|
||||||
|
loadHistory();
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void loadHistory() {
|
||||||
|
// Isolamento de dados: só carregar histórico deste filho
|
||||||
|
String childCode = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE)
|
||||||
|
.getString("child_access_code", null);
|
||||||
|
|
||||||
|
Log.d("FindU_Debug", "HistoryFragment -> childCode obtido: [" + childCode + "]");
|
||||||
|
|
||||||
|
if (childCode == null) {
|
||||||
|
Log.e("FindU_Debug", "ERRO FATAL: childCode é nulo no HistoryFragment! Verifique o registo do filho.");
|
||||||
|
if (getContext() != null) {
|
||||||
|
android.widget.Toast.makeText(getContext(), "Erro: Código do filho não encontrado.", android.widget.Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.collection("Alerts")
|
||||||
|
.whereEqualTo("childCode", childCode)
|
||||||
|
.orderBy("timestamp", Query.Direction.DESCENDING)
|
||||||
|
.addSnapshotListener((value, error) -> {
|
||||||
|
if (error != null) {
|
||||||
|
// IMPORTANTE: O erro do Firestore contém o link para criar o índice no Logcat
|
||||||
|
Log.e("FindU_Debug", "Erro na Query: " + error.getMessage());
|
||||||
|
if (getActivity() != null) {
|
||||||
|
android.widget.Toast.makeText(getActivity(), "Erro de Base de Dados. Verifique o Logcat para o link do Índice.", android.widget.Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
Log.d("FirebaseApp", "HistoryFragment -> Documentos recebidos: " + value.size());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (targetCal.get(Calendar.YEAR) == todayCal.get(Calendar.YEAR) &&
|
||||||
|
targetCal.get(Calendar.DAY_OF_YEAR) == todayCal.get(Calendar.DAY_OF_YEAR)) {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM", new Locale("pt", "PT"));
|
||||||
|
return "Hoje, " + sdf.format(date);
|
||||||
|
} else if (targetCal.get(Calendar.YEAR) == yesterdayCal.get(Calendar.YEAR) &&
|
||||||
|
targetCal.get(Calendar.DAY_OF_YEAR) == yesterdayCal.get(Calendar.DAY_OF_YEAR)) {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM", new Locale("pt", "PT"));
|
||||||
|
return "Ontem, " + sdf.format(date);
|
||||||
|
} else {
|
||||||
|
SimpleDateFormat sdf = new SimpleDateFormat("dd MMMM yyyy", new Locale("pt", "PT"));
|
||||||
|
// Capitalizar primeira letra no caso do MMMM ser retornado em minúsculo localmente
|
||||||
|
String f = sdf.format(date);
|
||||||
|
return f.substring(0, 1).toUpperCase() + f.substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package com.example.pap_findu.ui.map;
|
package com.example.pap_findu.ui.map;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.ClipData;
|
|
||||||
import android.content.ClipboardManager;
|
|
||||||
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.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -28,6 +30,7 @@ import com.google.android.gms.maps.model.LatLng;
|
|||||||
import com.google.android.gms.maps.model.Marker;
|
import com.google.android.gms.maps.model.Marker;
|
||||||
import com.google.android.gms.maps.model.MarkerOptions;
|
import com.google.android.gms.maps.model.MarkerOptions;
|
||||||
import com.google.android.material.button.MaterialButton;
|
import com.google.android.material.button.MaterialButton;
|
||||||
|
import com.google.android.material.card.MaterialCardView;
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
||||||
import com.google.firebase.auth.FirebaseAuth;
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
import com.google.firebase.auth.FirebaseUser;
|
import com.google.firebase.auth.FirebaseUser;
|
||||||
@@ -36,6 +39,7 @@ import com.google.firebase.database.DatabaseError;
|
|||||||
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.database.ValueEventListener;
|
import com.google.firebase.database.ValueEventListener;
|
||||||
|
import com.google.firebase.firestore.DocumentSnapshot;
|
||||||
import com.google.firebase.firestore.FirebaseFirestore;
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
|
||||||
public class MapFragment extends Fragment implements OnMapReadyCallback {
|
public class MapFragment extends Fragment implements OnMapReadyCallback {
|
||||||
@@ -47,8 +51,19 @@ 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 ---
|
||||||
|
private View cardChildInfo;
|
||||||
|
private TextView txtChildName;
|
||||||
|
private TextView txtChildBattery;
|
||||||
|
|
||||||
|
// --- VARIÁVEIS PARA A ZONA DE STATUS (VERDE/LARANJA) ---
|
||||||
|
private MaterialCardView cardZoneStatus, cardZoneIconBG;
|
||||||
|
private ImageView iconZone;
|
||||||
|
private TextView txtZoneTitle, txtZoneSubtitle;
|
||||||
|
|
||||||
private GoogleMap mMap;
|
private GoogleMap mMap;
|
||||||
private Marker childMarker;
|
private Marker childMarker;
|
||||||
private DatabaseReference locationRef;
|
private DatabaseReference locationRef;
|
||||||
@@ -56,6 +71,21 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
|
|
||||||
private String currentChildId = null;
|
private String currentChildId = null;
|
||||||
|
|
||||||
|
// NOVA VARIÁVEL: Guarda o nome real do filho
|
||||||
|
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
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
@@ -70,8 +100,26 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
btnAbrirChat = root.findViewById(R.id.btnAbrirChat);
|
btnAbrirChat = root.findViewById(R.id.btnAbrirChat);
|
||||||
btnSOS = root.findViewById(R.id.btnSOS);
|
btnSOS = root.findViewById(R.id.btnSOS);
|
||||||
|
|
||||||
|
// --- INICIALIZAR AS VIEWS DO CARTÃO ---
|
||||||
|
cardChildInfo = root.findViewById(R.id.cardChildInfo);
|
||||||
|
txtChildName = root.findViewById(R.id.txtChildName);
|
||||||
|
txtChildBattery = root.findViewById(R.id.txtChildBattery);
|
||||||
|
|
||||||
|
// --- INICIALIZAR AS VIEWS DA ZONA ---
|
||||||
|
cardZoneStatus = root.findViewById(R.id.cardZoneStatus);
|
||||||
|
cardZoneIconBG = root.findViewById(R.id.cardZoneIconBG);
|
||||||
|
iconZone = root.findViewById(R.id.iconZone);
|
||||||
|
txtZoneTitle = root.findViewById(R.id.txtZoneTitle);
|
||||||
|
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);
|
||||||
|
if (cardChildInfo != null) cardChildInfo.setVisibility(View.GONE); // Começa escondido
|
||||||
|
|
||||||
if (btnAddChild != null) {
|
if (btnAddChild != null) {
|
||||||
btnAddChild.setOnClickListener(v -> openAddChildScreen());
|
btnAddChild.setOnClickListener(v -> openAddChildScreen());
|
||||||
@@ -104,8 +152,24 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
.get()
|
.get()
|
||||||
.addOnCompleteListener(task -> {
|
.addOnCompleteListener(task -> {
|
||||||
if (task.isSuccessful() && task.getResult() != null && !task.getResult().isEmpty()) {
|
if (task.isSuccessful() && task.getResult() != null && !task.getResult().isEmpty()) {
|
||||||
currentChildId = task.getResult().getDocuments().get(0).getString("accessCode");
|
|
||||||
|
// Extraímos o documento todo
|
||||||
|
DocumentSnapshot document = task.getResult().getDocuments().get(0);
|
||||||
|
currentChildId = document.getString("accessCode");
|
||||||
|
|
||||||
|
// LER O NOME REAL DA BASE DE DADOS FIRESTORE
|
||||||
|
if (document.contains("name")) {
|
||||||
|
currentChildName = document.getString("name");
|
||||||
|
} else if (document.contains("nome")) {
|
||||||
|
currentChildName = document.getString("nome");
|
||||||
|
} else {
|
||||||
|
currentChildName = "Filho";
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@@ -134,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() {
|
||||||
@@ -150,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();
|
||||||
@@ -166,9 +247,19 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
private void startListeningToChildLocation() {
|
private void startListeningToChildLocation() {
|
||||||
if (currentChildId == null || mMap == null) return;
|
if (currentChildId == null || mMap == null) return;
|
||||||
|
|
||||||
// CORREÇÃO: Caminho direto na raiz como aparece no teu Firebase
|
FirebaseUser user = auth.getCurrentUser();
|
||||||
locationRef = FirebaseDatabase.getInstance().getReference(currentChildId).child("live_location");
|
|
||||||
|
|
||||||
|
// SE FOR O PAI, MOSTRAMOS O CARTÃO DO FILHO
|
||||||
|
if (user != null && !user.isAnonymous()) {
|
||||||
|
if (cardChildInfo != null) cardChildInfo.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
// CORREÇÃO: Agora usa estritamente o nome lido da base de dados (Ex: "Jorge")
|
||||||
|
if (txtChildName != null) {
|
||||||
|
txtChildName.setText(currentChildName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
locationRef = FirebaseDatabase.getInstance().getReference(currentChildId).child("live_location");
|
||||||
if (locationListener != null) locationRef.removeEventListener(locationListener);
|
if (locationListener != null) locationRef.removeEventListener(locationListener);
|
||||||
|
|
||||||
locationListener = new ValueEventListener() {
|
locationListener = new ValueEventListener() {
|
||||||
@@ -178,40 +269,152 @@ public class MapFragment extends Fragment implements OnMapReadyCallback {
|
|||||||
Double lat = snapshot.child("latitude").getValue(Double.class);
|
Double lat = snapshot.child("latitude").getValue(Double.class);
|
||||||
Double lng = snapshot.child("longitude").getValue(Double.class);
|
Double lng = snapshot.child("longitude").getValue(Double.class);
|
||||||
|
|
||||||
|
// LÊ A BATERIA DO FIREBASE
|
||||||
|
String bateria = snapshot.child("bateria").getValue(String.class);
|
||||||
|
|
||||||
|
// ATUALIZA O TEXTO DA BATERIA
|
||||||
|
if (bateria != null && txtChildBattery != null) {
|
||||||
|
txtChildBattery.setText(bateria);
|
||||||
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
// FILTRO: Só atualiza o mapa se a localização NÃO for a da Google HQ (Califórnia)
|
|
||||||
if (Math.abs(lat - 37.4219) > 0.001) {
|
if (Math.abs(lat - 37.4219) > 0.001) {
|
||||||
if (childMarker == null) {
|
if (childMarker == null) {
|
||||||
childMarker = mMap.addMarker(new MarkerOptions()
|
childMarker = mMap.addMarker(new MarkerOptions()
|
||||||
.position(childPos)
|
.position(childPos)
|
||||||
.title("Filho")
|
.title(currentChildName) // O pino no mapa também vai dizer o nome do filho
|
||||||
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
|
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
|
||||||
} else {
|
} else {
|
||||||
childMarker.setPosition(childPos);
|
childMarker.setPosition(childPos);
|
||||||
|
childMarker.setTitle(currentChildName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move a câmara automaticamente para Vila do Conde
|
|
||||||
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(childPos, 17f));
|
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(childPos, 17f));
|
||||||
Log.d("MapFragment", "Pai recebeu localização real: " + lat);
|
|
||||||
} else {
|
android.location.Location childLoc = new android.location.Location("child");
|
||||||
Log.d("MapFragment", "Pai ignorou localização de teste (Califórnia)");
|
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("MapFragment", "Erro ao ler Firebase: " + 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 || 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) ---
|
||||||
|
private void atualizarStatusZona(boolean naZonaSegura, String subtitulo) {
|
||||||
|
if (cardZoneStatus == null) return;
|
||||||
|
|
||||||
|
if (naZonaSegura) {
|
||||||
|
// MODO VERDE: Dentro de Zona Segura
|
||||||
|
cardZoneStatus.setCardBackgroundColor(Color.parseColor("#9DCA43"));
|
||||||
|
cardZoneIconBG.setCardBackgroundColor(Color.parseColor("#EAF4D4"));
|
||||||
|
iconZone.setColorFilter(Color.parseColor("#5F8B1A"));
|
||||||
|
|
||||||
|
txtZoneTitle.setText("Dentro de Zona Segura");
|
||||||
|
txtZoneTitle.setTextColor(Color.parseColor("#2D4608"));
|
||||||
|
txtZoneSubtitle.setText(subtitulo);
|
||||||
|
txtZoneSubtitle.setTextColor(Color.parseColor("#486B11"));
|
||||||
|
} else {
|
||||||
|
// MODO LARANJA: Em Movimento / Fora de Zonas
|
||||||
|
cardZoneStatus.setCardBackgroundColor(Color.parseColor("#FFA726"));
|
||||||
|
cardZoneIconBG.setCardBackgroundColor(Color.parseColor("#FFE0B2"));
|
||||||
|
iconZone.setColorFilter(Color.parseColor("#E65100"));
|
||||||
|
|
||||||
|
txtZoneTitle.setText("A movimentar-se");
|
||||||
|
txtZoneTitle.setTextColor(Color.parseColor("#822B00"));
|
||||||
|
txtZoneSubtitle.setText(subtitulo);
|
||||||
|
txtZoneSubtitle.setTextColor(Color.parseColor("#A63A00"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
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() {
|
||||||
@@ -232,4 +435,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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.example.pap_findu.ui.profile;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Base64;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -13,7 +14,6 @@ import androidx.fragment.app.Fragment;
|
|||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.example.pap_findu.EditProfileActivity;
|
import com.example.pap_findu.EditProfileActivity;
|
||||||
import com.example.pap_findu.R;
|
import com.example.pap_findu.R;
|
||||||
import com.example.pap_findu.SecurityActivity;
|
|
||||||
import com.example.pap_findu.databinding.FragmentProfileBinding;
|
import com.example.pap_findu.databinding.FragmentProfileBinding;
|
||||||
import com.example.pap_findu.models.User;
|
import com.example.pap_findu.models.User;
|
||||||
import com.google.firebase.auth.FirebaseAuth;
|
import com.google.firebase.auth.FirebaseAuth;
|
||||||
@@ -28,12 +28,12 @@ public class ProfileFragment extends Fragment {
|
|||||||
|
|
||||||
private FragmentProfileBinding binding;
|
private FragmentProfileBinding binding;
|
||||||
private FirebaseAuth mAuth;
|
private FirebaseAuth mAuth;
|
||||||
private DatabaseReference mDatabase;
|
|
||||||
private ValueEventListener mUserListener;
|
|
||||||
private DatabaseReference mUserRef;
|
private DatabaseReference mUserRef;
|
||||||
|
private ValueEventListener mUserListener;
|
||||||
|
|
||||||
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
ViewGroup container, Bundle savedInstanceState) {
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
binding = FragmentProfileBinding.inflate(inflater, container, false);
|
binding = FragmentProfileBinding.inflate(inflater, container, false);
|
||||||
View root = binding.getRoot();
|
View root = binding.getRoot();
|
||||||
|
|
||||||
@@ -41,49 +41,68 @@ public class ProfileFragment extends Fragment {
|
|||||||
FirebaseUser currentUser = mAuth.getCurrentUser();
|
FirebaseUser currentUser = mAuth.getCurrentUser();
|
||||||
|
|
||||||
if (currentUser == null) {
|
if (currentUser == null) {
|
||||||
// Should prompt login or handle error
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
mDatabase = FirebaseDatabase.getInstance().getReference("users");
|
|
||||||
mUserRef = mDatabase.child(currentUser.getUid());
|
|
||||||
|
|
||||||
setupListeners();
|
setupListeners();
|
||||||
|
|
||||||
// Listen for user data changes
|
// Verifica se é o Filho (Anónimo)
|
||||||
mUserListener = new ValueEventListener() {
|
if (currentUser.isAnonymous()) {
|
||||||
@Override
|
binding.profileName.setText("Conta de Criança");
|
||||||
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
binding.profileEmail.setText("Modo de Monitorização Ativo");
|
||||||
if (getContext() == null)
|
binding.profileImage.setImageResource(R.drawable.logo);
|
||||||
return;
|
|
||||||
|
|
||||||
User user = snapshot.getValue(User.class);
|
// Oculta opções que não fazem sentido para o Filho
|
||||||
if (user != null) {
|
binding.layoutEditProfile.setVisibility(View.GONE);
|
||||||
binding.profileName.setText(user.getName());
|
|
||||||
binding.profileEmail.setText(user.getEmail());
|
|
||||||
|
|
||||||
if (user.getProfileImageUrl() != null && !user.getProfileImageUrl().isEmpty()) {
|
} else {
|
||||||
Glide.with(ProfileFragment.this)
|
// É O PAI: Carrega os dados reais do Firebase
|
||||||
.load(user.getProfileImageUrl())
|
mUserRef = FirebaseDatabase.getInstance().getReference("users").child(currentUser.getUid());
|
||||||
.placeholder(R.drawable.logo) // Make sure logo exists or use R.mipmap.ic_launcher
|
|
||||||
.into(binding.profileImage);
|
mUserListener = new ValueEventListener() {
|
||||||
} else {
|
@Override
|
||||||
binding.profileImage.setImageResource(R.drawable.logo);
|
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
||||||
|
if (getContext() == null || binding == null) return;
|
||||||
|
|
||||||
|
if (snapshot.exists()) {
|
||||||
|
User user = snapshot.getValue(User.class);
|
||||||
|
if (user != null) {
|
||||||
|
binding.profileName.setText(user.getName());
|
||||||
|
binding.profileEmail.setText(user.getEmail());
|
||||||
|
|
||||||
|
// MAGIA: LER A FOTO EM FORMATO DE TEXTO (BASE64)
|
||||||
|
if (user.getProfileImageUrl() != null && !user.getProfileImageUrl().isEmpty()) {
|
||||||
|
try {
|
||||||
|
byte[] decodedString = Base64.decode(user.getProfileImageUrl(), Base64.DEFAULT);
|
||||||
|
Glide.with(ProfileFragment.this)
|
||||||
|
.load(decodedString)
|
||||||
|
.placeholder(R.drawable.logo)
|
||||||
|
.into(binding.profileImage);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Fallback caso a imagem seja um link antigo e não Base64
|
||||||
|
try {
|
||||||
|
Glide.with(ProfileFragment.this)
|
||||||
|
.load(user.getProfileImageUrl())
|
||||||
|
.placeholder(R.drawable.logo)
|
||||||
|
.into(binding.profileImage);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
binding.profileImage.setImageResource(R.drawable.logo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.profileImage.setImageResource(R.drawable.logo);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Fallback to Auth data if DB is empty
|
|
||||||
binding.profileName.setText(
|
|
||||||
currentUser.getDisplayName() != null ? currentUser.getDisplayName() : "Utilizador");
|
|
||||||
binding.profileEmail.setText(currentUser.getEmail());
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCancelled(@NonNull DatabaseError error) {
|
public void onCancelled(@NonNull DatabaseError error) {
|
||||||
if (getContext() != null)
|
if (getContext() != null)
|
||||||
Toast.makeText(getContext(), "Erro ao carregar perfil", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Erro ao carregar perfil", Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
@@ -93,10 +112,6 @@ public class ProfileFragment extends Fragment {
|
|||||||
startActivity(new Intent(getActivity(), EditProfileActivity.class));
|
startActivity(new Intent(getActivity(), EditProfileActivity.class));
|
||||||
});
|
});
|
||||||
|
|
||||||
binding.layoutSecurity.setOnClickListener(v -> {
|
|
||||||
startActivity(new Intent(getActivity(), SecurityActivity.class));
|
|
||||||
});
|
|
||||||
|
|
||||||
binding.btnLogout.setOnClickListener(v -> {
|
binding.btnLogout.setOnClickListener(v -> {
|
||||||
mAuth.signOut();
|
mAuth.signOut();
|
||||||
Intent intent = new Intent(getActivity(), com.example.pap_findu.login_activity.class);
|
Intent intent = new Intent(getActivity(), com.example.pap_findu.login_activity.class);
|
||||||
@@ -126,4 +141,4 @@ public class ProfileFragment extends Fragment {
|
|||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,50 @@
|
|||||||
package com.example.pap_findu.ui.zones;
|
package com.example.pap_findu.ui.zones;
|
||||||
|
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.GradientDrawable;
|
||||||
|
import android.location.Location;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
import 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.database.DataSnapshot;
|
||||||
|
import com.google.firebase.database.DatabaseError;
|
||||||
|
import com.google.firebase.database.DatabaseReference;
|
||||||
|
import com.google.firebase.database.FirebaseDatabase;
|
||||||
|
import com.google.firebase.database.ValueEventListener;
|
||||||
|
import com.google.firebase.firestore.DocumentSnapshot;
|
||||||
|
import com.google.firebase.firestore.FirebaseFirestore;
|
||||||
|
|
||||||
|
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 String currentChildId = null;
|
||||||
|
private String currentChildName = "Filho";
|
||||||
|
|
||||||
|
private DatabaseReference locationRef;
|
||||||
|
private ValueEventListener locationListener;
|
||||||
|
private com.google.firebase.firestore.ListenerRegistration safeZonesListener;
|
||||||
|
|
||||||
|
private Location childLocation = null;
|
||||||
|
private List<DocumentSnapshot> safeZonesList = new ArrayList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -20,18 +52,171 @@ 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);
|
||||||
|
checkSafetyStatus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
checkUserChildren();
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkUserChildren() {
|
||||||
|
FirebaseUser user = auth.getCurrentUser();
|
||||||
|
if (user == null) return;
|
||||||
|
|
||||||
|
if (user.isAnonymous()) {
|
||||||
|
currentChildId = requireContext().getSharedPreferences("FindU_Prefs", android.content.Context.MODE_PRIVATE)
|
||||||
|
.getString("child_access_code", null);
|
||||||
|
currentChildName = "Você";
|
||||||
|
if (currentChildId != null) {
|
||||||
|
listenToChildLocation();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
db.collection("children")
|
||||||
|
.whereEqualTo("parentId", user.getUid())
|
||||||
|
.limit(1)
|
||||||
|
.get()
|
||||||
|
.addOnCompleteListener(task -> {
|
||||||
|
if (task.isSuccessful() && task.getResult() != null && !task.getResult().isEmpty()) {
|
||||||
|
DocumentSnapshot document = task.getResult().getDocuments().get(0);
|
||||||
|
currentChildId = document.getString("accessCode");
|
||||||
|
|
||||||
|
if (document.contains("name")) {
|
||||||
|
currentChildName = document.getString("name");
|
||||||
|
} else if (document.contains("nome")) {
|
||||||
|
currentChildName = document.getString("nome");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentChildId != null) {
|
||||||
|
listenToChildLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void listenToChildLocation() {
|
||||||
|
locationRef = FirebaseDatabase.getInstance().getReference(currentChildId).child("live_location");
|
||||||
|
locationListener = new ValueEventListener() {
|
||||||
|
@Override
|
||||||
|
public void onDataChange(@NonNull DataSnapshot snapshot) {
|
||||||
|
if (snapshot.exists()) {
|
||||||
|
Double lat = snapshot.child("latitude").getValue(Double.class);
|
||||||
|
Double lng = snapshot.child("longitude").getValue(Double.class);
|
||||||
|
if (lat != null && lng != null) {
|
||||||
|
childLocation = new Location("");
|
||||||
|
childLocation.setLatitude(lat);
|
||||||
|
childLocation.setLongitude(lng);
|
||||||
|
checkSafetyStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancelled(@NonNull DatabaseError error) {}
|
||||||
|
};
|
||||||
|
locationRef.addValueEventListener(locationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkSafetyStatus() {
|
||||||
|
if (childLocation == null || binding == null || safeZonesList == null) return;
|
||||||
|
|
||||||
|
boolean isSafe = false;
|
||||||
|
|
||||||
|
for (DocumentSnapshot zoneDoc : safeZonesList) {
|
||||||
|
Double lat = zoneDoc.getDouble("latitude");
|
||||||
|
Double lng = zoneDoc.getDouble("longitude");
|
||||||
|
if (lat != null && lng != null) {
|
||||||
|
Location zoneLocation = new Location("");
|
||||||
|
zoneLocation.setLatitude(lat);
|
||||||
|
zoneLocation.setLongitude(lng);
|
||||||
|
|
||||||
|
if (childLocation.distanceTo(zoneLocation) <= 200.0) {
|
||||||
|
isSafe = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateUIBanner(isSafe);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateUIBanner(boolean isSafe) {
|
||||||
|
View root = binding.getRoot();
|
||||||
|
LinearLayout bannerLayout = root.findViewById(R.id.bannerZoneLayout);
|
||||||
|
TextView title = root.findViewById(R.id.bannerZoneTitle);
|
||||||
|
TextView subtitle = root.findViewById(R.id.bannerZoneSubtitle);
|
||||||
|
|
||||||
|
if (bannerLayout == null || title == null || subtitle == null) return;
|
||||||
|
|
||||||
|
GradientDrawable background = (GradientDrawable) bannerLayout.getBackground().mutate();
|
||||||
|
|
||||||
|
if (isSafe) {
|
||||||
|
// VERDE -> Seguro
|
||||||
|
background.setColor(Color.parseColor("#EAF4D4")); // Fundo verde clarinho
|
||||||
|
title.setTextColor(Color.parseColor("#0B6635"));
|
||||||
|
subtitle.setTextColor(Color.parseColor("#0B6635"));
|
||||||
|
|
||||||
|
title.setText("O " + currentChildName + " está numa zona segura");
|
||||||
|
subtitle.setText("A rotina tem estado estável.");
|
||||||
|
} else {
|
||||||
|
// LARANJA -> Fora da zona
|
||||||
|
background.setColor(Color.parseColor("#FFE0B2")); // Fundo laranja clarinho
|
||||||
|
title.setTextColor(Color.parseColor("#E65100"));
|
||||||
|
subtitle.setTextColor(Color.parseColor("#E65100"));
|
||||||
|
|
||||||
|
title.setText("Atenção: Fora de zona segura");
|
||||||
|
subtitle.setText("O " + currentChildName + " movimentou-se para fora de contexto.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
if (locationRef != null && locationListener != null) {
|
||||||
|
locationRef.removeEventListener(locationListener);
|
||||||
|
}
|
||||||
|
if (safeZonesListener != null) {
|
||||||
|
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:background="@drawable/edit_text_background"
|
android:orientation="horizontal">
|
||||||
android:hint="Digite o endereço..."
|
|
||||||
android:padding="16dp" />
|
<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:hint="Digite o endereço..."
|
||||||
|
android:imeOptions="actionSearch"
|
||||||
|
android:inputType="textPostalAddress"
|
||||||
|
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"
|
||||||
|
|||||||
@@ -9,28 +9,17 @@
|
|||||||
android:background="#F6F7FB"
|
android:background="#F6F7FB"
|
||||||
tools:context=".EditProfileActivity">
|
tools:context=".EditProfileActivity">
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
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"
|
||||||
@@ -48,7 +37,6 @@
|
|||||||
android:padding="24dp"
|
android:padding="24dp"
|
||||||
android:gravity="center_horizontal">
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
<!-- Profile Image -->
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
@@ -62,32 +50,35 @@
|
|||||||
app:shapeAppearanceOverlay="@style/ShapeAppearance.MaterialComponents.MediumComponent"
|
app:shapeAppearanceOverlay="@style/ShapeAppearance.MaterialComponents.MediumComponent"
|
||||||
android:src="@drawable/logo" />
|
android:src="@drawable/logo" />
|
||||||
|
|
||||||
<ImageView
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
android:layout_width="32dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="32dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:background="@drawable/bg_circle_button"
|
android:layout_marginEnd="-8dp"
|
||||||
android:padding="6dp"
|
android:layout_marginBottom="-8dp"
|
||||||
android:src="@android:drawable/ic_menu_camera"
|
android:src="@android:drawable/ic_menu_camera"
|
||||||
app:tint="#FFFFFF" />
|
app:backgroundTint="#3B82F6"
|
||||||
|
app:fabSize="mini"
|
||||||
|
app:tint="#FFFFFF"
|
||||||
|
android:clickable="false" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/btnChangePhoto"
|
android:id="@+id/btnChangePhoto"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="Alterar Foto"
|
android:text="Alterar Foto"
|
||||||
android:textColor="#3B82F6"
|
android:textColor="#3B82F6"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
|
android:padding="8dp"
|
||||||
android:background="?attr/selectableItemBackground" />
|
android:background="?attr/selectableItemBackground" />
|
||||||
|
|
||||||
<!-- Form -->
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
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:layout_marginTop="24dp"
|
||||||
android:hint="Nome Completo"
|
android:hint="Nome Completo"
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
@@ -102,38 +93,51 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:hint="Email"
|
android:hint="Email (Não editável)"
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/editEmail"
|
android:id="@+id/editEmail"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="textEmailAddress" />
|
android:inputType="textEmailAddress"
|
||||||
|
android:enabled="false"
|
||||||
|
android:textColor="#9CA3AF"/>
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:hint="Telefone (Opcional)"
|
|
||||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/editPhone"
|
android:id="@+id/btnChangePassword"
|
||||||
android:layout_width="match_parent"
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:inputType="phone" />
|
android:layout_height="56dp"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="MUDAR PALAVRA-PASSE"
|
||||||
|
android:textColor="#3B82F6"
|
||||||
|
app:strokeColor="#3B82F6"
|
||||||
|
app:cornerRadius="12dp" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnSaveProfile"
|
android:id="@+id/btnSaveProfile"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:layout_marginTop="40dp"
|
android:layout_marginTop="32dp"
|
||||||
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>
|
||||||
|
|||||||
@@ -39,6 +39,211 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent" />
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/cardChildInfo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="20dp"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
app:cardCornerRadius="24dp"
|
||||||
|
app:cardElevation="8dp"
|
||||||
|
app:cardBackgroundColor="#FFFFFF"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="56dp"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
app:cardCornerRadius="28dp"
|
||||||
|
app:cardBackgroundColor="#F0F4FF"
|
||||||
|
app:cardElevation="0dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:src="@android:drawable/ic_menu_myplaces"
|
||||||
|
app:tint="#A0AABF"/>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginStart="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/txtChildName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Nome do Filho"
|
||||||
|
android:textColor="#1F2937"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginTop="2dp">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:layout_width="10dp"
|
||||||
|
android:layout_height="10dp"
|
||||||
|
app:cardCornerRadius="5dp"
|
||||||
|
app:cardBackgroundColor="#4CAF50"
|
||||||
|
app:cardElevation="0dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Online"
|
||||||
|
android:textColor="#4CAF50"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:layout_marginStart="6dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="end">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:src="@android:drawable/ic_lock_idle_charging"
|
||||||
|
app:tint="#4CAF50"/>
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/txtChildBattery"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="--%"
|
||||||
|
android:textColor="#1F2937"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:layout_marginTop="6dp">
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:src="@android:drawable/ic_menu_mylocation"
|
||||||
|
app:tint="#3B82F6"/>
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="GPS"
|
||||||
|
android:textColor="#1F2937"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginStart="4dp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/cardZoneStatus"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardBackgroundColor="#9DCA43"
|
||||||
|
app:cardElevation="0dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.card.MaterialCardView
|
||||||
|
android:id="@+id/cardZoneIconBG"
|
||||||
|
android:layout_width="36dp"
|
||||||
|
android:layout_height="36dp"
|
||||||
|
app:cardCornerRadius="18dp"
|
||||||
|
app:cardBackgroundColor="#EAF4D4"
|
||||||
|
app:cardElevation="0dp">
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/iconZone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:src="@android:drawable/ic_menu_mylocation"
|
||||||
|
app:tint="#5F8B1A"/>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginStart="12dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/txtZoneTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Dentro de Zona Segura"
|
||||||
|
android:textColor="#2D4608"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/txtZoneSubtitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="A carregar..."
|
||||||
|
android:textColor="#486B11"
|
||||||
|
android:textSize="13sp"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
</LinearLayout>
|
||||||
|
</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"
|
||||||
@@ -49,8 +254,7 @@
|
|||||||
app:tint="@android:color/white"
|
app:tint="@android:color/white"
|
||||||
android:src="@android:drawable/ic_dialog_email"
|
android:src="@android:drawable/ic_dialog_email"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/btnSOS"
|
app:layout_constraintBottom_toTopOf="@+id/btnSOS"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent" />
|
||||||
android:contentDescription="Abrir Chat" />
|
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/btnSOS"
|
android:id="@+id/btnSOS"
|
||||||
|
|||||||
@@ -60,7 +60,6 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<!-- Account Settings Section -->
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -88,7 +87,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<!-- Item 1 -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/layoutEditProfile"
|
android:id="@+id/layoutEditProfile"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -121,47 +119,9 @@
|
|||||||
android:rotation="0" />
|
android:rotation="0" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="#F3F4F6" />
|
|
||||||
|
|
||||||
<!-- Item 2 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layoutSecurity"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:clickable="true"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="24dp"
|
|
||||||
android:layout_height="24dp"
|
|
||||||
android:src="@android:drawable/ic_lock_idle_lock"
|
|
||||||
app:tint="#4B5563" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Segurança e Senha"
|
|
||||||
android:textColor="#1F2937"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_width="20dp"
|
|
||||||
android:layout_height="20dp"
|
|
||||||
android:src="@android:drawable/ic_media_play"
|
|
||||||
app:tint="#9CA3AF"
|
|
||||||
android:rotation="0" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
||||||
<!-- App Settings Section -->
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -189,7 +149,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<!-- Item 1 -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -224,7 +183,6 @@
|
|||||||
android:layout_height="1dp"
|
android:layout_height="1dp"
|
||||||
android:background="#F3F4F6" />
|
android:background="#F3F4F6" />
|
||||||
|
|
||||||
<!-- Item 2 -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -274,10 +232,10 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:text="FindU Inc. © 2024"
|
android:text="FindU Inc. © 2026"
|
||||||
android:textColor="#D1D5DB"
|
android:textColor="#D1D5DB"
|
||||||
android:textSize="12sp" />
|
android:textSize="12sp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
@@ -47,6 +47,7 @@
|
|||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/bannerZoneLayout"
|
||||||
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"
|
||||||
@@ -57,6 +58,7 @@
|
|||||||
android:padding="18dp">
|
android:padding="18dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/bannerZoneTitle"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/zones_summary_title"
|
android:text="@string/zones_summary_title"
|
||||||
@@ -64,6 +66,7 @@
|
|||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/bannerZoneSubtitle"
|
||||||
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"
|
||||||
@@ -88,195 +91,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>
|
||||||
|
|||||||
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