diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index cf5be69..4077e0f 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -6,15 +6,28 @@
-
-
+
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 74dd639..b2c751a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..c5f3f6b
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "java.configuration.updateBuildConfiguration": "interactive"
+}
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 81ada05..2c341b0 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -1,3 +1,6 @@
+import java.util.Properties
+import java.io.FileInputStream
+
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.google.gms.google.services)
@@ -15,6 +18,16 @@ android {
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
+
+ // Read the API key from local.properties
+ val localProperties = Properties()
+ val localPropertiesFile = rootProject.file("local.properties")
+ if (localPropertiesFile.exists()) {
+ localProperties.load(FileInputStream(localPropertiesFile))
+ }
+ val mapsApiKey = localProperties.getProperty("MAPS_API_KEY") ?: ""
+
+ manifestPlaceholders["MAPS_API_KEY"] = mapsApiKey
}
buildTypes {
@@ -52,7 +65,11 @@ dependencies {
implementation(libs.googleid)
implementation("com.google.android.gms:play-services-maps:18.2.0")
implementation("com.google.android.gms:play-services-location:21.0.1")
+ implementation(libs.firebase.firestore)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
+ 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")
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d750f77..9db9f4a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -15,10 +15,11 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PAP_FindU">
-
+
+ android:value="AIzaSyAxen212OKqkfpu1AbWajLGTCTSdRhJWlM" />
+
@@ -28,24 +29,31 @@
+
-
+
+ android:label="@string/app_name" />
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap_findu/AddZoneActivity.java b/app/src/main/java/com/example/pap_findu/AddZoneActivity.java
new file mode 100644
index 0000000..b963420
--- /dev/null
+++ b/app/src/main/java/com/example/pap_findu/AddZoneActivity.java
@@ -0,0 +1,47 @@
+package com.example.pap_findu;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class AddZoneActivity extends AppCompatActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_add_zone);
+
+ ImageButton btnBack = findViewById(R.id.btnBack);
+ Button btnSave = findViewById(R.id.btnSave);
+ EditText editName = findViewById(R.id.editZoneName);
+ EditText editAddress = findViewById(R.id.editZoneAddress);
+
+ // Check if opened from Map with coordinates
+ if (getIntent().hasExtra("lat") && getIntent().hasExtra("lng")) {
+ double lat = getIntent().getDoubleExtra("lat", 0);
+ double lng = getIntent().getDoubleExtra("lng", 0);
+ editAddress.setText(String.format(java.util.Locale.getDefault(), "Lat: %.5f, Lng: %.5f", lat, lng));
+ }
+
+ btnBack.setOnClickListener(v -> finish());
+
+ btnSave.setOnClickListener(v -> {
+ String name = editName.getText().toString();
+ String address = editAddress.getText().toString();
+
+ if (name.isEmpty() || address.isEmpty()) {
+ Toast.makeText(this, "Preencha todos os campos", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ // Here we would save to Firebase/Database
+ Toast.makeText(this, "Zona '" + name + "' adicionada com sucesso!", Toast.LENGTH_LONG).show();
+ finish();
+ });
+ }
+}
diff --git a/app/src/main/java/com/example/pap_findu/ChatActivity.java b/app/src/main/java/com/example/pap_findu/ChatActivity.java
new file mode 100644
index 0000000..f9555a2
--- /dev/null
+++ b/app/src/main/java/com/example/pap_findu/ChatActivity.java
@@ -0,0 +1,77 @@
+package com.example.pap_findu;
+
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageButton;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.example.pap_findu.adapters.ChatAdapter;
+import com.example.pap_findu.models.ChatMessage;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+
+public class ChatActivity extends AppCompatActivity {
+
+ private RecyclerView recyclerChat;
+ private EditText editChatMessage;
+ private ImageButton btnSend;
+ private ImageButton btnBack;
+ private ChatAdapter adapter;
+ private List messageList;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.chat_activity);
+
+ recyclerChat = findViewById(R.id.recycler_chat);
+ editChatMessage = findViewById(R.id.edit_chat_message);
+ btnSend = findViewById(R.id.btnSend);
+ btnBack = findViewById(R.id.btnBack);
+
+ // Initialize Message List with some dummy data
+ messageList = new ArrayList<>();
+ messageList.add(new ChatMessage("Olá Miguel! Tudo bem?", true, "10:30"));
+ messageList.add(new ChatMessage("Cheguei bem à escola.", false, "10:32"));
+ messageList.add(new ChatMessage("Ainda bem! Qualquer coisa avisa.", true, "10:33"));
+
+ // Setup Adapter
+ adapter = new ChatAdapter(messageList);
+ recyclerChat.setLayoutManager(new LinearLayoutManager(this));
+ recyclerChat.setAdapter(adapter);
+
+ // Scroll to bottom
+ recyclerChat.scrollToPosition(messageList.size() - 1);
+
+ // Send Button Logic
+ btnSend.setOnClickListener(v -> {
+ String text = editChatMessage.getText().toString().trim();
+ if (!TextUtils.isEmpty(text)) {
+ sendMessage(text);
+ }
+ });
+
+ // Back Button Logic
+ btnBack.setOnClickListener(v -> finish());
+ }
+
+ private void sendMessage(String text) {
+ String currentTime = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(new Date());
+ ChatMessage newMessage = new ChatMessage(text, true, currentTime);
+
+ messageList.add(newMessage);
+ adapter.notifyItemInserted(messageList.size() - 1);
+ recyclerChat.scrollToPosition(messageList.size() - 1);
+
+ editChatMessage.setText("");
+ }
+}
diff --git a/app/src/main/java/com/example/pap_findu/CriarConta.java b/app/src/main/java/com/example/pap_findu/CriarConta.java
index e829a1e..3c2b9c1 100644
--- a/app/src/main/java/com/example/pap_findu/CriarConta.java
+++ b/app/src/main/java/com/example/pap_findu/CriarConta.java
@@ -9,6 +9,10 @@ import android.widget.EditText;
import android.widget.TextView;
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.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
@@ -62,27 +66,49 @@ public class CriarConta extends AppCompatActivity {
} 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();
+ 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() {
- @Override
- public void onComplete(@androidx.annotation.NonNull com.google.android.gms.tasks.Task 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 user = mAuth.getCurrentUser();
- // You could save the full name to the database or profile here
- 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();
- }
- }
- });
+ .addOnCompleteListener(CriarConta.this,
+ new com.google.android.gms.tasks.OnCompleteListener() {
+ @Override
+ public void onComplete(
+ @androidx.annotation.NonNull com.google.android.gms.tasks.Task 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();
+ }
+ }
+ });
}
}
});
diff --git a/app/src/main/java/com/example/pap_findu/EditProfileActivity.java b/app/src/main/java/com/example/pap_findu/EditProfileActivity.java
new file mode 100644
index 0000000..35fbd3f
--- /dev/null
+++ b/app/src/main/java/com/example/pap_findu/EditProfileActivity.java
@@ -0,0 +1,201 @@
+package com.example.pap_findu;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.MediaStore;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.bumptech.glide.Glide;
+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.textfield.TextInputEditText;
+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.storage.FirebaseStorage;
+import com.google.firebase.storage.StorageReference;
+import com.google.firebase.storage.UploadTask;
+
+public class EditProfileActivity extends AppCompatActivity {
+
+ private ImageView btnBack;
+ private ImageView editProfileImage;
+ private View btnChangePhoto;
+ private TextInputEditText editName;
+ private TextInputEditText editEmail;
+ private TextInputEditText editPhone;
+ private MaterialButton btnSaveProfile;
+
+ private FirebaseAuth mAuth;
+ private DatabaseReference mDatabase;
+ private StorageReference mStorageRef;
+ private FirebaseUser currentUser;
+ private Uri selectedImageUri;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_edit_profile);
+
+ mAuth = FirebaseAuth.getInstance();
+ currentUser = mAuth.getCurrentUser();
+ mDatabase = FirebaseDatabase.getInstance().getReference("users");
+ mStorageRef = FirebaseStorage.getInstance().getReference("profile_images");
+
+ if (currentUser == null) {
+ finish();
+ return;
+ }
+
+ initViews();
+ loadUserData();
+ setupListeners();
+ }
+
+ private void initViews() {
+ btnBack = findViewById(R.id.btnBack);
+ editProfileImage = findViewById(R.id.editProfileImage);
+ btnChangePhoto = findViewById(R.id.btnChangePhoto);
+ editName = findViewById(R.id.editName);
+ editEmail = findViewById(R.id.editEmail);
+ editPhone = findViewById(R.id.editPhone);
+ btnSaveProfile = findViewById(R.id.btnSaveProfile);
+ }
+
+ private void loadUserData() {
+ mDatabase.child(currentUser.getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
+ @Override
+ public void onDataChange(@NonNull DataSnapshot snapshot) {
+ User user = snapshot.getValue(User.class);
+ if (user != null) {
+ editName.setText(user.getName());
+ editEmail.setText(user.getEmail());
+ editPhone.setText(user.getPhone());
+
+ if (user.getProfileImageUrl() != null && !user.getProfileImageUrl().isEmpty()) {
+ Glide.with(EditProfileActivity.this)
+ .load(user.getProfileImageUrl())
+ .placeholder(R.drawable.logo)
+ .into(editProfileImage);
+ }
+ } else {
+ // Fallback to auth data if DB is empty
+ editName.setText(currentUser.getDisplayName());
+ editEmail.setText(currentUser.getEmail());
+ }
+ }
+
+ @Override
+ public void onCancelled(@NonNull DatabaseError error) {
+ Toast.makeText(EditProfileActivity.this, "Erro ao carregar dados", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ private void setupListeners() {
+ btnBack.setOnClickListener(v -> finish());
+
+ // Image Picker Launcher
+ ActivityResultLauncher imagePickerLauncher = registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ result -> {
+ if (result.getResultCode() == RESULT_OK && result.getData() != null) {
+ selectedImageUri = result.getData().getData();
+ editProfileImage.setImageURI(selectedImageUri);
+ }
+ });
+
+ btnChangePhoto.setOnClickListener(v -> {
+ Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+ imagePickerLauncher.launch(intent);
+ });
+
+ btnSaveProfile.setOnClickListener(v -> saveProfile());
+ }
+
+ private void saveProfile() {
+ String name = editName.getText().toString().trim();
+ String email = editEmail.getText().toString().trim();
+ String phone = editPhone.getText().toString().trim();
+
+ if (name.isEmpty() || email.isEmpty()) {
+ Toast.makeText(this, "Nome e Email são obrigatórios", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ btnSaveProfile.setEnabled(false);
+ btnSaveProfile.setText("Salvando...");
+
+ if (selectedImageUri != null) {
+ uploadImageAndSaveUser(name, email, phone);
+ } else {
+ saveUserToDb(name, email, phone, null);
+ }
+ }
+
+ private void uploadImageAndSaveUser(String name, String email, String phone) {
+ 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() {
+ @Override
+ public void onDataChange(@NonNull DataSnapshot snapshot) {
+ User user = snapshot.getValue(User.class);
+ if (user == null)
+ user = new User();
+
+ user.setName(name);
+ user.setEmail(email);
+ user.setPhone(phone);
+ if (imageUrl != null) {
+ user.setProfileImageUrl(imageUrl);
+ }
+
+ mDatabase.child(currentUser.getUid()).setValue(user)
+ .addOnSuccessListener(aVoid -> {
+ Toast.makeText(EditProfileActivity.this, "Perfil atualizado!", Toast.LENGTH_SHORT).show();
+ finish();
+ })
+ .addOnFailureListener(e -> {
+ Toast.makeText(EditProfileActivity.this, "Erro ao salvar perfil: " + e.getMessage(),
+ Toast.LENGTH_SHORT).show();
+ btnSaveProfile.setEnabled(true);
+ btnSaveProfile.setText("Salvar Alterações");
+ });
+ }
+
+ @Override
+ public void onCancelled(@NonNull DatabaseError error) {
+ btnSaveProfile.setEnabled(true);
+ }
+ });
+ }
+}
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 91c06b2..0734bb4 100644
--- a/app/src/main/java/com/example/pap_findu/MainActivity.java
+++ b/app/src/main/java/com/example/pap_findu/MainActivity.java
@@ -1,18 +1,13 @@
package com.example.pap_findu;
import android.os.Bundle;
-
import com.google.android.material.bottomnavigation.BottomNavigationView;
-
import androidx.appcompat.app.AppCompatActivity;
import androidx.navigation.NavController;
-import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
-
-import com.example.pap_findu.databinding.ActivityMainBinding;
-
import androidx.navigation.fragment.NavHostFragment;
+import com.example.pap_findu.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
@@ -26,6 +21,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(binding.getRoot());
BottomNavigationView navView = binding.navView;
+ // Configurações da barra de navegação
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_map, R.id.navigation_zones, R.id.navigation_alerts,
R.id.navigation_history, R.id.navigation_profile)
@@ -33,8 +29,10 @@ public class MainActivity extends AppCompatActivity {
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment_activity_main);
- NavController navController = navHostFragment.getNavController();
- NavigationUI.setupWithNavController(binding.navView, navController);
- }
+ if (navHostFragment != null) {
+ NavController navController = navHostFragment.getNavController();
+ NavigationUI.setupWithNavController(binding.navView, navController);
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap_findu/SecurityActivity.java b/app/src/main/java/com/example/pap_findu/SecurityActivity.java
new file mode 100644
index 0000000..ebf3de4
--- /dev/null
+++ b/app/src/main/java/com/example/pap_findu/SecurityActivity.java
@@ -0,0 +1,91 @@
+package com.example.pap_findu;
+
+import android.os.Bundle;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.android.material.button.MaterialButton;
+import com.google.android.material.textfield.TextInputEditText;
+import com.google.firebase.auth.AuthCredential;
+import com.google.firebase.auth.EmailAuthProvider;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+
+public class SecurityActivity extends AppCompatActivity {
+
+ private ImageView btnBack;
+ private TextInputEditText inputCurrentPassword;
+ private TextInputEditText inputNewPassword;
+ private TextInputEditText inputConfirmNewPassword;
+ private MaterialButton btnChangePassword;
+
+ private FirebaseAuth mAuth;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_security);
+
+ mAuth = FirebaseAuth.getInstance();
+
+ btnBack = findViewById(R.id.btnBack);
+ inputCurrentPassword = findViewById(R.id.inputCurrentPassword);
+ inputNewPassword = findViewById(R.id.inputNewPassword);
+ inputConfirmNewPassword = findViewById(R.id.inputConfirmNewPassword);
+ btnChangePassword = findViewById(R.id.btnChangePassword);
+
+ btnBack.setOnClickListener(v -> finish());
+ btnChangePassword.setOnClickListener(v -> changePassword());
+ }
+
+ private void changePassword() {
+ String currentPass = inputCurrentPassword.getText().toString();
+ String newPass = inputNewPassword.getText().toString();
+ String confirmPass = inputConfirmNewPassword.getText().toString();
+
+ if (currentPass.isEmpty() || newPass.isEmpty() || confirmPass.isEmpty()) {
+ Toast.makeText(this, "Preencha todos os campos", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (!newPass.equals(confirmPass)) {
+ Toast.makeText(this, "Nova senha e confirmação não coincidem", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ if (newPass.length() < 6) {
+ Toast.makeText(this, "A nova senha deve ter pelo menos 6 caracteres", Toast.LENGTH_SHORT).show();
+ return;
+ }
+
+ FirebaseUser user = mAuth.getCurrentUser();
+ if (user != null && user.getEmail() != null) {
+ btnChangePassword.setEnabled(false);
+ btnChangePassword.setText("Verificando...");
+
+ AuthCredential credential = EmailAuthProvider.getCredential(user.getEmail(), currentPass);
+
+ user.reauthenticate(credential).addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ btnChangePassword.setText("Atualizando...");
+ user.updatePassword(newPass).addOnCompleteListener(task1 -> {
+ if (task1.isSuccessful()) {
+ Toast.makeText(SecurityActivity.this, "Senha atualizada com sucesso!", Toast.LENGTH_SHORT)
+ .show();
+ finish();
+ } else {
+ Toast.makeText(SecurityActivity.this, "Erro ao atualizar senha", Toast.LENGTH_SHORT).show();
+ btnChangePassword.setEnabled(true);
+ }
+ });
+ } else {
+ Toast.makeText(SecurityActivity.this, "Senha atual incorreta", Toast.LENGTH_SHORT).show();
+ btnChangePassword.setEnabled(true);
+ btnChangePassword.setText("Atualizar Senha");
+ }
+ });
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/pap_findu/adapters/ChatAdapter.java b/app/src/main/java/com/example/pap_findu/adapters/ChatAdapter.java
new file mode 100644
index 0000000..7f3d31f
--- /dev/null
+++ b/app/src/main/java/com/example/pap_findu/adapters/ChatAdapter.java
@@ -0,0 +1,93 @@
+package com.example.pap_findu.adapters;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.example.pap_findu.R;
+import com.example.pap_findu.models.ChatMessage;
+
+import java.util.List;
+
+public class ChatAdapter extends RecyclerView.Adapter {
+ private static final int VIEW_TYPE_SENT = 1;
+ private static final int VIEW_TYPE_RECEIVED = 2;
+
+ private List messages;
+
+ public ChatAdapter(List messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ ChatMessage message = messages.get(position);
+ if (message.isSentByMe()) {
+ return VIEW_TYPE_SENT;
+ } else {
+ return VIEW_TYPE_RECEIVED;
+ }
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ if (viewType == VIEW_TYPE_SENT) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_chat_sent, parent, false);
+ return new SentMessageHolder(view);
+ } else {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.item_chat_received, parent, false);
+ return new ReceivedMessageHolder(view);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ ChatMessage message = messages.get(position);
+ if (holder.getItemViewType() == VIEW_TYPE_SENT) {
+ ((SentMessageHolder) holder).bind(message);
+ } else {
+ ((ReceivedMessageHolder) holder).bind(message);
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return messages.size();
+ }
+
+ private static class SentMessageHolder extends RecyclerView.ViewHolder {
+ TextView messageText, timeText;
+
+ SentMessageHolder(View itemView) {
+ super(itemView);
+ messageText = itemView.findViewById(R.id.text_message_body);
+ timeText = itemView.findViewById(R.id.text_message_time);
+ }
+
+ void bind(ChatMessage message) {
+ messageText.setText(message.getMessage());
+ timeText.setText(message.getTimestamp());
+ }
+ }
+
+ private static class ReceivedMessageHolder extends RecyclerView.ViewHolder {
+ TextView messageText, timeText;
+
+ ReceivedMessageHolder(View itemView) {
+ super(itemView);
+ messageText = itemView.findViewById(R.id.text_message_body);
+ timeText = itemView.findViewById(R.id.text_message_time);
+ }
+
+ void bind(ChatMessage message) {
+ messageText.setText(message.getMessage());
+ timeText.setText(message.getTimestamp());
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/pap_findu/login_activity.java b/app/src/main/java/com/example/pap_findu/login_activity.java
index 1a2326f..114cfaf 100644
--- a/app/src/main/java/com/example/pap_findu/login_activity.java
+++ b/app/src/main/java/com/example/pap_findu/login_activity.java
@@ -2,6 +2,8 @@ package com.example.pap_findu;
import android.content.Intent;
import android.os.Bundle;
+import android.text.InputType;
+import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
@@ -9,38 +11,63 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.Task;
+import com.google.android.material.button.MaterialButton;
+import com.google.firebase.Timestamp;
+import com.google.firebase.auth.AuthResult;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.firestore.DocumentSnapshot;
+import com.google.firebase.firestore.FirebaseFirestore;
+
+import java.util.Date;
+
public class login_activity extends AppCompatActivity {
private EditText emailEditText;
private EditText passwordEditText;
private Button btnLogin;
private TextView criarContaTextView;
- private com.google.firebase.auth.FirebaseAuth mAuth;
+
+ // --- NOVAS VARIÁVEIS PARA O FILHO ---
+ private MaterialButton btnChildLogin;
+ private FirebaseFirestore db;
+ private FirebaseAuth mAuth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.login_activity);
+
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
- // Initialize Firebase Auth
- mAuth = com.google.firebase.auth.FirebaseAuth.getInstance();
+ // Inicializar Firebase Auth e Firestore
+ mAuth = FirebaseAuth.getInstance();
+ db = FirebaseFirestore.getInstance();
+ // Vincular componentes
emailEditText = findViewById(R.id.emailEditText);
passwordEditText = findViewById(R.id.passwordEditText);
btnLogin = findViewById(R.id.btnLogin);
criarContaTextView = findViewById(R.id.criarContaTextView);
+ // Novo botão do filho (Certifique-se que adicionou no XML com id btnChildLogin)
+ btnChildLogin = findViewById(R.id.btnChildLogin);
+
+ // --- LÓGICA 1: LOGIN DO PAI (Seu código original) ---
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -53,27 +80,29 @@ public class login_activity extends AppCompatActivity {
}
mAuth.signInWithEmailAndPassword(email, password)
- .addOnCompleteListener(login_activity.this, new com.google.android.gms.tasks.OnCompleteListener() {
+ .addOnCompleteListener(login_activity.this, new OnCompleteListener() {
@Override
- public void onComplete(@androidx.annotation.NonNull com.google.android.gms.tasks.Task task) {
+ public void onComplete(@NonNull Task task) {
if (task.isSuccessful()) {
- // Sign in success, update UI with the signed-in user's information
- com.google.firebase.auth.FirebaseUser user = mAuth.getCurrentUser();
- Toast.makeText(login_activity.this, "Login com sucesso.",
- Toast.LENGTH_SHORT).show();
- Intent intent = new Intent(login_activity.this, MainActivity.class);
- startActivity(intent);
- finish();
+ Toast.makeText(login_activity.this, "Login com sucesso.", Toast.LENGTH_SHORT).show();
+ goToMainActivity();
} else {
- // If sign in fails, display a message to the user.
- Toast.makeText(login_activity.this, "Falha na autenticação.",
- Toast.LENGTH_SHORT).show();
+ Toast.makeText(login_activity.this, "Falha na autenticação.", Toast.LENGTH_SHORT).show();
}
}
});
}
});
+ // --- LÓGICA 2: LOGIN DO FILHO (Novo código) ---
+ btnChildLogin.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showChildLoginDialog();
+ }
+ });
+
+ // --- LÓGICA 3: CRIAR CONTA ---
criarContaTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -86,12 +115,92 @@ public class login_activity extends AppCompatActivity {
@Override
public void onStart() {
super.onStart();
- // Check if user is signed in (non-null) and update UI accordingly.
- com.google.firebase.auth.FirebaseUser currentUser = mAuth.getCurrentUser();
+ // Verifica se já está logado
+ FirebaseUser currentUser = mAuth.getCurrentUser();
if(currentUser != null){
- Intent intent = new Intent(login_activity.this, MainActivity.class);
- startActivity(intent);
- finish();
+ goToMainActivity();
}
}
-}
+
+ private void goToMainActivity() {
+ Intent intent = new Intent(login_activity.this, MainActivity.class);
+ // Limpa a pilha para não voltar ao login com o botão voltar
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ finish();
+ }
+
+ // ==========================================================
+ // MÉTODOS DE LOGIN DO FILHO
+ // ==========================================================
+
+ private void showChildLoginDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle("Acesso do Filho");
+ builder.setMessage("Digite o código de 6 dígitos gerado pelo pai:");
+
+ // Cria uma caixa de texto dentro do alerta
+ final EditText inputCode = new EditText(this);
+ inputCode.setInputType(InputType.TYPE_CLASS_NUMBER);
+ inputCode.setHint("Ex: 123456");
+ builder.setView(inputCode);
+
+ // Botão Entrar do Alerta
+ builder.setPositiveButton("Entrar", (dialog, which) -> {
+ String code = inputCode.getText().toString().trim();
+ if (!TextUtils.isEmpty(code)) {
+ verifyChildCode(code);
+ } else {
+ Toast.makeText(login_activity.this, "Código vazio.", Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ builder.setNegativeButton("Cancelar", (dialog, which) -> dialog.cancel());
+ builder.show();
+ }
+
+ private void verifyChildCode(String code) {
+ // Verifica no Firestore se o código existe
+ db.collection("login_codes").document(code).get()
+ .addOnSuccessListener(document -> {
+ if (document.exists()) {
+ boolean used = Boolean.TRUE.equals(document.getBoolean("used"));
+ Timestamp expiresAt = document.getTimestamp("expiresAt");
+
+ // Validações
+ if (used) {
+ Toast.makeText(this, "Este código já foi usado.", Toast.LENGTH_LONG).show();
+ return;
+ }
+ if (expiresAt != null && expiresAt.toDate().before(new Date())) {
+ Toast.makeText(this, "O código expirou.", Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ // SUCESSO: O código é bom!
+ String parentId = document.getString("parentId");
+ loginChildAnonymously(code, parentId);
+
+ } else {
+ Toast.makeText(this, "Código inválido.", Toast.LENGTH_SHORT).show();
+ }
+ })
+ .addOnFailureListener(e -> Toast.makeText(this, "Erro de conexão.", Toast.LENGTH_SHORT).show());
+ }
+
+ private void loginChildAnonymously(String code, String parentId) {
+ // Faz login anônimo (sem email)
+ mAuth.signInAnonymously()
+ .addOnCompleteListener(this, task -> {
+ if (task.isSuccessful()) {
+ // 1. Invalida o código para ninguém usar de novo
+ db.collection("login_codes").document(code).update("used", true);
+
+ Toast.makeText(this, "Conectado como Filho!", Toast.LENGTH_SHORT).show();
+ goToMainActivity();
+ } else {
+ Toast.makeText(this, "Erro no login anônimo.", Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap_findu/models/ChatMessage.java b/app/src/main/java/com/example/pap_findu/models/ChatMessage.java
new file mode 100644
index 0000000..724a799
--- /dev/null
+++ b/app/src/main/java/com/example/pap_findu/models/ChatMessage.java
@@ -0,0 +1,25 @@
+package com.example.pap_findu.models;
+
+public class ChatMessage {
+ private String message;
+ private boolean isSentByMe;
+ private String timestamp;
+
+ public ChatMessage(String message, boolean isSentByMe, String timestamp) {
+ this.message = message;
+ this.isSentByMe = isSentByMe;
+ this.timestamp = timestamp;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public boolean isSentByMe() {
+ return isSentByMe;
+ }
+
+ public String getTimestamp() {
+ return timestamp;
+ }
+}
diff --git a/app/src/main/java/com/example/pap_findu/models/User.java b/app/src/main/java/com/example/pap_findu/models/User.java
new file mode 100644
index 0000000..68bd357
--- /dev/null
+++ b/app/src/main/java/com/example/pap_findu/models/User.java
@@ -0,0 +1,49 @@
+package com.example.pap_findu.models;
+
+public class User {
+ private String name;
+ private String email;
+ private String profileImageUrl;
+ private String phone;
+
+ public User() {
+ // Default constructor required for calls to DataSnapshot.getValue(User.class)
+ }
+
+ public User(String name, String email) {
+ this.name = name;
+ this.email = email;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getProfileImageUrl() {
+ return profileImageUrl;
+ }
+
+ public void setProfileImageUrl(String profileImageUrl) {
+ this.profileImageUrl = profileImageUrl;
+ }
+
+ public String getPhone() {
+ return phone;
+ }
+
+ public void setPhone(String phone) {
+ this.phone = phone;
+ }
+}
diff --git a/app/src/main/java/com/example/pap_findu/ui/home/HomeFragment.java b/app/src/main/java/com/example/pap_findu/ui/home/HomeFragment.java
new file mode 100644
index 0000000..cbde57b
--- /dev/null
+++ b/app/src/main/java/com/example/pap_findu/ui/home/HomeFragment.java
@@ -0,0 +1,84 @@
+package com.example.pap_findu.ui.home;
+
+import android.app.AlertDialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
+import com.example.pap_findu.R;
+import com.google.firebase.Timestamp;
+import com.google.firebase.firestore.FirebaseFirestore;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class HomeFragment extends Fragment {
+
+ private Button btnAddChild;
+ private FirebaseFirestore db;
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
+
+ // 1. AQUI É O SEGREDO: Ele vai carregar o design do 'fragment_home.xml'
+ // Se o seu arquivo XML com o design azul tiver outro nome, mude aqui.
+ View root = inflater.inflate(R.layout.fragment_home, container, false);
+
+ // 2. Inicializar o Firebase e o Botão
+ db = FirebaseFirestore.getInstance();
+ btnAddChild = root.findViewById(R.id.btnAddChild);
+
+ // 3. Ação do Botão
+ if (btnAddChild != null) {
+ btnAddChild.setOnClickListener(v -> {
+ // Substitua pelo ID real do pai logado depois
+ String parentId = "ID_DO_PAI_TESTE";
+ generateCode(parentId);
+ });
+ }
+
+ return root;
+ }
+
+ private void generateCode(String parentId) {
+ String code = String.valueOf((int)(Math.random() * 900000) + 100000);
+ Timestamp expiresAt = new Timestamp(new Date(System.currentTimeMillis() + 10 * 60 * 1000));
+
+ Map codeMap = new HashMap<>();
+ codeMap.put("parentId", parentId);
+ codeMap.put("expiresAt", expiresAt);
+ codeMap.put("used", false);
+
+ db.collection("login_codes").document(code)
+ .set(codeMap)
+ .addOnSuccessListener(aVoid -> showCodeDialog(code))
+ .addOnFailureListener(e -> Toast.makeText(getContext(), "Erro ao criar código", Toast.LENGTH_SHORT).show());
+ }
+
+ private void showCodeDialog(String code) {
+ if (getContext() == null) return;
+
+ new AlertDialog.Builder(getContext())
+ .setTitle("Adicionar Novo Filho")
+ .setMessage("Código de acesso: " + code)
+ .setPositiveButton("Copiar", (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();
+ })
+ .setNegativeButton("Fechar", null)
+ .show();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap_findu/ui/map/AddChildBottomSheet.java b/app/src/main/java/com/example/pap_findu/ui/map/AddChildBottomSheet.java
new file mode 100644
index 0000000..e99c7f5
--- /dev/null
+++ b/app/src/main/java/com/example/pap_findu/ui/map/AddChildBottomSheet.java
@@ -0,0 +1,122 @@
+package com.example.pap_findu.ui.map;
+
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.example.pap_findu.R;
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
+import com.google.android.material.textfield.TextInputEditText;
+import com.google.firebase.Timestamp;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.FirebaseFirestore;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+public class AddChildBottomSheet extends BottomSheetDialogFragment {
+
+ private TextInputEditText etChildName;
+ private Button btnSaveChild;
+ private FirebaseFirestore db;
+ private FirebaseAuth auth;
+
+ // Interface para avisar o MapFragment que o código foi gerado
+ public interface OnChildCreatedListener {
+ void onCodeGenerated(String code);
+ }
+
+ private OnChildCreatedListener listener;
+
+ public void setListener(OnChildCreatedListener listener) {
+ this.listener = listener;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+
+ // ⚠️ IMPORTANTE: O nome aqui deve ser igual ao do seu arquivo XML
+ // Se o seu arquivo XML se chama 'bottom_add_child.xml', mude abaixo para R.layout.bottom_add_child
+ View view = inflater.inflate(R.layout.bottom_sheet_add_child, container, false);
+
+ db = FirebaseFirestore.getInstance();
+ auth = FirebaseAuth.getInstance();
+
+ // Vincula os campos do XML
+ // Certifique-se que no seu XML os IDs são: @+id/etChildName e @+id/btnSaveChild
+ etChildName = view.findViewById(R.id.etChildName);
+ btnSaveChild = view.findViewById(R.id.btnSaveChild);
+
+ if (btnSaveChild != null) {
+ btnSaveChild.setOnClickListener(v -> saveChildData());
+ }
+
+ return view;
+ }
+
+ private void saveChildData() {
+ if (etChildName == null) return;
+
+ String name = etChildName.getText().toString().trim();
+
+ if (TextUtils.isEmpty(name)) {
+ etChildName.setError("Nome é obrigatório");
+ return;
+ }
+
+ if (auth.getCurrentUser() == null) return;
+ String parentId = auth.getCurrentUser().getUid();
+
+ // 1. Gera código aleatório de 6 dígitos
+ String code = String.valueOf((int)(Math.random() * 900000) + 100000);
+ Timestamp createdAt = new Timestamp(new Date());
+
+ // 2. Prepara os dados do filho
+ Map childMap = new HashMap<>();
+ childMap.put("name", name);
+ childMap.put("parentId", parentId);
+ childMap.put("createdAt", createdAt);
+ childMap.put("linked", false);
+ childMap.put("accessCode", code);
+
+ // 3. Salva na coleção 'children'
+ db.collection("children")
+ .add(childMap)
+ .addOnSuccessListener(documentReference -> {
+ // 4. Se salvou o filho, agora salva o código de login
+ saveLoginCode(parentId, code);
+ })
+ .addOnFailureListener(e -> {
+ Toast.makeText(getContext(), "Erro ao salvar", Toast.LENGTH_SHORT).show();
+ });
+ }
+
+ private void saveLoginCode(String parentId, String code) {
+ // Código válido por 10 minutos
+ Timestamp expiresAt = new Timestamp(new Date(System.currentTimeMillis() + 10 * 60 * 1000));
+
+ Map codeMap = new HashMap<>();
+ codeMap.put("parentId", parentId);
+ codeMap.put("expiresAt", expiresAt);
+ codeMap.put("used", false); // Importante para ser usado apenas uma vez
+
+ db.collection("login_codes").document(code)
+ .set(codeMap)
+ .addOnSuccessListener(aVoid -> {
+ // Sucesso! Avisa a tela anterior e fecha a janela
+ if (listener != null) {
+ listener.onCodeGenerated(code);
+ }
+ dismiss();
+ });
+ }
+}
\ 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 2e6ba39..cd8fd37 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
@@ -1,105 +1,138 @@
package com.example.pap_findu.ui.map;
+import android.app.AlertDialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import com.example.pap_findu.R;
-import com.example.pap_findu.databinding.FragmentMapBinding;
+import com.google.android.gms.maps.SupportMapFragment;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.QuerySnapshot;
public class MapFragment extends Fragment {
- private FragmentMapBinding binding;
- private com.google.android.gms.maps.GoogleMap mMap;
+ private Button btnAddChild;
+ private View layoutEmptyState;
+ private View mapContainer;
+ private FirebaseFirestore db;
+ private FirebaseAuth auth;
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- binding = FragmentMapBinding.inflate(inflater, container, false);
- View root = binding.getRoot();
+ public View onCreateView(@NonNull LayoutInflater inflater,
+ ViewGroup container, Bundle savedInstanceState) {
- // Initialize Map
- com.google.android.gms.maps.SupportMapFragment mapFragment = (com.google.android.gms.maps.SupportMapFragment) getChildFragmentManager()
- .findFragmentById(R.id.map);
- if (mapFragment != null) {
- mapFragment.getMapAsync(googleMap -> {
- mMap = googleMap;
- // Check permissions and enable My Location
- if (androidx.core.content.ContextCompat.checkSelfPermission(requireContext(),
- android.Manifest.permission.ACCESS_FINE_LOCATION) == android.content.pm.PackageManager.PERMISSION_GRANTED) {
- mMap.setMyLocationEnabled(true);
+ View root = inflater.inflate(R.layout.fragment_map, container, false);
- // Move camera to finding User or Default
- com.google.android.gms.location.LocationServices.getFusedLocationProviderClient(requireActivity())
- .getLastLocation()
- .addOnSuccessListener(location -> {
- if (location != null) {
- com.google.android.gms.maps.model.LatLng current = new com.google.android.gms.maps.model.LatLng(
- location.getLatitude(), location.getLongitude());
- mMap.moveCamera(com.google.android.gms.maps.CameraUpdateFactory
- .newLatLngZoom(current, 15f));
- } else {
- // Default to Escola Profissional de Vila do Conde
- com.google.android.gms.maps.model.LatLng school = new com.google.android.gms.maps.model.LatLng(
- 41.3536, -8.7424);
- mMap.moveCamera(
- com.google.android.gms.maps.CameraUpdateFactory.newLatLngZoom(school, 15f));
- }
- });
+ db = FirebaseFirestore.getInstance();
+ auth = FirebaseAuth.getInstance();
+
+ // Agora o Java vai encontrar estes IDs porque colocamos no XML acima
+ layoutEmptyState = root.findViewById(R.id.layoutEmptyState);
+ mapContainer = root.findViewById(R.id.mapContainer);
+ btnAddChild = root.findViewById(R.id.btnAddChild);
+
+ if (btnAddChild != null) {
+ btnAddChild.setOnClickListener(v -> {
+ FirebaseUser user = auth.getCurrentUser();
+ if (user != null) {
+ openAddChildScreen(); // Agora esta função existe lá embaixo
} else {
- // Request permissions (Simplified for brevity, usually should use
- // RequestPermissionLauncher)
- requestPermissions(new String[] { android.Manifest.permission.ACCESS_FINE_LOCATION }, 100);
+ Toast.makeText(getContext(), "Erro: Utilizador não autenticado", Toast.LENGTH_SHORT).show();
}
-
- mMap.setOnMapLongClickListener(latLng -> {
- android.content.Intent intent = new android.content.Intent(getContext(),
- com.example.pap_findu.AddZoneActivity.class);
- intent.putExtra("lat", latLng.latitude);
- intent.putExtra("lng", latLng.longitude);
- startActivity(intent);
- });
});
}
- // Implement SOS Button logic
- binding.sosButton.setOnClickListener(v -> {
- android.content.Intent intent = new android.content.Intent(android.content.Intent.ACTION_DIAL);
- intent.setData(android.net.Uri.parse("tel:112"));
- startActivity(intent);
- });
-
- // Add dummy listeners for other interactive elements
- binding.zoomInButton.setOnClickListener(v -> {
- // Placeholder: Zoom In logic would go here if we had a real map
- android.widget.Toast.makeText(getContext(), "Zoom In", android.widget.Toast.LENGTH_SHORT).show();
- });
-
- binding.zoomOutButton.setOnClickListener(v -> {
- // Placeholder: Zoom Out logic
- android.widget.Toast.makeText(getContext(), "Zoom Out", android.widget.Toast.LENGTH_SHORT).show();
- });
-
- binding.navigationFab.setOnClickListener(v -> {
- android.widget.Toast.makeText(getContext(), "Centrar Localização", android.widget.Toast.LENGTH_SHORT)
- .show();
- });
-
- binding.messagesFab.setOnClickListener(v -> {
- android.content.Intent intent = new android.content.Intent(getContext(),
- com.example.pap_findu.ChatActivity.class);
- startActivity(intent);
- });
+ checkUserTypeAndShowScreen();
return root;
}
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- binding = null;
+ private void checkUserTypeAndShowScreen() {
+ FirebaseUser user = auth.getCurrentUser();
+ if (user == null) return;
+
+ if (user.isAnonymous()) {
+ if (btnAddChild != null) btnAddChild.setVisibility(View.GONE);
+ showMapState();
+ return;
+ }
+
+ checkIfHasChildren();
}
-}
+
+ private void checkIfHasChildren() {
+ FirebaseUser user = auth.getCurrentUser();
+ if (user == null) return;
+
+ db.collection("children")
+ .whereEqualTo("parentId", user.getUid())
+ .limit(1)
+ .get()
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ QuerySnapshot snapshot = task.getResult();
+ if (snapshot != null && !snapshot.isEmpty()) {
+ showMapState();
+ } else {
+ showEmptyState();
+ }
+ } else {
+ showEmptyState();
+ }
+ });
+ }
+
+ private void showMapState() {
+ if (layoutEmptyState != null) layoutEmptyState.setVisibility(View.GONE);
+ if (mapContainer != null) mapContainer.setVisibility(View.VISIBLE);
+
+ Fragment existingMap = getChildFragmentManager().findFragmentById(R.id.mapContainer);
+ if (existingMap == null) {
+ SupportMapFragment googleMapFragment = SupportMapFragment.newInstance();
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.mapContainer, googleMapFragment)
+ .commit();
+ }
+ }
+
+ private void showEmptyState() {
+ if (layoutEmptyState != null) layoutEmptyState.setVisibility(View.VISIBLE);
+ 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 -> {
+ showCodeDialog(code);
+ });
+
+ bottomSheet.show(getParentFragmentManager(), "AddChildSheet");
+ }
+
+ private void showCodeDialog(String code) {
+ if (getContext() == null) return;
+ 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();
+ })
+ .show();
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/example/pap_findu/ui/profile/ProfileFragment.java b/app/src/main/java/com/example/pap_findu/ui/profile/ProfileFragment.java
index dd49df7..8e417f1 100644
--- a/app/src/main/java/com/example/pap_findu/ui/profile/ProfileFragment.java
+++ b/app/src/main/java/com/example/pap_findu/ui/profile/ProfileFragment.java
@@ -1,41 +1,126 @@
package com.example.pap_findu.ui.profile;
+import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.TextView;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
-import androidx.lifecycle.ViewModelProvider;
+import com.bumptech.glide.Glide;
+import com.example.pap_findu.EditProfileActivity;
+import com.example.pap_findu.R;
+import com.example.pap_findu.SecurityActivity;
import com.example.pap_findu.databinding.FragmentProfileBinding;
+import com.example.pap_findu.models.User;
+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;
public class ProfileFragment extends Fragment {
private FragmentProfileBinding binding;
+ private FirebaseAuth mAuth;
+ private DatabaseReference mDatabase;
+ private ValueEventListener mUserListener;
+ private DatabaseReference mUserRef;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
binding = FragmentProfileBinding.inflate(inflater, container, false);
View root = binding.getRoot();
- binding.btnLogout.setOnClickListener(v -> {
- // Sign out of Firebase
- com.google.firebase.auth.FirebaseAuth.getInstance().signOut();
+ mAuth = FirebaseAuth.getInstance();
+ FirebaseUser currentUser = mAuth.getCurrentUser();
- // Navigate back to Login Activity
- android.content.Intent intent = new android.content.Intent(getActivity(),
- com.example.pap_findu.login_activity.class);
- intent.setFlags(
- android.content.Intent.FLAG_ACTIVITY_NEW_TASK | android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivity(intent);
- });
+ if (currentUser == null) {
+ // Should prompt login or handle error
+ return root;
+ }
+
+ mDatabase = FirebaseDatabase.getInstance().getReference("users");
+ mUserRef = mDatabase.child(currentUser.getUid());
+
+ setupListeners();
+
+ // Listen for user data changes
+ mUserListener = new ValueEventListener() {
+ @Override
+ public void onDataChange(@NonNull DataSnapshot snapshot) {
+ if (getContext() == null)
+ return;
+
+ User user = snapshot.getValue(User.class);
+ if (user != null) {
+ binding.profileName.setText(user.getName());
+ binding.profileEmail.setText(user.getEmail());
+
+ if (user.getProfileImageUrl() != null && !user.getProfileImageUrl().isEmpty()) {
+ Glide.with(ProfileFragment.this)
+ .load(user.getProfileImageUrl())
+ .placeholder(R.drawable.logo) // Make sure logo exists or use R.mipmap.ic_launcher
+ .into(binding.profileImage);
+ } 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
+ public void onCancelled(@NonNull DatabaseError error) {
+ if (getContext() != null)
+ Toast.makeText(getContext(), "Erro ao carregar perfil", Toast.LENGTH_SHORT).show();
+ }
+ };
return root;
}
+ private void setupListeners() {
+ binding.layoutEditProfile.setOnClickListener(v -> {
+ startActivity(new Intent(getActivity(), EditProfileActivity.class));
+ });
+
+ binding.layoutSecurity.setOnClickListener(v -> {
+ startActivity(new Intent(getActivity(), SecurityActivity.class));
+ });
+
+ binding.btnLogout.setOnClickListener(v -> {
+ mAuth.signOut();
+ Intent intent = new Intent(getActivity(), com.example.pap_findu.login_activity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ });
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ if (mUserRef != null && mUserListener != null) {
+ mUserRef.addValueEventListener(mUserListener);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (mUserRef != null && mUserListener != null) {
+ mUserRef.removeEventListener(mUserListener);
+ }
+ }
+
@Override
public void onDestroyView() {
super.onDestroyView();
diff --git a/app/src/main/res/drawable/bg_chat_received.xml b/app/src/main/res/drawable/bg_chat_received.xml
new file mode 100644
index 0000000..bfd10d3
--- /dev/null
+++ b/app/src/main/res/drawable/bg_chat_received.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_chat_sent.xml b/app/src/main/res/drawable/bg_chat_sent.xml
new file mode 100644
index 0000000..b347779
--- /dev/null
+++ b/app/src/main/res/drawable/bg_chat_sent.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_circle_button.xml b/app/src/main/res/drawable/bg_circle_button.xml
new file mode 100644
index 0000000..942db29
--- /dev/null
+++ b/app/src/main/res/drawable/bg_circle_button.xml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/app/src/main/res/drawable/bg_circle_light_blue.xml b/app/src/main/res/drawable/bg_circle_light_blue.xml
new file mode 100644
index 0000000..ef48895
--- /dev/null
+++ b/app/src/main/res/drawable/bg_circle_light_blue.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/bg_header_rounded.xml b/app/src/main/res/drawable/bg_header_rounded.xml
new file mode 100644
index 0000000..f86cfaf
--- /dev/null
+++ b/app/src/main/res/drawable/bg_header_rounded.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml
new file mode 100644
index 0000000..b9aa461
--- /dev/null
+++ b/app/src/main/res/drawable/ic_add.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml
new file mode 100644
index 0000000..4f91ba3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_add_zone.xml b/app/src/main/res/layout/activity_add_zone.xml
new file mode 100644
index 0000000..db80951
--- /dev/null
+++ b/app/src/main/res/layout/activity_add_zone.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_edit_profile.xml b/app/src/main/res/layout/activity_edit_profile.xml
new file mode 100644
index 0000000..de5a895
--- /dev/null
+++ b/app/src/main/res/layout/activity_edit_profile.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 0e4cefc..b35c652 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,12 +1,10 @@
-
-
+ android:background="#F3F6FB">
+ app:layout_constraintTop_toTopOf="parent" />
+ app:layout_constraintStart_toStartOf="parent" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_security.xml b/app/src/main/res/layout/activity_security.xml
new file mode 100644
index 0000000..ed495d4
--- /dev/null
+++ b/app/src/main/res/layout/activity_security.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/bottom_sheet_add_child.xml b/app/src/main/res/layout/bottom_sheet_add_child.xml
new file mode 100644
index 0000000..8f7bf7d
--- /dev/null
+++ b/app/src/main/res/layout/bottom_sheet_add_child.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/chat_activity.xml b/app/src/main/res/layout/chat_activity.xml
new file mode 100644
index 0000000..af788d3
--- /dev/null
+++ b/app/src/main/res/layout/chat_activity.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_alerts.xml b/app/src/main/res/layout/fragment_alerts.xml
index 9f4d75e..56bbede 100644
--- a/app/src/main/res/layout/fragment_alerts.xml
+++ b/app/src/main/res/layout/fragment_alerts.xml
@@ -5,7 +5,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F6F7FB"
- android:paddingBottom="96dp"
tools:context=".ui.alerts.AlertsFragment">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_map.xml b/app/src/main/res/layout/fragment_map.xml
index 269fa45..e140842 100644
--- a/app/src/main/res/layout/fragment_map.xml
+++ b/app/src/main/res/layout/fragment_map.xml
@@ -1,457 +1,132 @@
-
+
+ android:background="#F3F6FB">
-
+
-
+ android:layout_height="160dp"
+ android:background="@drawable/bg_header_rounded"
+ app:layout_constraintTop_toTopOf="parent" />
-
+
+
+
+
+ android:layout_height="match_parent"
+ android:padding="24dp">
+ android:id="@+id/bgIcon"
+ android:layout_width="100dp"
+ android:layout_height="100dp"
+ android:background="@drawable/bg_circle_light_blue"
+ app:layout_constraintBottom_toTopOf="@+id/tvNoChildTitle"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_chainStyle="packed" />
-
+
-
-
-
-
-
-
-
-
-
+ android:layout_marginTop="24dp"
+ android:text="Nenhum filho adicionado"
+ android:textSize="18sp"
+ android:textStyle="bold"
+ app:layout_constraintBottom_toTopOf="@+id/tvNoChildDesc"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/bgIcon" />
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="60dp"
+ android:text="Adicionar Filho"
+ android:textStyle="bold"
+ app:backgroundTint="#3D6DFF"
+ app:cornerRadius="12dp"
+ app:layout_constraintBottom_toBottomOf="parent" />
-
+
+
-
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent"
+ android:visibility="gone" />
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml
index 082826c..05b5cad 100644
--- a/app/src/main/res/layout/fragment_profile.xml
+++ b/app/src/main/res/layout/fragment_profile.xml
@@ -5,7 +5,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F6F7FB"
- android:paddingBottom="96dp"
tools:context=".ui.profile.ProfileFragment">
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_chat_sent.xml b/app/src/main/res/layout/item_chat_sent.xml
new file mode 100644
index 0000000..c05502c
--- /dev/null
+++ b/app/src/main/res/layout/item_chat_sent.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_activity.xml b/app/src/main/res/layout/login_activity.xml
index 8bc6822..88fcc73 100644
--- a/app/src/main/res/layout/login_activity.xml
+++ b/app/src/main/res/layout/login_activity.xml
@@ -95,6 +95,48 @@
android:textSize="16sp"
app:cornerRadius="12dp" />
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
index e49d78e..d31cfab 100644
--- a/app/src/main/res/values-night/themes.xml
+++ b/app/src/main/res/values-night/themes.xml
@@ -1,6 +1,6 @@
-