This commit is contained in:
2026-05-11 14:54:38 +01:00
parent a02aecc3c9
commit 3d817d0b81
21 changed files with 875 additions and 312 deletions

View File

@@ -5,12 +5,12 @@ plugins {
android {
namespace = "com.example.pap_teste"
compileSdk = 36
compileSdk = 35
defaultConfig {
applicationId = "com.example.pap_teste"
minSdk = 24
targetSdk = 36
targetSdk = 35
versionCode = 1
versionName = "1.0"
@@ -44,6 +44,11 @@ dependencies {
implementation(libs.firebase.database)
implementation(libs.glide)
implementation(libs.firebase.storage)
// Room
implementation(libs.room.runtime)
annotationProcessor(libs.room.compiler)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)

View File

@@ -27,7 +27,7 @@
<activity
android:name=".OnboardingActivity"
android:name=".SplashActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@@ -35,6 +35,10 @@
</intent-filter>
</activity>
<activity
android:name=".OnboardingActivity"
android:exported="false" />
<activity
android:name=".MainActivity"
android:exported="false" />
@@ -50,6 +54,42 @@
<activity
android:name=".ReservationActivity"
android:exported="false" />
<activity
android:name=".FavoritosActivity"
android:exported="false" />
<activity
android:name=".EstablishmentDashboardActivity"
android:exported="false" />
<activity
android:name=".AccountCreatedActivity"
android:exported="false" />
<activity
android:name=".NovaReservaActivity"
android:exported="false" />
<activity
android:name=".CheckInAntecipadoActivity"
android:exported="false" />
<activity
android:name=".MinhasReservasActivity"
android:exported="false" />
<activity
android:name=".PartilharReservaActivity"
android:exported="false" />
<activity
android:name=".ProfileDashboardActivity"
android:exported="false" />
<activity
android:name=".ExplorarRestaurantesActivity"
android:exported="false" />
</application>
</manifest>

View File

@@ -1,5 +1,7 @@
package com.example.pap_teste;
import static android.content.Intent.getIntent;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
@@ -50,13 +52,13 @@ public class AccountCreatedActivity extends AppCompatActivity {
this,
isEstablishment ? EstablishmentDashboardActivity.class : ClientDashboardActivity.class
);
nextScreen.putExtra(MainActivity.EXTRA_ACTION_MODE, MainActivity.AccountAction.CRIAR.name());
// nextScreen.putExtra(MainActivity.EXTRA_ACTION_MODE, MainActivity.AccountAction.CRIAR.name());
nextScreen.putExtra(MainActivity.EXTRA_DISPLAY_NAME, displayName);
nextScreen.putExtra(MainActivity.EXTRA_EMAIL, email);
nextScreen.putExtra(MainActivity.EXTRA_ACCOUNT_TYPE, accountType);
nextScreen.putExtra(MainActivity.EXTRA_ROLE, role);
startActivity(nextScreen);
finish();
//startActivity(nextScreen);
//finish();
});
if (btnBack != null) {

View File

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

View File

@@ -35,7 +35,8 @@ public class MainActivity extends AppCompatActivity {
// Check if user is already logged in
if (mAuth.getCurrentUser() != null) {
startActivity(new Intent(this, ClientDashboardActivity.class));
android.app.ActivityOptions options = android.app.ActivityOptions.makeCustomAnimation(this, android.R.anim.fade_in, android.R.anim.fade_out);
startActivity(new Intent(this, ClientDashboardActivity.class), options.toBundle());
finish();
return;
}
@@ -101,7 +102,8 @@ public class MainActivity extends AppCompatActivity {
User newUser = new User(uid, name, email, "CLIENTE");
db.collection("users").document(uid).set(newUser)
.addOnSuccessListener(aVoid -> {
startActivity(new Intent(this, ClientDashboardActivity.class));
android.app.ActivityOptions options = android.app.ActivityOptions.makeCustomAnimation(this, android.R.anim.fade_in, android.R.anim.fade_out);
startActivity(new Intent(this, ClientDashboardActivity.class), options.toBundle());
finish();
});
})

View File

@@ -9,23 +9,22 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Reserva;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import com.example.pap_teste.models.LocalReservation;
import com.example.pap_teste.viewmodels.ReservationViewModel;
import java.util.ArrayList;
import java.util.List;
public class MyReservationsFragment extends Fragment {
private RecyclerView rvReservations;
private ReservaAdapter adapter;
private List<Reserva> reservationList = new ArrayList<>();
private LocalReservaAdapter adapter;
private List<LocalReservation> reservationList = new ArrayList<>();
private ProgressBar progressBar;
private View emptyState;
private FirebaseFirestore db;
private ReservationViewModel viewModel;
@Nullable
@Override
@@ -36,63 +35,41 @@ public class MyReservationsFragment extends Fragment {
progressBar = view.findViewById(R.id.progressBar);
emptyState = view.findViewById(R.id.emptyState);
db = FirebaseFirestore.getInstance();
viewModel = new ViewModelProvider(this).get(ReservationViewModel.class);
setupAdapter();
loadReservations();
observeReservations();
return view;
}
private void setupAdapter() {
adapter = new ReservaAdapter(reservationList, new ReservaAdapter.OnReservaActionListener() {
adapter = new LocalReservaAdapter(reservationList, new LocalReservaAdapter.OnActionListener() {
@Override
public void onCheckIn(Reserva reserva) {
// Implement check-in logic if needed
Toast.makeText(getContext(), "Check-in disponível em breve.", Toast.LENGTH_SHORT).show();
public void onCancel(LocalReservation reservation) {
reservation.setStatus("CANCELADA");
viewModel.update(reservation);
Toast.makeText(getContext(), "Reserva cancelada.", Toast.LENGTH_SHORT).show();
}
@Override
public void onCancel(Reserva reserva) {
cancelReservation(reserva);
public void onEdit(LocalReservation reservation) {
// Show details or edit
Toast.makeText(getContext(), "Detalhes: " + reservation.getNotes(), Toast.LENGTH_LONG).show();
}
});
rvReservations.setLayoutManager(new LinearLayoutManager(getContext()));
rvReservations.setAdapter(adapter);
}
private void loadReservations() {
String userId = FirebaseAuth.getInstance().getUid();
if (userId == null) return;
private void observeReservations() {
progressBar.setVisibility(View.VISIBLE);
db.collection("reservations")
.whereEqualTo("userId", userId)
.orderBy("date", Query.Direction.DESCENDING)
.get()
.addOnCompleteListener(task -> {
viewModel.getAllReservations().observe(getViewLifecycleOwner(), reservations -> {
progressBar.setVisibility(View.GONE);
if (task.isSuccessful()) {
reservationList.clear();
for (QueryDocumentSnapshot document : task.getResult()) {
Reserva r = document.toObject(Reserva.class);
r.setId(document.getId());
reservationList.add(r);
}
reservationList.addAll(reservations);
adapter.notifyDataSetChanged();
emptyState.setVisibility(reservationList.isEmpty() ? View.VISIBLE : View.GONE);
} else {
Toast.makeText(getContext(), "Erro ao carregar reservas.", Toast.LENGTH_SHORT).show();
}
});
}
private void cancelReservation(Reserva reserva) {
db.collection("reservations").document(reserva.getId())
.update("status", "cancelled")
.addOnSuccessListener(aVoid -> {
Toast.makeText(getContext(), "Reserva cancelada.", Toast.LENGTH_SHORT).show();
loadReservations();
})
.addOnFailureListener(e -> Toast.makeText(getContext(), "Erro ao cancelar.", Toast.LENGTH_SHORT).show());
}
}

View File

@@ -1,333 +1,261 @@
package com.example.pap_teste;
import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
import android.os.Bundle;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.widget.Button;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.FoodCategory;
import com.example.pap_teste.models.LocalReservation;
import com.example.pap_teste.models.Mesa;
import com.example.pap_teste.models.Restaurant;
import com.example.pap_teste.viewmodels.ReservationViewModel;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
public class NovaReservaActivity extends AppCompatActivity {
private enum State {
CATEGORIES, RESTAURANTS, DETAILS
}
private enum State { CATEGORIES, RESTAURANTS, DETAILS }
private State currentState = State.CATEGORIES;
private String selectedCategory = null;
private com.example.pap_teste.models.Restaurant selectedRestaurant = null;
private Restaurant selectedRestaurant = null;
private Mesa selectedMesa = null;
private String selectedDate = null;
private String selectedTime = null;
private androidx.recyclerview.widget.RecyclerView rvCategories, rvRestaurants;
private android.view.View scrollNovaReserva;
private android.widget.TextView txtTitle;
private android.widget.ProgressBar progressBar;
private RecyclerView rvCategories, rvRestaurants, rvTables;
private View scrollNovaReserva;
private TextView txtTitle;
private ProgressBar progressBar;
private ReservationViewModel viewModel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_nova_reserva);
viewModel = new ViewModelProvider(this).get(ReservationViewModel.class);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.novaReservaRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
initViews();
setupCategories();
updateViewState();
}
private void initViews() {
rvCategories = findViewById(R.id.rvCategories);
rvRestaurants = findViewById(R.id.rvRestaurants);
rvTables = findViewById(R.id.rvTables);
scrollNovaReserva = findViewById(R.id.scrollNovaReserva);
txtTitle = findViewById(R.id.txtTituloNovaReserva);
progressBar = findViewById(R.id.progressBar);
Button back = findViewById(R.id.btnVoltar);
if (back != null) {
back.setOnClickListener(v -> handleBackNavigation());
}
setupCategories();
updateViewState();
findViewById(R.id.btnVoltar).setOnClickListener(v -> handleBackNavigation());
findViewById(R.id.btnSelectDate).setOnClickListener(v -> showDatePicker());
findViewById(R.id.btnSelectTime).setOnClickListener(v -> showTimePicker());
findViewById(R.id.btnConfirmarReserva).setOnClickListener(v -> validateAndSave());
}
private void handleBackNavigation() {
if (currentState == State.RESTAURANTS) {
currentState = State.CATEGORIES;
updateViewState();
} else if (currentState == State.DETAILS) {
currentState = State.RESTAURANTS;
updateViewState();
} else {
finish();
return;
}
updateViewState();
}
private void updateViewState() {
rvCategories
.setVisibility(currentState == State.CATEGORIES ? android.view.View.VISIBLE : android.view.View.GONE);
rvRestaurants
.setVisibility(currentState == State.RESTAURANTS ? android.view.View.VISIBLE : android.view.View.GONE);
scrollNovaReserva
.setVisibility(currentState == State.DETAILS ? android.view.View.VISIBLE : android.view.View.GONE);
rvCategories.setVisibility(currentState == State.CATEGORIES ? View.VISIBLE : View.GONE);
rvRestaurants.setVisibility(currentState == State.RESTAURANTS ? View.VISIBLE : View.GONE);
scrollNovaReserva.setVisibility(currentState == State.DETAILS ? View.VISIBLE : View.GONE);
if (currentState == State.CATEGORIES) {
txtTitle.setText("Escolha o tema");
} else if (currentState == State.RESTAURANTS) {
txtTitle.setText("Restaurantes: " + selectedCategory);
} else {
txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : ""));
setupReservationOptions();
switch (currentState) {
case CATEGORIES: txtTitle.setText("Escolha o tema"); break;
case RESTAURANTS: txtTitle.setText("Restaurantes: " + selectedCategory); break;
case DETAILS: txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : "")); break;
}
}
private void setupCategories() {
java.util.List<com.example.pap_teste.models.FoodCategory> cats = new java.util.ArrayList<>();
cats.add(new com.example.pap_teste.models.FoodCategory("Carnes", R.drawable.cat_carnes));
cats.add(new com.example.pap_teste.models.FoodCategory("Massas", R.drawable.cat_massas));
cats.add(new com.example.pap_teste.models.FoodCategory("Sushi", R.drawable.cat_sushi));
cats.add(new com.example.pap_teste.models.FoodCategory("Pizzas", R.drawable.cat_pizzas));
cats.add(new com.example.pap_teste.models.FoodCategory("Sobremesas", R.drawable.cat_sobremesas));
List<FoodCategory> cats = new ArrayList<>();
cats.add(new FoodCategory("Carnes", R.drawable.cat_carnes));
cats.add(new FoodCategory("Massas", R.drawable.cat_massas));
cats.add(new FoodCategory("Sushi", R.drawable.cat_sushi));
cats.add(new FoodCategory("Pizzas", R.drawable.cat_pizzas));
cats.add(new FoodCategory("Sobremesas", R.drawable.cat_sobremesas));
rvCategories.setLayoutManager(new androidx.recyclerview.widget.LinearLayoutManager(this));
rvCategories.setLayoutManager(new LinearLayoutManager(this));
rvCategories.setAdapter(new FoodCategoryAdapter(cats, category -> {
selectedCategory = category.getName();
currentState = State.RESTAURANTS;
setupRestaurants();
loadRestaurants();
updateViewState();
}));
}
private void setupRestaurants() {
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("Restaurantes");
if (progressBar != null)
progressBar.setVisibility(android.view.View.VISIBLE);
usersRef.orderByChild("category").equalTo(selectedCategory)
.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
private void loadRestaurants() {
progressBar.setVisibility(View.VISIBLE);
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("Restaurantes");
ref.orderByChild("category").equalTo(selectedCategory).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(
@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
if (progressBar != null)
progressBar.setVisibility(android.view.View.GONE);
filteredList.clear();
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
String role = ds.child("role").getValue(String.class);
String accountType = ds.child("accountType").getValue(String.class);
if ("ADMIN".equalsIgnoreCase(role) || "ESTABELECIMENTO".equalsIgnoreCase(accountType)) {
public void onDataChange(@NonNull DataSnapshot snapshot) {
progressBar.setVisibility(View.GONE);
List<Restaurant> list = new ArrayList<>();
for (DataSnapshot ds : snapshot.getChildren()) {
String name = ds.child("establishmentName").getValue(String.class);
if (name == null)
name = ds.child("displayName").getValue(String.class);
if (name == null) name = ds.child("displayName").getValue(String.class);
String email = ds.child("email").getValue(String.class);
String cat = ds.child("category").getValue(String.class);
String logoUrl = ds.child("logoUrl").getValue(String.class);
String logo = ds.child("logoUrl").getValue(String.class);
if (name != null && email != null) {
filteredList.add(new com.example.pap_teste.models.Restaurant(name, cat, email,
false, logoUrl));
list.add(new Restaurant(name, cat, email, false, logo));
}
}
}
rvRestaurants.setLayoutManager(
new androidx.recyclerview.widget.LinearLayoutManager(NovaReservaActivity.this));
rvRestaurants.setAdapter(new RestaurantAdapter(filteredList, restaurant -> {
rvRestaurants.setLayoutManager(new LinearLayoutManager(NovaReservaActivity.this));
rvRestaurants.setAdapter(new RestaurantAdapter(list, restaurant -> {
selectedRestaurant = restaurant;
currentState = State.DETAILS;
loadTables();
updateViewState();
}));
}
@Override
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();
public void onCancelled(@NonNull DatabaseError error) {
progressBar.setVisibility(View.GONE);
Toast.makeText(NovaReservaActivity.this, "Erro ao carregar.", Toast.LENGTH_SHORT).show();
}
});
}
private String selectedDate = null;
private String selectedTime = null;
private void setupReservationOptions() {
android.widget.Button btnDate = findViewById(R.id.btnSelectDate);
android.widget.Button btnTime = findViewById(R.id.btnSelectTime);
btnDate.setOnClickListener(v -> {
java.util.Calendar cal = java.util.Calendar.getInstance();
new android.app.DatePickerDialog(this, (view, year, month, dayOfMonth) -> {
selectedDate = dayOfMonth + "/" + (month + 1) + "/" + year;
btnDate.setText(selectedDate);
}, cal.get(java.util.Calendar.YEAR), cal.get(java.util.Calendar.MONTH),
cal.get(java.util.Calendar.DAY_OF_MONTH)).show();
});
btnTime.setOnClickListener(v -> {
java.util.Calendar cal = java.util.Calendar.getInstance();
new android.app.TimePickerDialog(this, (view, hourOfDay, minute) -> {
selectedTime = String.format(java.util.Locale.getDefault(), "%02d:%02d", hourOfDay, minute);
btnTime.setText(selectedTime);
}, cal.get(java.util.Calendar.HOUR_OF_DAY), cal.get(java.util.Calendar.MINUTE), true).show();
});
findViewById(R.id.btnConfirmarReserva).setOnClickListener(v -> saveReservation());
private void loadTables() {
progressBar.setVisibility(View.VISIBLE);
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("Mesas");
ref.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
progressBar.setVisibility(View.GONE);
List<Mesa> tables = new ArrayList<>();
for (DataSnapshot ds : snapshot.getChildren()) {
Mesa m = ds.getValue(Mesa.class);
if (m != null && selectedRestaurant.getEmail().equalsIgnoreCase(m.getRestauranteEmail())) {
tables.add(m);
}
}
rvTables.setLayoutManager(new GridLayoutManager(NovaReservaActivity.this, 3));
rvTables.setAdapter(new TableSelectionAdapter(tables, mesa -> selectedMesa = mesa));
}
private void saveReservation() {
android.widget.EditText etPartySize = findViewById(R.id.etPartySize);
int val = 0;
try {
val = Integer.parseInt(etPartySize.getText().toString());
} catch (Exception e) {
@Override
public void onCancelled(@NonNull DatabaseError error) {
progressBar.setVisibility(View.GONE);
}
});
}
final int partySize = val;
if (selectedDate == null || selectedTime == null || partySize == 0) {
android.widget.Toast.makeText(this, "Por favor, selecione data, hora e número de pessoas.",
android.widget.Toast.LENGTH_SHORT).show();
private void showDatePicker() {
Calendar cal = Calendar.getInstance();
new DatePickerDialog(this, (view, year, month, day) -> {
selectedDate = String.format(Locale.getDefault(), "%02d/%02d/%d", day, month + 1, year);
((Button)findViewById(R.id.btnSelectDate)).setText(selectedDate);
}, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show();
}
private void showTimePicker() {
Calendar cal = Calendar.getInstance();
new TimePickerDialog(this, (view, hour, min) -> {
selectedTime = String.format(Locale.getDefault(), "%02d:%02d", hour, min);
((Button)findViewById(R.id.btnSelectTime)).setText(selectedTime);
}, cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE), true).show();
}
private void validateAndSave() {
EditText etPartySize = findViewById(R.id.etPartySize);
EditText etNotes = findViewById(R.id.etNotes);
String partySizeStr = etPartySize.getText().toString();
if (selectedDate == null || selectedTime == null || selectedMesa == null || partySizeStr.isEmpty()) {
Toast.makeText(this, "Preencha todos os campos e escolha a mesa.", Toast.LENGTH_SHORT).show();
return;
}
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");
mesasRef.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
int totalMesas = 0;
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
com.example.pap_teste.models.Mesa m = ds.getValue(com.example.pap_teste.models.Mesa.class);
if (m != null && m.getRestauranteEmail() != null && restEmail.trim().equalsIgnoreCase(m.getRestauranteEmail().trim())) {
totalMesas++;
}
}
if (totalMesas == 0) {
proceedWithReservation(partySize);
int partySize = Integer.parseInt(partySizeStr);
if (partySize > selectedMesa.getCapacidade()) {
Toast.makeText(this, "A mesa escolhida só suporta " + selectedMesa.getCapacidade() + " pessoas.", Toast.LENGTH_SHORT).show();
return;
}
checkReservationsAndSave(totalMesas, partySize);
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
proceedWithReservation(partySize);
// Conflict check
viewModel.checkConflict(selectedDate, selectedMesa.getNumero(), isAvailable -> {
runOnUiThread(() -> {
if (isAvailable) {
saveToFirebaseAndRoom(partySize, etNotes.getText().toString());
} else {
Toast.makeText(this, "Esta mesa já está reservada para este dia.", Toast.LENGTH_LONG).show();
}
});
});
}
private void checkReservationsAndSave(int totalMesas, final int partySize) {
String restEmail = selectedRestaurant.getEmail();
com.google.firebase.database.DatabaseReference reservasRef = com.google.firebase.database.FirebaseDatabase
.getInstance().getReference("reservas");
reservasRef.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(
@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
int ocupadas = 0;
java.util.Map<String, Integer> ocupacaoPorHora = new java.util.HashMap<>();
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.getRestauranteEmail() != null &&
r.getRestauranteEmail().trim().equalsIgnoreCase(restEmail.trim()) &&
selectedDate.equals(r.getData()) && !"Cancelada".equals(r.getEstado())
&& !"Recusada".equals(r.getEstado())) {
int count = ocupacaoPorHora.getOrDefault(r.getHora(), 0) + 1;
ocupacaoPorHora.put(r.getHora(), count);
if (selectedTime.equals(r.getHora())) {
ocupadas++;
}
}
}
if (ocupadas >= totalMesas) {
String sugestao = "";
String[] horasComuns = { "12:00", "12:30", "13:00", "13:30", "14:00", "19:00", "19:30",
"20:00", "20:30", "21:00", "21:30", "22:00" };
for (String h : horasComuns) {
if (ocupacaoPorHora.getOrDefault(h, 0) < totalMesas && !h.equals(selectedTime)) {
sugestao = h;
break; // Encontramos a primeira sugestão livre
}
}
String msg = "Não há mesas disponíveis para as " + selectedTime + ".";
if (!sugestao.isEmpty()) {
msg += " Sugestão: tente reservar para as " + sugestao + ".";
} else {
msg += " Tente para outro dia.";
}
android.widget.Toast
.makeText(NovaReservaActivity.this, msg, android.widget.Toast.LENGTH_LONG).show();
} else {
proceedWithReservation(partySize);
}
}
@Override
public void onCancelled(
@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
proceedWithReservation(partySize);
}
});
}
private void proceedWithReservation(int partySize) {
String userEmail = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser() != null
? com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser().getEmail()
: "cliente@teste.com";
com.google.firebase.database.DatabaseReference ref = com.google.firebase.database.FirebaseDatabase.getInstance()
.getReference("reservas");
private void saveToFirebaseAndRoom(int guests, String notes) {
progressBar.setVisibility(View.VISIBLE);
DatabaseReference ref = FirebaseDatabase.getInstance().getReference("reservas");
String id = ref.push().getKey();
String userEmail = FirebaseAuth.getInstance().getCurrentUser() != null ? FirebaseAuth.getInstance().getCurrentUser().getEmail() : "anon@test.com";
com.example.pap_teste.models.Reserva reserva = new com.example.pap_teste.models.Reserva(
id,
userEmail,
selectedRestaurant.getName(),
selectedRestaurant.getEmail(),
selectedDate,
selectedTime,
partySize,
"Pendente");
LocalReservation local = new LocalReservation();
local.setFirebaseId(id);
local.setRestaurantName(selectedRestaurant.getName());
local.setRestaurantEmail(selectedRestaurant.getEmail());
local.setDate(selectedDate);
local.setTime(selectedTime);
local.setGuests(guests);
local.setTableNumber(selectedMesa.getNumero());
local.setNotes(notes);
local.setStatus("PENDENTE");
if (id != null) {
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);
ref.child(id).setValue(local).addOnCompleteListener(task -> {
progressBar.setVisibility(View.GONE);
if (task.isSuccessful()) {
android.widget.Toast
.makeText(NovaReservaActivity.this, "Reserva solicitada com sucesso!",
android.widget.Toast.LENGTH_SHORT)
.show();
viewModel.insert(local);
Toast.makeText(this, "Reserva realizada com sucesso!", Toast.LENGTH_LONG).show();
finish();
} else {
android.widget.Toast
.makeText(NovaReservaActivity.this, "Erro ao salvar reserva.",
android.widget.Toast.LENGTH_SHORT)
.show();
Toast.makeText(this, "Erro ao guardar no servidor.", Toast.LENGTH_SHORT).show();
}
});
}

View File

@@ -39,7 +39,8 @@ public class OnboardingActivity extends AppCompatActivity {
tagline.startAnimation(fadeIn);
btnGetStarted.setOnClickListener(v -> {
startActivity(new Intent(OnboardingActivity.this, MainActivity.class));
android.app.ActivityOptions options = android.app.ActivityOptions.makeCustomAnimation(this, android.R.anim.fade_in, android.R.anim.fade_out);
startActivity(new Intent(OnboardingActivity.this, MainActivity.class), options.toBundle());
finish();
});
}

View File

@@ -10,6 +10,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;

View File

@@ -0,0 +1,36 @@
package com.example.pap_teste;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
ImageView logo = findViewById(R.id.splash_logo);
TextView title = findViewById(R.id.splash_title);
TextView subtitle = findViewById(R.id.splash_subtitle);
Animation fadeIn = AnimationUtils.loadAnimation(this, android.R.anim.fade_in);
fadeIn.setDuration(1500);
logo.startAnimation(fadeIn);
title.startAnimation(fadeIn);
subtitle.startAnimation(fadeIn);
new Handler(Looper.getMainLooper()).postDelayed(() -> {
startActivity(new Intent(SplashActivity.this, OnboardingActivity.class));
finish();
}, 2500);
}
}

View File

@@ -0,0 +1,87 @@
package com.example.pap_teste;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Mesa;
import com.google.android.material.card.MaterialCardView;
import java.util.List;
public class TableSelectionAdapter extends RecyclerView.Adapter<TableSelectionAdapter.ViewHolder> {
private final List<Mesa> tables;
private final OnTableClickListener listener;
private int selectedPosition = -1;
public interface OnTableClickListener {
void onTableClick(Mesa mesa);
}
public TableSelectionAdapter(List<Mesa> tables, OnTableClickListener listener) {
this.tables = tables;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_table, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Mesa mesa = tables.get(position);
holder.txtNumber.setText("Mesa " + mesa.getNumero());
boolean isOccupied = "OCUPADA".equalsIgnoreCase(mesa.getEstado()) || "RESERVADA".equalsIgnoreCase(mesa.getEstado());
holder.txtStatus.setText(mesa.getEstado());
if (isOccupied) {
holder.card.setCardBackgroundColor(Color.parseColor("#F5F5F5"));
holder.card.setStrokeColor(Color.TRANSPARENT);
holder.txtStatus.setTextColor(Color.GRAY);
holder.itemView.setEnabled(false);
holder.imgStatus.setAlpha(0.3f);
} else {
boolean isSelected = selectedPosition == position;
holder.card.setCardBackgroundColor(isSelected ? holder.itemView.getContext().getColor(R.color.colorChipConfirmed) : Color.WHITE);
holder.card.setStrokeColor(isSelected ? holder.itemView.getContext().getColor(R.color.colorSuccess) : holder.itemView.getContext().getColor(R.color.colorBorder));
holder.txtStatus.setTextColor(isSelected ? holder.itemView.getContext().getColor(R.color.colorSuccess) : holder.itemView.getContext().getColor(R.color.colorTextSecondary));
holder.itemView.setEnabled(true);
holder.imgStatus.setAlpha(1.0f);
}
holder.itemView.setOnClickListener(v -> {
int previousSelected = selectedPosition;
selectedPosition = holder.getAdapterPosition();
if (previousSelected != -1) notifyItemChanged(previousSelected);
notifyItemChanged(selectedPosition);
listener.onTableClick(mesa);
});
}
@Override
public int getItemCount() {
return tables.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
MaterialCardView card;
TextView txtNumber, txtStatus;
ImageView imgStatus;
ViewHolder(View itemView) {
super(itemView);
card = itemView.findViewById(R.id.cardTable);
txtNumber = itemView.findViewById(R.id.txtTableNumber);
txtStatus = itemView.findViewById(R.id.txtTableStatus);
imgStatus = itemView.findViewById(R.id.imgTableStatus);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,25 @@
package com.example.pap_teste.models;
import com.google.firebase.Timestamp;
public class Reserva {
private String id;
private String userId;
private String restaurantId;
private String restaurantName;
private String clienteEmail;
private String restauranteName;
private String restauranteEmail;
private String data;
private String hora;
private int pessoas;
private int guests;
private String estado;
private String status;
private Timestamp date;
private String timeSlot;
private String specialRequests;
private Timestamp createdAt;
public Reserva() {
}
@@ -25,27 +36,41 @@ public class Reserva {
this.estado = estado;
}
// Existing getters/setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getClienteEmail() { return clienteEmail; }
public void setClienteEmail(String clienteEmail) { this.clienteEmail = clienteEmail; }
public String getRestauranteName() { return restauranteName; }
public void setRestauranteName(String restauranteName) { this.restauranteName = restauranteName; }
public String getRestauranteEmail() { return restauranteEmail; }
public void setRestauranteEmail(String restauranteEmail) { this.restauranteEmail = restauranteEmail; }
public String getData() { return data; }
public void setData(String data) { this.data = data; }
public String getHora() { return hora; }
public void setHora(String hora) { this.hora = hora; }
public int getPessoas() { return pessoas; }
public void setPessoas(int pessoas) { this.pessoas = pessoas; }
public String getEstado() { return estado; }
public void setEstado(String estado) { this.estado = estado; }
// New getters/setters to fix ReservationActivity
public String getUserId() { return userId != null ? userId : clienteEmail; }
public void setUserId(String userId) { this.userId = userId; }
public String getRestaurantId() { return restaurantId != null ? restaurantId : restauranteEmail; }
public void setRestaurantId(String restaurantId) { this.restaurantId = restaurantId; }
public String getRestaurantName() { return restaurantName != null ? restaurantName : restauranteName; }
public void setRestaurantName(String restaurantName) { this.restaurantName = restaurantName; }
public int getGuests() { return guests != 0 ? guests : pessoas; }
public void setGuests(int guests) { this.guests = guests; }
public String getStatus() { return status != null ? status : estado; }
public void setStatus(String status) { this.status = status; }
public Timestamp getDate() { return date; }
public void setDate(Timestamp date) { this.date = date; }
public String getTimeSlot() { return timeSlot != null ? timeSlot : hora; }
public void setTimeSlot(String timeSlot) { this.timeSlot = timeSlot; }
public String getSpecialRequests() { return specialRequests; }
public void setSpecialRequests(String specialRequests) { this.specialRequests = specialRequests; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}

View File

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

View File

@@ -161,6 +161,24 @@
android:textColor="@color/colorTextPrimary"
app:strokeColor="@color/colorDivider" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Escolha a mesa"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTables"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:listitem="@layout/item_table" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -180,6 +198,28 @@
android:hint="Ex: 2"
android:textColorHint="@color/colorTextHint"
android:textColor="@color/colorTextPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Notas adicionais (opcional)"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
<EditText
android:id="@+id/etNotes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/bg_input_modern"
android:inputType="textMultiLine"
android:minHeight="100dp"
android:gravity="top"
android:padding="16dp"
android:hint="Alguma preferência?"
android:textColorHint="@color/colorTextHint"
android:textColor="@color/colorTextPrimary" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary">
<ImageView
android:id="@+id/splash_logo"
android:layout_width="150dp"
android:layout_height="150dp"
android:src="@drawable/na_mesa"
app:layout_constraintBottom_toTopOf="@+id/splash_title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/splash_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:fontFamily="sans-serif-black"
android:text="NaMesa"
android:textColor="@color/white"
android:textSize="40sp"
app:layout_constraintBottom_toTopOf="@+id/splash_subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/splash_logo" />
<TextView
android:id="@+id/splash_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:alpha="0.8"
android:text="Sempre um lugar para si"
android:textColor="@color/white"
android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/splash_title" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
app:cardCornerRadius="16dp"
app:cardElevation="4dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/txtRestauranteNome"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Nome do Restaurante"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp"
android:textStyle="bold" />
<com.google.android.material.chip.Chip
android:id="@+id/chipStatus"
style="@style/Widget.Material3.Chip.Suggestion"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PENDENTE"
app:chipBackgroundColor="@color/colorChipPending"
android:textColor="@color/colorChipPendingText" />
</LinearLayout>
<TextView
android:id="@+id/txtDataHora"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="15/05/2026 às 20:00"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
<TextView
android:id="@+id/txtMesaPessoas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Mesa 5 • 4 pessoas"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="16dp"
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/btnCancelar"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cancelar"
android:textColor="@color/colorError" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnEditar"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Ver Detalhes"
android:textColor="@color/colorPrimary" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/cardTable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
app:cardCornerRadius="12dp"
app:cardElevation="2dp"
app:strokeWidth="2dp"
app:strokeColor="@color/colorBorder"
app:cardBackgroundColor="@color/white">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="12dp">
<ImageView
android:id="@+id/imgTableStatus"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_agenda"
app:tint="@color/colorTextSecondary" />
<TextView
android:id="@+id/txtTableNumber"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Mesa 1"
android:textColor="@color/colorTextPrimary"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtTableStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Livre"
android:textColor="@color/colorTextSecondary"
android:textSize="12sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -13,6 +13,7 @@ googleServices = "4.4.2"
firebaseDatabase = "22.0.1"
glide = "4.16.0"
firebaseStorage = "21.0.1"
room = "2.6.1"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -27,6 +28,9 @@ firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.r
firebase-database = { group = "com.google.firebase", name = "firebase-database", version.ref = "firebaseDatabase" }
glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }
firebase-storage = { group = "com.google.firebase", name = "firebase-storage", version.ref = "firebaseStorage" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }