diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2c341b0..18aa2a5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -72,4 +72,6 @@ dependencies { implementation("com.github.bumptech.glide:glide: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") } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9db9f4a..28adb82 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,7 +4,15 @@ + + + + + + + + + + diff --git a/app/src/main/java/com/example/pap_findu/LocationService.java b/app/src/main/java/com/example/pap_findu/LocationService.java new file mode 100644 index 0000000..7e35245 --- /dev/null +++ b/app/src/main/java/com/example/pap_findu/LocationService.java @@ -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 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); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap_findu/MainActivity.java b/app/src/main/java/com/example/pap_findu/MainActivity.java index 0734bb4..50f9811 100644 --- a/app/src/main/java/com/example/pap_findu/MainActivity.java +++ b/app/src/main/java/com/example/pap_findu/MainActivity.java @@ -1,6 +1,9 @@ package com.example.pap_findu; +import android.content.Intent; // <-- ADICIONADO +import android.os.Build; // <-- ADICIONADO import android.os.Bundle; + import com.google.android.material.bottomnavigation.BottomNavigationView; import androidx.appcompat.app.AppCompatActivity; import androidx.navigation.NavController; @@ -33,6 +36,20 @@ public class MainActivity extends AppCompatActivity { if (navHostFragment != null) { NavController navController = navHostFragment.getNavController(); 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); + } + // ======================================================== } } \ No newline at end of file diff --git a/app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java b/app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java index cd8fd37..4f05072 100644 --- a/app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java +++ b/app/src/main/java/com/example/pap_findu/ui/map/MapFragment.java @@ -37,7 +37,7 @@ public class MapFragment extends Fragment { db = FirebaseFirestore.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); mapContainer = root.findViewById(R.id.mapContainer); btnAddChild = root.findViewById(R.id.btnAddChild); @@ -46,7 +46,7 @@ public class MapFragment extends Fragment { btnAddChild.setOnClickListener(v -> { FirebaseUser user = auth.getCurrentUser(); if (user != null) { - openAddChildScreen(); // Agora esta função existe lá embaixo + openAddChildScreen(); } else { 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); } - // Esta é a função que estava faltando ou mal fechada private void openAddChildScreen() { AddChildBottomSheet bottomSheet = new AddChildBottomSheet(); bottomSheet.setListener(code -> { + // Quando o código é gerado, mostramos o diálogo de escolha showCodeDialog(code); }); bottomSheet.show(getParentFragmentManager(), "AddChildSheet"); } + // --- AQUI ESTÁ A MUDANÇA PRINCIPAL --- private void showCodeDialog(String code) { 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()) - .setTitle("Perfil Criado!") - .setMessage("Digite este código no telemóvel do filho:\n\n" + code) - .setPositiveButton("Copiar Código", (dialog, which) -> { - ClipboardManager clipboard = (ClipboardManager) requireContext().getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("Código", code); - clipboard.setPrimaryClip(clip); - Toast.makeText(getContext(), "Copiado!", Toast.LENGTH_SHORT).show(); + .setTitle("Filho Adicionado!") + .setMessage("O código de acesso é: " + code + "\n\n(Já copiado automaticamente)") + .setCancelable(false) // Impede fechar clicando fora + + // OPÇÃO 1: Ir para o Mapa (Atualiza a tela) + .setPositiveButton("Ir para o Mapa", (dialog, which) -> { + 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(); } diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 6d264cb..522750f 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -2,6 +2,7 @@ @@ -73,7 +74,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="24dp" - android:text="Nenhum filho adicionado" + android:text="Nenhum membro adicionado" android:textSize="18sp" android:textStyle="bold" app:layout_constraintBottom_toTopOf="@+id/tvNoChildDesc" @@ -87,7 +88,7 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" 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_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -97,11 +98,12 @@ android:id="@+id/btnAddChild" android:layout_width="match_parent" android:layout_height="60dp" - android:text="Adicionar Filho" + android:text="Adicionar Membro" android:textStyle="bold" app:backgroundTint="#3D6DFF" app:cornerRadius="12dp" - app:layout_constraintBottom_toBottomOf="parent" /> + app:layout_constraintBottom_toBottomOf="parent" + tools:layout_editor_absoluteX="24dp" /> diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml index e140842..75bfdd8 100644 --- a/app/src/main/res/layout/fragment_map.xml +++ b/app/src/main/res/layout/fragment_map.xml @@ -65,11 +65,11 @@ android:layout_width="50dp" android:layout_height="50dp" android:src="@android:drawable/ic_menu_add" - app:tint="#3D6DFF" app:layout_constraintBottom_toBottomOf="@+id/bgIcon" app:layout_constraintEnd_toEndOf="@+id/bgIcon" app:layout_constraintStart_toStartOf="@+id/bgIcon" - app:layout_constraintTop_toTopOf="@+id/bgIcon" /> + app:layout_constraintTop_toTopOf="@+id/bgIcon" + app:tint="#3D6DFF" />