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

View File

@@ -27,7 +27,7 @@
<activity <activity
android:name=".OnboardingActivity" android:name=".SplashActivity"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
@@ -35,6 +35,10 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".OnboardingActivity"
android:exported="false" />
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="false" /> android:exported="false" />
@@ -50,6 +54,42 @@
<activity <activity
android:name=".ReservationActivity" android:name=".ReservationActivity"
android:exported="false" /> 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> </application>
</manifest> </manifest>

View File

@@ -1,5 +1,7 @@
package com.example.pap_teste; package com.example.pap_teste;
import static android.content.Intent.getIntent;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.widget.Button; import android.widget.Button;
@@ -50,13 +52,13 @@ public class AccountCreatedActivity extends AppCompatActivity {
this, this,
isEstablishment ? EstablishmentDashboardActivity.class : ClientDashboardActivity.class 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_DISPLAY_NAME, displayName);
nextScreen.putExtra(MainActivity.EXTRA_EMAIL, email); nextScreen.putExtra(MainActivity.EXTRA_EMAIL, email);
nextScreen.putExtra(MainActivity.EXTRA_ACCOUNT_TYPE, accountType); nextScreen.putExtra(MainActivity.EXTRA_ACCOUNT_TYPE, accountType);
nextScreen.putExtra(MainActivity.EXTRA_ROLE, role); nextScreen.putExtra(MainActivity.EXTRA_ROLE, role);
startActivity(nextScreen); //startActivity(nextScreen);
finish(); //finish();
}); });
if (btnBack != null) { 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 // Check if user is already logged in
if (mAuth.getCurrentUser() != null) { 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(); finish();
return; return;
} }
@@ -101,7 +102,8 @@ public class MainActivity extends AppCompatActivity {
User newUser = new User(uid, name, email, "CLIENTE"); User newUser = new User(uid, name, email, "CLIENTE");
db.collection("users").document(uid).set(newUser) db.collection("users").document(uid).set(newUser)
.addOnSuccessListener(aVoid -> { .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(); finish();
}); });
}) })

View File

@@ -9,23 +9,22 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Reserva; import com.example.pap_teste.models.LocalReservation;
import com.google.firebase.auth.FirebaseAuth; import com.example.pap_teste.viewmodels.ReservationViewModel;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class MyReservationsFragment extends Fragment { public class MyReservationsFragment extends Fragment {
private RecyclerView rvReservations; private RecyclerView rvReservations;
private ReservaAdapter adapter; private LocalReservaAdapter adapter;
private List<Reserva> reservationList = new ArrayList<>(); private List<LocalReservation> reservationList = new ArrayList<>();
private ProgressBar progressBar; private ProgressBar progressBar;
private View emptyState; private View emptyState;
private FirebaseFirestore db; private ReservationViewModel viewModel;
@Nullable @Nullable
@Override @Override
@@ -36,63 +35,41 @@ public class MyReservationsFragment extends Fragment {
progressBar = view.findViewById(R.id.progressBar); progressBar = view.findViewById(R.id.progressBar);
emptyState = view.findViewById(R.id.emptyState); emptyState = view.findViewById(R.id.emptyState);
db = FirebaseFirestore.getInstance(); viewModel = new ViewModelProvider(this).get(ReservationViewModel.class);
setupAdapter(); setupAdapter();
loadReservations(); observeReservations();
return view; return view;
} }
private void setupAdapter() { private void setupAdapter() {
adapter = new ReservaAdapter(reservationList, new ReservaAdapter.OnReservaActionListener() { adapter = new LocalReservaAdapter(reservationList, new LocalReservaAdapter.OnActionListener() {
@Override @Override
public void onCheckIn(Reserva reserva) { public void onCancel(LocalReservation reservation) {
// Implement check-in logic if needed reservation.setStatus("CANCELADA");
Toast.makeText(getContext(), "Check-in disponível em breve.", Toast.LENGTH_SHORT).show(); viewModel.update(reservation);
Toast.makeText(getContext(), "Reserva cancelada.", Toast.LENGTH_SHORT).show();
} }
@Override @Override
public void onCancel(Reserva reserva) { public void onEdit(LocalReservation reservation) {
cancelReservation(reserva); // Show details or edit
Toast.makeText(getContext(), "Detalhes: " + reservation.getNotes(), Toast.LENGTH_LONG).show();
} }
}); });
rvReservations.setLayoutManager(new LinearLayoutManager(getContext()));
rvReservations.setAdapter(adapter); rvReservations.setAdapter(adapter);
} }
private void loadReservations() { private void observeReservations() {
String userId = FirebaseAuth.getInstance().getUid();
if (userId == null) return;
progressBar.setVisibility(View.VISIBLE); progressBar.setVisibility(View.VISIBLE);
db.collection("reservations") viewModel.getAllReservations().observe(getViewLifecycleOwner(), reservations -> {
.whereEqualTo("userId", userId) progressBar.setVisibility(View.GONE);
.orderBy("date", Query.Direction.DESCENDING) reservationList.clear();
.get() reservationList.addAll(reservations);
.addOnCompleteListener(task -> { adapter.notifyDataSetChanged();
progressBar.setVisibility(View.GONE); emptyState.setVisibility(reservationList.isEmpty() ? View.VISIBLE : 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);
}
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; package com.example.pap_teste;
import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
import android.os.Bundle; 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.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets; import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat; import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat; import androidx.core.view.WindowInsetsCompat;
import androidx.lifecycle.ViewModelProvider;
import android.widget.Button; 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 { public class NovaReservaActivity extends AppCompatActivity {
private enum State { private enum State { CATEGORIES, RESTAURANTS, DETAILS }
CATEGORIES, RESTAURANTS, DETAILS
}
private State currentState = State.CATEGORIES; private State currentState = State.CATEGORIES;
private String selectedCategory = null; 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 RecyclerView rvCategories, rvRestaurants, rvTables;
private android.view.View scrollNovaReserva; private View scrollNovaReserva;
private android.widget.TextView txtTitle; private TextView txtTitle;
private android.widget.ProgressBar progressBar; private ProgressBar progressBar;
private ReservationViewModel viewModel;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
EdgeToEdge.enable(this); EdgeToEdge.enable(this);
setContentView(R.layout.activity_nova_reserva); setContentView(R.layout.activity_nova_reserva);
viewModel = new ViewModelProvider(this).get(ReservationViewModel.class);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.novaReservaRoot), (v, insets) -> { ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.novaReservaRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets; return insets;
}); });
initViews();
setupCategories();
updateViewState();
}
private void initViews() {
rvCategories = findViewById(R.id.rvCategories); rvCategories = findViewById(R.id.rvCategories);
rvRestaurants = findViewById(R.id.rvRestaurants); rvRestaurants = findViewById(R.id.rvRestaurants);
rvTables = findViewById(R.id.rvTables);
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); progressBar = findViewById(R.id.progressBar);
Button back = findViewById(R.id.btnVoltar); findViewById(R.id.btnVoltar).setOnClickListener(v -> handleBackNavigation());
if (back != null) { findViewById(R.id.btnSelectDate).setOnClickListener(v -> showDatePicker());
back.setOnClickListener(v -> handleBackNavigation()); findViewById(R.id.btnSelectTime).setOnClickListener(v -> showTimePicker());
} findViewById(R.id.btnConfirmarReserva).setOnClickListener(v -> validateAndSave());
setupCategories();
updateViewState();
} }
private void handleBackNavigation() { private void handleBackNavigation() {
if (currentState == State.RESTAURANTS) { if (currentState == State.RESTAURANTS) {
currentState = State.CATEGORIES; currentState = State.CATEGORIES;
updateViewState();
} else if (currentState == State.DETAILS) { } else if (currentState == State.DETAILS) {
currentState = State.RESTAURANTS; currentState = State.RESTAURANTS;
updateViewState();
} else { } else {
finish(); finish();
return;
} }
updateViewState();
} }
private void updateViewState() { private void updateViewState() {
rvCategories rvCategories.setVisibility(currentState == State.CATEGORIES ? View.VISIBLE : View.GONE);
.setVisibility(currentState == State.CATEGORIES ? android.view.View.VISIBLE : android.view.View.GONE); rvRestaurants.setVisibility(currentState == State.RESTAURANTS ? View.VISIBLE : View.GONE);
rvRestaurants scrollNovaReserva.setVisibility(currentState == State.DETAILS ? View.VISIBLE : View.GONE);
.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);
if (currentState == State.CATEGORIES) { switch (currentState) {
txtTitle.setText("Escolha o tema"); case CATEGORIES: txtTitle.setText("Escolha o tema"); break;
} else if (currentState == State.RESTAURANTS) { case RESTAURANTS: txtTitle.setText("Restaurantes: " + selectedCategory); break;
txtTitle.setText("Restaurantes: " + selectedCategory); case DETAILS: txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : "")); break;
} else {
txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : ""));
setupReservationOptions();
} }
} }
private void setupCategories() { private void setupCategories() {
java.util.List<com.example.pap_teste.models.FoodCategory> cats = new java.util.ArrayList<>(); List<FoodCategory> cats = new ArrayList<>();
cats.add(new com.example.pap_teste.models.FoodCategory("Carnes", R.drawable.cat_carnes)); cats.add(new FoodCategory("Carnes", R.drawable.cat_carnes));
cats.add(new com.example.pap_teste.models.FoodCategory("Massas", R.drawable.cat_massas)); cats.add(new FoodCategory("Massas", R.drawable.cat_massas));
cats.add(new com.example.pap_teste.models.FoodCategory("Sushi", R.drawable.cat_sushi)); cats.add(new FoodCategory("Sushi", R.drawable.cat_sushi));
cats.add(new com.example.pap_teste.models.FoodCategory("Pizzas", R.drawable.cat_pizzas)); cats.add(new FoodCategory("Pizzas", R.drawable.cat_pizzas));
cats.add(new com.example.pap_teste.models.FoodCategory("Sobremesas", R.drawable.cat_sobremesas)); 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 -> { rvCategories.setAdapter(new FoodCategoryAdapter(cats, category -> {
selectedCategory = category.getName(); selectedCategory = category.getName();
currentState = State.RESTAURANTS; currentState = State.RESTAURANTS;
setupRestaurants(); loadRestaurants();
updateViewState(); updateViewState();
})); }));
} }
private void setupRestaurants() { private void loadRestaurants() {
java.util.List<com.example.pap_teste.models.Restaurant> filteredList = new java.util.ArrayList<>(); progressBar.setVisibility(View.VISIBLE);
com.google.firebase.database.DatabaseReference usersRef = com.google.firebase.database.FirebaseDatabase DatabaseReference ref = FirebaseDatabase.getInstance().getReference("Restaurantes");
.getInstance().getReference("Restaurantes"); ref.orderByChild("category").equalTo(selectedCategory).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
if (progressBar != null) public void onDataChange(@NonNull DataSnapshot snapshot) {
progressBar.setVisibility(android.view.View.VISIBLE); progressBar.setVisibility(View.GONE);
List<Restaurant> list = new ArrayList<>();
usersRef.orderByChild("category").equalTo(selectedCategory) for (DataSnapshot ds : snapshot.getChildren()) {
.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() { String name = ds.child("establishmentName").getValue(String.class);
@Override if (name == null) name = ds.child("displayName").getValue(String.class);
public void onDataChange( String email = ds.child("email").getValue(String.class);
@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) { String cat = ds.child("category").getValue(String.class);
if (progressBar != null) String logo = ds.child("logoUrl").getValue(String.class);
progressBar.setVisibility(android.view.View.GONE); if (name != null && email != null) {
filteredList.clear(); list.add(new Restaurant(name, cat, email, false, logo));
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)) {
String name = ds.child("establishmentName").getValue(String.class);
if (name == null)
name = ds.child("displayName").getValue(String.class);
String email = ds.child("email").getValue(String.class);
String cat = ds.child("category").getValue(String.class);
String logoUrl = ds.child("logoUrl").getValue(String.class);
if (name != null && email != null) {
filteredList.add(new com.example.pap_teste.models.Restaurant(name, cat, email,
false, logoUrl));
}
}
}
rvRestaurants.setLayoutManager(
new androidx.recyclerview.widget.LinearLayoutManager(NovaReservaActivity.this));
rvRestaurants.setAdapter(new RestaurantAdapter(filteredList, restaurant -> {
selectedRestaurant = restaurant;
currentState = State.DETAILS;
updateViewState();
}));
} }
}
rvRestaurants.setLayoutManager(new LinearLayoutManager(NovaReservaActivity.this));
rvRestaurants.setAdapter(new RestaurantAdapter(list, restaurant -> {
selectedRestaurant = restaurant;
currentState = State.DETAILS;
loadTables();
updateViewState();
}));
}
@Override @Override
public void onCancelled( public void onCancelled(@NonNull DatabaseError error) {
@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) { progressBar.setVisibility(View.GONE);
if (progressBar != null) Toast.makeText(NovaReservaActivity.this, "Erro ao carregar.", Toast.LENGTH_SHORT).show();
progressBar.setVisibility(android.view.View.GONE); }
android.widget.Toast.makeText(NovaReservaActivity.this, "Erro ao carregar restaurantes.", });
android.widget.Toast.LENGTH_SHORT).show();
}
});
} }
private String selectedDate = null; private void loadTables() {
private String selectedTime = null; 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 setupReservationOptions() { @Override
android.widget.Button btnDate = findViewById(R.id.btnSelectDate); public void onCancelled(@NonNull DatabaseError error) {
android.widget.Button btnTime = findViewById(R.id.btnSelectTime); progressBar.setVisibility(View.GONE);
}
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 saveReservation() { private void showDatePicker() {
android.widget.EditText etPartySize = findViewById(R.id.etPartySize); Calendar cal = Calendar.getInstance();
int val = 0; new DatePickerDialog(this, (view, year, month, day) -> {
try { selectedDate = String.format(Locale.getDefault(), "%02d/%02d/%d", day, month + 1, year);
val = Integer.parseInt(etPartySize.getText().toString()); ((Button)findViewById(R.id.btnSelectDate)).setText(selectedDate);
} catch (Exception e) { }, cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH)).show();
} }
final int partySize = val;
if (selectedDate == null || selectedTime == null || partySize == 0) { private void showTimePicker() {
android.widget.Toast.makeText(this, "Por favor, selecione data, hora e número de pessoas.", Calendar cal = Calendar.getInstance();
android.widget.Toast.LENGTH_SHORT).show(); 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; return;
} }
String restEmail = selectedRestaurant.getEmail(); 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;
}
if (progressBar != null) // Conflict check
progressBar.setVisibility(android.view.View.VISIBLE); viewModel.checkConflict(selectedDate, selectedMesa.getNumero(), isAvailable -> {
android.widget.Button btnConfirmar = findViewById(R.id.btnConfirmarReserva); runOnUiThread(() -> {
if (btnConfirmar != null) if (isAvailable) {
btnConfirmar.setEnabled(false); saveToFirebaseAndRoom(partySize, etNotes.getText().toString());
} else {
com.google.firebase.database.DatabaseReference mesasRef = com.google.firebase.database.FirebaseDatabase Toast.makeText(this, "Esta mesa já está reservada para este dia.", Toast.LENGTH_LONG).show();
.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);
return;
}
checkReservationsAndSave(totalMesas, partySize);
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
proceedWithReservation(partySize);
}
}); });
} }
private void checkReservationsAndSave(int totalMesas, final int partySize) { private void saveToFirebaseAndRoom(int guests, String notes) {
String restEmail = selectedRestaurant.getEmail(); progressBar.setVisibility(View.VISIBLE);
com.google.firebase.database.DatabaseReference reservasRef = com.google.firebase.database.FirebaseDatabase DatabaseReference ref = FirebaseDatabase.getInstance().getReference("reservas");
.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");
String id = ref.push().getKey(); 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( LocalReservation local = new LocalReservation();
id, local.setFirebaseId(id);
userEmail, local.setRestaurantName(selectedRestaurant.getName());
selectedRestaurant.getName(), local.setRestaurantEmail(selectedRestaurant.getEmail());
selectedRestaurant.getEmail(), local.setDate(selectedDate);
selectedDate, local.setTime(selectedTime);
selectedTime, local.setGuests(guests);
partySize, local.setTableNumber(selectedMesa.getNumero());
"Pendente"); local.setNotes(notes);
local.setStatus("PENDENTE");
if (id != null) { if (id != null) {
ref.child(id).setValue(reserva).addOnCompleteListener(task -> { ref.child(id).setValue(local).addOnCompleteListener(task -> {
if (progressBar != null) progressBar.setVisibility(View.GONE);
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 viewModel.insert(local);
.makeText(NovaReservaActivity.this, "Reserva solicitada com sucesso!", Toast.makeText(this, "Reserva realizada com sucesso!", Toast.LENGTH_LONG).show();
android.widget.Toast.LENGTH_SHORT)
.show();
finish(); finish();
} else { } else {
android.widget.Toast Toast.makeText(this, "Erro ao guardar no servidor.", Toast.LENGTH_SHORT).show();
.makeText(NovaReservaActivity.this, "Erro ao salvar reserva.",
android.widget.Toast.LENGTH_SHORT)
.show();
} }
}); });
} }

View File

@@ -39,7 +39,8 @@ public class OnboardingActivity extends AppCompatActivity {
tagline.startAnimation(fadeIn); tagline.startAnimation(fadeIn);
btnGetStarted.setOnClickListener(v -> { 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(); finish();
}); });
} }

View File

@@ -10,6 +10,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import android.widget.Toast;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser; 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; package com.example.pap_teste.models;
import com.google.firebase.Timestamp;
public class Reserva { public class Reserva {
private String id; private String id;
private String userId;
private String restaurantId;
private String restaurantName;
private String clienteEmail; private String clienteEmail;
private String restauranteName; private String restauranteName;
private String restauranteEmail; private String restauranteEmail;
private String data; private String data;
private String hora; private String hora;
private int pessoas; private int pessoas;
private int guests;
private String estado; private String estado;
private String status;
private Timestamp date;
private String timeSlot;
private String specialRequests;
private Timestamp createdAt;
public Reserva() { public Reserva() {
} }
@@ -25,27 +36,41 @@ public class Reserva {
this.estado = estado; this.estado = estado;
} }
// Existing getters/setters
public String getId() { return id; } public String getId() { return id; }
public void setId(String id) { this.id = id; } public void setId(String id) { this.id = id; }
public String getClienteEmail() { return clienteEmail; } public String getClienteEmail() { return clienteEmail; }
public void setClienteEmail(String clienteEmail) { this.clienteEmail = clienteEmail; } public void setClienteEmail(String clienteEmail) { this.clienteEmail = clienteEmail; }
public String getRestauranteName() { return restauranteName; } public String getRestauranteName() { return restauranteName; }
public void setRestauranteName(String restauranteName) { this.restauranteName = restauranteName; } public void setRestauranteName(String restauranteName) { this.restauranteName = restauranteName; }
public String getRestauranteEmail() { return restauranteEmail; } public String getRestauranteEmail() { return restauranteEmail; }
public void setRestauranteEmail(String restauranteEmail) { this.restauranteEmail = restauranteEmail; } public void setRestauranteEmail(String restauranteEmail) { this.restauranteEmail = restauranteEmail; }
public String getData() { return data; } public String getData() { return data; }
public void setData(String data) { this.data = data; } public void setData(String data) { this.data = data; }
public String getHora() { return hora; } public String getHora() { return hora; }
public void setHora(String hora) { this.hora = hora; } public void setHora(String hora) { this.hora = hora; }
public int getPessoas() { return pessoas; } public int getPessoas() { return pessoas; }
public void setPessoas(int pessoas) { this.pessoas = pessoas; } public void setPessoas(int pessoas) { this.pessoas = pessoas; }
public String getEstado() { return estado; } public String getEstado() { return estado; }
public void setEstado(String estado) { this.estado = estado; } public void setEstado(String estado) { this.estado = estado; }
// New getters/setters to fix ReservationActivity
public String getUserId() { return userId != null ? userId : clienteEmail; }
public void setUserId(String userId) { this.userId = userId; }
public String getRestaurantId() { return restaurantId != null ? restaurantId : restauranteEmail; }
public void setRestaurantId(String restaurantId) { this.restaurantId = restaurantId; }
public String getRestaurantName() { return restaurantName != null ? restaurantName : restauranteName; }
public void setRestaurantName(String restaurantName) { this.restaurantName = restaurantName; }
public int getGuests() { return guests != 0 ? guests : pessoas; }
public void setGuests(int guests) { this.guests = guests; }
public String getStatus() { return status != null ? status : estado; }
public void setStatus(String status) { this.status = status; }
public Timestamp getDate() { return date; }
public void setDate(Timestamp date) { this.date = date; }
public String getTimeSlot() { return timeSlot != null ? timeSlot : hora; }
public void setTimeSlot(String timeSlot) { this.timeSlot = timeSlot; }
public String getSpecialRequests() { return specialRequests; }
public 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" android:textColor="@color/colorTextPrimary"
app:strokeColor="@color/colorDivider" /> 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 <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@@ -180,6 +198,28 @@
android:hint="Ex: 2" android:hint="Ex: 2"
android:textColorHint="@color/colorTextHint" android:textColorHint="@color/colorTextHint"
android:textColor="@color/colorTextPrimary" /> 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> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </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" firebaseDatabase = "22.0.1"
glide = "4.16.0" glide = "4.16.0"
firebaseStorage = "21.0.1" firebaseStorage = "21.0.1"
room = "2.6.1"
[libraries] [libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" } 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" } firebase-database = { group = "com.google.firebase", name = "firebase-database", version.ref = "firebaseDatabase" }
glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" } glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }
firebase-storage = { group = "com.google.firebase", name = "firebase-storage", version.ref = "firebaseStorage" } 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] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }