implementei um plugin
This commit is contained in:
@@ -72,4 +72,6 @@ dependencies {
|
|||||||
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.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")
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,15 @@
|
|||||||
|
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
@@ -20,6 +28,12 @@
|
|||||||
android:name="com.google.android.geo.API_KEY"
|
android:name="com.google.android.geo.API_KEY"
|
||||||
android:value="AIzaSyAxen212OKqkfpu1AbWajLGTCTSdRhJWlM" />
|
android:value="AIzaSyAxen212OKqkfpu1AbWajLGTCTSdRhJWlM" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".LocationService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="false"
|
||||||
|
android:foregroundServiceType="location" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".CriarConta"
|
android:name=".CriarConta"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|||||||
122
app/src/main/java/com/example/pap_findu/LocationService.java
Normal file
122
app/src/main/java/com/example/pap_findu/LocationService.java
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package com.example.pap_findu;
|
||||||
|
|
||||||
|
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.os.Build;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Looper;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
import com.google.android.gms.location.FusedLocationProviderClient;
|
||||||
|
import com.google.android.gms.location.LocationCallback;
|
||||||
|
import com.google.android.gms.location.LocationRequest;
|
||||||
|
import com.google.android.gms.location.LocationResult;
|
||||||
|
import com.google.android.gms.location.LocationServices;
|
||||||
|
import com.google.android.gms.location.Priority;
|
||||||
|
import com.google.firebase.database.DatabaseReference;
|
||||||
|
import com.google.firebase.database.FirebaseDatabase;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class LocationService extends Service {
|
||||||
|
|
||||||
|
private FusedLocationProviderClient fusedLocationClient;
|
||||||
|
private LocationCallback locationCallback;
|
||||||
|
private DatabaseReference databaseReference;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
|
||||||
|
// Inicializa o cliente de GPS e a ligação ao Firebase
|
||||||
|
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
|
||||||
|
|
||||||
|
// ATENÇÃO: Substitua "ID_DA_CRIANCA" pelo ID real do utilizador logado no momento
|
||||||
|
databaseReference = FirebaseDatabase.getInstance().getReference("users/ID_DA_CRIANCA/live_location");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||||
|
// 1. Inicia a Notificação Persistente (Obrigatório para serviços em background)
|
||||||
|
createNotificationChannel();
|
||||||
|
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
|
||||||
|
.setContentTitle("FindU Ativo")
|
||||||
|
.setContentText("A monitorizar a localização em tempo real...")
|
||||||
|
// Substitua ic_launcher pelo ícone da sua app (ex: R.drawable.ic_launcher_foreground)
|
||||||
|
.setSmallIcon(R.mipmap.ic_launcher)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
startForeground(1, notification);
|
||||||
|
|
||||||
|
// 2. Inicia o Rastreamento de GPS
|
||||||
|
requestLocationUpdates();
|
||||||
|
|
||||||
|
return START_STICKY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignora o aviso de permissão porque vamos pedir a permissão na Activity ANTES de iniciar este serviço
|
||||||
|
@SuppressWarnings("MissingPermission")
|
||||||
|
private void requestLocationUpdates() {
|
||||||
|
// Configura a frequência do GPS (ex: a cada 10 segundos)
|
||||||
|
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10000)
|
||||||
|
.setMinUpdateDistanceMeters(5.0f) // Só envia se a pessoa se mover 5 metros (poupa bateria)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
locationCallback = new LocationCallback() {
|
||||||
|
@Override
|
||||||
|
public void onLocationResult(@NonNull LocationResult locationResult) {
|
||||||
|
super.onLocationResult(locationResult);
|
||||||
|
for (Location location : locationResult.getLocations()) {
|
||||||
|
|
||||||
|
// 3. Envia os dados atualizados para o Firebase Realtime Database
|
||||||
|
Map<String, Object> locationData = new HashMap<>();
|
||||||
|
locationData.put("latitude", location.getLatitude());
|
||||||
|
locationData.put("longitude", location.getLongitude());
|
||||||
|
locationData.put("last_updated", System.currentTimeMillis());
|
||||||
|
|
||||||
|
databaseReference.setValue(locationData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
// Quando o serviço for desligado, para de usar o GPS para poupar bateria
|
||||||
|
if (fusedLocationClient != null && locationCallback != null) {
|
||||||
|
fusedLocationClient.removeLocationUpdates(locationCallback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cria o Canal de Notificação (obrigatório a partir do Android 8.0)
|
||||||
|
private void createNotificationChannel() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(
|
||||||
|
"LocationChannel",
|
||||||
|
"Monitoramento de Localização",
|
||||||
|
NotificationManager.IMPORTANCE_LOW // Low para não fazer barulho nem vibrar a toda a hora
|
||||||
|
);
|
||||||
|
NotificationManager manager = getSystemService(NotificationManager.class);
|
||||||
|
if (manager != null) {
|
||||||
|
manager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.example.pap_findu;
|
package com.example.pap_findu;
|
||||||
|
|
||||||
|
import android.content.Intent; // <-- ADICIONADO
|
||||||
|
import android.os.Build; // <-- ADICIONADO
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
import com.google.android.material.bottomnavigation.BottomNavigationView;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.navigation.NavController;
|
import androidx.navigation.NavController;
|
||||||
@@ -33,6 +36,20 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================================================
|
||||||
|
// CÓDIGO ADICIONADO: INICIAR O RASTREAMENTO EM BACKGROUND
|
||||||
|
// ========================================================
|
||||||
|
Intent serviceIntent = new Intent(this, LocationService.class);
|
||||||
|
|
||||||
|
// O Android 8.0 (Oreo) ou superior exige o startForegroundService
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
startForegroundService(serviceIntent);
|
||||||
|
} else {
|
||||||
|
startService(serviceIntent);
|
||||||
|
}
|
||||||
|
// ========================================================
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,7 @@ public class MapFragment extends Fragment {
|
|||||||
db = FirebaseFirestore.getInstance();
|
db = FirebaseFirestore.getInstance();
|
||||||
auth = FirebaseAuth.getInstance();
|
auth = FirebaseAuth.getInstance();
|
||||||
|
|
||||||
// Agora o Java vai encontrar estes IDs porque colocamos no XML acima
|
// Encontra os IDs no XML
|
||||||
layoutEmptyState = root.findViewById(R.id.layoutEmptyState);
|
layoutEmptyState = root.findViewById(R.id.layoutEmptyState);
|
||||||
mapContainer = root.findViewById(R.id.mapContainer);
|
mapContainer = root.findViewById(R.id.mapContainer);
|
||||||
btnAddChild = root.findViewById(R.id.btnAddChild);
|
btnAddChild = root.findViewById(R.id.btnAddChild);
|
||||||
@@ -46,7 +46,7 @@ public class MapFragment extends Fragment {
|
|||||||
btnAddChild.setOnClickListener(v -> {
|
btnAddChild.setOnClickListener(v -> {
|
||||||
FirebaseUser user = auth.getCurrentUser();
|
FirebaseUser user = auth.getCurrentUser();
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
openAddChildScreen(); // Agora esta função existe lá embaixo
|
openAddChildScreen();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getContext(), "Erro: Utilizador não autenticado", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Erro: Utilizador não autenticado", Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
@@ -111,27 +111,41 @@ public class MapFragment extends Fragment {
|
|||||||
if (mapContainer != null) mapContainer.setVisibility(View.GONE);
|
if (mapContainer != null) mapContainer.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Esta é a função que estava faltando ou mal fechada
|
|
||||||
private void openAddChildScreen() {
|
private void openAddChildScreen() {
|
||||||
AddChildBottomSheet bottomSheet = new AddChildBottomSheet();
|
AddChildBottomSheet bottomSheet = new AddChildBottomSheet();
|
||||||
|
|
||||||
bottomSheet.setListener(code -> {
|
bottomSheet.setListener(code -> {
|
||||||
|
// Quando o código é gerado, mostramos o diálogo de escolha
|
||||||
showCodeDialog(code);
|
showCodeDialog(code);
|
||||||
});
|
});
|
||||||
|
|
||||||
bottomSheet.show(getParentFragmentManager(), "AddChildSheet");
|
bottomSheet.show(getParentFragmentManager(), "AddChildSheet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- AQUI ESTÁ A MUDANÇA PRINCIPAL ---
|
||||||
private void showCodeDialog(String code) {
|
private void showCodeDialog(String code) {
|
||||||
if (getContext() == null) return;
|
if (getContext() == null) return;
|
||||||
|
|
||||||
|
// 1. Copia o código automaticamente para a área de transferência
|
||||||
|
ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
||||||
|
ClipData clip = ClipData.newPlainText("Código", code);
|
||||||
|
clipboard.setPrimaryClip(clip);
|
||||||
|
Toast.makeText(getContext(), "Código copiado!", Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
|
// 2. Cria o Alerta com as Duas Opções
|
||||||
new AlertDialog.Builder(getContext())
|
new AlertDialog.Builder(getContext())
|
||||||
.setTitle("Perfil Criado!")
|
.setTitle("Filho Adicionado!")
|
||||||
.setMessage("Digite este código no telemóvel do filho:\n\n" + code)
|
.setMessage("O código de acesso é: " + code + "\n\n(Já copiado automaticamente)")
|
||||||
.setPositiveButton("Copiar Código", (dialog, which) -> {
|
.setCancelable(false) // Impede fechar clicando fora
|
||||||
ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE);
|
|
||||||
ClipData clip = ClipData.newPlainText("Código", code);
|
// OPÇÃO 1: Ir para o Mapa (Atualiza a tela)
|
||||||
clipboard.setPrimaryClip(clip);
|
.setPositiveButton("Ir para o Mapa", (dialog, which) -> {
|
||||||
Toast.makeText(getContext(), "Copiado!", Toast.LENGTH_SHORT).show();
|
checkIfHasChildren(); // <--- Esta função recarrega a tela e mostra o mapa
|
||||||
|
})
|
||||||
|
|
||||||
|
// OPÇÃO 2: Adicionar Outro (Fica na mesma tela)
|
||||||
|
.setNegativeButton("Adicionar Outro", (dialog, which) -> {
|
||||||
|
dialog.dismiss(); // Apenas fecha o alerta e continua na tela azul
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="#F3F6FB">
|
android:background="#F3F6FB">
|
||||||
@@ -73,7 +74,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
android:text="Nenhum filho adicionado"
|
android:text="Nenhum membro adicionado"
|
||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/tvNoChildDesc"
|
app:layout_constraintBottom_toTopOf="@+id/tvNoChildDesc"
|
||||||
@@ -87,7 +88,7 @@
|
|||||||
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="Adicione o seu primeiro filho para começar."
|
android:text="Adicione o seu primeiro membro para começar."
|
||||||
app:layout_constraintBottom_toTopOf="@+id/btnAddChild"
|
app:layout_constraintBottom_toTopOf="@+id/btnAddChild"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -97,11 +98,12 @@
|
|||||||
android:id="@+id/btnAddChild"
|
android:id="@+id/btnAddChild"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="60dp"
|
android:layout_height="60dp"
|
||||||
android:text="Adicionar Filho"
|
android:text="Adicionar Membro"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:backgroundTint="#3D6DFF"
|
app:backgroundTint="#3D6DFF"
|
||||||
app:cornerRadius="12dp"
|
app:cornerRadius="12dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent" />
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:layout_editor_absoluteX="24dp" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</androidx.cardview.widget.CardView>
|
</androidx.cardview.widget.CardView>
|
||||||
|
|||||||
@@ -65,11 +65,11 @@
|
|||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="50dp"
|
android:layout_height="50dp"
|
||||||
android:src="@android:drawable/ic_menu_add"
|
android:src="@android:drawable/ic_menu_add"
|
||||||
app:tint="#3D6DFF"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/bgIcon"
|
app:layout_constraintBottom_toBottomOf="@+id/bgIcon"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/bgIcon"
|
app:layout_constraintEnd_toEndOf="@+id/bgIcon"
|
||||||
app:layout_constraintStart_toStartOf="@+id/bgIcon"
|
app:layout_constraintStart_toStartOf="@+id/bgIcon"
|
||||||
app:layout_constraintTop_toTopOf="@+id/bgIcon" />
|
app:layout_constraintTop_toTopOf="@+id/bgIcon"
|
||||||
|
app:tint="#3D6DFF" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvNoChildTitle"
|
android:id="@+id/tvNoChildTitle"
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,4 +1,4 @@
|
|||||||
#Tue Jan 27 16:46:23 WET 2026
|
#Thu Mar 12 10:10:15 WET 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