Compare commits

...

10 Commits

Author SHA1 Message Date
86da99ee13 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
2026-03-17 17:01:31 +00:00
d6e9320b80 a localizaçao esta funcionando + ou - certo 2026-03-13 16:57:14 +00:00
86221e5179 a localizaçao esta funcionando + ou - certo 2026-03-12 17:16:29 +00:00
fba454ce01 Merge remote-tracking branch 'origin/main' 2026-03-12 16:03:15 +00:00
d51fb4e580 a localizaçao esta funcionando + ou - certo 2026-03-12 16:02:59 +00:00
4055051755 a localizaçao esta funcionando + ou - certo 2026-03-12 16:00:54 +00:00
aab826f651 Merge remote-tracking branch 'origin/main' 2026-03-12 10:40:43 +00:00
46076d43d4 implementei um plugin 2026-03-12 10:40:37 +00:00
b4861b2ad1 teste 2026-03-10 15:49:13 +00:00
878d7aaa2c ja da login cria conta e vai para o menu principal. 2026-02-03 17:16:56 +00:00
46 changed files with 2875 additions and 694 deletions

View File

@@ -6,15 +6,28 @@
<option name="selectionMode" value="DROPDOWN" />
</SelectionState>
<SelectionState runConfigName="login_activity">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-01-07T08:41:59.573594Z">
<option name="selectionMode" value="DIALOG" />
<DropdownSelection timestamp="2026-03-17T14:22:15.472961Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Medium_Phone.avd" />
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Pixel_7.avd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
<DialogSelection>
<targets>
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Pixel_7.avd" />
</handle>
</Target>
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="LocalEmulator" identifier="path=/Users/230408/.android/avd/Pixel_7_filho.avd" />
</handle>
</Target>
</targets>
</DialogSelection>
</SelectionState>
</selectionStates>
</component>

1
.idea/misc.xml generated
View File

@@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">

3
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"java.configuration.updateBuildConfiguration": "interactive"
}

View File

@@ -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,13 @@ 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")
implementation("com.google.android.gms:play-services-location:21.0.1")
implementation("com.google.firebase:firebase-database-ktx:20.3.0")
}

View File

@@ -4,7 +4,22 @@
<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_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<queries>
<package android:name="com.google.android.gms" />
<intent>
<action android:name="com.google.android.geo.API_KEY" />
</intent>
</queries>
<application
android:allowBackup="true"
@@ -14,38 +29,61 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.PAP_FindU">
android:theme="@style/Theme.PAP_FindU"
tools:targetApi="34">
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="YOUR_API_KEY_HERE" />
android:value="AIzaSyAxen212OKqkfpu1AbWajLGTCTSdRhJWlM" />
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<service
android:name=".LocationService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
<activity
android:name=".CriarConta"
android:exported="false" />
<activity
android:name=".Recuperar_Passe"
android:exported="false" />
<activity
android:name=".ChatActivity"
android:exported="false" />
<activity
android:name=".login_activity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".AddZoneActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name">
android:label="@string/app_name" />
<activity
android:name=".EditProfileActivity"
android:exported="false" />
<activity
android:name=".SecurityActivity"
android:exported="false" />
</activity>
</application>
</manifest>

View File

@@ -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();
});
}
}

View File

@@ -0,0 +1,136 @@
package com.example.pap_findu;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.Toast;
import androidx.annotation.NonNull;
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 com.google.firebase.auth.FirebaseAuth;
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 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<ChatMessage> messageList;
private DatabaseReference chatRef;
private String currentUserId;
private String accessCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chat_activity);
// 1. Inicializar Firebase e User
currentUserId = FirebaseAuth.getInstance().getUid();
// Recuperamos o código da "sala" (partilhado entre pai e filho)
accessCode = getSharedPreferences("FindU_Prefs", MODE_PRIVATE)
.getString("child_access_code", null);
if (accessCode == null) {
Toast.makeText(this, "Erro: Sala de chat não encontrada", Toast.LENGTH_SHORT).show();
finish();
return;
}
// Caminho no Firebase: chats / 123456 / messages
chatRef = FirebaseDatabase.getInstance().getReference("chats")
.child(accessCode)
.child("messages");
// 2. Ligar Componentes do Teu Layout
recyclerChat = findViewById(R.id.recycler_chat);
editChatMessage = findViewById(R.id.edit_chat_message);
btnSend = findViewById(R.id.btnSend);
btnBack = findViewById(R.id.btnBack);
// 3. Configurar RecyclerView e Adapter
messageList = new ArrayList<>();
adapter = new ChatAdapter(messageList);
recyclerChat.setLayoutManager(new LinearLayoutManager(this));
recyclerChat.setAdapter(adapter);
// 4. Lógica do Botão Enviar
btnSend.setOnClickListener(v -> {
String text = editChatMessage.getText().toString().trim();
if (!TextUtils.isEmpty(text)) {
sendMessageToFirebase(text);
}
});
// 5. Lógica do Botão Voltar
btnBack.setOnClickListener(v -> finish());
// 6. Começar a ouvir mensagens em tempo real
listenForMessages();
}
private void sendMessageToFirebase(String text) {
String currentTime = new SimpleDateFormat("HH:mm", Locale.getDefault()).format(new Date());
// Criamos a mensagem com o ID de quem envia
ChatMessage newMessage = new ChatMessage(text, currentUserId, currentTime);
// "Empurramos" para o Firebase (Gera um ID único automático)
chatRef.push().setValue(newMessage)
.addOnSuccessListener(aVoid -> {
editChatMessage.setText(""); // Limpa o campo se correu bem
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao enviar", Toast.LENGTH_SHORT).show();
});
}
private void listenForMessages() {
chatRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
messageList.clear();
for (DataSnapshot data : snapshot.getChildren()) {
ChatMessage msg = data.getValue(ChatMessage.class);
if (msg != null) {
// Se o senderId for o MEU, o adapter desenha à direita (azul)
msg.setSentByMe(msg.getSenderId().equals(currentUserId));
messageList.add(msg);
}
}
adapter.notifyDataSetChanged();
// Faz scroll automático para a última mensagem
if (messageList.size() > 0) {
recyclerChat.scrollToPosition(messageList.size() - 1);
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Log.e("ChatActivity", "Erro no Firebase: " + error.getMessage());
}
});
}
}

View File

@@ -2,7 +2,6 @@ package com.example.pap_findu;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
@@ -10,34 +9,48 @@ import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
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 {
private EditText inputFullName;
private EditText emailEditText;
private EditText passwordEditText;
private EditText inputConfirmPassword;
private EditText inputFullName, emailEditText, passwordEditText, inputConfirmPassword;
private CheckBox checkTerms;
private Button btnCreateAccount;
private TextView loginLink;
private com.google.firebase.auth.FirebaseAuth mAuth;
private FirebaseAuth mAuth;
private DatabaseReference mDatabase;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_criar_conta);
// Ajuste de Padding para EdgeToEdge
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 views
// 1. Inicializar Firebase
mAuth = FirebaseAuth.getInstance();
mDatabase = FirebaseDatabase.getInstance().getReference("users");
// 2. Inicializar Views
inputFullName = findViewById(R.id.inputFullName);
emailEditText = findViewById(R.id.emailEditText);
passwordEditText = findViewById(R.id.passwordEditText);
@@ -46,56 +59,69 @@ public class CriarConta extends AppCompatActivity {
btnCreateAccount = findViewById(R.id.btnCreateAccount);
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
btnCreateAccount.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String fullName = inputFullName.getText().toString();
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
String confirmPassword = inputConfirmPassword.getText().toString();
// 4. Link para Login
loginLink.setOnClickListener(v -> {
startActivity(new Intent(CriarConta.this, login_activity.class));
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(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 {
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(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) {
.addOnCompleteListener(this, 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
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 {
// 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();
}
Toast.makeText(CriarConta.this, "Erro ao salvar perfil no banco de dados.", 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();
}
});
}
}

View File

@@ -0,0 +1,238 @@
package com.example.pap_findu;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Base64;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
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.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 java.io.ByteArrayOutputStream;
public class EditProfileActivity extends AppCompatActivity {
private ImageView btnBack;
private ImageView editProfileImage;
private TextView btnChangePhoto;
private TextInputEditText editName;
private TextInputEditText editEmail;
// Botoes
private MaterialButton btnSaveProfile;
private MaterialButton btnChangePassword;
private FirebaseAuth mAuth;
private DatabaseReference mDatabase;
private FirebaseUser currentUser;
// NOVO: Variável para guardar o texto gigante da imagem (Base64) em vez do Uri
private String base64ImageString = null;
@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");
// O StorageReference foi removido pois já não precisamos dele!
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);
btnSaveProfile = findViewById(R.id.btnSaveProfile);
btnChangePassword = findViewById(R.id.btnChangePassword);
}
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());
if (user.getProfileImageUrl() != null && !user.getProfileImageUrl().isEmpty()) {
try {
// Tenta descodificar a imagem de formato Base64 (Texto para Imagem)
byte[] decodedString = Base64.decode(user.getProfileImageUrl(), Base64.DEFAULT);
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 {
// 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());
// 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
ActivityResultLauncher<Intent> imagePickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
Uri selectedImageUri = result.getData().getData();
editProfileImage.setImageURI(selectedImageUri);
// NOVO: Faz a conversão mágica da imagem para texto!
converterImagemParaTexto(selectedImageUri);
}
});
btnChangePhoto.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
imagePickerLauncher.launch(intent);
});
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() {
String name = editName.getText().toString().trim();
String email = editEmail.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("A Salvar...");
// Guardamos o perfil passando a String Base64 (se houver), em vez de fazer upload para o Storage
saveUserToDb(name, email, base64ImageString);
}
private void saveUserToDb(String name, String email, String imageUrlBase64) {
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);
// Se houver uma imagem convertida em Base64, guardamos na base de dados
if (imageUrlBase64 != null) {
user.setProfileImageUrl(imageUrlBase64);
}
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);
}
});
}
}

View File

@@ -0,0 +1,152 @@
package com.example.pap_findu;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.location.Location;
import android.os.BatteryManager;
import android.os.Build;
import android.os.IBinder;
import android.os.Looper;
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.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
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;
@Override
public void onCreate() {
super.onCreate();
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
String childCode = getSharedPreferences("FindU_Prefs", MODE_PRIVATE)
.getString("child_access_code", null);
if (childCode != null) {
// Caminho direto na raiz para o monitoramento em tempo real
databaseReference = FirebaseDatabase.getInstance().getReference(childCode).child("live_location");
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
createNotificationChannel();
Notification notification = new NotificationCompat.Builder(this, "LocationChannel")
.setContentTitle("FindU Ativo")
.setContentText("A partilhar localização e bateria em tempo real...")
.setSmallIcon(R.mipmap.ic_launcher)
.setPriority(NotificationCompat.PRIORITY_LOW)
.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION);
} else {
startForeground(1, notification);
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
requestLocationUpdates();
}
return START_STICKY;
}
private void requestLocationUpdates() {
LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 5000)
.setMinUpdateDistanceMeters(0)
.build();
locationCallback = new LocationCallback() {
@Override
public void onLocationResult(@NonNull LocationResult locationResult) {
for (Location location : locationResult.getLocations()) {
// Filtro para ignorar a localização padrão do emulador (Califórnia)
if (Math.abs(location.getLatitude() - 37.4219) > 0.001) {
updateFirebase(location);
}
}
}
};
try {
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, Looper.getMainLooper());
} catch (SecurityException e) {
Log.e("LocationService", "Erro de permissão GPS");
}
}
private void updateFirebase(Location location) {
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<>();
data.put("latitude", location.getLatitude());
data.put("longitude", location.getLongitude());
data.put("bateria", level + "%"); // Envia ex: "85%"
data.put("last_updated", System.currentTimeMillis());
databaseReference.setValue(data).addOnFailureListener(e -> {
Log.e("LocationService", "Erro ao enviar para Firebase: " + e.getMessage());
});
}
}
@Override
public void onDestroy() {
if (fusedLocationClient != null && locationCallback != null) {
fusedLocationClient.removeLocationUpdates(locationCallback);
}
super.onDestroy();
}
@Nullable @Override public IBinder onBind(Intent intent) { return null; }
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"LocationChannel",
"Monitoramento",
NotificationManager.IMPORTANCE_LOW
);
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) manager.createNotificationChannel(channel);
}
}
}

View File

@@ -1,22 +1,45 @@
package com.example.pap_findu;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.pap_findu.databinding.ActivityMainBinding;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import androidx.navigation.fragment.NavHostFragment;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private FirebaseAuth mAuth;
private final ActivityResultLauncher<String[]> requestPermissionLauncher =
registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), permissions -> {
Boolean fineLocationGranted = permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
if (fineLocationGranted != null && fineLocationGranted) {
decidirInicioDeServico();
} else {
Toast.makeText(this, "Sem permissão, o rastreamento não funcionará.", Toast.LENGTH_LONG).show();
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -25,6 +48,16 @@ public class MainActivity extends AppCompatActivity {
binding = ActivityMainBinding.inflate(getLayoutInflater());
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;
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_map, R.id.navigation_zones, R.id.navigation_alerts,
@@ -33,8 +66,53 @@ public class MainActivity extends AppCompatActivity {
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment_activity_main);
if (navHostFragment != null) {
NavController navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(binding.navView, navController);
}
}
private void checkAndRequestPermissions() {
List<String> permissionsNeeded = new ArrayList<>();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.POST_NOTIFICATIONS);
}
}
if (!permissionsNeeded.isEmpty()) {
requestPermissionLauncher.launch(permissionsNeeded.toArray(new String[0]));
} else {
decidirInicioDeServico();
}
}
// ========================================================
// 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.");
}
}
}
private void startLocationService() {
Intent serviceIntent = new Intent(this, LocationService.class);
ContextCompat.startForegroundService(this, serviceIntent);
}
}

View File

@@ -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");
}
});
}
}
}

View File

@@ -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<RecyclerView.ViewHolder> {
private static final int VIEW_TYPE_SENT = 1;
private static final int VIEW_TYPE_RECEIVED = 2;
private List<ChatMessage> messages;
public ChatAdapter(List<ChatMessage> 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());
}
}
}

View File

@@ -1,7 +1,10 @@
package com.example.pap_findu;
import android.content.Intent;
import android.content.SharedPreferences;
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,41 +12,59 @@ 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.FirebaseFirestore;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
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;
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();
mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance();
emailEditText = findViewById(R.id.emailEditText);
passwordEditText = findViewById(R.id.passwordEditText);
btnLogin = findViewById(R.id.btnLogin);
criarContaTextView = findViewById(R.id.criarContaTextView);
btnChildLogin = findViewById(R.id.btnChildLogin);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
btnLogin.setOnClickListener(v -> {
String email = emailEditText.getText().toString();
String password = passwordEditText.getText().toString();
@@ -53,45 +74,121 @@ public class login_activity extends AppCompatActivity {
}
mAuth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(login_activity.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) {
.addOnCompleteListener(login_activity.this, 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();
}
});
}
});
criarContaTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
btnChildLogin.setOnClickListener(v -> showChildLoginDialog());
criarContaTextView.setOnClickListener(v -> {
Intent intent = new Intent(login_activity.this, CriarConta.class);
startActivity(intent);
}
});
}
@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();
FirebaseUser currentUser = mAuth.getCurrentUser();
if(currentUser != null){
goToMainActivity();
}
}
private void goToMainActivity() {
Intent intent = new Intent(login_activity.this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
finish();
}
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:");
final EditText inputCode = new EditText(this);
inputCode.setInputType(InputType.TYPE_CLASS_NUMBER);
inputCode.setHint("Ex: 123456");
builder.setView(inputCode);
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) {
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");
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");
String childName = document.getString("childName");
// =======================================================
// ASSOCIAÇÃO AUTOMÁTICA: Cria o registo na coleção 'children'
// =======================================================
Map<String, Object> childData = new HashMap<>();
childData.put("parentId", parentId);
childData.put("accessCode", code);
childData.put("name", childName != null ? childName : "Filho");
childData.put("createdAt", new Timestamp(new Date()));
db.collection("children").document(code).set(childData)
.addOnSuccessListener(aVoid -> loginChildAnonymously(code, parentId))
.addOnFailureListener(e -> Toast.makeText(this, "Erro ao associar filho.", Toast.LENGTH_SHORT).show());
} 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) {
mAuth.signInAnonymously()
.addOnCompleteListener(this, task -> {
if (task.isSuccessful()) {
// Invalida o código no Firestore
db.collection("login_codes").document(code).update("used", true);
// Guarda o código para o LocationService usar
getSharedPreferences("FindU_Prefs", MODE_PRIVATE)
.edit()
.putString("child_access_code", code)
.apply();
Toast.makeText(this, "Conectado como Filho!", Toast.LENGTH_SHORT).show();
goToMainActivity();
} else {
Toast.makeText(this, "Erro no login anônimo.", Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@@ -0,0 +1,54 @@
package com.example.pap_findu.models;
public class ChatMessage {
private String message;
private String senderId;
private String timestamp;
private boolean isSentByMe;
// 1. Construtor Vazio (OBRIGATÓRIO para o Firebase conseguir ler os dados)
public ChatMessage() {
}
// 2. Construtor para enviar mensagens (usado na ChatActivity)
public ChatMessage(String message, String senderId, String timestamp) {
this.message = message;
this.senderId = senderId;
this.timestamp = timestamp;
}
// --- Getters e Setters ---
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getSenderId() {
return senderId;
}
public void setSenderId(String senderId) {
this.senderId = senderId;
}
public String getTimestamp() {
return timestamp;
}
public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}
public boolean isSentByMe() {
return isSentByMe;
}
// Este setter é usado na ChatActivity dentro do loop listenForMessages
public void setSentByMe(boolean sentByMe) {
isSentByMe = sentByMe;
}
}

View File

@@ -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;
}
}

View File

@@ -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<String, Object> 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();
}
}

View File

@@ -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<String, Object> 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<String, Object> 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();
});
}
}

View File

@@ -1,105 +1,318 @@
package com.example.pap_findu.ui.map;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.example.pap_findu.ChatActivity;
import com.example.pap_findu.R;
import com.example.pap_findu.databinding.FragmentMapBinding;
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.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
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;
public class MapFragment extends Fragment {
public class MapFragment extends Fragment implements OnMapReadyCallback {
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();
private FloatingActionButton btnAbrirChat;
private MaterialButton btnSOS;
// 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);
// --- VARIÁVEIS PARA O CARTÃO DE INFORMAÇÃO PREMIUM ---
private View cardChildInfo;
private TextView txtChildName;
private TextView txtChildBattery;
// 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));
}
});
} else {
// Request permissions (Simplified for brevity, usually should use
// RequestPermissionLauncher)
requestPermissions(new String[] { android.Manifest.permission.ACCESS_FINE_LOCATION }, 100);
// --- VARIÁVEIS PARA A ZONA DE STATUS (VERDE/LARANJA) ---
private MaterialCardView cardZoneStatus, cardZoneIconBG;
private ImageView iconZone;
private TextView txtZoneTitle, txtZoneSubtitle;
private GoogleMap mMap;
private Marker childMarker;
private DatabaseReference locationRef;
private ValueEventListener locationListener;
private String currentChildId = null;
// NOVA VARIÁVEL: Guarda o nome real do filho
private String currentChildName = "A carregar...";
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_map, container, false);
db = FirebaseFirestore.getInstance();
auth = FirebaseAuth.getInstance();
layoutEmptyState = root.findViewById(R.id.layoutEmptyState);
mapContainer = root.findViewById(R.id.mapContainer);
btnAddChild = root.findViewById(R.id.btnAddChild);
btnAbrirChat = root.findViewById(R.id.btnAbrirChat);
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);
if (btnAbrirChat != null) btnAbrirChat.setVisibility(View.GONE);
if (btnSOS != null) btnSOS.setVisibility(View.GONE);
if (cardChildInfo != null) cardChildInfo.setVisibility(View.GONE); // Começa escondido
if (btnAddChild != null) {
btnAddChild.setOnClickListener(v -> openAddChildScreen());
}
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;
}
private void checkUserTypeAndShowScreen() {
FirebaseUser user = auth.getCurrentUser();
if (user == null) return;
if (user.isAnonymous()) {
showMapState();
setupChildButtons();
} else {
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() && task.getResult() != null && !task.getResult().isEmpty()) {
// 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) {
showMapState();
setupChildButtons();
if (mMap != null) {
startListeningToChildLocation();
}
} else {
showEmptyState();
}
} else {
showEmptyState();
}
});
}
private void setupChildButtons() {
if (btnAbrirChat != null) {
btnAbrirChat.setVisibility(View.VISIBLE);
btnAbrirChat.setOnClickListener(v -> {
String roomCode = auth.getCurrentUser().isAnonymous() ?
requireContext().getSharedPreferences("FindU_Prefs", Context.MODE_PRIVATE).getString("child_access_code", null) :
currentChildId;
if (roomCode != null) {
requireContext().getSharedPreferences("FindU_Prefs", Context.MODE_PRIVATE).edit().putString("child_access_code", roomCode).apply();
startActivity(new Intent(getActivity(), ChatActivity.class));
}
});
}
if (btnSOS != null) btnSOS.setVisibility(View.VISIBLE);
}
private void showMapState() {
if (layoutEmptyState != null) layoutEmptyState.setVisibility(View.GONE);
if (mapContainer != null) mapContainer.setVisibility(View.VISIBLE);
SupportMapFragment mapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapContainer);
if (mapFragment != null) {
mapFragment.getMapAsync(this);
}
}
@Override
public void onMapReady(@NonNull GoogleMap googleMap) {
mMap = googleMap;
FirebaseUser user = auth.getCurrentUser();
if (user != null && user.isAnonymous()) {
try { mMap.setMyLocationEnabled(true); } catch (SecurityException e) {}
} else {
if (currentChildId != null) {
startListeningToChildLocation();
} else {
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(0, 0), 2f));
}
}
}
private void startListeningToChildLocation() {
if (currentChildId == null || mMap == null) return;
FirebaseUser user = auth.getCurrentUser();
// 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);
locationListener = new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
if (snapshot.exists() && mMap != null) {
Double lat = snapshot.child("latitude").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) {
LatLng childPos = new LatLng(lat, lng);
if (Math.abs(lat - 37.4219) > 0.001) {
if (childMarker == null) {
childMarker = mMap.addMarker(new MarkerOptions()
.position(childPos)
.title(currentChildName) // O pino no mapa também vai dizer o nome do filho
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));
} else {
childMarker.setPosition(childPos);
childMarker.setTitle(currentChildName);
}
mMap.animateCamera(CameraUpdateFactory.newLatLngZoom(childPos, 17f));
// TESTE DE DESIGN
atualizarStatusZona(true, "A atualizar localização...");
}
}
}
}
@Override public void onCancelled(@NonNull DatabaseError error) {}
};
locationRef.addValueEventListener(locationListener);
}
// --- 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("Fora das zonas seguras");
txtZoneSubtitle.setTextColor(Color.parseColor("#A63A00"));
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
if (locationRef != null && locationListener != null) locationRef.removeEventListener(locationListener);
}
private void showEmptyState() {
if (layoutEmptyState != null) layoutEmptyState.setVisibility(View.VISIBLE);
if (mapContainer != null) mapContainer.setVisibility(View.GONE);
}
private void openAddChildScreen() {
AddChildBottomSheet bottomSheet = new AddChildBottomSheet();
bottomSheet.setListener(this::showCodeDialog);
bottomSheet.show(getParentFragmentManager(), "AddChildSheet");
}
private void showCodeDialog(String code) {
new AlertDialog.Builder(requireContext())
.setTitle("Filho Adicionado!")
.setMessage("Código: " + code)
.setPositiveButton("OK", (d, w) -> checkIfHasChildren())
.show();
}
}

View File

@@ -1,41 +1,141 @@
package com.example.pap_findu.ui.profile;
import android.content.Intent;
import android.os.Bundle;
import android.util.Base64;
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.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 mUserRef;
private ValueEventListener mUserListener;
@Override
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) {
return root;
}
setupListeners();
// Verifica se é o Filho (Anónimo)
if (currentUser.isAnonymous()) {
binding.profileName.setText("Conta de Criança");
binding.profileEmail.setText("Modo de Monitorização Ativo");
binding.profileImage.setImageResource(R.drawable.logo);
// Oculta opções que não fazem sentido para o Filho
binding.layoutEditProfile.setVisibility(View.GONE);
} else {
// É O PAI: Carrega os dados reais do Firebase
mUserRef = FirebaseDatabase.getInstance().getReference("users").child(currentUser.getUid());
mUserListener = new ValueEventListener() {
@Override
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);
}
}
}
}
@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.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();

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#FFFFFF"/>
<stroke android:width="1dp" android:color="#E5E7EB"/>
<corners android:radius="16dp"
android:bottomLeftRadius="4dp"/>
</shape>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#1F6AEF"/>
<corners android:radius="16dp"
android:bottomRightRadius="4dp"/>
</shape>

View File

@@ -0,0 +1,4 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#3B82F6"/>
</shape>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<solid android:color="#E3F2FD"/> </shape>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="#3D6DFF"/> <corners android:bottomLeftRadius="30dp" android:bottomRightRadius="30dp" />
</shape>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
</vector>

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F3F6FB"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_header_top"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="20dp">
<ImageButton
android:id="@+id/btnBack"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:rotation="180"
android:src="@drawable/ic_navigation_plane"
android:tint="#FFFFFF" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Adicionar Zona"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome da Zona"
android:textColor="#1F2937"
android:textSize="14sp"
android:textStyle="bold" />
<EditText
android:id="@+id/editZoneName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:hint="Ex: Casa, Escola"
android:padding="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Endereço ou Localização"
android:textColor="#1F2937"
android:textSize="14sp"
android:textStyle="bold" />
<EditText
android:id="@+id/editZoneAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/edit_text_background"
android:hint="Digite o endereço..."
android:padding="16dp" />
<Button
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:backgroundTint="#1F6AEF"
android:text="Guardar Zona"
android:textColor="#FFFFFF"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#F6F7FB"
tools:context=".EditProfileActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="#FFFFFF"
android:elevation="4dp"
android:gravity="center_vertical">
<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
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Editar Perfil"
android:textColor="#1F2937"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:gravity="center_horizontal">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/editProfileImage"
android:layout_width="120dp"
android:layout_height="120dp"
android:background="#E5E7EB"
android:scaleType="centerCrop"
app:shapeAppearanceOverlay="@style/ShapeAppearance.MaterialComponents.MediumComponent"
android:src="@drawable/logo" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="-8dp"
android:layout_marginBottom="-8dp"
android:src="@android:drawable/ic_menu_camera"
app:backgroundTint="#3B82F6"
app:fabSize="mini"
app:tint="#FFFFFF"
android:clickable="false" />
</FrameLayout>
<TextView
android:id="@+id/btnChangePhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Alterar Foto"
android:textColor="#3B82F6"
android:textStyle="bold"
android:clickable="true"
android:padding="8dp"
android:background="?attr/selectableItemBackground" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:hint="Nome Completo"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName" />
</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="Email (Não editável)"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:enabled="false"
android:textColor="#9CA3AF"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnChangePassword"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="24dp"
android:text="MUDAR PALAVRA-PASSE"
android:textColor="#3B82F6"
app:strokeColor="#3B82F6"
app:cornerRadius="12dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSaveProfile"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="32dp"
android:text="Salvar Alterações"
app:cornerRadius="12dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F3F6FB"
tools:context=".MainActivity">
android:background="#F3F6FB">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment_activity_main"
@@ -14,29 +12,20 @@
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:navGraph="@navigation/mobile_navigation"
app:layout_constraintBottom_toTopOf="@+id/nav_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/mobile_navigation" />
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/nav_view"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/bg_bottom_nav"
android:paddingTop="6dp"
android:paddingBottom="10dp"
app:itemIconSize="24dp"
app:itemIconTint="@color/bottom_nav_selector"
app:itemTextColor="@color/bottom_nav_selector"
app:labelVisibilityMode="labeled"
app:menu="@menu/bottom_tabs"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="@menu/bottom_tabs" />
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#F6F7FB"
tools:context=".SecurityActivity">
<!-- Header -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="#FFFFFF"
android:elevation="4dp"
android:gravity="center_vertical">
<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
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Segurança e Senha"
android:textColor="#1F2937"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Alterar Palavra-passe"
android:textColor="#374151"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Palavra-passe Atual"
app:endIconMode="password_toggle"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputCurrentPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</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="Nova Palavra-passe"
app:endIconMode="password_toggle"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputNewPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</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="Confirmar Nova Palavra-passe"
app:endIconMode="password_toggle"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputConfirmNewPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnChangePassword"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="32dp"
android:text="Atualizar Senha"
app:cornerRadius="12dp" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp"
android:background="@android:color/white">
<View
android:layout_width="40dp"
android:layout_height="4dp"
android:layout_gravity="center"
android:layout_marginBottom="20dp"
android:background="#E0E0E0" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Adicionar Novo Filho"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#333" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Preencha as informações abaixo"
android:textColor="#757575"
android:layout_marginBottom="16dp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nome do Filho"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etChildName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSaveChild"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginTop="24dp"
android:text="Criar Perfil e Gerar Código"
android:textStyle="bold"
app:cornerRadius="8dp"
app:backgroundTint="#3D6DFF" />
</LinearLayout>

View File

@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F3F6FB">
<!-- Header -->
<FrameLayout
android:id="@+id/chatHeader"
android:layout_width="match_parent"
android:layout_height="90dp"
app:layout_constraintTop_toTopOf="parent">
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_header_top" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="16dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<ImageButton
android:id="@+id/btnBack"
android:layout_width="40dp"
android:layout_height="40dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_navigation_plane"
android:rotation="180"
app:tint="#FFFFFF" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Chat de Família"
android:textColor="#FFFFFF"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="3 membros online"
android:textColor="#E1EAFF"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>
</FrameLayout>
<!-- Message List -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_chat"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingBottom="16dp"
android:clipToPadding="false"
app:layout_constraintBottom_toTopOf="@+id/footerInput"
app:layout_constraintTop_toBottomOf="@+id/chatHeader" />
<!-- Input Footer -->
<LinearLayout
android:id="@+id/footerInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFFFFF"
android:elevation="8dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent">
<EditText
android:id="@+id/edit_chat_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/edit_text_background_light_gray"
android:hint="Escreve uma mensagem..."
android:padding="12dp"
android:textSize="14sp" />
<ImageButton
android:id="@+id/btnSend"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_navigation_plane"
app:tint="#1F6AEF" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -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">
<LinearLayout

View File

@@ -5,7 +5,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F6F7FB"
android:paddingBottom="96dp"
tools:context=".ui.history.HistoryFragment">
<LinearLayout

View File

@@ -0,0 +1,130 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F3F6FB">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/layoutEmptyState"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"> <View
android:id="@+id/viewHeader"
android:layout_width="match_parent"
android:layout_height="160dp"
android:background="@drawable/bg_header_rounded"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvAppTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="FindU"
android:textColor="@android:color/white"
android:textSize="22sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.cardview.widget.CardView
android:id="@+id/cardEmptyState"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="30dp"
android:layout_marginBottom="24dp"
app:cardCornerRadius="20dp"
app:cardElevation="4dp"
app:layout_constraintBottom_toTopOf="@+id/btnSOS"
app:layout_constraintTop_toBottomOf="@+id/tvAppTitle">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp">
<View
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" />
<ImageView
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" />
<TextView
android:id="@+id/tvNoChildTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Nenhum membro 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" />
<TextView
android:id="@+id/tvNoChildDesc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
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"
app:layout_constraintTop_toBottomOf="@+id/tvNoChildTitle" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAddChild"
android:layout_width="match_parent"
android:layout_height="60dp"
android:text="Adicionar Membro"
android:textStyle="bold"
app:backgroundTint="#3D6DFF"
app:cornerRadius="12dp"
app:layout_constraintBottom_toBottomOf="parent"
tools:layout_editor_absoluteX="24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSOS"
android:layout_width="match_parent"
android:layout_height="65dp"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="24dp"
android:text="SOS"
app:backgroundTint="#D32F2F"
app:cornerRadius="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/mapContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,455 +1,266 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F3F6FB"
tools:context=".ui.map.MapFragment">
<androidx.core.widget.NestedScrollView
android:id="@+id/scrollView"
<LinearLayout
android:id="@+id/layoutEmptyState"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ainda não tem filhos associados."
android:textSize="18sp"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/btnAddChild"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Adicionar Filho" />
</LinearLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/mapContainer"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/sosButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="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:paddingBottom="24dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="210dp">
<View
android:layout_width="match_parent"
android:layout_height="210dp"
android:background="@drawable/bg_header_top" />
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:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/headerTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-medium"
android:text="@string/header_title"
android:textColor="#FFFFFF"
android:textSize="28sp"
android:textStyle="bold" />
<TextView
android:id="@+id/headerSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/header_subtitle"
android:textColor="#E1EAFF"
android:textSize="14sp" />
</LinearLayout>
</FrameLayout>
<androidx.cardview.widget.CardView
android:id="@+id/userStatusCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="-60dp"
android:elevation="6dp"
android:translationZ="6dp"
app:cardCornerRadius="22dp"
app:cardUseCompatPadding="false">
<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="60dp"
android:layout_height="60dp"
android:background="@drawable/bg_avatar_placeholder"
android:padding="6dp">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/status_avatar_emoji"
android:textSize="26sp" />
</FrameLayout>
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_marginStart="16dp"
android:layout_weight="1"
android:orientation="vertical">
android:orientation="vertical"
android:layout_marginStart="16dp">
<TextView
android:id="@+id/txtChildName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/status_name"
android:textColor="#111827"
android:textSize="18sp"
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:layout_marginTop="4dp"
android:orientation="horizontal"
android:gravity="center_vertical"
android:orientation="horizontal">
android:layout_marginTop="2dp">
<View
<com.google.android.material.card.MaterialCardView
android:layout_width="10dp"
android:layout_height="10dp"
android:background="@drawable/bg_status_online" />
app:cardCornerRadius="5dp"
app:cardBackgroundColor="#4CAF50"
app:cardElevation="0dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:text="@string/status_online"
android:textColor="#1FB66F"
android:text="Online"
android:textColor="#4CAF50"
android:textStyle="bold"
android:textSize="14sp"
android:textStyle="bold" />
android:layout_marginStart="6dp"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="vertical">
android:orientation="vertical"
android:gravity="end">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
android:orientation="horizontal"
android:gravity="center_vertical">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/ic_battery" />
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:layout_marginStart="4dp"
android:text="@string/battery_percent"
android:textColor="#111827"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/ic_signal_gps" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="@string/gps_label"
android:textColor="#111827"
android:textStyle="bold" />
android:text="GPS"
android:textColor="#1F2937"
android:textSize="15sp"
android:textStyle="bold"
android:layout_marginStart="4dp"/>
</LinearLayout>
</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"
android:background="@drawable/bg_safe_banner"
android:gravity="center_vertical"
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="16dp">
<FrameLayout
android:layout_width="40dp"
android:layout_height="40dp"
android:background="@drawable/bg_safe_banner_icon">
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:layout_width="20dp"
android:layout_height="20dp"
android:layout_gravity="center"
android:src="@drawable/ic_location_pin" />
</FrameLayout>
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_marginStart="12dp"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/status_safe_zone_line1"
android:textColor="#0B6635"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="@string/status_safe_zone_line2"
android:textColor="#0B6635" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/mapContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="20dp"
app:cardCornerRadius="36dp"
app:cardElevation="4dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="380dp"
android:background="@drawable/bg_map_card"
android:clipToPadding="false"
android:padding="24dp">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/safeZone"
android:layout_width="280dp"
android:layout_height="280dp"
android:background="@drawable/bg_safe_zone_circle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/userMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<View
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/bg_marker_user" />
android:layout_marginStart="12dp">
<TextView
android:id="@+id/txtZoneTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="@string/map_marker_miguel"
android:textColor="#111827"
android:text="Dentro de Zona Segura"
android:textColor="#2D4608"
android:textSize="15sp"
android:textStyle="bold"/>
</LinearLayout>
<LinearLayout
android:id="@+id/homeMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginBottom="48dp"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
<FrameLayout
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_marker_avatar">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/zone_home_icon"
android:textSize="18sp" />
</FrameLayout>
<TextView
android:id="@+id/txtZoneSubtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:background="@drawable/bg_marker_label"
android:text="@string/map_marker_home"
android:textColor="#111827"
android:textStyle="bold" />
android:text="A carregar..."
android:textColor="#486B11"
android:textSize="13sp"/>
</LinearLayout>
<LinearLayout
android:id="@+id/schoolMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="24dp"
android:gravity="center_vertical"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="@+id/safeZone"
app:layout_constraintTop_toTopOf="@+id/safeZone">
<FrameLayout
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_marker_avatar">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="@string/zone_school_icon"
android:textSize="18sp" />
</FrameLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:background="@drawable/bg_marker_label"
android:text="@string/map_marker_school"
android:textColor="#111827"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:id="@+id/zoomControls"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageButton
android:id="@+id/zoomInButton"
android:layout_width="52dp"
android:layout_height="52dp"
android:background="@drawable/bg_zoom_button"
android:contentDescription="@string/zoom_in"
android:src="@drawable/ic_zoom_plus" />
<ImageButton
android:id="@+id/zoomOutButton"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_marginTop="10dp"
android:background="@drawable/bg_zoom_button"
android:contentDescription="@string/zoom_out"
android:src="@drawable/ic_zoom_minus" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/navigationFab"
android:id="@+id/btnAbrirChat"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginBottom="12dp"
android:contentDescription="@string/navigate"
app:backgroundTint="#1F6AEF"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:srcCompat="@drawable/ic_navigation_plane" />
<FrameLayout
android:id="@+id/messagesFabContainer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginBottom="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/messagesFab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/messages"
app:backgroundTint="#1DB45A"
app:srcCompat="@drawable/ic_chat_bubble" />
<TextView
android:id="@+id/messagesBadge"
android:layout_width="22dp"
android:layout_height="22dp"
android:layout_gravity="top|end"
android:background="@drawable/bg_badge"
android:gravity="center"
android:text="@string/messages_badge"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:textStyle="bold" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:backgroundTint="#4CAF50"
app:tint="@android:color/white"
android:src="@android:drawable/ic_dialog_email"
app:layout_constraintBottom_toTopOf="@+id/btnSOS"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/sosButton"
style="@style/Widget.MaterialComponents.Button"
android:layout_width="match_parent"
android:layout_height="64dp"
android:id="@+id/btnSOS"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="16dp"
android:backgroundTint="#E53935"
app:cornerRadius="18dp"
android:drawablePadding="12dp"
android:drawableEnd="@android:drawable/ic_menu_call"
app:icon="@android:drawable/ic_dialog_alert"
app:iconPadding="12dp"
app:iconTint="#FFFFFF"
android:text="@string/sos_button_text"
android:textAllCaps="true"
android:textColor="#FFFFFF"
android:layout_marginBottom="24dp"
android:text="BOTÃO SOS DE EMERGÊNCIA"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold"
app:iconGravity="textStart"
app:backgroundTint="#D32F2F"
app:cornerRadius="12dp"
app:icon="@android:drawable/ic_menu_call"
app:iconGravity="textEnd"
app:iconPadding="16dp"
app:iconTint="@android:color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />

View File

@@ -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">
<LinearLayout
@@ -30,6 +29,7 @@
android:orientation="vertical">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/profileImage"
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#FFFFFF"
@@ -40,6 +40,7 @@
android:src="@drawable/logo" />
<TextView
android:id="@+id/profileName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
@@ -49,6 +50,7 @@
android:textStyle="bold" />
<TextView
android:id="@+id/profileEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
@@ -58,7 +60,6 @@
</LinearLayout>
</FrameLayout>
<!-- Account Settings Section -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -86,8 +87,8 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Item 1 -->
<LinearLayout
android:id="@+id/layoutEditProfile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
@@ -118,46 +119,9 @@
android:rotation="0" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#F3F4F6" />
<!-- Item 2 -->
<LinearLayout
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>
</com.google.android.material.card.MaterialCardView>
<!-- App Settings Section -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -185,7 +149,6 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Item 1 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -220,7 +183,6 @@
android:layout_height="1dp"
android:background="#F3F4F6" />
<!-- Item 2 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -270,7 +232,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="FindU Inc. © 2024"
android:text="FindU Inc. © 2026"
android:textColor="#D1D5DB"
android:textSize="12sp" />

View File

@@ -5,7 +5,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F5F7FB"
android:paddingBottom="96dp"
tools:context=".ui.zones.ZonesFragment">
<LinearLayout

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="start"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/text_message_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_chat_received"
android:maxWidth="260dp"
android:padding="12dp"
android:text="Message Text"
android:textColor="#1F2937"
android:textSize="16sp" />
<TextView
android:id="@+id/text_message_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="12:00"
android:textColor="#9CA3AF"
android:textSize="10sp" />
</LinearLayout>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/text_message_body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_chat_sent"
android:maxWidth="260dp"
android:padding="12dp"
android:text="Message Text"
android:textColor="#FFFFFF"
android:textSize="16sp" />
<TextView
android:id="@+id/text_message_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="12:00"
android:textColor="#9CA3AF"
android:textSize="10sp" />
</LinearLayout>

View File

@@ -95,6 +95,48 @@
android:textSize="16sp"
app:cornerRadius="12dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:gravity="center"
android:orientation="horizontal">
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:background="#E0E0E0" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:text="OU"
android:textColor="#9E9E9E"
android:textSize="12sp" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1"
android:background="#E0E0E0" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnChildLogin"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="55dp"
android:text="Sou Filho (Entrar com Código)"
android:textAllCaps="false"
android:textColor="#3D6DFF"
android:textSize="16sp"
app:strokeColor="#3D6DFF"
app:strokeWidth="1dp"
app:cornerRadius="12dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.PAP_FindU" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="Theme.PAP_FindU" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>

View File

@@ -1,6 +1,6 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.PAP_FindU" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<style name="Theme.PAP_FindU" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>

View File

@@ -17,6 +17,7 @@ firebaseAuth = "24.0.1"
credentials = "1.5.0"
credentialsPlayServicesAuth = "1.5.0"
googleid = "1.1.1"
firebaseFirestore = "26.1.0"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -35,6 +36,7 @@ firebase-auth = { group = "com.google.firebase", name = "firebase-auth", version
credentials = { group = "androidx.credentials", name = "credentials", version.ref = "credentials" }
credentials-play-services-auth = { group = "androidx.credentials", name = "credentials-play-services-auth", version.ref = "credentialsPlayServicesAuth" }
googleid = { group = "com.google.android.libraries.identity.googleid", name = "googleid", version.ref = "googleid" }
firebase-firestore = { group = "com.google.firebase", name = "firebase-firestore", version.ref = "firebaseFirestore" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

View File

@@ -1,4 +1,4 @@
#Tue Nov 25 08:59:16 WET 2025
#Thu Mar 12 10:10:15 WET 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip