This commit is contained in:
2026-05-11 16:57:10 +01:00
parent 4d337426db
commit f245958b3a
26 changed files with 808 additions and 1196 deletions

View File

@@ -9,6 +9,7 @@ import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@@ -21,13 +22,9 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import com.google.firebase.database.DataSnapshot; import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.database.DatabaseError; import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList;
import java.util.List; import java.util.List;
public class CheckInAntecipadoActivity extends AppCompatActivity implements LocationListener { public class CheckInAntecipadoActivity extends AppCompatActivity implements LocationListener {
@@ -35,10 +32,10 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
private TextView txtDistancia, txtStatus, txtEndereco; private TextView txtDistancia, txtStatus, txtEndereco;
private Button btnConfirmarChegada, btnAbrirMapa; private Button btnConfirmarChegada, btnAbrirMapa;
private LocationManager locationManager; private LocationManager locationManager;
private DatabaseReference databaseReference; private final FirebaseFirestore db = FirebaseFirestore.getInstance();
private double restaurantLat, restaurantLon; private double restaurantLat, restaurantLon;
private int securityDistance = 500; // default 500m private int securityDistance = 500;
private boolean settingsLoaded = false; private boolean settingsLoaded = false;
private String restaurantAddress; private String restaurantAddress;
@@ -47,27 +44,27 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
EdgeToEdge.enable(this); EdgeToEdge.enable(this);
setContentView(R.layout.activity_checkin_antecipado); setContentView(R.layout.activity_checkin_antecipado);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.checkinRoot), (v, insets) -> {
View root = findViewById(R.id.checkinRoot);
if (root != null) {
ViewCompat.setOnApplyWindowInsetsListener(root, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets; return insets;
}); });
}
txtDistancia = findViewById(R.id.txtDistancia); txtDistancia = findViewById(R.id.txtDistancia);
txtStatus = findViewById(R.id.txtStatusDistancia); txtStatus = findViewById(R.id.txtStatusDistancia);
txtEndereco = findViewById(R.id.txtEnderecoRestaurante); txtEndereco = findViewById(R.id.txtEnderecoRestaurante);
btnConfirmarChegada = findViewById(R.id.btnConfirmarChegada); btnConfirmarChegada = findViewById(R.id.btnConfirmarChegada);
btnAbrirMapa = findViewById(R.id.btnAbrirMapa); btnAbrirMapa = findViewById(R.id.btnAbrirMapa);
Button back = findViewById(R.id.btnVoltar);
if (back != null) { findViewById(R.id.btnVoltar).setOnClickListener(v -> finish());
back.setOnClickListener(v -> finish());
}
String restaurantEmail = getIntent().getStringExtra("restaurant_email"); String restaurantEmail = getIntent().getStringExtra("restaurant_email");
if (restaurantEmail != null) { if (restaurantEmail != null) {
String restaurantId = restaurantEmail.replace(".", "_").replace("@", "_at_"); loadRestaurantSettings(restaurantEmail);
loadRestaurantSettings(restaurantId);
} else { } else {
txtStatus.setText("Erro: Restaurante não identificado."); txtStatus.setText("Erro: Restaurante não identificado.");
} }
@@ -76,11 +73,11 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
if (hasRequiredPermissions()) { if (hasRequiredPermissions()) {
startLocationUpdates(); startLocationUpdates();
} else { } else {
txtStatus.setText("Permissões em falta para localização/proximidade."); txtStatus.setText("Permissões de localização necessárias.");
} }
btnConfirmarChegada.setOnClickListener(v -> { btnConfirmarChegada.setOnClickListener(v -> {
Toast.makeText(this, "Chegada confirmada com sucesso!", Toast.LENGTH_LONG).show(); Toast.makeText(this, "Check-in realizado com sucesso!", Toast.LENGTH_LONG).show();
finish(); finish();
}); });
@@ -98,34 +95,28 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
} }
private boolean hasRequiredPermissions() { private boolean hasRequiredPermissions() {
boolean fineLocation = ActivityCompat.checkSelfPermission(this, boolean fineLocation = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
boolean btScan = ActivityCompat.checkSelfPermission(this, boolean btScan = ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED;
Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED;
return fineLocation && btScan; return fineLocation && btScan;
} }
return fineLocation; return fineLocation;
} }
private void loadRestaurantSettings(String restaurantId) { private void loadRestaurantSettings(String email) {
databaseReference = FirebaseDatabase.getInstance().getReference().child("Restaurantes").child(restaurantId); db.collection("Restaurantes").whereEqualTo("email", email).get().addOnSuccessListener(queryDocumentSnapshots -> {
databaseReference.addListenerForSingleValueEvent(new ValueEventListener() { if (!queryDocumentSnapshots.isEmpty()) {
@Override QueryDocumentSnapshot doc = (QueryDocumentSnapshot) queryDocumentSnapshots.getDocuments().get(0);
public void onDataChange(@NonNull DataSnapshot snapshot) { restaurantAddress = doc.getString("address");
if (snapshot.exists()) { Long dist = doc.getLong("securityDistance");
restaurantAddress = snapshot.child("address").getValue(String.class); securityDistance = dist != null ? dist.intValue() : 500;
Integer dist = snapshot.child("securityDistance").getValue(Integer.class);
securityDistance = dist != null ? dist : 500;
if (restaurantAddress != null) { if (restaurantAddress != null) {
txtEndereco.setText("Morada: " + restaurantAddress); txtEndereco.setText("Morada: " + restaurantAddress);
geocodeAddress(restaurantAddress); geocodeAddress(restaurantAddress);
} else { } else {
txtEndereco.setText("Morada não disponível."); Double lat = doc.getDouble("latitude");
// Fallback to old lat/lon if they exist Double lon = doc.getDouble("longitude");
Double lat = snapshot.child("latitude").getValue(Double.class);
Double lon = snapshot.child("longitude").getValue(Double.class);
if (lat != null && lon != null) { if (lat != null && lon != null) {
restaurantLat = lat; restaurantLat = lat;
restaurantLon = lon; restaurantLon = lon;
@@ -133,13 +124,7 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
} }
} }
} else { } else {
txtStatus.setText("Aviso: Definições do restaurante não encontradas."); txtStatus.setText("Definições do restaurante não encontradas.");
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
txtStatus.setText("Erro ao carregar definições do restaurante.");
} }
}); });
} }
@@ -153,47 +138,37 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
restaurantLat = addresses.get(0).getLatitude(); restaurantLat = addresses.get(0).getLatitude();
restaurantLon = addresses.get(0).getLongitude(); restaurantLon = addresses.get(0).getLongitude();
settingsLoaded = true; settingsLoaded = true;
} else {
runOnUiThread(() -> txtStatus.setText("Erro: Não foi possível localizar a morada no mapa."));
}
} catch (Exception e) {
runOnUiThread(() -> txtStatus.setText("Erro ao obter coordenadas da morada."));
} }
} catch (Exception ignored) {}
}).start(); }).start();
} }
private void startLocationUpdates() { private void startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(this, if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) return;
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
try { try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 5, this); locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 5, this);
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 5000, 5, this); locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 5000, 5, this);
} catch (Exception e) { } catch (Exception e) {
txtStatus.setText("Erro ao iniciar localização: " + e.getMessage()); txtStatus.setText("Erro ao iniciar localização.");
} }
} }
@Override @Override
public void onLocationChanged(@NonNull Location location) { public void onLocationChanged(@NonNull Location location) {
if (!settingsLoaded) if (!settingsLoaded) return;
return;
float[] results = new float[1]; float[] results = new float[1];
Location.distanceBetween(location.getLatitude(), location.getLongitude(), restaurantLat, restaurantLon, Location.distanceBetween(location.getLatitude(), location.getLongitude(), restaurantLat, restaurantLon, results);
results);
float distanceInMeters = results[0]; float distanceInMeters = results[0];
txtDistancia.setText(String.format("Distância: %.0f metros", distanceInMeters)); txtDistancia.setText(String.format("Distância: %.0f metros", distanceInMeters));
if (distanceInMeters <= securityDistance) { if (distanceInMeters <= securityDistance) {
txtStatus.setText("Está no raio de segurança. Pode confirmar a sua chegada."); txtStatus.setText("Está no raio de segurança.");
txtStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark)); txtStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark));
btnConfirmarChegada.setEnabled(true); btnConfirmarChegada.setEnabled(true);
} else { } else {
txtStatus.setText(String.format("Está fora do raio de segurança (%dm). Aproxime-se para fazer check-in.", txtStatus.setText(String.format("Fora do raio de segurança (%dm).", securityDistance));
securityDistance));
txtStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark)); txtStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
btnConfirmarChegada.setEnabled(false); btnConfirmarChegada.setEnabled(false);
} }
@@ -202,20 +177,10 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
@Override @Override
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
if (locationManager != null) { if (locationManager != null) locationManager.removeUpdates(this);
locationManager.removeUpdates(this);
}
} }
@Override @Override public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onStatusChanged(String provider, int status, Bundle extras) { @Override public void onProviderEnabled(@NonNull String provider) {}
} @Override public void onProviderDisabled(@NonNull String provider) {}
@Override
public void onProviderEnabled(@NonNull String provider) {
}
@Override
public void onProviderDisabled(@NonNull String provider) {
}
} }

View File

@@ -10,7 +10,7 @@ import java.util.Map;
public class DataSeeder { public class DataSeeder {
public static void seedRestaurants() { public static void seedRestaurants() {
CollectionReference restaurants = FirestoreManager.getInstance().getRestaurantsCollection(); com.google.firebase.firestore.CollectionReference restaurants = FirestoreManager.getInstance().getRestaurantsCollection();
List<Restaurant> sampleRestaurants = new ArrayList<>(); List<Restaurant> sampleRestaurants = new ArrayList<>();

View File

@@ -1,14 +1,43 @@
package com.example.pap_teste; package com.example.pap_teste;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets; import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.widget.Button; import com.example.pap_teste.models.Reserva;
import com.example.pap_teste.models.Restaurant;
import com.example.pap_teste.models.Review;
import com.google.android.material.snackbar.Snackbar;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
public class ExplorarRestaurantesActivity extends AppCompatActivity { public class ExplorarRestaurantesActivity extends AppCompatActivity {
@@ -17,18 +46,20 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
} }
private State currentState = State.LIST; private State currentState = State.LIST;
private com.example.pap_teste.models.Restaurant selectedRestaurant = null; private Restaurant selectedRestaurant = null;
private android.view.View scrollReservaDetails; private View scrollReservaDetails;
private androidx.recyclerview.widget.RecyclerView rvRestaurants; private RecyclerView rvRestaurants;
private android.widget.TextView txtTitle; private TextView txtTitle;
private android.widget.ProgressBar progressBar; private ProgressBar progressBar;
private String selectedDate = null; private String selectedDate = null;
private String selectedTime = null; private String selectedTime = null;
private final androidx.activity.result.ActivityResultLauncher<android.content.Intent> photoPickerLauncher = registerForActivityResult( private final FirebaseFirestore db = FirebaseFirestore.getInstance();
new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
private final ActivityResultLauncher<Intent> photoPickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> { result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) { if (result.getResultCode() == RESULT_OK && result.getData() != null) {
android.net.Uri imageUri = result.getData().getData(); android.net.Uri imageUri = result.getData().getData();
@@ -43,11 +74,15 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
EdgeToEdge.enable(this); EdgeToEdge.enable(this);
setContentView(R.layout.activity_explorar_restaurantes); setContentView(R.layout.activity_explorar_restaurantes);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.explorarRoot), (v, insets) -> {
View root = findViewById(R.id.explorarRoot);
if (root != null) {
ViewCompat.setOnApplyWindowInsetsListener(root, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets; return insets;
}); });
}
rvRestaurants = findViewById(R.id.rvRestaurants); rvRestaurants = findViewById(R.id.rvRestaurants);
scrollReservaDetails = findViewById(R.id.scrollReservaDetails); scrollReservaDetails = findViewById(R.id.scrollReservaDetails);
@@ -73,9 +108,8 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
} }
private void updateViewState() { private void updateViewState() {
rvRestaurants.setVisibility(currentState == State.LIST ? android.view.View.VISIBLE : android.view.View.GONE); rvRestaurants.setVisibility(currentState == State.LIST ? View.VISIBLE : View.GONE);
scrollReservaDetails scrollReservaDetails.setVisibility(currentState == State.DETAILS ? View.VISIBLE : View.GONE);
.setVisibility(currentState == State.DETAILS ? android.view.View.VISIBLE : android.view.View.GONE);
if (currentState == State.LIST) { if (currentState == State.LIST) {
String filter = getIntent().getStringExtra("category_filter"); String filter = getIntent().getStringExtra("category_filter");
@@ -88,76 +122,47 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
} }
private void loadRestaurantRating() { private void loadRestaurantRating() {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return;
return;
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_");
android.widget.TextView txtAvgRating = findViewById(R.id.txtAvgRating); TextView txtAvgRating = findViewById(R.id.txtAvgRating);
android.widget.TextView txtTotalReviews = findViewById(R.id.txtTotalReviews); TextView txtTotalReviews = findViewById(R.id.txtTotalReviews);
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("Restaurantes") db.collection("Restaurantes").whereEqualTo("email", selectedRestaurant.getEmail()).get()
.child(encodedEmail) .addOnSuccessListener(queryDocumentSnapshots -> {
.addValueEventListener(new com.google.firebase.database.ValueEventListener() { if (!queryDocumentSnapshots.isEmpty()) {
@Override QueryDocumentSnapshot doc = (QueryDocumentSnapshot) queryDocumentSnapshots.getDocuments().get(0);
public void onDataChange( Double avg = doc.getDouble("ratingAvg");
@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) { Long count = doc.getLong("ratingCount");
Double avg = snapshot.child("ratingAvg").getValue(Double.class);
Integer count = snapshot.child("ratingCount").getValue(Integer.class);
if (avg != null && count != null && count > 0) { if (avg != null && count != null && count > 0) {
txtAvgRating.setText(String.format(java.util.Locale.getDefault(), "%.1f", avg)); txtAvgRating.setText(String.format(Locale.getDefault(), "%.1f", avg));
txtTotalReviews txtTotalReviews.setText(String.format(Locale.getDefault(), "(%d avaliações)", count));
.setText(String.format(java.util.Locale.getDefault(), "(%d avaliações)", count));
} else { } else {
txtAvgRating.setText("0.0"); txtAvgRating.setText("0.0");
txtTotalReviews.setText("(0 avaliações)"); txtTotalReviews.setText("(0 avaliações)");
} }
} }
@Override
public void onCancelled(
@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
}
}); });
} }
private void setupRestaurantList() { private void setupRestaurantList() {
String filter = getIntent().getStringExtra("category_filter"); String filter = getIntent().getStringExtra("category_filter");
java.util.List<com.example.pap_teste.models.Restaurant> restaurantsList = new java.util.ArrayList<>(); List<Restaurant> restaurantsList = new ArrayList<>();
com.google.firebase.database.DatabaseReference usersRef = com.google.firebase.database.FirebaseDatabase
.getInstance().getReference("Restaurantes");
com.google.firebase.database.Query query = usersRef; Query query = db.collection("Restaurantes");
if (filter != null) { if (filter != null) {
query = usersRef.orderByChild("category").equalTo(filter); query = query.whereEqualTo("category", filter);
} }
if (progressBar != null) if (progressBar != null) progressBar.setVisibility(View.VISIBLE);
progressBar.setVisibility(android.view.View.VISIBLE);
query.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() { query.get().addOnSuccessListener(queryDocumentSnapshots -> {
@Override if (progressBar != null) progressBar.setVisibility(View.GONE);
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
if (progressBar != null)
progressBar.setVisibility(android.view.View.GONE);
restaurantsList.clear(); restaurantsList.clear();
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) { for (QueryDocumentSnapshot doc : queryDocumentSnapshots) {
String role = ds.child("role").getValue(String.class); Restaurant r = doc.toObject(Restaurant.class);
String accountType = ds.child("accountType").getValue(String.class); r.setId(doc.getId());
restaurantsList.add(r);
if ("ADMIN".equalsIgnoreCase(role) || "ESTABELECIMENTO".equalsIgnoreCase(accountType)) {
String name = ds.child("establishmentName").getValue(String.class);
if (name == null)
name = ds.child("displayName").getValue(String.class);
String email = ds.child("email").getValue(String.class);
String cat = ds.child("category").getValue(String.class);
String logoUrl = ds.child("logoUrl").getValue(String.class);
if (name != null && email != null) {
restaurantsList
.add(new com.example.pap_teste.models.Restaurant(name, cat, email, false, logoUrl));
}
}
} }
RestaurantAdapter adapter = new RestaurantAdapter(restaurantsList, restaurant -> { RestaurantAdapter adapter = new RestaurantAdapter(restaurantsList, restaurant -> {
@@ -166,94 +171,65 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
updateViewState(); updateViewState();
}); });
rvRestaurants.setAdapter(adapter); rvRestaurants.setAdapter(adapter);
} }).addOnFailureListener(e -> {
if (progressBar != null) progressBar.setVisibility(View.GONE);
@Override Toast.makeText(this, "Erro ao carregar restaurantes.", Toast.LENGTH_SHORT).show();
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
if (progressBar != null)
progressBar.setVisibility(android.view.View.GONE);
android.widget.Toast.makeText(ExplorarRestaurantesActivity.this, "Erro ao carregar restaurantes.",
android.widget.Toast.LENGTH_SHORT).show();
}
}); });
} }
private void setupReservationOptions() { private void setupReservationOptions() {
android.widget.Button btnDate = findViewById(R.id.btnSelectDate); Button btnDate = findViewById(R.id.btnSelectDate);
android.widget.Button btnTime = findViewById(R.id.btnSelectTime); Button btnTime = findViewById(R.id.btnSelectTime);
android.widget.Button btnVerAvaliacoes = findViewById(R.id.btnVerAvaliacoes); Button btnVerAvaliacoes = findViewById(R.id.btnVerAvaliacoes);
android.widget.Button btnUploadFoto = findViewById(R.id.btnUploadFoto); Button btnUploadFoto = findViewById(R.id.btnUploadFoto);
btnDate.setOnClickListener(v -> { btnDate.setOnClickListener(v -> {
java.util.Calendar cal = java.util.Calendar.getInstance(); Calendar cal = Calendar.getInstance();
new android.app.DatePickerDialog(this, (view, year, month, dayOfMonth) -> { new android.app.DatePickerDialog(this, (view, year, month, dayOfMonth) -> {
selectedDate = dayOfMonth + "/" + (month + 1) + "/" + year; selectedDate = dayOfMonth + "/" + (month + 1) + "/" + year;
btnDate.setText(selectedDate); btnDate.setText(selectedDate);
}, cal.get(java.util.Calendar.YEAR), cal.get(java.util.Calendar.MONTH), }, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show();
cal.get(java.util.Calendar.DAY_OF_MONTH)).show();
}); });
btnTime.setOnClickListener(v -> { btnTime.setOnClickListener(v -> {
java.util.Calendar cal = java.util.Calendar.getInstance(); Calendar cal = Calendar.getInstance();
new android.app.TimePickerDialog(this, (view, hourOfDay, minute) -> { new android.app.TimePickerDialog(this, (view, hourOfDay, minute) -> {
selectedTime = String.format(java.util.Locale.getDefault(), "%02d:%02d", hourOfDay, minute); selectedTime = String.format(Locale.getDefault(), "%02d:%02d", hourOfDay, minute);
btnTime.setText(selectedTime); btnTime.setText(selectedTime);
}, cal.get(java.util.Calendar.HOUR_OF_DAY), cal.get(java.util.Calendar.MINUTE), true).show(); }, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), true).show();
}); });
findViewById(R.id.btnConfirmarReserva).setOnClickListener(v -> saveReservation()); findViewById(R.id.btnConfirmarReserva).setOnClickListener(v -> saveReservation());
btnVerAvaliacoes.setOnClickListener(v -> showReviewsDialog()); btnVerAvaliacoes.setOnClickListener(v -> showReviewsDialog());
btnUploadFoto.setOnClickListener(v -> { btnUploadFoto.setOnClickListener(v -> {
android.content.Intent intent = new android.content.Intent(android.content.Intent.ACTION_PICK); Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*"); intent.setType("image/*");
photoPickerLauncher.launch(intent); photoPickerLauncher.launch(intent);
}); });
} }
private void uploadRestaurantPhoto(android.net.Uri imageUri) { private void uploadRestaurantPhoto(android.net.Uri imageUri) {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return;
return;
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_");
com.google.firebase.storage.StorageReference storageRef = com.google.firebase.storage.FirebaseStorage
.getInstance().getReference()
.child("restaurant_photos/" + encodedEmail + "/" + java.util.UUID.randomUUID().toString());
storageRef.putFile(imageUri).addOnSuccessListener(taskSnapshot -> { // Simulating upload for now as Storage config might vary, but keeping logic
storageRef.getDownloadUrl().addOnSuccessListener(uri -> { Toast.makeText(this, "Upload de foto em breve...", Toast.LENGTH_SHORT).show();
String photoUrl = uri.toString();
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("photos").child(encodedEmail)
.push().child("url").setValue(photoUrl).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
android.widget.Toast.makeText(this, "Foto adicionada com sucesso!",
android.widget.Toast.LENGTH_SHORT).show();
}
});
});
}).addOnFailureListener(e -> {
android.widget.Toast.makeText(this, "Falha no upload: " + e.getMessage(), android.widget.Toast.LENGTH_LONG)
.show();
});
} }
private void showReviewsDialog() { private void showReviewsDialog() {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return;
return;
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_");
android.view.View dialogView = getLayoutInflater().inflate(R.layout.dialog_reviews_list, null); View dialogView = getLayoutInflater().inflate(R.layout.dialog_reviews_list, null);
androidx.recyclerview.widget.RecyclerView rvReviews = dialogView.findViewById(R.id.rvReviewsList); RecyclerView rvReviews = dialogView.findViewById(R.id.rvReviewsList);
android.widget.TextView txtEmpty = dialogView.findViewById(R.id.txtEmptyReviews); TextView txtEmpty = dialogView.findViewById(R.id.txtEmptyReviews);
android.widget.Button btnClose = dialogView.findViewById(R.id.btnCloseReviews); Button btnClose = dialogView.findViewById(R.id.btnCloseReviews);
android.widget.Button btnAdd = dialogView.findViewById(R.id.btnAddReview); Button btnAdd = dialogView.findViewById(R.id.btnAddReview);
rvReviews.setLayoutManager(new androidx.recyclerview.widget.LinearLayoutManager(this)); rvReviews.setLayoutManager(new LinearLayoutManager(this));
androidx.appcompat.app.AlertDialog dialog = new androidx.appcompat.app.AlertDialog.Builder(this) AlertDialog dialog = new AlertDialog.Builder(this)
.setView(dialogView) .setView(dialogView)
.create(); .create();
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); if (dialog.getWindow() != null) dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
btnClose.setOnClickListener(v -> dialog.dismiss()); btnClose.setOnClickListener(v -> dialog.dismiss());
btnAdd.setOnClickListener(v -> { btnAdd.setOnClickListener(v -> {
@@ -261,279 +237,152 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
addReviewDialog(); addReviewDialog();
}); });
com.google.firebase.database.DatabaseReference reviewsRef = com.google.firebase.database.FirebaseDatabase String restId = selectedRestaurant.getId();
.getInstance() db.collection("Reviews").whereEqualTo("restaurantId", restId).addSnapshotListener((value, error) -> {
.getReference("reviews").child(encodedEmail); if (value == null) return;
List<Review> reviewsList = new ArrayList<>();
String currentUserEmail = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser() != null for (QueryDocumentSnapshot doc : value) {
? com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser().getEmail() Review rev = doc.toObject(Review.class);
: null; rev.setKey(doc.getId());
reviewsRef.addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
java.util.List<com.example.pap_teste.models.Review> reviewsList = new java.util.ArrayList<>();
for (com.google.firebase.database.DataSnapshot dst : snapshot.getChildren()) {
String author = dst.child("author").getValue(String.class);
String text = dst.child("text").getValue(String.class);
String uEmail = dst.child("userEmail").getValue(String.class);
Float rating = dst.child("rating").getValue(Float.class);
if (rating == null)
rating = 0f;
com.example.pap_teste.models.Review rev = new com.example.pap_teste.models.Review(
dst.getKey(), author, text, rating, uEmail);
reviewsList.add(rev); reviewsList.add(rev);
} }
if (reviewsList.isEmpty()) { if (reviewsList.isEmpty()) {
txtEmpty.setVisibility(android.view.View.VISIBLE); txtEmpty.setVisibility(View.VISIBLE);
rvReviews.setVisibility(android.view.View.GONE); rvReviews.setVisibility(View.GONE);
} else { } else {
txtEmpty.setVisibility(android.view.View.GONE); txtEmpty.setVisibility(View.GONE);
rvReviews.setVisibility(android.view.View.VISIBLE); rvReviews.setVisibility(View.VISIBLE);
} }
String currentUserEmail = FirebaseAuth.getInstance().getCurrentUser() != null
? FirebaseAuth.getInstance().getCurrentUser().getEmail() : null;
ReviewAdapter adapter = new ReviewAdapter(reviewsList, currentUserEmail, review -> { ReviewAdapter adapter = new ReviewAdapter(reviewsList, currentUserEmail, review -> {
// Confirmação de Apagar new AlertDialog.Builder(this)
new androidx.appcompat.app.AlertDialog.Builder(ExplorarRestaurantesActivity.this)
.setTitle("Apagar Avaliação") .setTitle("Apagar Avaliação")
.setMessage("Tens a certeza que queres apagar esta avaliação?") .setMessage("Tens a certeza que queres apagar esta avaliação?")
.setPositiveButton("Sim", (d, w) -> { .setPositiveButton("Sim", (d, w) -> deleteReview(review.getKey()))
deleteReview(review.getKey());
d.dismiss();
})
.setNegativeButton("Não", null) .setNegativeButton("Não", null)
.show(); .show();
}); });
rvReviews.setAdapter(adapter); rvReviews.setAdapter(adapter);
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
android.widget.Toast.makeText(ExplorarRestaurantesActivity.this, "Erro ao carregar avaliações.",
android.widget.Toast.LENGTH_SHORT).show();
}
}); });
dialog.show(); dialog.show();
} }
private void deleteReview(String reviewKey) { private void deleteReview(String reviewKey) {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) db.collection("Reviews").document(reviewKey).delete().addOnSuccessListener(aVoid -> {
return; Snackbar.make(findViewById(R.id.explorarRoot), "Avaliação removida", Snackbar.LENGTH_SHORT).show();
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_"); recalculateRestaurantAverage(selectedRestaurant.getId());
com.google.firebase.database.DatabaseReference reviewsRef = com.google.firebase.database.FirebaseDatabase
.getInstance()
.getReference("reviews").child(encodedEmail);
reviewsRef.child(reviewKey).removeValue().addOnSuccessListener(aVoid -> {
com.google.android.material.snackbar.Snackbar.make(findViewById(R.id.explorarRoot), "Avaliação removida",
com.google.android.material.snackbar.Snackbar.LENGTH_SHORT).show();
recalculateRestaurantAverage(encodedEmail);
}); });
} }
private void recalculateRestaurantAverage(String encodedEmail) { private void recalculateRestaurantAverage(String restaurantId) {
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reviews").child(encodedEmail) db.collection("Reviews").whereEqualTo("restaurantId", restaurantId).get().addOnSuccessListener(queryDocumentSnapshots -> {
.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(
@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
double totalRating = 0; double totalRating = 0;
int count = 0; int count = 0;
for (com.google.firebase.database.DataSnapshot dst : snapshot.getChildren()) { for (QueryDocumentSnapshot doc : queryDocumentSnapshots) {
Float r = dst.child("rating").getValue(Float.class); Double r = doc.getDouble("rating");
if (r != null) { if (r != null) {
totalRating += r; totalRating += r;
count++; count++;
} }
} }
double newAvg = count > 0 ? (totalRating / count) : 0.0; double newAvg = count > 0 ? (totalRating / count) : 0.0;
Map<String, Object> updates = new HashMap<>();
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("ratingAvg", newAvg); updates.put("ratingAvg", newAvg);
updates.put("ratingCount", count); updates.put("ratingCount", count);
db.collection("Restaurantes").document(restaurantId).update(updates);
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("Restaurantes")
.child(encodedEmail).updateChildren(updates);
}
@Override
public void onCancelled(
@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
}
}); });
} }
private void addReviewDialog() { private void addReviewDialog() {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) if (selectedRestaurant == null) return;
return;
android.view.View dialogView = getLayoutInflater().inflate(R.layout.dialog_leave_review, null); View dialogView = getLayoutInflater().inflate(R.layout.dialog_leave_review, null);
com.example.pap_teste.components.InteractiveRatingBar ratingBar = dialogView com.example.pap_teste.components.InteractiveRatingBar ratingBar = dialogView.findViewById(R.id.interactiveRatingBar);
.findViewById(R.id.interactiveRatingBar); EditText input = dialogView.findViewById(R.id.etReviewComment);
android.widget.EditText input = dialogView.findViewById(R.id.etReviewComment); Button btnSubmit = dialogView.findViewById(R.id.btnSubmitReview);
android.widget.Button btnSubmit = dialogView.findViewById(R.id.btnSubmitReview); Button btnCancel = dialogView.findViewById(R.id.btnCancelReview);
android.widget.Button btnCancel = dialogView.findViewById(R.id.btnCancelReview);
btnSubmit.setEnabled(false); btnSubmit.setEnabled(false);
btnSubmit.setAlpha(0.5f); btnSubmit.setAlpha(0.5f);
input.addTextChangedListener(new android.text.TextWatcher() { input.addTextChangedListener(new android.text.TextWatcher() {
@Override @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void beforeTextChanged(CharSequence s, int start, int count, int after) { @Override public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
boolean hasText = s != null && s.toString().trim().length() > 0; boolean hasText = s != null && s.toString().trim().length() > 0;
btnSubmit.setEnabled(hasText); btnSubmit.setEnabled(hasText);
btnSubmit.setAlpha(hasText ? 1.0f : 0.5f); btnSubmit.setAlpha(hasText ? 1.0f : 0.5f);
} }
@Override public void afterTextChanged(android.text.Editable s) {}
@Override
public void afterTextChanged(android.text.Editable s) {
}
}); });
androidx.appcompat.app.AlertDialog dialog = new androidx.appcompat.app.AlertDialog.Builder(this) AlertDialog dialog = new AlertDialog.Builder(this).setView(dialogView).create();
.setView(dialogView) if (dialog.getWindow() != null) dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
.create();
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
btnCancel.setOnClickListener(v -> dialog.dismiss()); btnCancel.setOnClickListener(v -> dialog.dismiss());
btnSubmit.setOnClickListener(v -> { btnSubmit.setOnClickListener(v -> {
float rating = (float) ratingBar.getRating(); float rating = (float) ratingBar.getRating();
String revText = input.getText().toString().trim(); String revText = input.getText().toString().trim();
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
com.google.firebase.auth.FirebaseUser currentUser = com.google.firebase.auth.FirebaseAuth.getInstance() String author = "Visitante";
.getCurrentUser(); String userEmail = user != null ? user.getEmail() : null;
if (currentUser != null && currentUser.getEmail() != null) {
String userDoc = currentUser.getEmail().replace(".", "_").replace("@", "_at_"); submitReviewToFirestore(author, revText, rating, userEmail);
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("Clientes").child(userDoc).get()
.addOnSuccessListener(snapshot -> {
String authorName = snapshot.exists()
&& snapshot.child("displayName").getValue(String.class) != null
? snapshot.child("displayName").getValue(String.class)
: "Visitante";
submitReviewToFirebase(authorName, revText, rating);
dialog.dismiss(); dialog.dismiss();
}).addOnFailureListener(e -> {
submitReviewToFirebase("Visitante", revText, rating);
dialog.dismiss();
});
} else {
submitReviewToFirebase("Visitante", revText, rating);
dialog.dismiss();
}
}); });
dialog.show(); dialog.show();
} }
private void submitReviewToFirebase(String authorName, String revText, float newRating) { private void submitReviewToFirestore(String author, String text, float rating, String userEmail) {
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_"); Map<String, Object> review = new HashMap<>();
review.put("restaurantId", selectedRestaurant.getId());
review.put("author", author);
review.put("text", text);
review.put("rating", rating);
review.put("userEmail", userEmail);
review.put("timestamp", com.google.firebase.Timestamp.now());
com.google.firebase.auth.FirebaseUser currentUser = com.google.firebase.auth.FirebaseAuth.getInstance() db.collection("Reviews").add(review).addOnSuccessListener(documentReference -> {
.getCurrentUser(); Snackbar.make(findViewById(R.id.explorarRoot), "Obrigado pela tua avaliação!", Snackbar.LENGTH_LONG).show();
String uEmail = currentUser != null ? currentUser.getEmail() : null; recalculateRestaurantAverage(selectedRestaurant.getId());
// 1. Guardar a Review
java.util.Map<String, Object> review = new java.util.HashMap<>();
review.put("author", authorName);
review.put("text", revText);
review.put("rating", newRating);
review.put("userEmail", uEmail);
review.put("timestamp", com.google.firebase.database.ServerValue.TIMESTAMP);
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reviews")
.child(encodedEmail).push().setValue(review).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
com.google.android.material.snackbar.Snackbar
.make(findViewById(R.id.explorarRoot), "Obrigado pela tua avaliação!",
com.google.android.material.snackbar.Snackbar.LENGTH_LONG)
.show();
}
});
// 2. Atualizar Média no Restaurante
com.google.firebase.database.DatabaseReference restRef = com.google.firebase.database.FirebaseDatabase
.getInstance().getReference("Restaurantes").child(encodedEmail);
restRef.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
Double currentAvg = snapshot.child("ratingAvg").getValue(Double.class);
Integer currentCount = snapshot.child("ratingCount").getValue(Integer.class);
if (currentAvg == null)
currentAvg = 0.0;
if (currentCount == null)
currentCount = 0;
double newAvg = ((currentAvg * currentCount) + newRating) / (currentCount + 1);
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("ratingAvg", newAvg);
updates.put("ratingCount", currentCount + 1);
restRef.updateChildren(updates);
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
}
}); });
} }
private void saveReservation() { private void saveReservation() {
android.widget.EditText etPartySize = findViewById(R.id.etPartySize); EditText etPartySize = findViewById(R.id.etPartySize);
int partySize = 0; int partySize = 0;
try { try {
partySize = Integer.parseInt(etPartySize.getText().toString()); partySize = Integer.parseInt(etPartySize.getText().toString());
} catch (Exception e) { } catch (Exception ignored) {}
}
if (selectedDate == null || selectedTime == null || partySize == 0) { if (selectedDate == null || selectedTime == null || partySize == 0) {
android.widget.Toast.makeText(this, "Por favor, selecione data, hora e número de pessoas.", Toast.makeText(this, "Por favor, selecione data, hora e número de pessoas.", Toast.LENGTH_SHORT).show();
android.widget.Toast.LENGTH_SHORT).show();
return; return;
} }
String userEmail = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser() != null String userEmail = FirebaseAuth.getInstance().getCurrentUser() != null
? com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser().getEmail() ? FirebaseAuth.getInstance().getCurrentUser().getEmail() : "cliente@teste.com";
: "cliente@teste.com";
com.google.firebase.database.DatabaseReference ref = com.google.firebase.database.FirebaseDatabase.getInstance() Reserva reserva = new Reserva(
.getReference("reservas"); null,
String id = ref.push().getKey();
com.example.pap_teste.models.Reserva reserva = new com.example.pap_teste.models.Reserva(
id,
userEmail, userEmail,
selectedRestaurant.getName(), selectedRestaurant.getName(),
selectedRestaurant.getEmail(), selectedRestaurant.getEmail(),
selectedDate, selectedDate,
selectedTime, selectedTime,
partySize, partySize,
"Pendente"); "Pendente"
);
if (id != null) { db.collection("Reservas").add(reserva).addOnSuccessListener(documentReference -> {
ref.child(id).setValue(reserva).addOnCompleteListener(task -> { Toast.makeText(this, "Reserva solicitada com sucesso!", Toast.LENGTH_SHORT).show();
if (task.isSuccessful()) {
android.widget.Toast
.makeText(this, "Reserva solicitada com sucesso!", android.widget.Toast.LENGTH_SHORT)
.show();
finish(); finish();
} else { }).addOnFailureListener(e -> Toast.makeText(this, "Erro ao salvar reserva.", Toast.LENGTH_SHORT).show());
android.widget.Toast.makeText(this, "Erro ao salvar reserva.", android.widget.Toast.LENGTH_SHORT)
.show();
}
});
}
} }
} }

View File

@@ -1,47 +1,51 @@
package com.example.pap_teste; package com.example.pap_teste;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets; import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.RecyclerView;
import android.widget.Button;
import android.widget.Toast;
import com.example.pap_teste.models.Restaurant; import com.example.pap_teste.models.Restaurant;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot; import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.database.DatabaseError; import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class FavoritosActivity extends AppCompatActivity { public class FavoritosActivity extends AppCompatActivity {
private androidx.recyclerview.widget.RecyclerView rv; private RecyclerView rv;
private RestaurantAdapter adapter; private RestaurantAdapter adapter;
private List<Restaurant> list; private List<Restaurant> list;
private android.widget.ProgressBar progressBar; private ProgressBar progressBar;
private android.view.View emptyState; private View emptyState;
private final FirebaseFirestore db = FirebaseFirestore.getInstance();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
EdgeToEdge.enable(this); EdgeToEdge.enable(this);
setContentView(R.layout.activity_favoritos); setContentView(R.layout.activity_favoritos);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.favoritosRoot), (v, insets) -> {
View root = findViewById(R.id.favoritosRoot);
if (root != null) {
ViewCompat.setOnApplyWindowInsetsListener(root, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets; return insets;
}); });
}
Button back = findViewById(R.id.btnVoltar); Button back = findViewById(R.id.btnVoltar);
if (back != null) { if (back != null) {
@@ -54,8 +58,8 @@ public class FavoritosActivity extends AppCompatActivity {
list = new ArrayList<>(); list = new ArrayList<>();
adapter = new RestaurantAdapter(list, restaurant -> { adapter = new RestaurantAdapter(list, restaurant -> {
android.content.Intent intent = new android.content.Intent(this, ExplorarRestaurantesActivity.class); Intent intent = new Intent(this, RestaurantDetailActivity.class);
intent.putExtra("category_filter", restaurant.getCategory()); // just as demo intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_ID, restaurant.getId());
startActivity(intent); startActivity(intent);
}); });
rv.setAdapter(adapter); rv.setAdapter(adapter);
@@ -65,35 +69,24 @@ public class FavoritosActivity extends AppCompatActivity {
private void setupFavoritesList() { private void setupFavoritesList() {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null && user.getEmail() != null) { if (user != null) {
String encodedUserEmail = user.getEmail().replace(".", "_").replace("@", "_at_"); if (progressBar != null) progressBar.setVisibility(View.VISIBLE);
DatabaseReference favRef = FirebaseDatabase.getInstance().getReference("Clientes")
.child(encodedUserEmail).child("favorites"); db.collection("Clientes").document(user.getUid()).collection("favorites")
.addSnapshotListener((value, error) -> {
if (progressBar != null) progressBar.setVisibility(View.GONE);
if (value == null) return;
favRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
if (progressBar != null)
progressBar.setVisibility(android.view.View.GONE);
list.clear(); list.clear();
for (DataSnapshot ds : snapshot.getChildren()) { for (QueryDocumentSnapshot doc : value) {
Restaurant restaurant = ds.getValue(Restaurant.class); Restaurant restaurant = doc.toObject(Restaurant.class);
if (restaurant != null) { restaurant.setId(doc.getId());
list.add(restaurant); list.add(restaurant);
} }
}
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
if (emptyState != null) { if (emptyState != null) {
emptyState.setVisibility(list.isEmpty() ? android.view.View.VISIBLE : android.view.View.GONE); emptyState.setVisibility(list.isEmpty() ? View.VISIBLE : View.GONE);
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
if (progressBar != null)
progressBar.setVisibility(android.view.View.GONE);
Toast.makeText(FavoritosActivity.this, "Erro ao carregar favoritos.", Toast.LENGTH_SHORT).show();
} }
}); });
} }

View File

@@ -12,17 +12,15 @@ import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Restaurant; import com.example.pap_teste.models.Restaurant;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser; import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DataSnapshot; import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.database.DatabaseError; import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.util.List; import java.util.List;
public class FeaturedRestaurantAdapter extends RecyclerView.Adapter<FeaturedRestaurantAdapter.ViewHolder> { public class FeaturedRestaurantAdapter extends RecyclerView.Adapter<FeaturedRestaurantAdapter.ViewHolder> {
private List<Restaurant> restaurants; private List<Restaurant> restaurants;
private RestaurantAdapter.OnRestaurantClickListener listener; private RestaurantAdapter.OnRestaurantClickListener listener;
private final FirebaseFirestore db = FirebaseFirestore.getInstance();
public FeaturedRestaurantAdapter(List<Restaurant> restaurants, public FeaturedRestaurantAdapter(List<Restaurant> restaurants,
RestaurantAdapter.OnRestaurantClickListener listener) { RestaurantAdapter.OnRestaurantClickListener listener) {
@@ -45,20 +43,17 @@ public class FeaturedRestaurantAdapter extends RecyclerView.Adapter<FeaturedRest
if (holder.txtRating != null) { if (holder.txtRating != null) {
if (restaurant.getRatingAvg() != null && restaurant.getRatingAvg() > 0) { if (restaurant.getRatingAvg() != null && restaurant.getRatingAvg() > 0) {
holder.txtRating holder.txtRating.setText(String.format(java.util.Locale.getDefault(), "%.1f", restaurant.getRatingAvg()));
.setText(String.format(java.util.Locale.getDefault(), "%.1f", restaurant.getRatingAvg()));
} else { } else {
holder.txtRating.setText("Novo"); holder.txtRating.setText("Novo");
} }
} }
if (restaurant.getLogoUrl() != null && !restaurant.getLogoUrl().isEmpty()) { if (restaurant.getImageURL() != null && !restaurant.getImageURL().isEmpty()) {
com.bumptech.glide.Glide.with(holder.itemView.getContext()) com.bumptech.glide.Glide.with(holder.itemView.getContext())
.load(restaurant.getLogoUrl()) .load(restaurant.getImageURL())
.centerCrop() .centerCrop()
.into(holder.imgCover); .into(holder.imgCover);
} else {
holder.imgCover.setImageResource(R.mipmap.ic_launcher);
} }
holder.itemView.setOnClickListener(v -> { holder.itemView.setOnClickListener(v -> {
@@ -69,49 +64,28 @@ public class FeaturedRestaurantAdapter extends RecyclerView.Adapter<FeaturedRest
if (holder.btnFavorite != null) { if (holder.btnFavorite != null) {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null && user.getEmail() != null && restaurant.getEmail() != null) { if (user != null && restaurant.getId() != null) {
String encodedUserEmail = user.getEmail().replace(".", "_").replace("@", "_at_"); DocumentReference favRef = db.collection("Clientes").document(user.getUid())
String encodedRestEmail = restaurant.getEmail().replace(".", "_").replace("@", "_at_"); .collection("favorites").document(restaurant.getId());
DatabaseReference favRef = FirebaseDatabase.getInstance().getReference("Clientes")
.child(encodedUserEmail).child("favorites").child(encodedRestEmail);
// Read initial state favRef.get().addOnSuccessListener(documentSnapshot -> {
favRef.addListenerForSingleValueEvent(new ValueEventListener() { boolean exists = documentSnapshot.exists();
@Override holder.btnFavorite.setTag(exists);
public void onDataChange(@NonNull DataSnapshot snapshot) { updateFavoriteUI(holder, exists);
holder.btnFavorite.setTag(snapshot.exists());
if (snapshot.exists()) {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on);
holder.btnFavorite.setColorFilter(androidx.core.content.ContextCompat
.getColor(holder.itemView.getContext(), R.color.colorPrimary));
} else {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off);
holder.btnFavorite.clearColorFilter();
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
}); });
holder.btnFavorite.setOnClickListener(v -> { holder.btnFavorite.setOnClickListener(v -> {
// Optimistic UI update
boolean isFav = holder.btnFavorite.getTag() != null && (Boolean) holder.btnFavorite.getTag(); boolean isFav = holder.btnFavorite.getTag() != null && (Boolean) holder.btnFavorite.getTag();
boolean newFavState = !isFav; boolean newFavState = !isFav;
holder.btnFavorite.setTag(newFavState); holder.btnFavorite.setTag(newFavState);
updateFavoriteUI(holder, newFavState);
if (newFavState) { if (newFavState) {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on); favRef.set(restaurant);
holder.btnFavorite.setColorFilter(androidx.core.content.ContextCompat
.getColor(holder.itemView.getContext(), R.color.colorPrimary));
favRef.setValue(restaurant);
com.google.android.material.snackbar.Snackbar.make(v, "Adicionado aos Favoritos!", com.google.android.material.snackbar.Snackbar.make(v, "Adicionado aos Favoritos!",
com.google.android.material.snackbar.Snackbar.LENGTH_SHORT).show(); com.google.android.material.snackbar.Snackbar.LENGTH_SHORT).show();
} else { } else {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off); favRef.delete();
holder.btnFavorite.clearColorFilter();
favRef.removeValue();
com.google.android.material.snackbar.Snackbar.make(v, "Removido dos Favoritos.", com.google.android.material.snackbar.Snackbar.make(v, "Removido dos Favoritos.",
com.google.android.material.snackbar.Snackbar.LENGTH_SHORT).show(); com.google.android.material.snackbar.Snackbar.LENGTH_SHORT).show();
} }
@@ -120,6 +94,17 @@ public class FeaturedRestaurantAdapter extends RecyclerView.Adapter<FeaturedRest
} }
} }
private void updateFavoriteUI(ViewHolder holder, boolean isFav) {
if (isFav) {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on);
holder.btnFavorite.setColorFilter(androidx.core.content.ContextCompat
.getColor(holder.itemView.getContext(), R.color.colorPrimary));
} else {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off);
holder.btnFavorite.clearColorFilter();
}
}
@Override @Override
public int getItemCount() { public int getItemCount() {
return restaurants.size(); return restaurants.size();
@@ -134,7 +119,7 @@ public class FeaturedRestaurantAdapter extends RecyclerView.Adapter<FeaturedRest
super(itemView); super(itemView);
txtName = itemView.findViewById(R.id.restaurantName); txtName = itemView.findViewById(R.id.restaurantName);
txtCategory = itemView.findViewById(R.id.restaurantCuisine); txtCategory = itemView.findViewById(R.id.restaurantCuisine);
txtRating = null; // Removed because it's combined in restaurantCuisine in the XML or not present txtRating = null;
imgCover = itemView.findViewById(R.id.restaurantImage); imgCover = itemView.findViewById(R.id.restaurantImage);
btnFavorite = itemView.findViewById(R.id.btnFavorite); btnFavorite = itemView.findViewById(R.id.btnFavorite);
} }

View File

@@ -23,14 +23,14 @@ public class FirestoreManager {
} }
public CollectionReference getUsersCollection() { public CollectionReference getUsersCollection() {
return db.collection("users"); return db.collection("Clientes");
} }
public CollectionReference getRestaurantsCollection() { public CollectionReference getRestaurantsCollection() {
return db.collection("restaurants"); return db.collection("Restaurantes");
} }
public CollectionReference getReservationsCollection() { public CollectionReference getReservationsCollection() {
return db.collection("reservations"); return db.collection("Reservas");
} }
} }

View File

@@ -38,7 +38,12 @@ public class FoodCategoryAdapter extends RecyclerView.Adapter<FoodCategoryAdapte
public void onBindViewHolder(@NonNull ViewHolder holder, int position) { public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
FoodCategory category = categories.get(position); FoodCategory category = categories.get(position);
holder.txtName.setText(category.getName()); holder.txtName.setText(category.getName());
if (category.getImageResId() != 0) { if (category.getImageUrl() != null && !category.getImageUrl().isEmpty()) {
com.bumptech.glide.Glide.with(holder.itemView.getContext())
.load(category.getImageUrl())
.centerCrop()
.into(holder.imgCategory);
} else if (category.getImageResId() != 0) {
com.bumptech.glide.Glide.with(holder.itemView.getContext()) com.bumptech.glide.Glide.with(holder.itemView.getContext())
.load(category.getImageResId()) .load(category.getImageResId())
.centerCrop() .centerCrop()

View File

@@ -2,17 +2,25 @@ package com.example.pap_teste;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.EditText;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Restaurant; import com.example.pap_teste.models.Restaurant;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.QueryDocumentSnapshot; import com.google.firebase.firestore.QueryDocumentSnapshot;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -20,9 +28,12 @@ public class HomeFragment extends Fragment {
private RecyclerView rvFeatured, rvNearYou; private RecyclerView rvFeatured, rvNearYou;
private RestaurantAdapter featuredAdapter, nearYouAdapter; private RestaurantAdapter featuredAdapter, nearYouAdapter;
private List<Restaurant> featuredList = new ArrayList<>(); private final List<Restaurant> allRestaurants = new ArrayList<>();
private List<Restaurant> nearYouList = new ArrayList<>(); private final List<Restaurant> featuredList = new ArrayList<>();
private FirebaseFirestore db; private final List<Restaurant> nearYouList = new ArrayList<>();
private ChipGroup categoryChips;
private EditText etSearch;
private final FirebaseFirestore db = FirebaseFirestore.getInstance();
@Nullable @Nullable
@Override @Override
@@ -31,11 +42,13 @@ public class HomeFragment extends Fragment {
rvFeatured = view.findViewById(R.id.rvFeatured); rvFeatured = view.findViewById(R.id.rvFeatured);
rvNearYou = view.findViewById(R.id.rvNearYou); rvNearYou = view.findViewById(R.id.rvNearYou);
categoryChips = view.findViewById(R.id.categoryChips);
db = FirebaseFirestore.getInstance(); etSearch = view.findViewById(R.id.etSearch);
setupRecyclers(); setupRecyclers();
loadCategories();
loadRestaurants(); loadRestaurants();
setupSearch();
return view; return view;
} }
@@ -50,21 +63,89 @@ public class HomeFragment extends Fragment {
rvNearYou.setLayoutManager(new LinearLayoutManager(getContext())); rvNearYou.setLayoutManager(new LinearLayoutManager(getContext()));
} }
private void loadCategories() {
db.collection("Categorias").get().addOnSuccessListener(queryDocumentSnapshots -> {
categoryChips.removeAllViews();
// Add "Tudo" chip
addCategoryChip("Tudo", true);
for (QueryDocumentSnapshot doc : queryDocumentSnapshots) {
String name = doc.getString("name");
if (name != null) {
addCategoryChip(name, false);
}
}
categoryChips.setOnCheckedStateChangeListener((group, checkedIds) -> {
if (checkedIds.isEmpty()) {
filterRestaurants("Tudo");
} else {
Chip chip = group.findViewById(checkedIds.get(0));
filterRestaurants(chip.getText().toString());
}
});
});
}
private void addCategoryChip(String name, boolean isSelected) {
Chip chip = (Chip) getLayoutInflater().inflate(R.layout.layout_filter_chip, categoryChips, false);
chip.setText(name);
chip.setChecked(isSelected);
categoryChips.addView(chip);
}
private void loadRestaurants() { private void loadRestaurants() {
db.collection("restaurants").get().addOnCompleteListener(task -> { FirestoreManager.getInstance().getRestaurantsCollection().get().addOnCompleteListener(task -> {
if (task.isSuccessful()) { if (task.isSuccessful()) {
featuredList.clear(); allRestaurants.clear();
nearYouList.clear();
for (QueryDocumentSnapshot document : task.getResult()) { for (QueryDocumentSnapshot document : task.getResult()) {
Restaurant r = document.toObject(Restaurant.class); Restaurant r = document.toObject(Restaurant.class);
r.setId(document.getId()); r.setId(document.getId());
allRestaurants.add(r);
}
filterRestaurants("Tudo");
}
});
}
private void setupSearch() {
etSearch.addTextChangedListener(new TextWatcher() {
@Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override public void onTextChanged(CharSequence s, int start, int before, int count) {
searchRestaurants(s.toString());
}
@Override public void afterTextChanged(Editable s) {}
});
}
private void filterRestaurants(String category) {
featuredList.clear();
nearYouList.clear();
for (Restaurant r : allRestaurants) {
if (category.equals("Tudo") || category.equalsIgnoreCase(r.getCategory())) {
featuredList.add(r); featuredList.add(r);
nearYouList.add(r); nearYouList.add(r);
} }
}
featuredAdapter.notifyDataSetChanged(); featuredAdapter.notifyDataSetChanged();
nearYouAdapter.notifyDataSetChanged(); nearYouAdapter.notifyDataSetChanged();
} }
});
private void searchRestaurants(String query) {
featuredList.clear();
nearYouList.clear();
for (Restaurant r : allRestaurants) {
if (r.getName().toLowerCase().contains(query.toLowerCase()) ||
r.getCategory().toLowerCase().contains(query.toLowerCase())) {
featuredList.add(r);
nearYouList.add(r);
}
}
featuredAdapter.notifyDataSetChanged();
nearYouAdapter.notifyDataSetChanged();
} }
private void onRestaurantClick(Restaurant restaurant) { private void onRestaurantClick(Restaurant restaurant) {
@@ -72,8 +153,8 @@ public class HomeFragment extends Fragment {
intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_ID, restaurant.getId()); intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_ID, restaurant.getId());
intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_NAME, restaurant.getName()); intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_NAME, restaurant.getName());
intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_IMAGE, restaurant.getImageURL()); intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_IMAGE, restaurant.getImageURL());
intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_CUISINE, restaurant.getCuisine()); intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_CUISINE, restaurant.getCategory());
intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_RATING, restaurant.getRating()); intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_RATING, restaurant.getRatingAvg());
intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_DESC, restaurant.getDescription()); intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_DESC, restaurant.getDescription());
intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_ADDRESS, restaurant.getAddress()); intent.putExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_ADDRESS, restaurant.getAddress());
startActivity(intent); startActivity(intent);

View File

@@ -1,67 +0,0 @@
package com.example.pap_teste;
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_teste.models.LocalReservation;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.chip.Chip;
import java.util.List;
public class LocalReservaAdapter extends RecyclerView.Adapter<LocalReservaAdapter.ViewHolder> {
private final List<LocalReservation> reservations;
private final OnActionListener listener;
public interface OnActionListener {
void onCancel(LocalReservation reservation);
void onEdit(LocalReservation reservation);
}
public LocalReservaAdapter(List<LocalReservation> reservations, OnActionListener listener) {
this.reservations = reservations;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_reserva_local, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
LocalReservation res = reservations.get(position);
holder.restaurantName.setText(res.getRestaurantName());
holder.dateInfo.setText(res.getDate() + " às " + res.getTime());
holder.tableInfo.setText("Mesa " + res.getTableNumber() + "" + res.getGuests() + " pessoas");
holder.statusChip.setText(res.getStatus());
holder.btnCancel.setOnClickListener(v -> listener.onCancel(res));
holder.itemView.setOnClickListener(v -> listener.onEdit(res));
}
@Override
public int getItemCount() {
return reservations.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView restaurantName, dateInfo, tableInfo;
Chip statusChip;
MaterialButton btnCancel;
ViewHolder(View itemView) {
super(itemView);
restaurantName = itemView.findViewById(R.id.txtRestauranteNome);
dateInfo = itemView.findViewById(R.id.txtDataHora);
tableInfo = itemView.findViewById(R.id.txtMesaPessoas);
statusChip = itemView.findViewById(R.id.chipStatus);
btnCancel = itemView.findViewById(R.id.btnCancelar);
}
}
}

View File

@@ -4,8 +4,11 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import com.example.pap_teste.models.User; import com.example.pap_teste.models.User;
import com.google.android.material.button.MaterialButton; import com.google.android.material.button.MaterialButton;
import com.google.android.material.button.MaterialButtonToggleGroup; import com.google.android.material.button.MaterialButtonToggleGroup;
@@ -13,7 +16,6 @@ import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.firestore.FirebaseFirestore;
import java.util.Date;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
public static final String EXTRA_DISPLAY_NAME = "extra_display_name"; public static final String EXTRA_DISPLAY_NAME = "extra_display_name";
@@ -30,6 +32,7 @@ public class MainActivity extends AppCompatActivity {
private TextInputLayout layoutName; private TextInputLayout layoutName;
private TextInputEditText inputName, inputEmail, inputPassword; private TextInputEditText inputName, inputEmail, inputPassword;
private MaterialButton btnPrimaryAction; private MaterialButton btnPrimaryAction;
private ProgressBar progressBar;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -38,17 +41,14 @@ public class MainActivity extends AppCompatActivity {
mAuth = FirebaseAuth.getInstance(); mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance(); db = FirebaseFirestore.getInstance();
progressBar = findViewById(R.id.progressBarLogin); // Assuming it exists or I'll add it
// Check if user is already logged in
if (mAuth.getCurrentUser() != null) { if (mAuth.getCurrentUser() != null) {
android.app.ActivityOptions options = android.app.ActivityOptions.makeCustomAnimation(this, android.R.anim.fade_in, android.R.anim.fade_out); checkUserRoleAndRedirect(mAuth.getCurrentUser().getUid());
startActivity(new Intent(this, ClientDashboardActivity.class), options.toBundle()); } else {
finish();
return;
}
initViews(); initViews();
} }
}
private void initViews() { private void initViews() {
layoutName = findViewById(R.id.layoutName); layoutName = findViewById(R.id.layoutName);
@@ -63,20 +63,37 @@ public class MainActivity extends AppCompatActivity {
isLoginMode = checkedId == R.id.btnEntrar; isLoginMode = checkedId == R.id.btnEntrar;
layoutName.setVisibility(isLoginMode ? View.GONE : View.VISIBLE); layoutName.setVisibility(isLoginMode ? View.GONE : View.VISIBLE);
btnPrimaryAction.setText(isLoginMode ? "Entrar" : "Criar Conta"); btnPrimaryAction.setText(isLoginMode ? "Entrar" : "Criar Conta");
findViewById(R.id.txtForgotPassword).setVisibility(isLoginMode ? View.VISIBLE : View.GONE);
} }
}); });
btnPrimaryAction.setOnClickListener(v -> handleAuth()); btnPrimaryAction.setOnClickListener(v -> handleAuth());
findViewById(R.id.txtForgotPassword).setOnClickListener(v -> {
String email = inputEmail.getText().toString().trim();
if (TextUtils.isEmpty(email)) {
Toast.makeText(this, "Insira o seu email.", Toast.LENGTH_SHORT).show();
return;
} }
mAuth.sendPasswordResetEmail(email).addOnSuccessListener(aVoid ->
Toast.makeText(this, "Email de recuperação enviado.", Toast.LENGTH_SHORT).show()); private void checkUserRoleAndRedirect(String uid) {
if (progressBar != null) progressBar.setVisibility(View.VISIBLE);
// ONLY allow Clientes in the mobile app
db.collection("Clientes").document(uid).get().addOnSuccessListener(docClient -> {
if (progressBar != null) progressBar.setVisibility(View.GONE);
if (docClient.exists()) {
startActivity(new Intent(this, ClientDashboardActivity.class));
finish();
} else {
// Check if it's a Restaurant to give a better error message
db.collection("Restaurantes").document(uid).get().addOnSuccessListener(docRest -> {
if (docRest.exists()) {
Toast.makeText(this, "Contas de Restaurante devem usar o Dashboard Web.", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Conta de Cliente não encontrada.", Toast.LENGTH_LONG).show();
}
mAuth.signOut();
initViews();
});
}
}).addOnFailureListener(e -> {
if (progressBar != null) progressBar.setVisibility(View.GONE);
Toast.makeText(this, "Erro ao verificar conta: " + e.getMessage(), Toast.LENGTH_SHORT).show();
initViews();
}); });
} }
@@ -86,19 +103,22 @@ public class MainActivity extends AppCompatActivity {
String name = inputName.getText().toString().trim(); String name = inputName.getText().toString().trim();
if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password)) { if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password)) {
Toast.makeText(this, "Preencha todos os campos.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Preencha os campos obrigatorios.", Toast.LENGTH_SHORT).show();
return; return;
} }
if (progressBar != null) progressBar.setVisibility(View.VISIBLE);
if (isLoginMode) { if (isLoginMode) {
mAuth.signInWithEmailAndPassword(email, password) mAuth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener(authResult -> { .addOnSuccessListener(authResult -> checkUserRoleAndRedirect(authResult.getUser().getUid()))
startActivity(new Intent(this, ClientDashboardActivity.class)); .addOnFailureListener(e -> {
finish(); if (progressBar != null) progressBar.setVisibility(View.GONE);
}) Toast.makeText(this, "Falha no login: " + e.getMessage(), Toast.LENGTH_LONG).show();
.addOnFailureListener(e -> Toast.makeText(this, "Erro ao entrar: " + e.getMessage(), Toast.LENGTH_SHORT).show()); });
} else { } else {
if (TextUtils.isEmpty(name)) { if (TextUtils.isEmpty(name)) {
if (progressBar != null) progressBar.setVisibility(View.GONE);
Toast.makeText(this, "Insira o seu nome.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Insira o seu nome.", Toast.LENGTH_SHORT).show();
return; return;
} }
@@ -106,15 +126,16 @@ public class MainActivity extends AppCompatActivity {
.addOnSuccessListener(authResult -> { .addOnSuccessListener(authResult -> {
String uid = authResult.getUser().getUid(); String uid = authResult.getUser().getUid();
User newUser = new User(uid, name, email, "CLIENTE"); User newUser = new User(uid, name, email, "CLIENTE");
db.collection("users").document(uid).set(newUser) db.collection("Clientes").document(uid).set(newUser)
.addOnSuccessListener(aVoid -> { .addOnSuccessListener(aVoid -> {
android.app.ActivityOptions options = android.app.ActivityOptions.makeCustomAnimation(this, android.R.anim.fade_in, android.R.anim.fade_out); startActivity(new Intent(this, ClientDashboardActivity.class));
startActivity(new Intent(this, ClientDashboardActivity.class), options.toBundle());
finish(); finish();
}); });
}) })
.addOnFailureListener(e -> Toast.makeText(this, "Erro ao registar: " + e.getMessage(), Toast.LENGTH_SHORT).show()); .addOnFailureListener(e -> {
if (progressBar != null) progressBar.setVisibility(View.GONE);
Toast.makeText(this, "Erro no registo: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
} }
} }
} }

View File

@@ -2,11 +2,12 @@ package com.example.pap_teste;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets; import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
@@ -14,11 +15,11 @@ import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Reserva; import com.example.pap_teste.models.Reserva;
import com.google.firebase.database.DataSnapshot; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.DatabaseError; import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.DatabaseReference; import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.firestore.Query;
import com.google.firebase.database.ValueEventListener; import com.google.firebase.firestore.QueryDocumentSnapshot;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -28,40 +29,36 @@ public class MinhasReservasActivity extends AppCompatActivity {
private RecyclerView rvMinhasReservas; private RecyclerView rvMinhasReservas;
private ReservaAdapter adapter; private ReservaAdapter adapter;
private final List<Reserva> reservaList = new ArrayList<>(); private final List<Reserva> reservaList = new ArrayList<>();
private DatabaseReference databaseReference; private final FirebaseFirestore db = FirebaseFirestore.getInstance();
private String clientEmail; private String clientEmail;
private android.widget.ProgressBar progressBar; private ProgressBar progressBar;
private android.view.View emptyState; private View emptyState;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
EdgeToEdge.enable(this); EdgeToEdge.enable(this);
setContentView(R.layout.activity_minhas_reservas); setContentView(R.layout.activity_minhas_reservas);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.minhasReservasRoot), (v, insets) -> {
View root = findViewById(R.id.minhasReservasRoot);
if (root != null) {
ViewCompat.setOnApplyWindowInsetsListener(root, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets; return insets;
}); });
}
clientEmail = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (clientEmail == null) {
com.google.firebase.auth.FirebaseUser user = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
if (user != null) { if (user != null) {
clientEmail = user.getEmail(); clientEmail = user.getEmail();
} }
}
// Fallback for testing if still null
if (clientEmail == null) {
clientEmail = "cliente@teste.com";
}
rvMinhasReservas = findViewById(R.id.rvMinhasReservas); rvMinhasReservas = findViewById(R.id.rvMinhasReservas);
progressBar = findViewById(R.id.progressBar); progressBar = findViewById(R.id.progressBar);
emptyState = findViewById(R.id.emptyState); emptyState = findViewById(R.id.emptyState);
Button btnVoltar = findViewById(R.id.btnVoltar);
btnVoltar.setOnClickListener(v -> finish()); findViewById(R.id.btnVoltar).setOnClickListener(v -> finish());
setupAdapter(); setupAdapter();
loadReservations(); loadReservations();
@@ -85,48 +82,33 @@ public class MinhasReservasActivity extends AppCompatActivity {
} }
private void loadReservations() { private void loadReservations() {
databaseReference = FirebaseDatabase.getInstance().getReference("reservas"); if (clientEmail == null) return;
if (progressBar != null) progressBar.setVisibility(android.view.View.VISIBLE);
if (emptyState != null) emptyState.setVisibility(android.view.View.GONE); if (progressBar != null) progressBar.setVisibility(View.VISIBLE);
db.collection("Reservas").whereEqualTo("clienteEmail", clientEmail)
.orderBy("createdAt", Query.Direction.DESCENDING)
.addSnapshotListener((value, error) -> {
if (progressBar != null) progressBar.setVisibility(View.GONE);
if (value == null) return;
databaseReference.orderByChild("clienteEmail").equalTo(clientEmail)
.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
if (progressBar != null) progressBar.setVisibility(android.view.View.GONE);
reservaList.clear(); reservaList.clear();
for (DataSnapshot dataSnapshot : snapshot.getChildren()) { for (QueryDocumentSnapshot doc : value) {
Reserva reserva = dataSnapshot.getValue(Reserva.class); Reserva r = doc.toObject(Reserva.class);
if (reserva != null) { r.setId(doc.getId());
reservaList.add(reserva); reservaList.add(r);
} }
}
// Order reservations (newest first based on ID or we can just reverse the list)
java.util.Collections.reverse(reservaList);
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
if (emptyState != null) { if (emptyState != null) {
emptyState.setVisibility(reservaList.isEmpty() ? android.view.View.VISIBLE : android.view.View.GONE); emptyState.setVisibility(reservaList.isEmpty() ? View.VISIBLE : View.GONE);
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
if (progressBar != null) progressBar.setVisibility(android.view.View.GONE);
Toast.makeText(MinhasReservasActivity.this, "Erro ao carregar reservas.", Toast.LENGTH_SHORT)
.show();
} }
}); });
} }
private void cancelReservation(Reserva reserva) { private void cancelReservation(Reserva reserva) {
databaseReference.child(reserva.getId()).child("estado").setValue("Cancelada") if (reserva.getId() == null) return;
.addOnCompleteListener(task -> { db.collection("Reservas").document(reserva.getId()).update("estado", "Cancelada")
if (task.isSuccessful()) { .addOnSuccessListener(aVoid -> Toast.makeText(this, "Reserva cancelada.", Toast.LENGTH_SHORT).show());
Toast.makeText(this, "Reserva cancelada.", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Erro ao cancelar reserva.", Toast.LENGTH_SHORT).show();
}
});
} }
} }

View File

@@ -5,26 +5,31 @@ import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.LocalReservation;
import com.example.pap_teste.viewmodels.ReservationViewModel; import com.example.pap_teste.models.Reserva;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class MyReservationsFragment extends Fragment { public class MyReservationsFragment extends Fragment {
private RecyclerView rvReservations; private RecyclerView rvReservations;
private LocalReservaAdapter adapter; private ReservaAdapter adapter;
private List<LocalReservation> reservationList = new ArrayList<>(); private List<Reserva> reservationList = new ArrayList<>();
private ProgressBar progressBar; private ProgressBar progressBar;
private View emptyState; private View emptyState;
private ReservationViewModel viewModel;
@Nullable @Nullable
@Override @Override
@@ -35,39 +40,59 @@ public class MyReservationsFragment extends Fragment {
progressBar = view.findViewById(R.id.progressBar); progressBar = view.findViewById(R.id.progressBar);
emptyState = view.findViewById(R.id.emptyState); emptyState = view.findViewById(R.id.emptyState);
viewModel = new ViewModelProvider(this).get(ReservationViewModel.class);
setupAdapter(); setupAdapter();
observeReservations(); loadReservationsFromFirestore();
return view; return view;
} }
private void setupAdapter() { private void setupAdapter() {
adapter = new LocalReservaAdapter(reservationList, new LocalReservaAdapter.OnActionListener() { adapter = new ReservaAdapter(reservationList, new ReservaAdapter.OnReservaActionListener() {
@Override @Override
public void onCancel(LocalReservation reservation) { public void onCheckIn(Reserva reserva) {
reservation.setStatus("CANCELADA"); Toast.makeText(getContext(), "Funcionalidade de Check-in em breve", Toast.LENGTH_SHORT).show();
viewModel.update(reservation);
Toast.makeText(getContext(), "Reserva cancelada.", Toast.LENGTH_SHORT).show();
} }
@Override @Override
public void onEdit(LocalReservation reservation) { public void onCancel(Reserva reserva) {
// Show details or edit cancelReservation(reserva);
Toast.makeText(getContext(), "Detalhes: " + reservation.getNotes(), Toast.LENGTH_LONG).show();
} }
}); });
rvReservations.setLayoutManager(new LinearLayoutManager(getContext())); rvReservations.setLayoutManager(new LinearLayoutManager(getContext()));
rvReservations.setAdapter(adapter); rvReservations.setAdapter(adapter);
} }
private void observeReservations() { private void cancelReservation(Reserva reserva) {
progressBar.setVisibility(View.VISIBLE); if (reserva.getId() == null) return;
viewModel.getAllReservations().observe(getViewLifecycleOwner(), reservations -> { FirestoreManager.getInstance().getReservationsCollection().document(reserva.getId())
.update("estado", "Cancelada")
.addOnSuccessListener(aVoid -> Toast.makeText(getContext(), "Reserva cancelada.", Toast.LENGTH_SHORT).show());
}
private void loadReservationsFromFirestore() {
String email = FirebaseAuth.getInstance().getCurrentUser() != null
? FirebaseAuth.getInstance().getCurrentUser().getEmail() : null;
if (email == null) {
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
emptyState.setVisibility(View.VISIBLE);
return;
}
progressBar.setVisibility(View.VISIBLE);
FirestoreManager.getInstance().getReservationsCollection()
.whereEqualTo("clienteEmail", email)
.orderBy("createdAt", Query.Direction.DESCENDING)
.addSnapshotListener((value, error) -> {
progressBar.setVisibility(View.GONE);
if (value == null) return;
reservationList.clear(); reservationList.clear();
reservationList.addAll(reservations); for (QueryDocumentSnapshot doc : value) {
Reserva r = doc.toObject(Reserva.class);
r.setId(doc.getId());
reservationList.add(r);
}
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
emptyState.setVisibility(reservationList.isEmpty() ? View.VISIBLE : View.GONE); emptyState.setVisibility(reservationList.isEmpty() ? View.VISIBLE : View.GONE);
}); });

View File

@@ -9,27 +9,24 @@ import android.widget.EditText;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets; import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.FoodCategory; import com.example.pap_teste.models.FoodCategory;
import com.example.pap_teste.models.LocalReservation;
import com.example.pap_teste.models.Mesa; import com.example.pap_teste.models.Mesa;
import com.example.pap_teste.models.Reserva;
import com.example.pap_teste.models.Restaurant; import com.example.pap_teste.models.Restaurant;
import com.example.pap_teste.viewmodels.ReservationViewModel;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.DataSnapshot; import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.database.DatabaseError; import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.List; import java.util.List;
@@ -50,7 +47,7 @@ public class NovaReservaActivity extends AppCompatActivity {
private View scrollNovaReserva; private View scrollNovaReserva;
private TextView txtTitle; private TextView txtTitle;
private ProgressBar progressBar; private ProgressBar progressBar;
private ReservationViewModel viewModel; private final FirebaseFirestore db = FirebaseFirestore.getInstance();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -58,13 +55,14 @@ public class NovaReservaActivity extends AppCompatActivity {
EdgeToEdge.enable(this); EdgeToEdge.enable(this);
setContentView(R.layout.activity_nova_reserva); setContentView(R.layout.activity_nova_reserva);
viewModel = new ViewModelProvider(this).get(ReservationViewModel.class); View root = findViewById(R.id.novaReservaRoot);
if (root != null) {
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.novaReservaRoot), (v, insets) -> { ViewCompat.setOnApplyWindowInsetsListener(root, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets; return insets;
}); });
}
initViews(); initViews();
setupCategories(); setupCategories();
@@ -110,12 +108,12 @@ public class NovaReservaActivity extends AppCompatActivity {
} }
private void setupCategories() { private void setupCategories() {
db.collection("Categorias").get().addOnSuccessListener(queryDocumentSnapshots -> {
List<FoodCategory> cats = new ArrayList<>(); List<FoodCategory> cats = new ArrayList<>();
cats.add(new FoodCategory("Carnes", R.drawable.cat_carnes)); for (QueryDocumentSnapshot doc : queryDocumentSnapshots) {
cats.add(new FoodCategory("Massas", R.drawable.cat_massas)); FoodCategory cat = doc.toObject(FoodCategory.class);
cats.add(new FoodCategory("Sushi", R.drawable.cat_sushi)); cats.add(cat);
cats.add(new FoodCategory("Pizzas", R.drawable.cat_pizzas)); }
cats.add(new FoodCategory("Sobremesas", R.drawable.cat_sobremesas));
rvCategories.setLayoutManager(new LinearLayoutManager(this)); rvCategories.setLayoutManager(new LinearLayoutManager(this));
rvCategories.setAdapter(new FoodCategoryAdapter(cats, category -> { rvCategories.setAdapter(new FoodCategoryAdapter(cats, category -> {
@@ -124,65 +122,44 @@ public class NovaReservaActivity extends AppCompatActivity {
loadRestaurants(); loadRestaurants();
updateViewState(); updateViewState();
})); }));
});
} }
private void loadRestaurants() { private void loadRestaurants() {
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("Restaurantes"); db.collection("Restaurantes").whereEqualTo("category", selectedCategory).get().addOnSuccessListener(queryDocumentSnapshots -> {
ref.orderByChild("category").equalTo(selectedCategory).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
List<Restaurant> list = new ArrayList<>(); List<Restaurant> list = new ArrayList<>();
for (DataSnapshot ds : snapshot.getChildren()) { for (QueryDocumentSnapshot doc : queryDocumentSnapshots) {
String name = ds.child("establishmentName").getValue(String.class); Restaurant r = doc.toObject(Restaurant.class);
if (name == null) name = ds.child("displayName").getValue(String.class); r.setId(doc.getId());
String email = ds.child("email").getValue(String.class); list.add(r);
String cat = ds.child("category").getValue(String.class);
String logo = ds.child("logoUrl").getValue(String.class);
if (name != null && email != null) {
list.add(new Restaurant(name, cat, email, false, logo));
} }
} rvRestaurants.setLayoutManager(new LinearLayoutManager(this));
rvRestaurants.setLayoutManager(new LinearLayoutManager(NovaReservaActivity.this));
rvRestaurants.setAdapter(new RestaurantAdapter(list, restaurant -> { rvRestaurants.setAdapter(new RestaurantAdapter(list, restaurant -> {
selectedRestaurant = restaurant; selectedRestaurant = restaurant;
currentState = State.DETAILS; currentState = State.DETAILS;
loadTables(); loadTables();
updateViewState(); updateViewState();
})); }));
} }).addOnFailureListener(e -> {
@Override
public void onCancelled(@NonNull DatabaseError error) {
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
Toast.makeText(NovaReservaActivity.this, "Erro ao carregar.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Erro ao carregar restaurantes.", Toast.LENGTH_SHORT).show();
}
}); });
} }
private void loadTables() { private void loadTables() {
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("Mesas"); db.collection("Mesas").whereEqualTo("restauranteEmail", selectedRestaurant.getEmail()).get().addOnSuccessListener(queryDocumentSnapshots -> {
ref.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
List<Mesa> tables = new ArrayList<>(); List<Mesa> tables = new ArrayList<>();
for (DataSnapshot ds : snapshot.getChildren()) { for (QueryDocumentSnapshot doc : queryDocumentSnapshots) {
Mesa m = ds.getValue(Mesa.class); Mesa m = doc.toObject(Mesa.class);
if (m != null && selectedRestaurant.getEmail().equalsIgnoreCase(m.getRestauranteEmail())) { m.setId(doc.getId());
tables.add(m); tables.add(m);
} }
} rvTables.setLayoutManager(new GridLayoutManager(this, 3));
rvTables.setLayoutManager(new GridLayoutManager(NovaReservaActivity.this, 3));
rvTables.setAdapter(new TableSelectionAdapter(tables, mesa -> selectedMesa = mesa)); rvTables.setAdapter(new TableSelectionAdapter(tables, mesa -> selectedMesa = mesa));
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
progressBar.setVisibility(View.GONE);
}
}); });
} }
@@ -204,60 +181,69 @@ public class NovaReservaActivity extends AppCompatActivity {
private void validateAndSave() { private void validateAndSave() {
EditText etPartySize = findViewById(R.id.etPartySize); EditText etPartySize = findViewById(R.id.etPartySize);
EditText etNotes = findViewById(R.id.etNotes);
String partySizeStr = etPartySize.getText().toString(); String partySizeStr = etPartySize.getText().toString();
if (selectedDate == null || selectedTime == null || selectedMesa == null || partySizeStr.isEmpty()) { if (selectedDate == null || selectedTime == null || selectedMesa == null || partySizeStr.isEmpty()) {
Toast.makeText(this, "Preencha todos os campos e escolha a mesa.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Preencha todos os campos.", Toast.LENGTH_SHORT).show();
return; return;
} }
int partySize = Integer.parseInt(partySizeStr); int partySize = Integer.parseInt(partySizeStr);
if (partySize > selectedMesa.getCapacidade()) { if (partySize > selectedMesa.getCapacidade()) {
Toast.makeText(this, "A mesa escolhida só suporta " + selectedMesa.getCapacidade() + " pessoas.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "A mesa só suporta " + selectedMesa.getCapacidade() + " pessoas.", Toast.LENGTH_SHORT).show();
return; return;
} }
// Conflict check checkConflictAndSave(partySize);
viewModel.checkConflict(selectedDate, selectedMesa.getNumero(), isAvailable -> {
runOnUiThread(() -> {
if (isAvailable) {
saveToFirebaseAndRoom(partySize, etNotes.getText().toString());
} else {
Toast.makeText(this, "Esta mesa já está reservada para este dia.", Toast.LENGTH_LONG).show();
}
});
});
} }
private void saveToFirebaseAndRoom(int guests, String notes) { private void checkConflictAndSave(int guests) {
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("reservas"); db.collection("Reservas")
String id = ref.push().getKey(); .whereEqualTo("restauranteEmail", selectedRestaurant.getEmail())
String userEmail = FirebaseAuth.getInstance().getCurrentUser() != null ? FirebaseAuth.getInstance().getCurrentUser().getEmail() : "anon@test.com"; .whereEqualTo("data", selectedDate)
.whereEqualTo("hora", selectedTime)
.get()
.addOnSuccessListener(queryDocumentSnapshots -> {
boolean conflict = false;
for (QueryDocumentSnapshot doc : queryDocumentSnapshots) {
Long tableNum = doc.getLong("tableNumber");
if (tableNum != null && tableNum == selectedMesa.getNumero()) {
conflict = true;
break;
}
}
LocalReservation local = new LocalReservation(); if (conflict) {
local.setFirebaseId(id); progressBar.setVisibility(View.GONE);
local.setRestaurantName(selectedRestaurant.getName()); Toast.makeText(this, "Esta mesa já está reservada.", Toast.LENGTH_LONG).show();
local.setRestaurantEmail(selectedRestaurant.getEmail()); } else {
local.setDate(selectedDate); saveToFirestore(guests);
local.setTime(selectedTime); }
local.setGuests(guests); });
local.setTableNumber(selectedMesa.getNumero()); }
local.setNotes(notes);
local.setStatus("PENDENTE"); private void saveToFirestore(int guests) {
String userEmail = FirebaseAuth.getInstance().getCurrentUser() != null ? FirebaseAuth.getInstance().getCurrentUser().getEmail() : "anon@test.com";
if (id != null) { Reserva reserva = new Reserva(
ref.child(id).setValue(local).addOnCompleteListener(task -> { null,
userEmail,
selectedRestaurant.getName(),
selectedRestaurant.getEmail(),
selectedDate,
selectedTime,
guests,
"Pendente"
);
reserva.setTableNumber(selectedMesa.getNumero());
db.collection("Reservas").add(reserva).addOnSuccessListener(documentReference -> {
progressBar.setVisibility(View.GONE); progressBar.setVisibility(View.GONE);
if (task.isSuccessful()) {
viewModel.insert(local);
Toast.makeText(this, "Reserva realizada com sucesso!", Toast.LENGTH_LONG).show(); Toast.makeText(this, "Reserva realizada com sucesso!", Toast.LENGTH_LONG).show();
finish(); finish();
} else { }).addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao guardar no servidor.", Toast.LENGTH_SHORT).show(); progressBar.setVisibility(View.GONE);
} Toast.makeText(this, "Erro ao guardar reserva.", Toast.LENGTH_SHORT).show();
}); });
} }
}
} }

View File

@@ -3,71 +3,60 @@ package com.example.pap_teste;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets; import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import android.view.View;
import android.net.Uri;
import android.widget.ImageView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.storage.FirebaseStorage; import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference; import com.google.firebase.storage.StorageReference;
import java.util.UUID;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.auth.FirebaseAuth;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID;
public class ProfileDashboardActivity extends AppCompatActivity { public class ProfileDashboardActivity extends AppCompatActivity {
private EditText inputName, inputPhone, inputEmailEdit; private EditText inputName, inputPhone, inputEmailEdit;
private String email, documentId, photoUrl; private String photoUrl;
private DatabaseReference databaseReference;
private ImageView imgProfile; private ImageView imgProfile;
private androidx.activity.result.ActivityResultLauncher<Intent> imagePickerLauncher; private androidx.activity.result.ActivityResultLauncher<Intent> imagePickerLauncher;
private final FirebaseFirestore db = FirebaseFirestore.getInstance();
private final FirebaseAuth mAuth = FirebaseAuth.getInstance();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
EdgeToEdge.enable(this); EdgeToEdge.enable(this);
setContentView(R.layout.activity_profile_dashboard); setContentView(R.layout.activity_profile_dashboard);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.profileRoot), (v, insets) -> {
View root = findViewById(R.id.profileRoot);
if (root != null) {
ViewCompat.setOnApplyWindowInsetsListener(root, (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets; return insets;
}); });
email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
String currentName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME);
if (email != null) {
documentId = email.replace(".", "_").replace("@", "_at_");
} }
databaseReference = FirebaseDatabase.getInstance().getReference().child("Clientes");
inputName = findViewById(R.id.inputProfileName); inputName = findViewById(R.id.inputProfileName);
inputPhone = findViewById(R.id.inputProfilePhone); inputPhone = findViewById(R.id.inputProfilePhone);
inputEmailEdit = findViewById(R.id.inputProfileEmail); inputEmailEdit = findViewById(R.id.inputProfileEmail);
imgProfile = findViewById(R.id.imgProfile); imgProfile = findViewById(R.id.imgProfile);
if (currentName != null) { fetchProfileData();
inputName.setText(currentName);
}
if (email != null) {
inputEmailEdit.setText(email);
}
fetchAdditionalProfileData();
imagePickerLauncher = registerForActivityResult( imagePickerLauncher = registerForActivityResult(
new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(), new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
@@ -82,7 +71,7 @@ public class ProfileDashboardActivity extends AppCompatActivity {
findViewById(R.id.cardProfileBig).setOnClickListener(v -> { findViewById(R.id.cardProfileBig).setOnClickListener(v -> {
String[] options = {"Galeria", "URL da Imagem"}; String[] options = {"Galeria", "URL da Imagem"};
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Escolher Foto de Perfil"); builder.setTitle("Escolher Foto de Perfil");
builder.setItems(options, (dialog, which) -> { builder.setItems(options, (dialog, which) -> {
if (which == 0) { if (which == 0) {
@@ -96,79 +85,48 @@ public class ProfileDashboardActivity extends AppCompatActivity {
builder.show(); builder.show();
}); });
Button btnSave = findViewById(R.id.btnSaveProfile); findViewById(R.id.btnVoltar).setOnClickListener(v -> finish());
Button btnBack = findViewById(R.id.btnVoltar); findViewById(R.id.btnSaveProfile).setOnClickListener(v -> saveProfile());
Button btnFavs = findViewById(R.id.btnFavoritos); findViewById(R.id.btnLogOut).setOnClickListener(v -> performLogOut());
Button btnRes = findViewById(R.id.btnMinhasReservas); findViewById(R.id.btnFavoritos).setOnClickListener(v -> startActivity(new Intent(this, FavoritosActivity.class)));
Button btnLogOut = findViewById(R.id.btnLogOut); findViewById(R.id.btnMinhasReservas).setOnClickListener(v -> startActivity(new Intent(this, MinhasReservasActivity.class)));
btnBack.setOnClickListener(v -> finish());
btnSave.setOnClickListener(v -> saveProfile());
btnLogOut.setOnClickListener(v -> performLogOut());
btnFavs.setOnClickListener(v -> {
startActivity(new Intent(this, FavoritosActivity.class));
});
btnRes.setOnClickListener(v -> {
startActivity(new Intent(this, MinhasReservasActivity.class));
});
} }
private void performLogOut() { private void performLogOut() {
try { mAuth.signOut();
FirebaseAuth.getInstance().signOut(); Toast.makeText(this, "Sessão terminada.", Toast.LENGTH_SHORT).show();
Toast.makeText(this, "Sessão terminada com sucesso.", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(this, MainActivity.class); Intent intent = new Intent(this, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent); startActivity(intent);
finish(); finish();
} catch (Exception e) {
Toast.makeText(this, "Erro ao terminar sessão: " + e.getMessage(), Toast.LENGTH_LONG).show();
android.util.Log.e("LogOut", "Error during sign out", e);
}
} }
private void showUrlInputDialog() { private void showUrlInputDialog() {
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Inserir URL da Imagem"); builder.setTitle("Inserir URL da Imagem");
final EditText input = new EditText(this); final EditText input = new EditText(this);
input.setInputType(android.text.InputType.TYPE_TEXT_VARIATION_URI); input.setInputType(android.text.InputType.TYPE_TEXT_VARIATION_URI);
input.setHint("https://exemplo.com/foto.jpg");
builder.setView(input); builder.setView(input);
builder.setPositiveButton("Confirmar", (dialog, which) -> { builder.setPositiveButton("Confirmar", (dialog, which) -> {
String url = input.getText().toString().trim(); photoUrl = input.getText().toString().trim();
if (!TextUtils.isEmpty(url)) { if (!TextUtils.isEmpty(photoUrl)) {
this.photoUrl = url;
Glide.with(this).load(photoUrl).circleCrop().into(imgProfile); Glide.with(this).load(photoUrl).circleCrop().into(imgProfile);
Toast.makeText(this, "URL definida!", Toast.LENGTH_SHORT).show();
} }
}); });
builder.setNegativeButton("Cancelar", (dialog, which) -> dialog.cancel()); builder.setNegativeButton("Cancelar", null);
builder.show(); builder.show();
} }
private void fetchAdditionalProfileData() { private void fetchProfileData() {
if (documentId == null) FirebaseUser user = mAuth.getCurrentUser();
return; if (user == null) return;
databaseReference.child(documentId).get().addOnSuccessListener(snapshot -> {
db.collection("Clientes").document(user.getUid()).get().addOnSuccessListener(snapshot -> {
if (snapshot.exists()) { if (snapshot.exists()) {
String phone = snapshot.child("ownerPhone").getValue(String.class); inputName.setText(snapshot.getString("displayName"));
if (phone == null) inputPhone.setText(snapshot.getString("phone"));
phone = snapshot.child("phone").getValue(String.class); inputEmailEdit.setText(snapshot.getString("email"));
if (phone != null) photoUrl = snapshot.getString("photoUrl");
inputPhone.setText(phone);
String dbEmail = snapshot.child("email").getValue(String.class);
if (dbEmail != null)
inputEmailEdit.setText(dbEmail);
photoUrl = snapshot.child("photoUrl").getValue(String.class);
if (photoUrl != null && !photoUrl.isEmpty()) { if (photoUrl != null && !photoUrl.isEmpty()) {
Glide.with(this).load(photoUrl).circleCrop().into(imgProfile); Glide.with(this).load(photoUrl).circleCrop().into(imgProfile);
} }
@@ -176,65 +134,36 @@ public class ProfileDashboardActivity extends AppCompatActivity {
}); });
} }
private void uploadImageToFirebase(Uri imageUri) { private void uploadImageToFirebase(android.net.Uri imageUri) {
if (documentId == null) FirebaseUser user = mAuth.getCurrentUser();
return; if (user == null) return;
StorageReference storageRef = FirebaseStorage.getInstance().getReference() StorageReference storageRef = FirebaseStorage.getInstance().getReference()
.child("profile_pics/" + UUID.randomUUID().toString()); .child("profile_pics/" + user.getUid());
storageRef.putFile(imageUri).addOnSuccessListener(taskSnapshot -> { storageRef.putFile(imageUri).addOnSuccessListener(taskSnapshot -> {
storageRef.getDownloadUrl().addOnSuccessListener(uri -> { storageRef.getDownloadUrl().addOnSuccessListener(uri -> {
photoUrl = uri.toString(); photoUrl = uri.toString();
Glide.with(this).load(photoUrl).circleCrop().into(imgProfile); Glide.with(this).load(photoUrl).circleCrop().into(imgProfile);
db.collection("Clientes").document(user.getUid()).update("photoUrl", photoUrl);
// Salvar a nova URL da foto imediatamente na DB
Map<String, Object> photoUpdate = new HashMap<>();
photoUpdate.put("photoUrl", photoUrl);
databaseReference.child(documentId).updateChildren(photoUpdate).addOnCompleteListener(dbTask -> {
if (dbTask.isSuccessful()) {
Toast.makeText(this, "Foto atualizada com sucesso!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Foto enviada, mas não foi possível atualizar o perfil.", Toast.LENGTH_SHORT).show();
}
}); });
}); });
}).addOnFailureListener(e -> {
Toast.makeText(this, "Falha no upload: " + e.getMessage(), Toast.LENGTH_LONG).show();
android.util.Log.e("ProfileUpload", "Upload failed", e);
});
} }
private void saveProfile() { private void saveProfile() {
if (documentId == null) FirebaseUser user = mAuth.getCurrentUser();
return; if (user == null) return;
String newName = inputName.getText().toString().trim();
String newPhone = inputPhone.getText().toString().trim();
String newEmail = inputEmailEdit.getText().toString().trim();
if (TextUtils.isEmpty(newName)) {
Toast.makeText(this, "Indique um nome.", Toast.LENGTH_SHORT).show();
return;
}
Map<String, Object> updates = new HashMap<>(); Map<String, Object> updates = new HashMap<>();
updates.put("displayName", newName); updates.put("displayName", inputName.getText().toString().trim());
updates.put("phone", newPhone); updates.put("phone", inputPhone.getText().toString().trim());
updates.put("email", newEmail); updates.put("email", inputEmailEdit.getText().toString().trim());
if (photoUrl != null) { if (photoUrl != null) updates.put("photoUrl", photoUrl);
updates.put("photoUrl", photoUrl);
}
databaseReference.child(documentId).updateChildren(updates) db.collection("Clientes").document(user.getUid()).update(updates)
.addOnSuccessListener(aVoid -> { .addOnSuccessListener(aVoid -> {
Toast.makeText(this, "Perfil atualizado!", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Perfil atualizado!", Toast.LENGTH_SHORT).show();
Intent resultIntent = new Intent();
resultIntent.putExtra(MainActivity.EXTRA_DISPLAY_NAME, newName);
setResult(RESULT_OK, resultIntent);
finish(); finish();
}) });
.addOnFailureListener(
e -> Toast.makeText(this, "Falha ao atualizar perfil.", Toast.LENGTH_SHORT).show());
} }
} }

View File

@@ -39,11 +39,6 @@ public class ProfileFragment extends Fragment {
} }
} }
view.findViewById(R.id.btnSeedData).setOnClickListener(v -> {
DataSeeder.seedRestaurants();
Toast.makeText(getContext(), "Dados semeados com sucesso!", Toast.LENGTH_SHORT).show();
});
view.findViewById(R.id.btnLogout).setOnClickListener(v -> { view.findViewById(R.id.btnLogout).setOnClickListener(v -> {
FirebaseAuth.getInstance().signOut(); FirebaseAuth.getInstance().signOut();
Intent intent = new Intent(getActivity(), MainActivity.class); Intent intent = new Intent(getActivity(), MainActivity.class);

View File

@@ -1,24 +0,0 @@
package com.example.pap_teste.data;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import com.example.pap_teste.models.LocalReservation;
@Database(entities = {LocalReservation.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase instance;
public abstract ReservationDao reservationDao();
public static synchronized AppDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "namesa_database")
.fallbackToDestructiveMigration()
.build();
}
return instance;
}
}

View File

@@ -1,29 +0,0 @@
package com.example.pap_teste.data;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Update;
import com.example.pap_teste.models.LocalReservation;
import java.util.List;
@Dao
public interface ReservationDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(LocalReservation reservation);
@Update
void update(LocalReservation reservation);
@Delete
void delete(LocalReservation reservation);
@Query("SELECT * FROM local_reservations ORDER BY date DESC, time DESC")
LiveData<List<LocalReservation>> getAllReservations();
@Query("SELECT * FROM local_reservations WHERE date = :date AND tableNumber = :tableId AND status != 'CANCELLED'")
List<LocalReservation> getReservationsForTable(String date, int tableId);
}

View File

@@ -3,17 +3,21 @@ package com.example.pap_teste.models;
public class FoodCategory { public class FoodCategory {
private String name; private String name;
private int imageResId; private int imageResId;
private String imageUrl;
public FoodCategory() {}
public FoodCategory(String name, int imageResId) { public FoodCategory(String name, int imageResId) {
this.name = name; this.name = name;
this.imageResId = imageResId; this.imageResId = imageResId;
} }
public String getName() { public String getName() { return name; }
return name; public void setName(String name) { this.name = name; }
}
public int getImageResId() { public int getImageResId() { return imageResId; }
return imageResId; public void setImageResId(int imageResId) { this.imageResId = imageResId; }
}
public String getImageUrl() { return imageUrl; }
public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
} }

View File

@@ -1,51 +0,0 @@
package com.example.pap_teste.models;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "local_reservations")
public class LocalReservation {
@PrimaryKey(autoGenerate = true)
private int id;
private String firebaseId;
private String restaurantName;
private String restaurantEmail;
private String date;
private String time;
private int guests;
private String status;
private int tableNumber;
private String notes;
public LocalReservation() {}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getFirebaseId() { return firebaseId; }
public void setFirebaseId(String firebaseId) { this.firebaseId = firebaseId; }
public String getRestaurantName() { return restaurantName; }
public void setRestaurantName(String restaurantName) { this.restaurantName = restaurantName; }
public String getRestaurantEmail() { return restaurantEmail; }
public void setRestaurantEmail(String restaurantEmail) { this.restaurantEmail = restaurantEmail; }
public String getDate() { return date; }
public void setDate(String date) { this.date = date; }
public String getTime() { return time; }
public void setTime(String time) { this.time = time; }
public int getGuests() { return guests; }
public void setGuests(int guests) { this.guests = guests; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public int getTableNumber() { return tableNumber; }
public void setTableNumber(int tableNumber) { this.tableNumber = tableNumber; }
public String getNotes() { return notes; }
public void setNotes(String notes) { this.notes = notes; }
}

View File

@@ -1,23 +1,19 @@
package com.example.pap_teste.models; package com.example.pap_teste.models;
import com.google.firebase.Timestamp; import com.google.firebase.Timestamp;
import java.util.Date;
public class Reserva { public class Reserva {
private String id; private String id;
private String userId;
private String restaurantId;
private String restaurantName;
private String clienteEmail; private String clienteEmail;
private String restauranteName; private String restauranteName;
private String restauranteEmail; private String restauranteEmail;
private String data; private String data;
private String hora; private String hora;
private com.google.firebase.Timestamp date;
private int pessoas; private int pessoas;
private int guests; private int tableNumber;
private String estado; private String estado; // "Pendente", "Confirmada", "Cancelada"
private String status;
private Timestamp date;
private String timeSlot;
private String specialRequests; private String specialRequests;
private Timestamp createdAt; private Timestamp createdAt;
@@ -34,43 +30,56 @@ public class Reserva {
this.hora = hora; this.hora = hora;
this.pessoas = pessoas; this.pessoas = pessoas;
this.estado = estado; this.estado = estado;
this.createdAt = new Timestamp(new Date());
} }
// Existing getters/setters
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getClienteEmail() { return clienteEmail; } public String getClienteEmail() { return clienteEmail; }
public void setClienteEmail(String clienteEmail) { this.clienteEmail = clienteEmail; } public void setClienteEmail(String clienteEmail) { this.clienteEmail = clienteEmail; }
public String getRestauranteName() { return restauranteName; } public String getRestauranteName() { return restauranteName; }
public void setRestauranteName(String restauranteName) { this.restauranteName = restauranteName; } public void setRestauranteName(String restauranteName) { this.restauranteName = restauranteName; }
public String getRestauranteEmail() { return restauranteEmail; } public String getRestauranteEmail() { return restauranteEmail; }
public void setRestauranteEmail(String restauranteEmail) { this.restauranteEmail = restauranteEmail; } public void setRestauranteEmail(String restauranteEmail) { this.restauranteEmail = restauranteEmail; }
public String getData() { return data; } public String getData() { return data; }
public void setData(String data) { this.data = data; } public void setData(String data) { this.data = data; }
public String getHora() { return hora; } public String getHora() { return hora; }
public void setHora(String hora) { this.hora = hora; } public void setHora(String hora) { this.hora = hora; }
public com.google.firebase.Timestamp getDate() { return date; }
public void setDate(com.google.firebase.Timestamp date) { this.date = date; }
public int getPessoas() { return pessoas; } public int getPessoas() { return pessoas; }
public void setPessoas(int pessoas) { this.pessoas = pessoas; } public void setPessoas(int pessoas) { this.pessoas = pessoas; }
public int getTableNumber() { return tableNumber; }
public void setTableNumber(int tableNumber) { this.tableNumber = tableNumber; }
public String getEstado() { return estado; } public String getEstado() { return estado; }
public void setEstado(String estado) { this.estado = estado; } public void setEstado(String estado) { this.estado = estado; }
// New getters/setters to fix ReservationActivity
public String getUserId() { return userId != null ? userId : clienteEmail; }
public void setUserId(String userId) { this.userId = userId; }
public String getRestaurantId() { return restaurantId != null ? restaurantId : restauranteEmail; }
public void setRestaurantId(String restaurantId) { this.restaurantId = restaurantId; }
public String getRestaurantName() { return restaurantName != null ? restaurantName : restauranteName; }
public void setRestaurantName(String restaurantName) { this.restaurantName = restaurantName; }
public int getGuests() { return guests != 0 ? guests : pessoas; }
public void setGuests(int guests) { this.guests = guests; }
public String getStatus() { return status != null ? status : estado; }
public void setStatus(String status) { this.status = status; }
public Timestamp getDate() { return date; }
public void setDate(Timestamp date) { this.date = date; }
public String getTimeSlot() { return timeSlot != null ? timeSlot : hora; }
public void setTimeSlot(String timeSlot) { this.timeSlot = timeSlot; }
public String getSpecialRequests() { return specialRequests; } public String getSpecialRequests() { return specialRequests; }
public void setSpecialRequests(String specialRequests) { this.specialRequests = specialRequests; } public void setSpecialRequests(String specialRequests) { this.specialRequests = specialRequests; }
public Timestamp getCreatedAt() { return createdAt; } public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; } public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
// Compatibility mappings for ReservationActivity
public String getUserId() { return clienteEmail; }
public void setUserId(String userId) { this.clienteEmail = userId; }
public String getRestaurantId() { return restauranteEmail; }
public void setRestaurantId(String restaurantId) { this.restauranteEmail = restaurantId; }
public String getRestaurantName() { return restauranteName; }
public void setRestaurantName(String restaurantName) { this.restauranteName = restaurantName; }
public int getGuests() { return pessoas; }
public void setGuests(int guests) { this.pessoas = guests; }
public String getStatus() { return estado; }
public void setStatus(String status) { this.estado = status; }
public String getTimeSlot() { return hora; }
public void setTimeSlot(String timeSlot) { this.hora = timeSlot; }
} }

View File

@@ -6,29 +6,26 @@ import java.util.Map;
public class Restaurant { public class Restaurant {
private String id; private String id;
private String name; private String name;
private String cuisine;
private String category; private String category;
private String email; private String email;
private String description; private String description;
private String imageURL; private String imageURL;
private String logoUrl;
private double rating; private double rating;
private Double ratingAvg; private Double ratingAvg;
private Integer ratingCount;
private String address; private String address;
private Map<String, String> openingHours; // { "open": "09:00", "close": "22:00" } private Map<String, String> openingHours;
private List<String> availableSlots; private List<String> availableSlots;
private boolean favorite; private boolean favorite;
public Restaurant() {} public Restaurant() {}
public Restaurant(String name, String category, String email, boolean favorite, String logoUrl) { public Restaurant(String name, String category, String email, boolean favorite, String imageURL) {
this.name = name; this.name = name;
this.category = category; this.category = category;
this.email = email; this.email = email;
this.favorite = favorite; this.favorite = favorite;
this.logoUrl = logoUrl; this.imageURL = imageURL;
this.cuisine = category;
this.imageURL = logoUrl;
} }
public String getId() { return id; } public String getId() { return id; }
@@ -37,10 +34,7 @@ public class Restaurant {
public String getName() { return name; } public String getName() { return name; }
public void setName(String name) { this.name = name; } public void setName(String name) { this.name = name; }
public String getCuisine() { return cuisine; } public String getCategory() { return category; }
public void setCuisine(String cuisine) { this.cuisine = cuisine; }
public String getCategory() { return category != null ? category : cuisine; }
public void setCategory(String category) { this.category = category; } public void setCategory(String category) { this.category = category; }
public String getEmail() { return email; } public String getEmail() { return email; }
@@ -49,18 +43,18 @@ public class Restaurant {
public String getDescription() { return description; } public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; } public void setDescription(String description) { this.description = description; }
public String getImageURL() { return imageURL != null ? imageURL : logoUrl; } public String getImageURL() { return imageURL; }
public void setImageURL(String imageURL) { this.imageURL = imageURL; } public void setImageURL(String imageURL) { this.imageURL = imageURL; }
public String getLogoUrl() { return logoUrl != null ? logoUrl : imageURL; }
public void setLogoUrl(String logoUrl) { this.logoUrl = logoUrl; }
public double getRating() { return rating; } public double getRating() { return rating; }
public void setRating(double rating) { this.rating = rating; } public void setRating(double rating) { this.rating = rating; }
public Double getRatingAvg() { return ratingAvg; } public Double getRatingAvg() { return ratingAvg != null ? ratingAvg : rating; }
public void setRatingAvg(Double ratingAvg) { this.ratingAvg = ratingAvg; } public void setRatingAvg(Double ratingAvg) { this.ratingAvg = ratingAvg; }
public Integer getRatingCount() { return ratingCount != null ? ratingCount : 0; }
public void setRatingCount(Integer ratingCount) { this.ratingCount = ratingCount; }
public String getAddress() { return address; } public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; } public void setAddress(String address) { this.address = address; }
@@ -72,4 +66,10 @@ public class Restaurant {
public boolean isFavorite() { return favorite; } public boolean isFavorite() { return favorite; }
public void setFavorite(boolean favorite) { this.favorite = favorite; } public void setFavorite(boolean favorite) { this.favorite = favorite; }
// Compatibility mappings
public String getCuisine() { return category; }
public void setCuisine(String cuisine) { this.category = cuisine; }
public String getLogoUrl() { return imageURL; }
public void setLogoUrl(String logoUrl) { this.imageURL = logoUrl; }
} }

View File

@@ -8,15 +8,16 @@ public class User {
private String name; private String name;
private String email; private String email;
private String photoURL; private String photoURL;
private String role; // "CLIENTE" or "RESTAURANTE"
private Timestamp createdAt; private Timestamp createdAt;
public User() {} public User() {}
public User(String id, String name, String email, String photoURL) { public User(String id, String name, String email, String role) {
this.id = id; this.id = id;
this.name = name; this.name = name;
this.email = email; this.email = email;
this.photoURL = photoURL; this.role = role;
this.createdAt = new Timestamp(new Date()); this.createdAt = new Timestamp(new Date());
} }
@@ -28,6 +29,8 @@ public class User {
public void setEmail(String email) { this.email = email; } public void setEmail(String email) { this.email = email; }
public String getPhotoURL() { return photoURL; } public String getPhotoURL() { return photoURL; }
public void setPhotoURL(String photoURL) { this.photoURL = photoURL; } public void setPhotoURL(String photoURL) { this.photoURL = photoURL; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public Timestamp getCreatedAt() { return createdAt; } public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; } public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
} }

View File

@@ -1,53 +0,0 @@
package com.example.pap_teste.viewmodels;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.example.pap_teste.data.AppDatabase;
import com.example.pap_teste.data.ReservationDao;
import com.example.pap_teste.models.LocalReservation;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ReservationViewModel extends AndroidViewModel {
private final ReservationDao reservationDao;
private final ExecutorService executorService;
private final LiveData<List<LocalReservation>> allReservations;
public ReservationViewModel(@NonNull Application application) {
super(application);
AppDatabase db = AppDatabase.getInstance(application);
reservationDao = db.reservationDao();
allReservations = reservationDao.getAllReservations();
executorService = Executors.newSingleThreadExecutor();
}
public LiveData<List<LocalReservation>> getAllReservations() {
return allReservations;
}
public void insert(LocalReservation reservation) {
executorService.execute(() -> reservationDao.insert(reservation));
}
public void update(LocalReservation reservation) {
executorService.execute(() -> reservationDao.update(reservation));
}
public void delete(LocalReservation reservation) {
executorService.execute(() -> reservationDao.delete(reservation));
}
public void checkConflict(String date, int tableId, OnConflictCheckListener listener) {
executorService.execute(() -> {
List<LocalReservation> conflicts = reservationDao.getReservationsForTable(date, tableId);
listener.onResult(conflicts.isEmpty());
});
}
public interface OnConflictCheckListener {
void onResult(boolean isAvailable);
}
}

View File

@@ -99,7 +99,7 @@
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
android:hint="Nome Completo" android:hint="Nome Completo"
android:visibility="gone" android:visibility="gone"
app:startIconDrawable="@android:drawable/ic_menu_my_places"> app:startIconDrawable="@android:drawable/ic_menu_edit">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputName" android:id="@+id/inputName"
@@ -161,4 +161,15 @@
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<ProgressBar
android:id="@+id/progressBarLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:indeterminateTint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -102,24 +102,6 @@
android:layout_height="1dp" android:layout_height="1dp"
android:background="@color/colorBorder" /> android:background="@color/colorBorder" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSeedData"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="start|center_vertical"
android:text="Popular Base de Dados (Dev)"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp"
app:icon="@android:drawable/ic_menu_save"
app:iconPadding="16dp"
app:iconTint="@color/colorTextSecondary" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorBorder" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnLogout" android:id="@+id/btnLogout"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.TextButton"

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checkable="true"
android:textColor="@color/colorTextPrimary"
app:chipBackgroundColor="@color/colorChip"
app:chipStrokeWidth="0dp"
app:rippleColor="@color/colorPrimary" />