This commit is contained in:
2026-04-28 17:03:00 +01:00
parent f08e661796
commit c3aba53468
16 changed files with 1258 additions and 706 deletions

View File

@@ -2,7 +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.widget.Button; import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import androidx.activity.EdgeToEdge; import androidx.activity.EdgeToEdge;
@@ -10,214 +15,279 @@ 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 androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.FoodCategory;
import com.example.pap_teste.models.Restaurant;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class ClientDashboardActivity extends AppCompatActivity { public class ClientDashboardActivity extends AppCompatActivity {
private String email, displayName, role; private String email, displayName;
private TextView txtGreeting; private TextView txtGreeting;
private androidx.activity.result.ActivityResultLauncher<Intent> profileLauncher; private ImageView imgProfile;
private EditText etSearch;
private ChipGroup chipGroupCategories;
private RecyclerView rvFeatured, rvMainRestaurants;
private ProgressBar progressBar;
private View layoutFeatured, layoutAllRestaurants;
@Override private List<Restaurant> allRestaurants = new ArrayList<>();
protected void onCreate(Bundle savedInstanceState) { private List<Restaurant> filteredRestaurants = new ArrayList<>();
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this); private RestaurantAdapter mainAdapter;
setContentView(R.layout.activity_client_dashboard); private FeaturedRestaurantAdapter featuredAdapter;
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.clientRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
txtGreeting = findViewById(R.id.txtClientGreeting); private final String[] CATEGORIES = {"Tudo", "Carnes", "Massas", "Sushi", "Pizzas", "Sobremesas"};
private String currentCategoryFilter = "Tudo";
private String currentSearchFilter = "";
email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); @Override
displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); protected void onCreate(Bundle savedInstanceState) {
role = getIntent().getStringExtra(MainActivity.EXTRA_ROLE); super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_client_dashboard);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.clientRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
profileLauncher = registerForActivityResult( email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(), displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME);
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
String updatedName = result.getData()
.getStringExtra(MainActivity.EXTRA_DISPLAY_NAME);
if (updatedName != null) {
displayName = updatedName;
updateGreeting();
}
}
});
updateGreeting(); initViews();
fetchProfilePicture(); setupBottomNavigation();
setupCategories(); setupSearch();
setupActions(); setupCategories();
loadNextReservation();
updateGreeting();
fetchProfilePicture();
fetchRestaurants();
}
private void initViews() {
txtGreeting = findViewById(R.id.txtClientGreeting);
imgProfile = findViewById(R.id.imgProfile);
etSearch = findViewById(R.id.etSearch);
chipGroupCategories = findViewById(R.id.chipGroupCategories);
rvFeatured = findViewById(R.id.rvFeatured);
rvMainRestaurants = findViewById(R.id.rvMainRestaurants);
progressBar = findViewById(R.id.progressBar);
layoutFeatured = findViewById(R.id.layoutFeatured);
layoutAllRestaurants = findViewById(R.id.layoutAllRestaurants);
rvFeatured.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
rvMainRestaurants.setLayoutManager(new LinearLayoutManager(this));
// Click listener for restaurants to open booking flow
RestaurantAdapter.OnRestaurantClickListener clickListener = restaurant -> {
Intent intent = new Intent(this, ExplorarRestaurantesActivity.class);
// Reusing existing activity for details if needed, or pass data to NovaReservaActivity
// We pass the filter so it can maybe open directly or we just pass restaurant email
intent.putExtra("category_filter", restaurant.getCategory());
startActivity(intent);
};
featuredAdapter = new FeaturedRestaurantAdapter(new ArrayList<>(), clickListener);
mainAdapter = new RestaurantAdapter(filteredRestaurants, clickListener);
rvFeatured.setAdapter(featuredAdapter);
rvMainRestaurants.setAdapter(mainAdapter);
}
private void setupBottomNavigation() {
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigation);
bottomNav.setOnItemSelectedListener(item -> {
int id = item.getItemId();
if (id == R.id.nav_home) {
return true;
} else if (id == R.id.nav_reservations) {
Intent intent = new Intent(this, MinhasReservasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, email);
startActivity(intent);
return false; // Don't highlight if we open new activity
} else if (id == R.id.nav_profile) {
Intent intent = new Intent(this, ProfileDashboardActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, email);
intent.putExtra(MainActivity.EXTRA_DISPLAY_NAME, displayName);
startActivity(intent);
return false;
}
return false;
});
}
private void updateGreeting() {
if (displayName != null && !displayName.isEmpty()) {
txtGreeting.setText("Olá, " + displayName.split(" ")[0] + "!");
} else {
txtGreeting.setText("Olá, Visitante!");
} }
}
private void updateGreeting() { private void fetchProfilePicture() {
txtGreeting.setText(String.format("Olá, %s", displayName != null ? displayName : "convidado")); if (email == null) return;
} String documentId = email.replace(".", "_").replace("@", "_at_");
FirebaseDatabase.getInstance().getReference().child("users").child(documentId).child("photoUrl")
.get().addOnSuccessListener(snapshot -> {
if (!isDestroyed() && snapshot.exists() && snapshot.getValue(String.class) != null) {
com.bumptech.glide.Glide.with(this).load(snapshot.getValue(String.class)).circleCrop().into(imgProfile);
}
});
}
private void fetchProfilePicture() { private void fetchRestaurants() {
if (email == null) return; progressBar.setVisibility(View.VISIBLE);
String documentId = email.replace(".", "_").replace("@", "_at_"); layoutFeatured.setVisibility(View.GONE);
com.google.firebase.database.FirebaseDatabase.getInstance().getReference() layoutAllRestaurants.setVisibility(View.GONE);
.child("users").child(documentId).child("photoUrl")
.get().addOnSuccessListener(snapshot -> {
if (!isDestroyed() && snapshot.exists()) {
String photoUrl = snapshot.getValue(String.class);
if (photoUrl != null && !photoUrl.isEmpty()) {
android.widget.ImageView imgProfile = findViewById(R.id.imgProfile);
com.bumptech.glide.Glide.with(this).load(photoUrl).circleCrop().into(imgProfile);
}
}
});
}
private void setupCategories() { DatabaseReference usersRef = FirebaseDatabase.getInstance().getReference("users");
RecyclerView rv = findViewById(R.id.rvCategories); usersRef.addListenerForSingleValueEvent(new ValueEventListener() {
List<FoodCategory> cats = new ArrayList<>(); @Override
cats.add(new FoodCategory("Carnes", R.drawable.cat_carnes)); public void onDataChange(@androidx.annotation.NonNull DataSnapshot snapshot) {
cats.add(new FoodCategory("Massas", R.drawable.cat_massas)); progressBar.setVisibility(View.GONE);
cats.add(new FoodCategory("Sushi", R.drawable.cat_sushi)); allRestaurants.clear();
cats.add(new FoodCategory("Pizzas", R.drawable.cat_pizzas));
cats.add(new FoodCategory("Sobremesas", R.drawable.cat_sobremesas));
FoodCategoryAdapter adapter = new FoodCategoryAdapter(cats, category -> { for (DataSnapshot ds : snapshot.getChildren()) {
Intent intent = new Intent(this, ExplorarRestaurantesActivity.class); String role = ds.child("role").getValue(String.class);
intent.putExtra("category_filter", category.getName()); String accountType = ds.child("accountType").getValue(String.class);
startActivity(intent);
}); if ("ADMIN".equalsIgnoreCase(role) || "ESTABELECIMENTO".equalsIgnoreCase(accountType)) {
rv.setAdapter(adapter); 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);
private void setupActions() { String cat = ds.child("category").getValue(String.class);
findViewById(R.id.cardProfile).setOnClickListener(v -> { String logoUrl = ds.child("logoUrl").getValue(String.class);
Intent intent = new Intent(this, ProfileDashboardActivity.class); Double ratingAvg = ds.child("ratingAvg").getValue(Double.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, email); Integer ratingCount = ds.child("ratingCount").getValue(Integer.class);
intent.putExtra(MainActivity.EXTRA_DISPLAY_NAME, displayName);
profileLauncher.launch(intent); if (name != null && email != null) {
}); Restaurant r = new Restaurant(name, cat != null ? cat : "Vário", email, false, logoUrl);
if (ratingAvg != null) r.setRatingAvg(ratingAvg);
findViewById(R.id.btnVoltar).setOnClickListener(v -> finish()); if (ratingCount != null) r.setRatingCount(ratingCount);
allRestaurants.add(r);
findViewById(R.id.btnPartilhar).setOnClickListener(
v -> startActivity(new Intent(this, PartilharReservaActivity.class)));
findViewById(R.id.btnNovaReserva)
.setOnClickListener(v -> startActivity(new Intent(this, NovaReservaActivity.class)));
findViewById(R.id.btnMinhasReservas)
.setOnClickListener(v -> {
Intent intent = new Intent(this, MinhasReservasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, email);
startActivity(intent);
});
}
private void loadNextReservation() {
if (email == null) return;
com.google.firebase.database.DatabaseReference ref = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reservas");
ref.orderByChild("clienteEmail").equalTo(email).addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(com.google.firebase.database.DataSnapshot snapshot) {
com.example.pap_teste.models.Reserva proxima = null;
long timeProx = Long.MAX_VALUE;
long currentTime = System.currentTimeMillis();
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd/MM/yyyy HH:mm", java.util.Locale.getDefault());
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
com.example.pap_teste.models.Reserva r = ds.getValue(com.example.pap_teste.models.Reserva.class);
if (r != null && (r.getEstado().startsWith("Confirmada") || r.getEstado().equals("Pendente"))) {
try {
java.util.Date rDate = sdf.parse(r.getData() + " " + r.getHora());
if (rDate != null) {
long rTime = rDate.getTime();
// Consider reservations from the last 2 hours and in the future
if (rTime >= currentTime - 2L * 60 * 60 * 1000) {
if (rTime < timeProx) {
timeProx = rTime;
proxima = r;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
TextView txtTitle = findViewById(R.id.txtResTitle);
TextView txtTime = findViewById(R.id.txtResTime);
Button btnCheckIn = findViewById(R.id.btnCheckIn);
Button btnPartilhar = findViewById(R.id.btnPartilhar);
if (proxima != null) {
txtTitle.setText(proxima.getRestauranteName());
// Formatting to show "Hoje", "Amanhã" or the date itself
String dateStr = proxima.getData();
try {
java.util.Date rDate = sdf.parse(proxima.getData() + " " + proxima.getHora());
if (rDate != null) {
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.setTime(rDate);
java.util.Calendar today = java.util.Calendar.getInstance();
java.util.Calendar tmr = java.util.Calendar.getInstance();
tmr.add(java.util.Calendar.DAY_OF_YEAR, 1);
if (cal.get(java.util.Calendar.YEAR) == today.get(java.util.Calendar.YEAR) && cal.get(java.util.Calendar.DAY_OF_YEAR) == today.get(java.util.Calendar.DAY_OF_YEAR)) {
dateStr = "Hoje";
} else if (cal.get(java.util.Calendar.YEAR) == tmr.get(java.util.Calendar.YEAR) && cal.get(java.util.Calendar.DAY_OF_YEAR) == tmr.get(java.util.Calendar.DAY_OF_YEAR)) {
dateStr = "Amanhã";
}
}
} catch (Exception e) {
// ignore
}
txtTime.setText(String.format("%s às %s • %d pessoas", dateStr, proxima.getHora(), proxima.getPessoas()));
btnCheckIn.setVisibility(android.view.View.VISIBLE);
btnPartilhar.setVisibility(android.view.View.VISIBLE);
String encodedEmail = proxima.getRestauranteEmail().replace(".", "_").replace("@", "_at_");
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users").child(encodedEmail)
.child("logoUrl").get().addOnSuccessListener(s -> {
if (!isDestroyed() && s.exists() && s.getValue() != null) {
android.widget.ImageView imgResIcon = findViewById(R.id.imgResIcon);
com.bumptech.glide.Glide.with(ClientDashboardActivity.this).load(s.getValue(String.class)).circleCrop().into(imgResIcon);
}
});
com.example.pap_teste.models.Reserva finalProxima = proxima;
btnCheckIn.setOnClickListener(v -> {
boolean hasLocationPermission = androidx.core.app.ActivityCompat.checkSelfPermission(ClientDashboardActivity.this, android.Manifest.permission.ACCESS_FINE_LOCATION) == android.content.pm.PackageManager.PERMISSION_GRANTED;
if (!hasLocationPermission) {
android.widget.Toast.makeText(ClientDashboardActivity.this, "Dê permissões de localização para aceder ao check-in.", android.widget.Toast.LENGTH_SHORT).show();
} else {
Intent intent = new Intent(ClientDashboardActivity.this, CheckInAntecipadoActivity.class);
intent.putExtra("restaurant_email", finalProxima.getRestauranteEmail());
startActivity(intent);
}
});
} else {
txtTitle.setText("Sem reservas ativas");
txtTime.setText("Explore os restaurantes e reserve!");
btnCheckIn.setVisibility(android.view.View.GONE);
btnPartilhar.setVisibility(android.view.View.GONE);
android.widget.ImageView imgResIcon = findViewById(R.id.imgResIcon);
imgResIcon.setImageResource(R.drawable.circle_bg);
}
} }
@Override }
public void onCancelled(com.google.firebase.database.DatabaseError error) {} }
}); applyFilters();
}
@Override
public void onCancelled(@androidx.annotation.NonNull DatabaseError error) {
progressBar.setVisibility(View.GONE);
}
});
}
private void setupCategories() {
for (String category : CATEGORIES) {
Chip chip = new Chip(this);
chip.setText(category);
chip.setCheckable(true);
chip.setClickable(true);
// Default styling
chip.setChipBackgroundColorResource(R.color.colorSurface);
chip.setTextColor(getResources().getColor(R.color.colorTextSecondary));
chip.setChipStrokeWidth(0f);
if (category.equals("Tudo")) {
chip.setChecked(true);
chip.setChipBackgroundColorResource(R.color.colorPrimary);
chip.setTextColor(getResources().getColor(R.color.white));
}
chip.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked) {
// Reset all other chips visual state
for (int i = 0; i < chipGroupCategories.getChildCount(); i++) {
Chip c = (Chip) chipGroupCategories.getChildAt(i);
if (c != chip) {
c.setChipBackgroundColorResource(R.color.colorSurface);
c.setTextColor(getResources().getColor(R.color.colorTextSecondary));
}
}
chip.setChipBackgroundColorResource(R.color.colorPrimary);
chip.setTextColor(getResources().getColor(R.color.white));
currentCategoryFilter = category;
applyFilters();
} else if (currentCategoryFilter.equals(category)) {
// Prevent unchecking the currently selected chip
chip.setChecked(true);
}
});
chipGroupCategories.addView(chip);
} }
}
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) {
currentSearchFilter = s.toString().toLowerCase().trim();
applyFilters();
}
@Override
public void afterTextChanged(Editable s) {}
});
}
private void applyFilters() {
filteredRestaurants.clear();
String normalizedSearch = normalizeString(currentSearchFilter);
for (Restaurant r : allRestaurants) {
boolean matchesCategory = currentCategoryFilter.equals("Tudo") || currentCategoryFilter.equals(r.getCategory());
String normalizedName = normalizeString(r.getName());
boolean matchesSearch = currentSearchFilter.isEmpty() || normalizedName.contains(normalizedSearch);
if (matchesCategory && matchesSearch) {
filteredRestaurants.add(r);
}
}
mainAdapter.notifyDataSetChanged();
// Update featured (just the first 3 for demo, or based on a specific logic)
List<Restaurant> featuredList = new ArrayList<>();
for (int i = 0; i < Math.min(3, filteredRestaurants.size()); i++) {
featuredList.add(filteredRestaurants.get(i));
}
featuredAdapter = new FeaturedRestaurantAdapter(featuredList, mainAdapter instanceof RestaurantAdapter ?
restaurant -> {
Intent intent = new Intent(this, ExplorarRestaurantesActivity.class);
intent.putExtra("category_filter", restaurant.getCategory());
startActivity(intent);
} : null);
rvFeatured.setAdapter(featuredAdapter);
layoutFeatured.setVisibility(featuredList.isEmpty() ? View.GONE : View.VISIBLE);
layoutAllRestaurants.setVisibility(filteredRestaurants.isEmpty() ? View.GONE : View.VISIBLE);
}
private String normalizeString(String str) {
if (str == null) return "";
// Remove accents and special marks, and convert to lowercase
String normalized = java.text.Normalizer.normalize(str, java.text.Normalizer.Form.NFD);
normalized = normalized.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
return normalized.toLowerCase().trim();
}
} }

View File

@@ -22,6 +22,7 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
private android.view.View scrollReservaDetails; private android.view.View scrollReservaDetails;
private androidx.recyclerview.widget.RecyclerView rvRestaurants; private androidx.recyclerview.widget.RecyclerView rvRestaurants;
private android.widget.TextView txtTitle; private android.widget.TextView txtTitle;
private android.widget.ProgressBar progressBar;
private String selectedDate = null; private String selectedDate = null;
private String selectedTime = null; private String selectedTime = null;
@@ -52,6 +53,7 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
rvRestaurants = findViewById(R.id.rvRestaurants); rvRestaurants = findViewById(R.id.rvRestaurants);
scrollReservaDetails = findViewById(R.id.scrollReservaDetails); scrollReservaDetails = findViewById(R.id.scrollReservaDetails);
txtTitle = findViewById(R.id.txtTituloExplorar); txtTitle = findViewById(R.id.txtTituloExplorar);
progressBar = findViewById(R.id.progressBar);
Button back = findViewById(R.id.btnVoltar); Button back = findViewById(R.id.btnVoltar);
if (back != null) { if (back != null) {
@@ -82,9 +84,39 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
} else { } else {
txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : "")); txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : ""));
setupReservationOptions(); setupReservationOptions();
loadRestaurantRating();
} }
} }
private void loadRestaurantRating() {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return;
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_");
android.widget.TextView txtAvgRating = findViewById(R.id.txtAvgRating);
android.widget.TextView txtTotalReviews = findViewById(R.id.txtTotalReviews);
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users")
.child(encodedEmail)
.addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
Double avg = snapshot.child("ratingAvg").getValue(Double.class);
Integer count = snapshot.child("ratingCount").getValue(Integer.class);
if (avg != null && count != null && count > 0) {
txtAvgRating.setText(String.format(java.util.Locale.getDefault(), "%.1f", avg));
txtTotalReviews.setText(String.format(java.util.Locale.getDefault(), "(%d avaliações)", count));
} else {
txtAvgRating.setText("0.0");
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<>(); java.util.List<com.example.pap_teste.models.Restaurant> restaurantsList = new java.util.ArrayList<>();
@@ -95,9 +127,12 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
query = usersRef.orderByChild("category").equalTo(filter); query = usersRef.orderByChild("category").equalTo(filter);
} }
if (progressBar != null) progressBar.setVisibility(android.view.View.VISIBLE);
query.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() { query.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override @Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) { 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 (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
String role = ds.child("role").getValue(String.class); String role = ds.child("role").getValue(String.class);
@@ -126,6 +161,7 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
@Override @Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) { 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(); android.widget.Toast.makeText(ExplorarRestaurantesActivity.this, "Erro ao carregar restaurantes.", android.widget.Toast.LENGTH_SHORT).show();
} }
}); });
@@ -189,32 +225,69 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return; if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return;
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_"); String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_");
android.view.View dialogView = getLayoutInflater().inflate(R.layout.dialog_reviews_list, null);
androidx.recyclerview.widget.RecyclerView rvReviews = dialogView.findViewById(R.id.rvReviewsList);
android.widget.TextView txtEmpty = dialogView.findViewById(R.id.txtEmptyReviews);
android.widget.Button btnClose = dialogView.findViewById(R.id.btnCloseReviews);
android.widget.Button btnAdd = dialogView.findViewById(R.id.btnAddReview);
rvReviews.setLayoutManager(new androidx.recyclerview.widget.LinearLayoutManager(this));
androidx.appcompat.app.AlertDialog dialog = new androidx.appcompat.app.AlertDialog.Builder(this)
.setView(dialogView)
.create();
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
btnClose.setOnClickListener(v -> dialog.dismiss());
btnAdd.setOnClickListener(v -> {
dialog.dismiss();
addReviewDialog();
});
com.google.firebase.database.DatabaseReference reviewsRef = com.google.firebase.database.FirebaseDatabase.getInstance() com.google.firebase.database.DatabaseReference reviewsRef = com.google.firebase.database.FirebaseDatabase.getInstance()
.getReference("reviews").child(encodedEmail); .getReference("reviews").child(encodedEmail);
reviewsRef.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() { String currentUserEmail = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser() != null ?
com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser().getEmail() : null;
reviewsRef.addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override @Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) { public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
java.util.List<String> reviewsList = new java.util.ArrayList<>(); java.util.List<com.example.pap_teste.models.Review> reviewsList = new java.util.ArrayList<>();
for (com.google.firebase.database.DataSnapshot dst : snapshot.getChildren()) { for (com.google.firebase.database.DataSnapshot dst : snapshot.getChildren()) {
String author = dst.child("author").getValue(String.class); String author = dst.child("author").getValue(String.class);
String text = dst.child("text").getValue(String.class); String text = dst.child("text").getValue(String.class);
if (author != null && text != null) { String uEmail = dst.child("userEmail").getValue(String.class);
reviewsList.add(author + "\n" + text); 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);
} }
String[] reviewsArray = reviewsList.toArray(new String[0]);
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(ExplorarRestaurantesActivity.this)
.setTitle("Avaliações")
.setItems(reviewsArray, null)
.setPositiveButton("Fechar", null)
.setNeutralButton("Adicionar", (dialog, which) -> addReviewDialog());
if (reviewsList.isEmpty()) { if (reviewsList.isEmpty()) {
builder.setMessage("Ainda sem avaliações."); txtEmpty.setVisibility(android.view.View.VISIBLE);
rvReviews.setVisibility(android.view.View.GONE);
} else {
txtEmpty.setVisibility(android.view.View.GONE);
rvReviews.setVisibility(android.view.View.VISIBLE);
} }
builder.show();
ReviewAdapter adapter = new ReviewAdapter(reviewsList, currentUserEmail, review -> {
// Confirmação de Apagar
new androidx.appcompat.app.AlertDialog.Builder(ExplorarRestaurantesActivity.this)
.setTitle("Apagar Avaliação")
.setMessage("Tens a certeza que queres apagar esta avaliação?")
.setPositiveButton("Sim", (d, w) -> {
deleteReview(review.getKey());
d.dismiss();
})
.setNegativeButton("Não", null)
.show();
});
rvReviews.setAdapter(adapter);
} }
@Override @Override
@@ -222,49 +295,157 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
android.widget.Toast.makeText(ExplorarRestaurantesActivity.this, "Erro ao carregar avaliações.", android.widget.Toast.LENGTH_SHORT).show(); android.widget.Toast.makeText(ExplorarRestaurantesActivity.this, "Erro ao carregar avaliações.", android.widget.Toast.LENGTH_SHORT).show();
} }
}); });
dialog.show();
}
private void deleteReview(String reviewKey) {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return;
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_");
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) {
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reviews").child(encodedEmail)
.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
double totalRating = 0;
int count = 0;
for (com.google.firebase.database.DataSnapshot dst : snapshot.getChildren()) {
Float r = dst.child("rating").getValue(Float.class);
if (r != null) {
totalRating += r;
count++;
}
}
double newAvg = count > 0 ? (totalRating / count) : 0.0;
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("ratingAvg", newAvg);
updates.put("ratingCount", count);
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users")
.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) return; if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return;
android.widget.EditText input = new android.widget.EditText(this); android.view.View dialogView = getLayoutInflater().inflate(R.layout.dialog_leave_review, null);
input.setHint("Escreva a sua avaliação aqui..."); com.example.pap_teste.components.InteractiveRatingBar ratingBar = dialogView.findViewById(R.id.interactiveRatingBar);
new androidx.appcompat.app.AlertDialog.Builder(this) android.widget.EditText input = dialogView.findViewById(R.id.etReviewComment);
.setTitle("Adicionar Avaliação") android.widget.Button btnSubmit = dialogView.findViewById(R.id.btnSubmitReview);
.setView(input) android.widget.Button btnCancel = dialogView.findViewById(R.id.btnCancelReview);
.setPositiveButton("Enviar", (dialog, which) -> {
String revText = input.getText().toString().trim(); btnSubmit.setEnabled(false);
if (!revText.isEmpty()) { btnSubmit.setAlpha(0.5f);
com.google.firebase.auth.FirebaseUser currentUser = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
if (currentUser != null && currentUser.getEmail() != null) { input.addTextChangedListener(new android.text.TextWatcher() {
String userDoc = currentUser.getEmail().replace(".", "_").replace("@", "_at_"); @Override
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users").child(userDoc).get().addOnSuccessListener(snapshot -> { public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
String authorName = snapshot.exists() && snapshot.child("displayName").getValue(String.class) != null
? snapshot.child("displayName").getValue(String.class) @Override
: "Visitante"; public void onTextChanged(CharSequence s, int start, int before, int count) {
submitReviewToFirebase(authorName, revText); boolean hasText = s != null && s.toString().trim().length() > 0;
}).addOnFailureListener(e -> submitReviewToFirebase("Visitante", revText)); btnSubmit.setEnabled(hasText);
} else { btnSubmit.setAlpha(hasText ? 1.0f : 0.5f);
submitReviewToFirebase("Visitante", revText); }
}
} @Override
}) public void afterTextChanged(android.text.Editable s) {}
.setNegativeButton("Cancelar", null) });
.show();
androidx.appcompat.app.AlertDialog dialog = new androidx.appcompat.app.AlertDialog.Builder(this)
.setView(dialogView)
.create();
dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent);
btnCancel.setOnClickListener(v -> dialog.dismiss());
btnSubmit.setOnClickListener(v -> {
float rating = (float) ratingBar.getRating();
String revText = input.getText().toString().trim();
com.google.firebase.auth.FirebaseUser currentUser = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
if (currentUser != null && currentUser.getEmail() != null) {
String userDoc = currentUser.getEmail().replace(".", "_").replace("@", "_at_");
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users").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();
}).addOnFailureListener(e -> {
submitReviewToFirebase("Visitante", revText, rating);
dialog.dismiss();
});
} else {
submitReviewToFirebase("Visitante", revText, rating);
dialog.dismiss();
}
});
dialog.show();
} }
private void submitReviewToFirebase(String authorName, String revText) { private void submitReviewToFirebase(String authorName, String revText, float newRating) {
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_"); String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_");
com.google.firebase.auth.FirebaseUser currentUser = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
String uEmail = currentUser != null ? currentUser.getEmail() : null;
// 1. Guardar a Review
java.util.Map<String, Object> review = new java.util.HashMap<>(); java.util.Map<String, Object> review = new java.util.HashMap<>();
review.put("author", authorName); review.put("author", authorName);
review.put("text", revText); 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") com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reviews")
.child(encodedEmail).push().setValue(review).addOnCompleteListener(task -> { .child(encodedEmail).push().setValue(review).addOnCompleteListener(task -> {
if (task.isSuccessful()) { if (task.isSuccessful()) {
android.widget.Toast.makeText(this, "Avaliação enviada!", android.widget.Toast.LENGTH_SHORT).show(); 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("users").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() {

View File

@@ -29,6 +29,8 @@ public class FavoritosActivity extends AppCompatActivity {
private androidx.recyclerview.widget.RecyclerView rv; private androidx.recyclerview.widget.RecyclerView rv;
private RestaurantAdapter adapter; private RestaurantAdapter adapter;
private List<Restaurant> list; private List<Restaurant> list;
private android.widget.ProgressBar progressBar;
private android.view.View emptyState;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -47,8 +49,15 @@ public class FavoritosActivity extends AppCompatActivity {
} }
rv = findViewById(R.id.rvFavoritos); rv = findViewById(R.id.rvFavoritos);
progressBar = findViewById(R.id.progressBar);
emptyState = findViewById(R.id.emptyState);
list = new ArrayList<>(); list = new ArrayList<>();
adapter = new RestaurantAdapter(list, null); adapter = new RestaurantAdapter(list, restaurant -> {
android.content.Intent intent = new android.content.Intent(this, ExplorarRestaurantesActivity.class);
intent.putExtra("category_filter", restaurant.getCategory()); // just as demo
startActivity(intent);
});
rv.setAdapter(adapter); rv.setAdapter(adapter);
setupFavoritesList(); setupFavoritesList();
@@ -64,6 +73,7 @@ public class FavoritosActivity extends AppCompatActivity {
favRef.addValueEventListener(new ValueEventListener() { favRef.addValueEventListener(new ValueEventListener() {
@Override @Override
public void onDataChange(@NonNull DataSnapshot snapshot) { 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 (DataSnapshot ds : snapshot.getChildren()) {
Restaurant restaurant = ds.getValue(Restaurant.class); Restaurant restaurant = ds.getValue(Restaurant.class);
@@ -72,10 +82,15 @@ public class FavoritosActivity extends AppCompatActivity {
} }
} }
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
if (emptyState != null) {
emptyState.setVisibility(list.isEmpty() ? android.view.View.VISIBLE : android.view.View.GONE);
}
} }
@Override @Override
public void onCancelled(@NonNull DatabaseError error) { 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(); Toast.makeText(FavoritosActivity.this, "Erro ao carregar favoritos.", Toast.LENGTH_SHORT).show();
} }
}); });

View File

@@ -30,6 +30,8 @@ public class MinhasReservasActivity extends AppCompatActivity {
private final List<Reserva> reservaList = new ArrayList<>(); private final List<Reserva> reservaList = new ArrayList<>();
private DatabaseReference databaseReference; private DatabaseReference databaseReference;
private String clientEmail; private String clientEmail;
private android.widget.ProgressBar progressBar;
private android.view.View emptyState;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -56,6 +58,8 @@ public class MinhasReservasActivity extends AppCompatActivity {
} }
rvMinhasReservas = findViewById(R.id.rvMinhasReservas); rvMinhasReservas = findViewById(R.id.rvMinhasReservas);
progressBar = findViewById(R.id.progressBar);
emptyState = findViewById(R.id.emptyState);
Button btnVoltar = findViewById(R.id.btnVoltar); Button btnVoltar = findViewById(R.id.btnVoltar);
btnVoltar.setOnClickListener(v -> finish()); btnVoltar.setOnClickListener(v -> finish());
@@ -82,10 +86,14 @@ public class MinhasReservasActivity extends AppCompatActivity {
private void loadReservations() { private void loadReservations() {
databaseReference = FirebaseDatabase.getInstance().getReference("reservas"); databaseReference = FirebaseDatabase.getInstance().getReference("reservas");
if (progressBar != null) progressBar.setVisibility(android.view.View.VISIBLE);
if (emptyState != null) emptyState.setVisibility(android.view.View.GONE);
databaseReference.orderByChild("clienteEmail").equalTo(clientEmail) databaseReference.orderByChild("clienteEmail").equalTo(clientEmail)
.addValueEventListener(new ValueEventListener() { .addValueEventListener(new ValueEventListener() {
@Override @Override
public void onDataChange(@NonNull DataSnapshot snapshot) { 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 (DataSnapshot dataSnapshot : snapshot.getChildren()) {
Reserva reserva = dataSnapshot.getValue(Reserva.class); Reserva reserva = dataSnapshot.getValue(Reserva.class);
@@ -93,11 +101,18 @@ public class MinhasReservasActivity extends AppCompatActivity {
reservaList.add(reserva); reservaList.add(reserva);
} }
} }
// 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) {
emptyState.setVisibility(reservaList.isEmpty() ? android.view.View.VISIBLE : android.view.View.GONE);
}
} }
@Override @Override
public void onCancelled(@NonNull DatabaseError error) { 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) Toast.makeText(MinhasReservasActivity.this, "Erro ao carregar reservas.", Toast.LENGTH_SHORT)
.show(); .show();
} }

View File

@@ -24,6 +24,7 @@ public class NovaReservaActivity extends AppCompatActivity {
private androidx.recyclerview.widget.RecyclerView rvCategories, rvRestaurants; private androidx.recyclerview.widget.RecyclerView rvCategories, rvRestaurants;
private android.view.View scrollNovaReserva; private android.view.View scrollNovaReserva;
private android.widget.TextView txtTitle; private android.widget.TextView txtTitle;
private android.widget.ProgressBar progressBar;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -40,6 +41,7 @@ public class NovaReservaActivity extends AppCompatActivity {
rvRestaurants = findViewById(R.id.rvRestaurants); rvRestaurants = findViewById(R.id.rvRestaurants);
scrollNovaReserva = findViewById(R.id.scrollNovaReserva); scrollNovaReserva = findViewById(R.id.scrollNovaReserva);
txtTitle = findViewById(R.id.txtTituloNovaReserva); txtTitle = findViewById(R.id.txtTituloNovaReserva);
progressBar = findViewById(R.id.progressBar);
Button back = findViewById(R.id.btnVoltar); Button back = findViewById(R.id.btnVoltar);
if (back != null) { if (back != null) {
@@ -101,9 +103,12 @@ public class NovaReservaActivity extends AppCompatActivity {
java.util.List<com.example.pap_teste.models.Restaurant> filteredList = new java.util.ArrayList<>(); java.util.List<com.example.pap_teste.models.Restaurant> filteredList = new java.util.ArrayList<>();
com.google.firebase.database.DatabaseReference usersRef = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users"); com.google.firebase.database.DatabaseReference usersRef = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users");
if (progressBar != null) progressBar.setVisibility(android.view.View.VISIBLE);
usersRef.orderByChild("category").equalTo(selectedCategory).addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() { usersRef.orderByChild("category").equalTo(selectedCategory).addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override @Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) { public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
if (progressBar != null) progressBar.setVisibility(android.view.View.GONE);
filteredList.clear(); filteredList.clear();
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) { for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
String role = ds.child("role").getValue(String.class); String role = ds.child("role").getValue(String.class);
@@ -132,6 +137,7 @@ public class NovaReservaActivity extends AppCompatActivity {
@Override @Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) { 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(NovaReservaActivity.this, "Erro ao carregar restaurantes.", android.widget.Toast.LENGTH_SHORT).show(); android.widget.Toast.makeText(NovaReservaActivity.this, "Erro ao carregar restaurantes.", android.widget.Toast.LENGTH_SHORT).show();
} }
}); });
@@ -179,6 +185,10 @@ public class NovaReservaActivity extends AppCompatActivity {
String restEmail = selectedRestaurant.getEmail(); String restEmail = selectedRestaurant.getEmail();
if (progressBar != null) progressBar.setVisibility(android.view.View.VISIBLE);
android.widget.Button btnConfirmar = findViewById(R.id.btnConfirmarReserva);
if (btnConfirmar != null) btnConfirmar.setEnabled(false);
com.google.firebase.database.DatabaseReference mesasRef = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("Mesas"); com.google.firebase.database.DatabaseReference mesasRef = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("Mesas");
mesasRef.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() { mesasRef.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@@ -276,6 +286,10 @@ public class NovaReservaActivity extends AppCompatActivity {
if (id != null) { if (id != null) {
ref.child(id).setValue(reserva).addOnCompleteListener(task -> { ref.child(id).setValue(reserva).addOnCompleteListener(task -> {
if (progressBar != null) progressBar.setVisibility(android.view.View.GONE);
android.widget.Button btnConfirmar = findViewById(R.id.btnConfirmarReserva);
if (btnConfirmar != null) btnConfirmar.setEnabled(true);
if (task.isSuccessful()) { if (task.isSuccessful()) {
android.widget.Toast android.widget.Toast
.makeText(NovaReservaActivity.this, "Reserva solicitada com sucesso!", android.widget.Toast.LENGTH_SHORT) .makeText(NovaReservaActivity.this, "Reserva solicitada com sucesso!", android.widget.Toast.LENGTH_SHORT)

View File

@@ -41,7 +41,25 @@ public class ReservaAdapter extends RecyclerView.Adapter<ReservaAdapter.ViewHold
Reserva reserva = reservas.get(position); Reserva reserva = reservas.get(position);
holder.txtRestaurante.setText(reserva.getRestauranteName()); holder.txtRestaurante.setText(reserva.getRestauranteName());
holder.txtDataHora.setText(reserva.getData() + " às " + reserva.getHora() + "" + reserva.getPessoas() + "p"); holder.txtDataHora.setText(reserva.getData() + " às " + reserva.getHora() + "" + reserva.getPessoas() + "p");
holder.txtStatus.setText("Estado: " + reserva.getEstado());
if (holder.chipStatus != null) {
String state = reserva.getEstado() != null ? reserva.getEstado() : "Pendente";
holder.chipStatus.setText(state);
int colorBg, colorText;
android.content.Context ctx = holder.itemView.getContext();
if ("Confirmada".equals(state)) {
colorBg = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipConfirmed);
colorText = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipConfirmedText);
} else if ("Cancelada".equals(state) || "Recusada".equals(state)) {
colorBg = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipCancelled);
colorText = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipCancelledText);
} else {
colorBg = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipPending);
colorText = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipPendingText);
}
holder.chipStatus.setChipBackgroundColor(android.content.res.ColorStateList.valueOf(colorBg));
holder.chipStatus.setTextColor(colorText);
}
// Enable check-in only if confirmed // Enable check-in only if confirmed
boolean isConfirmed = "Confirmada".equals(reserva.getEstado()); boolean isConfirmed = "Confirmada".equals(reserva.getEstado());
@@ -89,6 +107,7 @@ public class ReservaAdapter extends RecyclerView.Adapter<ReservaAdapter.ViewHold
public static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
TextView txtRestaurante, txtDataHora, txtStatus, txtLocationWarning; TextView txtRestaurante, txtDataHora, txtStatus, txtLocationWarning;
Button btnCheckIn, btnCancelar; Button btnCheckIn, btnCancelar;
com.google.android.material.chip.Chip chipStatus;
public ViewHolder(@NonNull View itemView) { public ViewHolder(@NonNull View itemView) {
super(itemView); super(itemView);
@@ -98,6 +117,7 @@ public class ReservaAdapter extends RecyclerView.Adapter<ReservaAdapter.ViewHold
btnCheckIn = itemView.findViewById(R.id.btnCheckIn); btnCheckIn = itemView.findViewById(R.id.btnCheckIn);
btnCancelar = itemView.findViewById(R.id.btnCancelar); btnCancelar = itemView.findViewById(R.id.btnCancelar);
txtLocationWarning = itemView.findViewById(R.id.txtLocationWarning); txtLocationWarning = itemView.findViewById(R.id.txtLocationWarning);
chipStatus = itemView.findViewById(R.id.chipReservaStatus);
} }
} }
} }

View File

@@ -3,6 +3,7 @@ package com.example.pap_teste;
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.Button;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
@@ -46,6 +47,14 @@ public class RestaurantAdapter extends RecyclerView.Adapter<RestaurantAdapter.Vi
holder.text1.setText(restaurant.getName()); holder.text1.setText(restaurant.getName());
holder.text2.setText(restaurant.getCategory() + (restaurant.isAvailable() ? " - Disponível" : " - Indisponível")); holder.text2.setText(restaurant.getCategory() + (restaurant.isAvailable() ? " - Disponível" : " - Indisponível"));
if (holder.txtRating != null) {
if (restaurant.getRatingAvg() != null && restaurant.getRatingAvg() > 0) {
holder.txtRating.setText(String.format(java.util.Locale.getDefault(), "%.1f", restaurant.getRatingAvg()));
} else {
holder.txtRating.setText("Novo");
}
}
if (restaurant.getLogoUrl() != null && !restaurant.getLogoUrl().isEmpty()) { if (restaurant.getLogoUrl() != null && !restaurant.getLogoUrl().isEmpty()) {
com.bumptech.glide.Glide.with(holder.itemView.getContext()) com.bumptech.glide.Glide.with(holder.itemView.getContext())
.load(restaurant.getLogoUrl()) .load(restaurant.getLogoUrl())
@@ -61,6 +70,14 @@ public class RestaurantAdapter extends RecyclerView.Adapter<RestaurantAdapter.Vi
} }
}); });
if (holder.btnReservar != null) {
holder.btnReservar.setOnClickListener(v -> {
if (listener != null) {
listener.onRestaurantClick(restaurant);
}
});
}
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser(); FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null && user.getEmail() != null && restaurant.getEmail() != null) { if (user != null && user.getEmail() != null && restaurant.getEmail() != null) {
String encodedUserEmail = user.getEmail().replace(".", "_").replace("@", "_at_"); String encodedUserEmail = user.getEmail().replace(".", "_").replace("@", "_at_");
@@ -68,13 +85,17 @@ public class RestaurantAdapter extends RecyclerView.Adapter<RestaurantAdapter.Vi
DatabaseReference favRef = FirebaseDatabase.getInstance().getReference("users") DatabaseReference favRef = FirebaseDatabase.getInstance().getReference("users")
.child(encodedUserEmail).child("favorites").child(encodedRestEmail); .child(encodedUserEmail).child("favorites").child(encodedRestEmail);
favRef.addValueEventListener(new ValueEventListener() { // Read initial state
favRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override @Override
public void onDataChange(@NonNull DataSnapshot snapshot) { public void onDataChange(@NonNull DataSnapshot snapshot) {
holder.btnFavorite.setTag(snapshot.exists());
if (snapshot.exists()) { if (snapshot.exists()) {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on); 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 { } else {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off); holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off);
holder.btnFavorite.clearColorFilter();
} }
} }
@@ -83,19 +104,22 @@ public class RestaurantAdapter extends RecyclerView.Adapter<RestaurantAdapter.Vi
}); });
holder.btnFavorite.setOnClickListener(v -> { holder.btnFavorite.setOnClickListener(v -> {
favRef.addListenerForSingleValueEvent(new ValueEventListener() { // Optimistic UI update
@Override boolean isFav = holder.btnFavorite.getTag() != null && (Boolean) holder.btnFavorite.getTag();
public void onDataChange(@NonNull DataSnapshot snapshot) { boolean newFavState = !isFav;
if (snapshot.exists()) { holder.btnFavorite.setTag(newFavState);
favRef.removeValue();
} else { if (newFavState) {
favRef.setValue(restaurant); holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on);
} 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.LENGTH_SHORT).show();
@Override } else {
public void onCancelled(@NonNull DatabaseError error) { } holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off);
}); holder.btnFavorite.clearColorFilter();
favRef.removeValue();
com.google.android.material.snackbar.Snackbar.make(v, "Removido dos Favoritos.", com.google.android.material.snackbar.Snackbar.LENGTH_SHORT).show();
}
}); });
} }
} }
@@ -106,15 +130,18 @@ public class RestaurantAdapter extends RecyclerView.Adapter<RestaurantAdapter.Vi
} }
public static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
TextView text1, text2; TextView text1, text2, txtRating;
ImageButton btnFavorite; ImageButton btnFavorite;
android.widget.ImageView imgThumb; android.widget.ImageView imgThumb;
Button btnReservar;
public ViewHolder(@NonNull View itemView) { public ViewHolder(@NonNull View itemView) {
super(itemView); super(itemView);
text1 = itemView.findViewById(R.id.txtRestaurantName); text1 = itemView.findViewById(R.id.txtRestaurantName);
text2 = itemView.findViewById(R.id.txtRestaurantCategory); text2 = itemView.findViewById(R.id.txtRestaurantCategory);
txtRating = itemView.findViewById(R.id.txtRestaurantRating);
btnFavorite = itemView.findViewById(R.id.btnFavorite); btnFavorite = itemView.findViewById(R.id.btnFavorite);
imgThumb = itemView.findViewById(R.id.imgRestaurantThumb); imgThumb = itemView.findViewById(R.id.imgRestaurantThumb);
btnReservar = itemView.findViewById(R.id.btnReservarCard);
} }
} }
} }

View File

@@ -6,6 +6,8 @@ public class Restaurant {
private String email; private String email;
private boolean available; private boolean available;
private String logoUrl; private String logoUrl;
private Double ratingAvg;
private Integer ratingCount;
// No-argument constructor required for Firebase // No-argument constructor required for Firebase
public Restaurant() { public Restaurant() {
@@ -37,4 +39,9 @@ public class Restaurant {
public void setEmail(String email) { this.email = email; } public void setEmail(String email) { this.email = email; }
public void setAvailable(boolean available) { this.available = available; } public void setAvailable(boolean available) { this.available = available; }
public void setLogoUrl(String logoUrl) { this.logoUrl = logoUrl; } public void setLogoUrl(String logoUrl) { this.logoUrl = logoUrl; }
public Double getRatingAvg() { return ratingAvg; }
public void setRatingAvg(Double ratingAvg) { this.ratingAvg = ratingAvg; }
public Integer getRatingCount() { return ratingCount; }
public void setRatingCount(Integer ratingCount) { this.ratingCount = ratingCount; }
} }

View File

@@ -8,283 +8,232 @@
android:background="@color/colorBackground" android:background="@color/colorBackground"
tools:context=".ClientDashboardActivity"> tools:context=".ClientDashboardActivity">
<!-- Top Bar with Profile Icon -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardProfile"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
app:cardCornerRadius="25dp"
app:cardElevation="2dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/imgProfile"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/circle_bg" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/txtClientGreeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:text="Olá, convidado"
android:textColor="@color/colorTextPrimary"
android:textSize="22sp"
android:fontFamily="sans-serif-medium"
app:layout_constraintBottom_toBottomOf="@id/cardProfile"
app:layout_constraintStart_toEndOf="@id/cardProfile"
app:layout_constraintTop_toTopOf="@id/cardProfile" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:text="Sair"
android:textAllCaps="false"
android:textColor="@color/colorError"
app:layout_constraintBottom_toBottomOf="@id/cardProfile"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/cardProfile" />
<ScrollView <ScrollView
android:id="@+id/clientScroll" android:id="@+id/clientScroll"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="24dp"
android:fillViewport="true" android:fillViewport="true"
android:scrollbars="none" android:scrollbars="none"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/bottomNavigation"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/cardProfile"> app:layout_constraintEnd_toEndOf="parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingBottom="40dp"> android:paddingBottom="24dp">
<!-- Categories Section --> <!-- Header -->
<TextView <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="Categorias"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:fontFamily="sans-serif-medium" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCategories"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="12dp" android:paddingHorizontal="24dp"
android:clipToPadding="false" android:paddingTop="32dp">
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_food_category" />
<!-- Integrated Next Reservation Card --> <TextView
<TextView android:id="@+id/txtClientGreeting"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="24dp" android:text="Olá, convidado!"
android:layout_marginTop="32dp" android:textColor="@color/colorTextPrimary"
android:text="A sua próxima reserva" android:textSize="28sp"
android:textColor="@color/colorTextPrimary" android:textStyle="bold"
android:textSize="20sp" android:fontFamily="sans-serif"
android:fontFamily="sans-serif-medium" /> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/imgNotification"
android:layout_marginEnd="16dp" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Encontre a sua próxima mesa."
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@id/txtClientGreeting"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/imgNotification" />
<ImageButton
android:id="@+id/imgNotification"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_circle_white"
android:src="@android:drawable/ic_popup_reminder"
app:tint="@color/colorTextPrimary"
android:layout_marginEnd="12dp"
app:layout_constraintTop_toTopOf="@id/cardProfile"
app:layout_constraintBottom_toBottomOf="@id/cardProfile"
app:layout_constraintEnd_toStartOf="@id/cardProfile" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardProfile"
android:layout_width="50dp"
android:layout_height="50dp"
app:cardCornerRadius="25dp"
app:cardElevation="2dp"
app:strokeWidth="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:id="@+id/imgProfile"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/circle_bg" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
<!-- Search Bar -->
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/cardNextReservation"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="56dp"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="24dp"
android:layout_marginTop="16dp" android:layout_marginTop="24dp"
app:cardBackgroundColor="@color/colorSurface" app:cardCornerRadius="16dp"
app:cardCornerRadius="24dp" app:cardElevation="4dp"
app:cardElevation="6dp"> app:strokeWidth="0dp"
app:cardBackgroundColor="@color/colorSurface">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="horizontal"
android:padding="24dp"> android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<RelativeLayout <ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_search"
app:tint="@color/colorTextSecondary" />
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="match_parent"
android:layout_marginStart="12dp"
<TextView android:background="@null"
android:id="@+id/txtResTitle" android:hint="Pesquisar restaurantes..."
android:layout_width="wrap_content" android:textColor="@color/colorTextPrimary"
android:layout_height="wrap_content" android:textColorHint="@color/colorTextHint"
android:text="Sabor &amp; Arte" android:textSize="16sp"
android:textColor="@color/colorTextPrimary" android:maxLines="1"
android:textSize="20sp" android:inputType="text" />
android:fontFamily="sans-serif-medium" />
<TextView
android:id="@+id/txtResTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/txtResTitle"
android:layout_marginTop="6dp"
android:text="Amanhã às 20h00 • 2 pessoas"
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
<ImageView
android:id="@+id/imgResIcon"
android:layout_width="56dp"
android:layout_height="56dp"
android:layout_alignParentEnd="true"
android:src="@drawable/circle_bg"
app:tint="@color/colorPrimary" />
</RelativeLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="20dp"
android:background="@color/colorDivider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnCheckIn"
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
app:cornerRadius="14dp"
app:backgroundTint="@color/colorPrimary"
android:text="Check-in"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="15sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnPartilhar"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginStart="8dp"
android:layout_weight="1"
app:cornerRadius="14dp"
android:text="Partilhar"
android:textAllCaps="false"
android:textSize="15sp" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<!-- Actions Section --> <!-- Category Pills -->
<TextView <HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="40dp"
android:text="Mais opções"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:fontFamily="sans-serif-medium" />
<GridLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginTop="24dp"
android:layout_marginTop="16dp" android:scrollbars="none"
android:columnCount="1"> android:clipToPadding="false"
android:paddingHorizontal="20dp">
<com.google.android.material.card.MaterialCardView <com.google.android.material.chip.ChipGroup
android:id="@+id/cardNewRes" android:id="@+id/chipGroupCategories"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="90dp" android:layout_height="wrap_content"
android:layout_margin="8dp" app:singleLine="true"
app:cardCornerRadius="16dp" app:singleSelection="true">
app:cardElevation="2dp" <!-- Chips will be added programmatically -->
app:cardBackgroundColor="@color/colorSurface"> </com.google.android.material.chip.ChipGroup>
<LinearLayout </HorizontalScrollView>
android:id="@+id/btnNovaReserva"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:background="?attr/selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reservar"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView <!-- Loading Spinner -->
android:id="@+id/cardMinhasReservas" <ProgressBar
android:layout_width="match_parent" android:id="@+id/progressBar"
android:layout_height="90dp"
android:layout_margin="8dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/colorSurface">
<LinearLayout
android:id="@+id/btnMinhasReservas"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:background="?attr/selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Minhas Reservas"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</GridLayout>
<!-- Status Section -->
<TextView
android:id="@+id/txtClientStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="40dp"
android:text="Tudo pronto para a sua próxima refeição!"
android:textAlignment="center"
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
<TextView
android:id="@+id/txtClientRole"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginTop="8dp" android:layout_marginTop="40dp"
android:text="Modo Cliente" android:visibility="visible"
android:textColor="#94A3B8" android:indeterminateTint="@color/colorPrimary" />
android:textSize="12sp" />
<!-- Featured Carousel -->
<LinearLayout
android:id="@+id/layoutFeatured"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="Restaurantes Populares"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFeatured"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_featured" />
</LinearLayout>
<!-- Main Restaurant Grid/List -->
<LinearLayout
android:id="@+id/layoutAllRestaurants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="32dp">
<TextView
android:id="@+id/txtListTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="Todos os Restaurantes"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMainRestaurants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingBottom="16dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
<!-- Bottom Navigation Bar -->
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorSurface"
app:itemIconTint="@color/colorPrimary"
app:itemTextColor="@color/colorPrimary"
app:menu="@menu/bottom_nav_menu"
app:elevation="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -8,60 +8,77 @@
android:background="@color/colorBackground" android:background="@color/colorBackground"
tools:context=".ExplorarRestaurantesActivity"> tools:context=".ExplorarRestaurantesActivity">
<!-- Header Section -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar" android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content" android:layout_width="48dp"
android:layout_height="wrap_content" android:layout_height="48dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="← Voltar" app:icon="@android:drawable/ic_menu_revert"
android:textAllCaps="false" app:iconTint="@color/colorTextPrimary"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/txtTituloExplorar" android:id="@+id/txtTituloExplorar"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="Explorar restaurantes" android:text="Explorar restaurantes"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextPrimary"
android:textSize="28sp" android:textSize="28sp"
android:fontFamily="sans-serif-medium" android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/btnVoltar" android:fontFamily="sans-serif"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/btnVoltar"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="@id/btnVoltar"
android:layout_marginTop="20dp" /> app:layout_constraintStart_toEndOf="@id/btnVoltar"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Loading Spinner -->
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:indeterminateTint="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Restaurant List -->
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvRestaurants" android:id="@+id/rvRestaurants"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="24dp" android:layout_marginTop="16dp"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="24dp" android:paddingBottom="24dp"
android:visibility="visible" android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloExplorar" app:layout_constraintTop_toBottomOf="@id/btnVoltar"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant" /> tools:listitem="@layout/item_restaurant" />
<!-- Details and Reservation Flow (Overlay) -->
<ScrollView <ScrollView
android:id="@+id/scrollReservaDetails" android:id="@+id/scrollReservaDetails"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="24dp" android:layout_marginTop="16dp"
android:fillViewport="true" android:fillViewport="true"
android:visibility="gone" android:visibility="gone"
android:scrollbars="none" android:scrollbars="none"
android:background="@color/colorBackground"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloExplorar"> app:layout_constraintTop_toBottomOf="@id/btnVoltar">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -69,12 +86,48 @@
android:orientation="vertical" android:orientation="vertical"
android:paddingBottom="48dp"> android:paddingBottom="48dp">
<!-- Rating Section -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingHorizontal="24dp" android:paddingHorizontal="24dp"
android:layout_marginTop="8dp"> android:layout_marginTop="16dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/star_on"
app:tint="@color/colorWarning" />
<TextView
android:id="@+id/txtAvgRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="0.0"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtTotalReviews"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="(0 avaliações)"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp" />
</LinearLayout>
<!-- Action Buttons for Details -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:layout_marginTop="16dp">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnVerAvaliacoes" android:id="@+id/btnVerAvaliacoes"
@@ -84,7 +137,7 @@
android:layout_weight="1" android:layout_weight="1"
android:layout_marginEnd="8dp" android:layout_marginEnd="8dp"
app:cornerRadius="12dp" app:cornerRadius="12dp"
android:text="Avaliações" android:text="Ver / Deixar Avaliação"
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
app:strokeColor="@color/colorPrimary" /> app:strokeColor="@color/colorPrimary" />
@@ -103,13 +156,15 @@
app:strokeColor="@color/colorError" /> app:strokeColor="@color/colorError" />
</LinearLayout> </LinearLayout>
<!-- Reservation Form Card -->
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="24dp"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
app:cardCornerRadius="20dp" app:cardCornerRadius="24dp"
app:cardElevation="2dp" app:cardElevation="4dp"
app:strokeWidth="0dp"
app:cardBackgroundColor="@color/colorSurface"> app:cardBackgroundColor="@color/colorSurface">
<LinearLayout <LinearLayout
@@ -121,60 +176,69 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Data da reserva" android:text="Detalhes da Reserva"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextPrimary"
android:textSize="16sp" android:textSize="20sp"
android:fontFamily="sans-serif-medium" /> android:textStyle="bold"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Data da reserva"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnSelectDate" android:id="@+id/btnSelectDate"
style="@style/Widget.Material3.Button.OutlinedButton" style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="54dp" android:layout_height="56dp"
android:layout_marginTop="12dp" android:layout_marginTop="8dp"
app:cornerRadius="12dp" app:cornerRadius="12dp"
android:text="Selecionar Data" android:text="Selecionar Data"
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/colorTextSecondary" /> android:textColor="@color/colorTextPrimary"
app:strokeColor="@color/colorDivider" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="20dp"
android:text="Hora da reserva" android:text="Hora da reserva"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextSecondary"
android:textSize="16sp" android:textSize="14sp" />
android:fontFamily="sans-serif-medium" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnSelectTime" android:id="@+id/btnSelectTime"
style="@style/Widget.Material3.Button.OutlinedButton" style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="54dp" android:layout_height="56dp"
android:layout_marginTop="12dp" android:layout_marginTop="8dp"
app:cornerRadius="12dp" app:cornerRadius="12dp"
android:text="Selecionar Hora" android:text="Selecionar Hora"
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/colorTextSecondary" /> android:textColor="@color/colorTextPrimary"
app:strokeColor="@color/colorDivider" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="20dp"
android:text="Número de pessoas" android:text="Número de pessoas"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextSecondary"
android:textSize="16sp" android:textSize="14sp" />
android:fontFamily="sans-serif-medium" />
<EditText <EditText
android:id="@+id/etPartySize" android:id="@+id/etPartySize"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="54dp" android:layout_height="56dp"
android:layout_marginTop="12dp" android:layout_marginTop="8dp"
android:background="@drawable/input_bg" android:background="@drawable/bg_input_modern"
android:inputType="number" android:inputType="number"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
android:hint="Ex: 2" android:hint="Ex: 2"
android:textColorHint="@color/colorTextHint"
android:textColor="@color/colorTextPrimary" /> android:textColor="@color/colorTextPrimary" />
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
@@ -182,15 +246,16 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirmarReserva" android:id="@+id/btnConfirmarReserva"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="60dp"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
app:cornerRadius="14dp" app:cornerRadius="16dp"
app:backgroundTint="@color/colorPrimary" app:backgroundTint="@color/colorPrimary"
android:text="Confirmar Reserva" android:text="Confirmar Reserva"
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" /> android:textStyle="bold"
android:textSize="18sp" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -8,44 +8,82 @@
android:background="@color/colorBackground" android:background="@color/colorBackground"
tools:context=".FavoritosActivity"> tools:context=".FavoritosActivity">
<!-- Header Section -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar" android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content" android:layout_width="48dp"
android:layout_height="wrap_content" android:layout_height="48dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="← Voltar" app:icon="@android:drawable/ic_menu_revert"
android:textAllCaps="false" app:iconTint="@color/colorTextPrimary"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/txtTituloFavoritos" android:id="@+id/txtTituloFavoritos"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Restaurantes favoritos" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="Os Meus Favoritos"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextPrimary"
android:textSize="28sp" android:textSize="28sp"
android:fontFamily="sans-serif-medium" android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/btnVoltar" android:fontFamily="sans-serif"
app:layout_constraintTop_toTopOf="@id/btnVoltar"
app:layout_constraintBottom_toBottomOf="@id/btnVoltar"
app:layout_constraintStart_toEndOf="@id/btnVoltar"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Loading Spinner -->
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="visible"
android:indeterminateTint="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent" />
android:layout_marginTop="20dp" />
<!-- Empty State -->
<LinearLayout
android:id="@+id/emptyState"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@android:drawable/btn_star_big_off"
app:tint="@color/colorDivider" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Ainda não tens favoritos guardados."
android:textColor="@color/colorTextSecondary"
android:textSize="16sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFavoritos" android:id="@+id/rvFavoritos"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="24dp" android:layout_marginTop="16dp"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="24dp" android:paddingBottom="24dp"
app:layout_constraintTop_toBottomOf="@id/btnVoltar"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloFavoritos"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant" /> tools:listitem="@layout/item_restaurant" />

View File

@@ -8,41 +8,81 @@
android:background="@color/colorBackground" android:background="@color/colorBackground"
tools:context=".MinhasReservasActivity"> tools:context=".MinhasReservasActivity">
<!-- Header Section -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar" android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content" android:layout_width="48dp"
android:layout_height="wrap_content" android:layout_height="48dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="← Voltar" app:icon="@android:drawable/ic_menu_revert"
android:textAllCaps="false" app:iconTint="@color/colorTextPrimary"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/txtTituloMinhasReservas" android:id="@+id/txtTituloMinhasReservas"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="Minhas Reservas" android:text="Minhas Reservas"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextPrimary"
android:textSize="28sp" android:textSize="28sp"
android:fontFamily="sans-serif-medium" android:textStyle="bold"
android:fontFamily="sans-serif"
app:layout_constraintTop_toTopOf="@id/btnVoltar"
app:layout_constraintBottom_toBottomOf="@id/btnVoltar"
app:layout_constraintStart_toEndOf="@id/btnVoltar"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Loading Spinner -->
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:indeterminateTint="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintTop_toBottomOf="@id/btnVoltar"
android:layout_marginTop="20dp" /> <!-- Empty State -->
<LinearLayout
android:id="@+id/emptyState"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@android:drawable/ic_menu_today"
app:tint="@color/colorDivider" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Sem reservas encontradas."
android:textColor="@color/colorTextSecondary"
android:textSize="16sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMinhasReservas" android:id="@+id/rvMinhasReservas"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="24dp" android:layout_marginTop="16dp"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="16dp" android:paddingBottom="24dp"
app:layout_constraintTop_toBottomOf="@id/txtTituloMinhasReservas" app:layout_constraintTop_toBottomOf="@id/btnVoltar"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View File

@@ -8,73 +8,88 @@
android:background="@color/colorBackground" android:background="@color/colorBackground"
tools:context=".NovaReservaActivity"> tools:context=".NovaReservaActivity">
<!-- Header Section -->
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar" android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.IconButton"
android:layout_width="wrap_content" android:layout_width="48dp"
android:layout_height="wrap_content" android:layout_height="48dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:text="← Voltar" app:icon="@android:drawable/ic_menu_revert"
android:textAllCaps="false" app:iconTint="@color/colorTextPrimary"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/txtTituloNovaReserva" android:id="@+id/txtTituloNovaReserva"
android:layout_width="wrap_content" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:text="Nova reserva" android:text="Nova reserva"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextPrimary"
android:textSize="28sp" android:textSize="28sp"
android:fontFamily="sans-serif-medium" android:textStyle="bold"
app:layout_constraintTop_toBottomOf="@id/btnVoltar" android:fontFamily="sans-serif"
app:layout_constraintTop_toTopOf="@id/btnVoltar"
app:layout_constraintBottom_toBottomOf="@id/btnVoltar"
app:layout_constraintStart_toEndOf="@id/btnVoltar"
app:layout_constraintEnd_toEndOf="parent" />
<!-- Loading Spinner -->
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:indeterminateTint="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent" />
android:layout_marginTop="20dp" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCategories" android:id="@+id/rvCategories"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="24dp" android:layout_marginTop="16dp"
android:visibility="visible" android:visibility="visible"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="16dp" android:paddingBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloNovaReserva" app:layout_constraintTop_toBottomOf="@id/btnVoltar"
tools:listitem="@layout/item_food_category" /> tools:listitem="@layout/item_food_category" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvRestaurants" android:id="@+id/rvRestaurants"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="24dp" android:layout_marginTop="16dp"
android:visibility="gone" android:visibility="gone"
android:clipToPadding="false" android:clipToPadding="false"
android:paddingBottom="16dp" android:paddingBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloNovaReserva" app:layout_constraintTop_toBottomOf="@id/btnVoltar"
tools:listitem="@layout/item_restaurant" /> tools:listitem="@layout/item_restaurant" />
<ScrollView <ScrollView
android:id="@+id/scrollNovaReserva" android:id="@+id/scrollNovaReserva"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginTop="24dp" android:layout_marginTop="16dp"
android:fillViewport="true" android:fillViewport="true"
android:visibility="gone" android:visibility="gone"
android:scrollbars="none" android:scrollbars="none"
android:background="@color/colorBackground"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloNovaReserva"> app:layout_constraintTop_toBottomOf="@id/btnVoltar">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -87,8 +102,9 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="24dp"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
app:cardCornerRadius="20dp" app:cardCornerRadius="24dp"
app:cardElevation="2dp" app:cardElevation="4dp"
app:strokeWidth="0dp"
app:cardBackgroundColor="@color/colorSurface"> app:cardBackgroundColor="@color/colorSurface">
<LinearLayout <LinearLayout
@@ -100,62 +116,69 @@
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Data da reserva" android:text="Detalhes da Reserva"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextPrimary"
android:textSize="16sp" android:textSize="20sp"
android:fontFamily="sans-serif-medium" /> android:textStyle="bold"
android:layout_marginBottom="24dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Data da reserva"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnSelectDate" android:id="@+id/btnSelectDate"
style="@style/Widget.Material3.Button.OutlinedButton" style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="54dp" android:layout_height="56dp"
android:layout_marginTop="12dp" android:layout_marginTop="8dp"
app:cornerRadius="12dp" app:cornerRadius="12dp"
android:text="Selecionar Data" android:text="Selecionar Data"
android:textAllCaps="false" android:textAllCaps="false"
android:textSize="16sp" android:textColor="@color/colorTextPrimary"
android:textColor="@color/colorTextSecondary" /> app:strokeColor="@color/colorDivider" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="20dp"
android:text="Hora da reserva" android:text="Hora da reserva"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextSecondary"
android:textSize="16sp" android:textSize="14sp" />
android:fontFamily="sans-serif-medium" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnSelectTime" android:id="@+id/btnSelectTime"
style="@style/Widget.Material3.Button.OutlinedButton" style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="54dp" android:layout_height="56dp"
android:layout_marginTop="12dp" android:layout_marginTop="8dp"
app:cornerRadius="12dp" app:cornerRadius="12dp"
android:text="Selecionar Hora" android:text="Selecionar Hora"
android:textAllCaps="false" android:textAllCaps="false"
android:textSize="16sp" android:textColor="@color/colorTextPrimary"
android:textColor="@color/colorTextSecondary" /> app:strokeColor="@color/colorDivider" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="24dp" android:layout_marginTop="20dp"
android:text="Número de pessoas" android:text="Número de pessoas"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextSecondary"
android:textSize="16sp" android:textSize="14sp" />
android:fontFamily="sans-serif-medium" />
<EditText <EditText
android:id="@+id/etPartySize" android:id="@+id/etPartySize"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="54dp" android:layout_height="56dp"
android:layout_marginTop="12dp" android:layout_marginTop="8dp"
android:background="@drawable/input_bg" android:background="@drawable/bg_input_modern"
android:inputType="number" android:inputType="number"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
android:hint="Ex: 2" android:hint="Ex: 2"
android:textColorHint="@color/colorTextHint"
android:textColor="@color/colorTextPrimary" /> android:textColor="@color/colorTextPrimary" />
</LinearLayout> </LinearLayout>
@@ -164,15 +187,16 @@
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirmarReserva" android:id="@+id/btnConfirmarReserva"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="60dp"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
app:cornerRadius="14dp" app:cornerRadius="16dp"
app:backgroundTint="@color/colorPrimary" app:backgroundTint="@color/colorPrimary"
android:text="Confirmar Reserva" android:text="Confirmar Reserva"
android:textAllCaps="false" android:textAllCaps="false"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" /> android:textStyle="bold"
android:textSize="18sp" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -3,13 +3,12 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="20dp"
android:layout_marginVertical="8dp" android:layout_marginVertical="8dp"
app:cardCornerRadius="16dp" app:cardCornerRadius="20dp"
app:cardElevation="4dp" app:cardElevation="2dp"
app:cardBackgroundColor="@color/colorSurface" app:cardBackgroundColor="@color/colorSurface"
app:strokeWidth="1dp" app:strokeWidth="0dp">
app:strokeColor="@color/colorDivider">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -17,59 +16,91 @@
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:padding="20dp">
<TextView <LinearLayout
android:id="@+id/txtReservaRestaurante" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Nome do Restaurante" android:orientation="horizontal"
android:textColor="@color/colorTextPrimary" android:gravity="center_vertical">
android:textSize="18sp"
android:fontFamily="sans-serif-medium" /> <TextView
android:id="@+id/txtReservaRestaurante"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Nome do Restaurante"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<TextView <com.google.android.material.chip.Chip
android:id="@+id/txtReservaDataHora" android:id="@+id/chipReservaStatus"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp" android:text="Pendente"
android:text="Hoje às 20:00" android:textSize="12sp"
android:textColor="@color/colorTextSecondary" android:textStyle="bold"
android:textSize="14sp" /> android:textColor="@color/colorChipPendingText"
app:chipBackgroundColor="@color/colorChipPending"
<TextView app:chipStrokeWidth="0dp" />
android:id="@+id/txtReservaStatus" </LinearLayout>
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Estado: Pendente"
android:textSize="14sp"
android:textColor="@color/colorSecondary"
android:fontFamily="sans-serif-medium" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:orientation="horizontal"
android:layout_marginTop="8dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@android:drawable/ic_menu_recent_history"
app:tint="@color/colorTextSecondary" />
<TextView
android:id="@+id/txtReservaDataHora"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="Hoje às 20:00"
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
</LinearLayout>
<TextView
android:id="@+id/txtReservaStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:text="Pendente" /> <!-- Kept for backward compatibility if needed in adapter -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="horizontal"> android:orientation="horizontal">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnCheckIn" android:id="@+id/btnCheckIn"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="50dp" android:layout_height="52dp"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginEnd="6dp" android:layout_marginEnd="8dp"
app:cornerRadius="12dp" app:cornerRadius="12dp"
app:backgroundTint="@color/colorPrimary" app:backgroundTint="@color/colorPrimary"
android:text="Check-in" android:text="Check-in"
android:textAllCaps="false" android:textAllCaps="false"
android:textStyle="bold"
android:textColor="@color/white" /> android:textColor="@color/white" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnCancelar" android:id="@+id/btnCancelar"
style="@style/Widget.Material3.Button.OutlinedButton" style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="50dp" android:layout_height="52dp"
android:layout_weight="1" android:layout_weight="1"
android:layout_marginStart="6dp" android:layout_marginStart="8dp"
app:cornerRadius="12dp" app:cornerRadius="12dp"
app:strokeColor="@color/colorError" app:strokeColor="@color/colorError"
android:text="Cancelar" android:text="Cancelar"
@@ -81,7 +112,7 @@
android:id="@+id/txtLocationWarning" android:id="@+id/txtLocationWarning"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="12dp"
android:text="Dê permissões de localização para aceder ao check-in." android:text="Dê permissões de localização para aceder ao check-in."
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/colorWarning" android:textColor="@color/colorWarning"

View File

@@ -3,66 +3,115 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="20dp"
android:layout_marginVertical="8dp" android:layout_marginVertical="12dp"
app:cardCornerRadius="16dp" app:cardCornerRadius="24dp"
app:cardElevation="2dp" app:cardElevation="6dp"
app:cardBackgroundColor="@color/colorSurface" app:cardBackgroundColor="@color/colorSurface"
app:strokeWidth="1dp" app:strokeWidth="0dp">
app:strokeColor="@color/colorDivider">
<RelativeLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
android:padding="16dp">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/imgRestaurantThumb" android:id="@+id/imgRestaurantThumb"
android:layout_width="60dp" android:layout_width="0dp"
android:layout_height="60dp" android:layout_height="180dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher" android:src="@mipmap/ic_launcher"
app:shapeAppearanceOverlay="@style/ShapeAppearance.Material3.Corner.Medium" /> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
<LinearLayout app:layout_constraintEnd_toEndOf="parent" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/imgRestaurantThumb"
android:layout_toStartOf="@id/btnFavorite"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/txtRestaurantName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Restaurant Name"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp"
android:fontFamily="sans-serif-medium" />
<TextView
android:id="@+id/txtRestaurantCategory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Category"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
</LinearLayout>
<ImageButton <ImageButton
android:id="@+id/btnFavorite" android:id="@+id/btnFavorite"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:layout_alignParentEnd="true" android:layout_marginTop="12dp"
android:layout_centerVertical="true" android:layout_marginEnd="12dp"
android:background="?attr/selectableItemBackgroundBorderless" android:background="@drawable/bg_circle_white"
android:padding="12dp" android:padding="12dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:src="@android:drawable/btn_star_big_off" /> android:src="@android:drawable/btn_star_big_off"
</RelativeLayout> app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:tint="@color/colorTextPrimary" />
<LinearLayout
android:id="@+id/bottomContent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp"
app:layout_constraintTop_toBottomOf="@id/imgRestaurantThumb"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<TextView
android:id="@+id/txtRestaurantName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Restaurant Name"
android:textColor="@color/colorTextPrimary"
android:textSize="22sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="6dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/txtRestaurantCategory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Category"
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=" • "
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
<ImageView
android:layout_width="16dp"
android:layout_height="16dp"
android:src="@android:drawable/star_on"
app:tint="@color/colorWarning" />
<TextView
android:id="@+id/txtRestaurantRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="N/A"
android:textColor="@color/colorTextPrimary"
android:textSize="15sp"
android:textStyle="bold" />
</LinearLayout>
<!-- New Elegant Reserve Button -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnReservarCard"
android:layout_width="match_parent"
android:layout_height="52dp"
android:layout_marginTop="16dp"
app:cornerRadius="12dp"
app:backgroundTint="@color/colorPrimary"
android:text="Reservar Mesa"
android:textAllCaps="false"
android:textColor="@color/white"
android:textStyle="bold"
android:textSize="16sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

View File

@@ -1,23 +1,30 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Brand Colors --> <!-- Brand Colors - Warm Premium Palette -->
<color name="colorPrimary">#06C167</color> <color name="colorPrimary">#FF5A5F</color> <!-- Airbnb/UberEats Coral -->
<color name="colorPrimaryVariant">#05A357</color> <color name="colorPrimaryVariant">#FF3B30</color>
<color name="colorSecondary">#06C167</color> <color name="colorSecondary">#FF5A5F</color>
<!-- Neutral Colors --> <!-- Neutral Colors -->
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="colorBackground">#F8FAFC</color> <color name="colorBackground">#F7F7F9</color> <!-- Soft grey for modern background -->
<color name="colorSurface">#FFFFFF</color> <color name="colorSurface">#FFFFFF</color>
<color name="colorTextPrimary">#1E293B</color> <color name="colorTextPrimary">#1A1A1A</color> <!-- Dark but not pure black -->
<color name="colorTextSecondary">#64748B</color> <color name="colorTextSecondary">#757575</color>
<color name="colorTextHint">#BDBDBD</color>
<!-- Semantic Colors --> <!-- Semantic Colors -->
<color name="colorSuccess">#10B981</color> <color name="colorSuccess">#34C759</color> <!-- iOS green -->
<color name="colorError">#EF4444</color> <color name="colorError">#FF3B30</color>
<color name="colorWarning">#F59E0B</color> <color name="colorWarning">#FFCC00</color>
<color name="colorDivider">#E2E8F0</color> <color name="colorDivider">#E0E0E0</color>
<color name="colorChipPending">#FFE0B2</color>
<color name="colorChipPendingText">#E65100</color>
<color name="colorChipConfirmed">#C8E6C9</color>
<color name="colorChipConfirmedText">#1B5E20</color>
<color name="colorChipCancelled">#FFCDD2</color>
<color name="colorChipCancelledText">#B71C1C</color>
<!-- Legacy compatibility --> <!-- Legacy compatibility -->
<color name="my_light_primary">#BE1F13</color> <color name="my_light_primary">#BE1F13</color>