Compare commits

...

8 Commits

Author SHA1 Message Date
f4d2889718 Merge remote-tracking branch 'origin/teste' into teste 2026-07-01 09:03:47 +01:00
fedcf44c49 30/06 2026-07-01 09:03:41 +01:00
b2bb591bb7 30/06 2026-07-01 09:03:25 +01:00
585af79ffa 30/06 2026-06-30 17:00:39 +01:00
9d57ed4d0a 30/06 2026-06-30 17:00:31 +01:00
824ff28001 22\06 2026-06-25 18:25:45 +01:00
86dbe2d319 22\06 2026-06-25 18:25:36 +01:00
8c1e01dc4c 22\06 2026-06-22 08:39:29 +01:00
18 changed files with 746 additions and 106 deletions

View File

@@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-04-14T16:06:26.670067Z"> <DropdownSelection timestamp="2026-06-30T16:03:11.250011Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=b93659d0e5dd" /> <DeviceId pluginId="LocalEmulator" identifier="path=/Users/230409/.android/avd/Pixel_8_Pro.avd" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

BIN
OperatingHoursDay.class Normal file

Binary file not shown.

BIN
TestFirebase.class Normal file

Binary file not shown.

17
TestFirebase.java Normal file
View File

@@ -0,0 +1,17 @@
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
public class TestFirebase {
public static void main(String[] args) throws Exception {
PropertyDescriptor[] pds = Introspector.getBeanInfo(OperatingHoursDay.class).getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
System.out.println("Property: " + pd.getName() + ", readMethod: " + (pd.getReadMethod() != null ? pd.getReadMethod().getName() : "null"));
}
}
}
class OperatingHoursDay {
private boolean isOpen;
public boolean getIsOpen() { return isOpen; }
public void setIsOpen(boolean isOpen) { this.isOpen = isOpen; }
}

View File

@@ -31,6 +31,15 @@ import com.google.firebase.storage.StorageReference;
import java.util.UUID; import java.util.UUID;
import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.view.LayoutInflater;
import android.view.View;
import android.app.TimePickerDialog;
import java.util.ArrayList;
import java.util.List;
import com.example.pap_teste.models.ScheduleDay;
public class DefinicoesAdminActivity extends AppCompatActivity { public class DefinicoesAdminActivity extends AppCompatActivity {
@@ -42,6 +51,9 @@ public class DefinicoesAdminActivity extends AppCompatActivity {
private String photoUrl; private String photoUrl;
private ActivityResultLauncher<Intent> imagePickerLauncher; private ActivityResultLauncher<Intent> imagePickerLauncher;
private String[] categories = {"Carnes", "Massas", "Sushi", "Pizzas", "Sobremesas", "Italiana", "Moderna"}; private String[] categories = {"Carnes", "Massas", "Sushi", "Pizzas", "Sobremesas", "Italiana", "Moderna"};
private String[] dayNames = {"Domingo", "Segunda-feira", "Terça-feira", "Quarta-feira", "Quinta-feira", "Sexta-feira", "Sábado"};
private String[] dayKeys = {"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"};
private List<View> scheduleViews = new ArrayList<>();
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@@ -105,6 +117,29 @@ public class DefinicoesAdminActivity extends AppCompatActivity {
}).show(); }).show();
}); });
LinearLayout containerSchedule = findViewById(R.id.containerSchedule);
LayoutInflater inflater = LayoutInflater.from(this);
for (int i = 0; i < dayNames.length; i++) {
View view = inflater.inflate(R.layout.item_schedule_day, containerSchedule, false);
TextView tvDayName = view.findViewById(R.id.tvDayName);
TextView tvOpenTime = view.findViewById(R.id.tvOpenTime);
TextView tvCloseTime = view.findViewById(R.id.tvCloseTime);
CheckBox cbClosed = view.findViewById(R.id.cbClosed);
tvDayName.setText(dayNames[i]);
tvOpenTime.setOnClickListener(v -> showTimePicker(tvOpenTime));
tvCloseTime.setOnClickListener(v -> showTimePicker(tvCloseTime));
cbClosed.setOnCheckedChangeListener((buttonView, isChecked) -> {
tvOpenTime.setEnabled(!isChecked);
tvCloseTime.setEnabled(!isChecked);
});
containerSchedule.addView(view);
scheduleViews.add(view);
}
loadCurrentSettings(); loadCurrentSettings();
btnSave.setOnClickListener(v -> saveSettings()); btnSave.setOnClickListener(v -> saveSettings());
@@ -130,6 +165,13 @@ public class DefinicoesAdminActivity extends AppCompatActivity {
builder.show(); builder.show();
} }
private void showTimePicker(TextView targetTextView) {
java.util.Calendar cal = java.util.Calendar.getInstance();
new TimePickerDialog(this, (view, hourOfDay, minute) -> {
targetTextView.setText(String.format(java.util.Locale.getDefault(), "%02d:%02d", hourOfDay, minute));
}, cal.get(java.util.Calendar.HOUR_OF_DAY), cal.get(java.util.Calendar.MINUTE), true).show();
}
private void uploadImageToFirebase(Uri imageUri) { private void uploadImageToFirebase(Uri imageUri) {
if (documentId == null) return; if (documentId == null) return;
@@ -179,6 +221,27 @@ public class DefinicoesAdminActivity extends AppCompatActivity {
Glide.with(DefinicoesAdminActivity.this).load(photoUrl).circleCrop().into(imgLogo); Glide.with(DefinicoesAdminActivity.this).load(photoUrl).circleCrop().into(imgLogo);
} }
} }
if (snapshot.hasChild("schedule")) {
for (int i = 0; i < dayKeys.length; i++) {
DataSnapshot daySnapshot = snapshot.child("schedule").child(dayKeys[i]);
if (daySnapshot.exists()) {
ScheduleDay sd = daySnapshot.getValue(ScheduleDay.class);
if (sd != null) {
View view = scheduleViews.get(i);
CheckBox cbClosed = view.findViewById(R.id.cbClosed);
TextView tvOpenTime = view.findViewById(R.id.tvOpenTime);
TextView tvCloseTime = view.findViewById(R.id.tvCloseTime);
cbClosed.setChecked(sd.isClosed());
if (sd.getOpenTime() != null) tvOpenTime.setText(sd.getOpenTime());
if (sd.getCloseTime() != null) tvCloseTime.setText(sd.getCloseTime());
tvOpenTime.setEnabled(!sd.isClosed());
tvCloseTime.setEnabled(!sd.isClosed());
}
}
}
}
} }
} }
@@ -213,6 +276,17 @@ public class DefinicoesAdminActivity extends AppCompatActivity {
updates.put("logoUrl", photoUrl); updates.put("logoUrl", photoUrl);
} }
Map<String, ScheduleDay> scheduleMap = new HashMap<>();
for (int i = 0; i < dayKeys.length; i++) {
View view = scheduleViews.get(i);
CheckBox cbClosed = view.findViewById(R.id.cbClosed);
TextView tvOpenTime = view.findViewById(R.id.tvOpenTime);
TextView tvCloseTime = view.findViewById(R.id.tvCloseTime);
scheduleMap.put(dayKeys[i], new ScheduleDay(cbClosed.isChecked(), tvOpenTime.getText().toString(), tvCloseTime.getText().toString()));
}
updates.put("schedule", scheduleMap);
databaseReference.child(documentId).updateChildren(updates) databaseReference.child(documentId).updateChildren(updates)
.addOnSuccessListener(aVoid -> { .addOnSuccessListener(aVoid -> {
Toast.makeText(this, "Definições guardadas com sucesso!", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Definições guardadas com sucesso!", Toast.LENGTH_SHORT).show();

View File

@@ -99,6 +99,7 @@ public class EstablishmentDashboardActivity extends AppCompatActivity {
loadProximasReservas(); loadProximasReservas();
loadEstatisticas(); loadEstatisticas();
checkPendingReservationsOnLogin();
} }
private void loadProximasReservas() { private void loadProximasReservas() {
@@ -231,20 +232,93 @@ public class EstablishmentDashboardActivity extends AppCompatActivity {
.addValueEventListener(new com.google.firebase.database.ValueEventListener() { .addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override @Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) { public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
int totalMesas = 0;
int mesasOcupadas = 0;
int mesasLivres = 0; int mesasLivres = 0;
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) { for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
com.example.pap_teste.models.Mesa mesa = ds.getValue(com.example.pap_teste.models.Mesa.class); com.example.pap_teste.models.Mesa mesa = ds.getValue(com.example.pap_teste.models.Mesa.class);
if (mesa != null && mesa.getRestauranteEmail() != null && mesa.getRestauranteEmail().trim().equalsIgnoreCase(finalEmail)) { if (mesa != null && (mesa.getRestauranteEmail() == null || mesa.getRestauranteEmail().trim().equalsIgnoreCase(finalEmail))) {
if ("Livre".equalsIgnoreCase(mesa.getEstado())) { totalMesas++;
if ("Ocupada".equalsIgnoreCase(mesa.getEstado()) || "Reservada".equalsIgnoreCase(mesa.getEstado())) {
mesasOcupadas++;
} else if ("Livre".equalsIgnoreCase(mesa.getEstado())) {
mesasLivres++; mesasLivres++;
} }
} }
} }
if (txtMesasLivres != null) txtMesasLivres.setText(String.format(java.util.Locale.US, "%02d", mesasLivres)); if (txtMesasLivres != null) txtMesasLivres.setText(mesasOcupadas + " / " + totalMesas);
TextView txtMesasLivresSub = findViewById(R.id.txtMesasLivresSub);
if (txtMesasLivresSub != null) txtMesasLivresSub.setText(mesasLivres + " livres");
} }
@Override @Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) { public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
} }
}); });
} }
private void checkPendingReservationsOnLogin() {
String email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
if (email == null) {
if (com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser() != null) {
email = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser().getEmail();
} else {
return;
}
}
final String finalEmail = email != null ? email.trim() : "";
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reservas")
.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
java.util.List<com.example.pap_teste.models.Reserva> pendingReservations = new java.util.ArrayList<>();
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(finalEmail)) {
if ("Pendente".equals(r.getEstado())) {
pendingReservations.add(r);
}
}
}
if (!pendingReservations.isEmpty()) {
showPendingReservationsDialog(pendingReservations);
}
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
}
});
}
private void showPendingReservationsDialog(java.util.List<com.example.pap_teste.models.Reserva> pendingReservations) {
if (isFinishing() || isDestroyed()) return;
android.app.AlertDialog.Builder builder = new android.app.AlertDialog.Builder(this);
builder.setTitle("Reservas Pendentes (" + pendingReservations.size() + ")");
StringBuilder message = new StringBuilder("Tem reservas a aguardar confirmação:\n\n");
for (com.example.pap_teste.models.Reserva r : pendingReservations) {
message.append("").append(r.getData()).append(" às ").append(r.getHora())
.append(" - ").append(r.getPessoas()).append(" pessoas")
.append("\n Cliente: ").append(r.getClienteEmail()).append("\n\n");
}
builder.setMessage(message.toString());
builder.setPositiveButton("Ver Reservas", (dialog, which) -> {
Intent intent = new Intent(EstablishmentDashboardActivity.this, DetalhesReservasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, getIntent().getStringExtra(MainActivity.EXTRA_EMAIL));
startActivity(intent);
});
builder.setNegativeButton("Fechar", (dialog, which) -> dialog.dismiss());
android.app.AlertDialog dialog = builder.create();
dialog.show();
android.widget.TextView textView = dialog.findViewById(android.R.id.message);
if (textView != null) {
textView.setTextSize(18);
textView.setGravity(android.view.Gravity.CENTER);
}
}
} }

View File

@@ -89,6 +89,13 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : "")); txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : ""));
setupReservationOptions(); setupReservationOptions();
loadRestaurantRating(); loadRestaurantRating();
boolean isFechado = selectedRestaurant != null && selectedRestaurant.isFechadoWebsite();
findViewById(R.id.btnSelectDate).setEnabled(!isFechado);
findViewById(R.id.btnSelectTime).setEnabled(!isFechado);
android.view.View etPartySize = findViewById(R.id.etPartySize);
if (etPartySize != null) etPartySize.setEnabled(!isFechado);
findViewById(R.id.btnConfirmarReserva).setEnabled(!isFechado);
} }
} }
@@ -160,8 +167,32 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
String logoUrl = ds.child("logoUrl").getValue(String.class); String logoUrl = ds.child("logoUrl").getValue(String.class);
if (name != null && email != null) { if (name != null && email != null) {
restaurantsList com.example.pap_teste.models.Restaurant rest = new com.example.pap_teste.models.Restaurant(name, cat, email, false, logoUrl);
.add(new com.example.pap_teste.models.Restaurant(name, cat, email, false, logoUrl)); Boolean fechadoWebsite = ds.child("fechadoWebsite").getValue(Boolean.class);
if (fechadoWebsite != null) rest.setFechadoWebsite(fechadoWebsite);
if (ds.hasChild("schedule")) {
java.util.Map<String, com.example.pap_teste.models.ScheduleDay> schMap = new java.util.HashMap<>();
for (com.google.firebase.database.DataSnapshot schDs : ds.child("schedule").getChildren()) {
com.example.pap_teste.models.ScheduleDay sd = schDs.getValue(com.example.pap_teste.models.ScheduleDay.class);
schMap.put(schDs.getKey(), sd);
}
rest.setSchedule(schMap);
} else if (ds.hasChild("operatingHours")) {
java.util.Map<String, com.example.pap_teste.models.ScheduleDay> schMap = new java.util.HashMap<>();
for (com.google.firebase.database.DataSnapshot schDs : ds.child("operatingHours").getChildren()) {
com.example.pap_teste.models.ScheduleDay sd = schDs.getValue(com.example.pap_teste.models.ScheduleDay.class);
if (schDs.hasChild("isOpen")) {
Boolean isOpen = schDs.child("isOpen").getValue(Boolean.class);
if (isOpen != null && sd != null) {
sd.setClosed(!isOpen);
}
}
schMap.put(schDs.getKey(), sd);
}
rest.setSchedule(schMap);
}
restaurantsList.add(rest);
} }
} }
} }
@@ -192,18 +223,72 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
btnDate.setOnClickListener(v -> { btnDate.setOnClickListener(v -> {
java.util.Calendar cal = java.util.Calendar.getInstance(); java.util.Calendar cal = java.util.Calendar.getInstance();
new android.app.DatePickerDialog(this, (view, year, month, dayOfMonth) -> { android.app.DatePickerDialog dpd = new android.app.DatePickerDialog(this, (view, year, month, dayOfMonth) -> {
selectedDate = dayOfMonth + "/" + (month + 1) + "/" + year; java.util.Calendar selectedCal = java.util.Calendar.getInstance();
btnDate.setText(selectedDate); selectedCal.set(year, month, dayOfMonth);
}, cal.get(java.util.Calendar.YEAR), cal.get(java.util.Calendar.MONTH), int dayOfWeek = selectedCal.get(java.util.Calendar.DAY_OF_WEEK);
cal.get(java.util.Calendar.DAY_OF_MONTH)).show();
boolean isClosed = selectedRestaurant.isClosedForDay(dayOfWeek);
if (isClosed) {
android.widget.Toast.makeText(this, "O restaurante encontra-se encerrado na data selecionada.", android.widget.Toast.LENGTH_LONG).show();
selectedDate = null;
btnDate.setText("Selecionar Data");
btnTime.setEnabled(false);
} else {
selectedDate = dayOfMonth + "/" + (month + 1) + "/" + year;
btnDate.setText(selectedDate);
btnTime.setEnabled(true);
}
}, cal.get(java.util.Calendar.YEAR), cal.get(java.util.Calendar.MONTH), cal.get(java.util.Calendar.DAY_OF_MONTH));
dpd.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
dpd.show();
}); });
btnTime.setOnClickListener(v -> { btnTime.setOnClickListener(v -> {
if (selectedDate == null) {
android.widget.Toast.makeText(this, "Por favor, selecione primeiro a data.", android.widget.Toast.LENGTH_SHORT).show();
return;
}
java.util.Calendar cal = java.util.Calendar.getInstance(); java.util.Calendar cal = java.util.Calendar.getInstance();
new android.app.TimePickerDialog(this, (view, hourOfDay, minute) -> { new android.app.TimePickerDialog(this, (view, hourOfDay, minute) -> {
selectedTime = String.format(java.util.Locale.getDefault(), "%02d:%02d", hourOfDay, minute); String chosenTime = String.format(java.util.Locale.getDefault(), "%02d:%02d", hourOfDay, minute);
btnTime.setText(selectedTime);
boolean isOutOfHours = false;
if (selectedDate != null && selectedRestaurant.getSchedule() != null) {
try {
java.util.Date d = new java.text.SimpleDateFormat("d/M/yyyy", java.util.Locale.getDefault()).parse(selectedDate);
java.util.Calendar selectedCal = java.util.Calendar.getInstance();
if (d != null) selectedCal.setTime(d);
int dayOfWeek = selectedCal.get(java.util.Calendar.DAY_OF_WEEK);
com.example.pap_teste.models.ScheduleDay sd = selectedRestaurant.getScheduleForDay(dayOfWeek);
if (sd != null && !sd.isClosed()) {
if (sd.getOpenTime() != null && sd.getCloseTime() != null && !sd.getOpenTime().isEmpty() && !sd.getCloseTime().isEmpty()) {
String open = sd.getOpenTime();
String close = sd.getCloseTime();
if (open.compareTo(close) < 0) {
if (chosenTime.compareTo(open) < 0 || chosenTime.compareTo(close) > 0) {
isOutOfHours = true;
}
} else {
if (chosenTime.compareTo(open) < 0 && chosenTime.compareTo(close) > 0) {
isOutOfHours = true;
}
}
}
}
} catch (Exception e) {}
}
if (isOutOfHours) {
android.widget.Toast.makeText(this, "Por favor selecione uma hora dentro do horário de funcionamento.", android.widget.Toast.LENGTH_LONG).show();
selectedTime = null;
btnTime.setText("Selecionar Hora");
} else {
selectedTime = chosenTime;
btnTime.setText(selectedTime);
}
}, cal.get(java.util.Calendar.HOUR_OF_DAY), cal.get(java.util.Calendar.MINUTE), true).show(); }, cal.get(java.util.Calendar.HOUR_OF_DAY), cal.get(java.util.Calendar.MINUTE), true).show();
}); });

View File

@@ -79,6 +79,18 @@ public class NovaReservaActivity extends AppCompatActivity {
} else { } else {
txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : "")); txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : ""));
setupReservationOptions(); setupReservationOptions();
android.widget.TextView txtFechado = findViewById(R.id.txtRestauranteFechado);
boolean isFechado = selectedRestaurant != null && selectedRestaurant.isFechadoWebsite();
if (txtFechado != null) {
txtFechado.setVisibility(isFechado ? android.view.View.VISIBLE : android.view.View.GONE);
}
findViewById(R.id.btnSelectDate).setEnabled(!isFechado);
findViewById(R.id.btnSelectTime).setEnabled(!isFechado);
findViewById(R.id.etPartySize).setEnabled(!isFechado);
findViewById(R.id.btnConfirmarReserva).setEnabled(!isFechado);
} }
} }
@@ -127,10 +139,44 @@ public class NovaReservaActivity extends AppCompatActivity {
String email = ds.child("email").getValue(String.class); String email = ds.child("email").getValue(String.class);
String cat = ds.child("category").getValue(String.class); String cat = ds.child("category").getValue(String.class);
String logoUrl = ds.child("logoUrl").getValue(String.class); String logoUrl = ds.child("logoUrl").getValue(String.class);
Boolean fechadoWebsite = ds.child("fechadoWebsite").getValue(Boolean.class);
if (name != null && email != null) { if (name != null && email != null) {
filteredList.add(new com.example.pap_teste.models.Restaurant(name, cat, email, com.example.pap_teste.models.Restaurant rest = new com.example.pap_teste.models.Restaurant(name, cat, email,
false, logoUrl)); false, logoUrl);
if (fechadoWebsite != null) {
rest.setFechadoWebsite(fechadoWebsite);
}
if (ds.hasChild("schedule")) {
java.util.Map<String, com.example.pap_teste.models.ScheduleDay> schMap = new java.util.HashMap<>();
for (com.google.firebase.database.DataSnapshot schDs : ds.child("schedule").getChildren()) {
com.example.pap_teste.models.ScheduleDay sd = schDs.getValue(com.example.pap_teste.models.ScheduleDay.class);
schMap.put(schDs.getKey(), sd);
}
rest.setSchedule(schMap);
} else if (ds.hasChild("operatingHours")) {
java.util.Map<String, com.example.pap_teste.models.ScheduleDay> schMap = new java.util.HashMap<>();
for (com.google.firebase.database.DataSnapshot schDs : ds.child("operatingHours").getChildren()) {
com.example.pap_teste.models.ScheduleDay sd = schDs.getValue(com.example.pap_teste.models.ScheduleDay.class);
// Handle case where operatingHours has isOpen instead of closed
if (schDs.hasChild("isOpen")) {
Boolean isOpen = schDs.child("isOpen").getValue(Boolean.class);
if (isOpen != null && sd != null) {
sd.setClosed(!isOpen);
}
}
schMap.put(schDs.getKey(), sd);
}
rest.setSchedule(schMap);
} else if (ds.hasChild("horario")) {
java.util.Map<String, com.example.pap_teste.models.ScheduleDay> schMap = new java.util.HashMap<>();
for (com.google.firebase.database.DataSnapshot schDs : ds.child("horario").getChildren()) {
com.example.pap_teste.models.ScheduleDay sd = schDs.getValue(com.example.pap_teste.models.ScheduleDay.class);
schMap.put(schDs.getKey(), sd);
}
rest.setSchedule(schMap);
}
filteredList.add(rest);
} }
} }
} }
@@ -164,18 +210,72 @@ public class NovaReservaActivity extends AppCompatActivity {
btnDate.setOnClickListener(v -> { btnDate.setOnClickListener(v -> {
java.util.Calendar cal = java.util.Calendar.getInstance(); java.util.Calendar cal = java.util.Calendar.getInstance();
new android.app.DatePickerDialog(this, (view, year, month, dayOfMonth) -> { android.app.DatePickerDialog dpd = new android.app.DatePickerDialog(this, (view, year, month, dayOfMonth) -> {
selectedDate = dayOfMonth + "/" + (month + 1) + "/" + year; java.util.Calendar selectedCal = java.util.Calendar.getInstance();
btnDate.setText(selectedDate); selectedCal.set(year, month, dayOfMonth);
}, cal.get(java.util.Calendar.YEAR), cal.get(java.util.Calendar.MONTH), int dayOfWeek = selectedCal.get(java.util.Calendar.DAY_OF_WEEK);
cal.get(java.util.Calendar.DAY_OF_MONTH)).show();
boolean isClosed = selectedRestaurant.isClosedForDay(dayOfWeek);
if (isClosed) {
android.widget.Toast.makeText(this, "O restaurante encontra-se encerrado na data selecionada.", android.widget.Toast.LENGTH_LONG).show();
selectedDate = null;
btnDate.setText("Selecionar Data");
btnTime.setEnabled(false);
} else {
selectedDate = dayOfMonth + "/" + (month + 1) + "/" + year;
btnDate.setText(selectedDate);
btnTime.setEnabled(true);
}
}, cal.get(java.util.Calendar.YEAR), cal.get(java.util.Calendar.MONTH), cal.get(java.util.Calendar.DAY_OF_MONTH));
dpd.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
dpd.show();
}); });
btnTime.setOnClickListener(v -> { btnTime.setOnClickListener(v -> {
if (selectedDate == null) {
android.widget.Toast.makeText(this, "Por favor, selecione primeiro a data.", android.widget.Toast.LENGTH_SHORT).show();
return;
}
java.util.Calendar cal = java.util.Calendar.getInstance(); java.util.Calendar cal = java.util.Calendar.getInstance();
new android.app.TimePickerDialog(this, (view, hourOfDay, minute) -> { new android.app.TimePickerDialog(this, (view, hourOfDay, minute) -> {
selectedTime = String.format(java.util.Locale.getDefault(), "%02d:%02d", hourOfDay, minute); String chosenTime = String.format(java.util.Locale.getDefault(), "%02d:%02d", hourOfDay, minute);
btnTime.setText(selectedTime);
boolean isOutOfHours = false;
if (selectedDate != null && selectedRestaurant.getSchedule() != null) {
try {
java.util.Date d = new java.text.SimpleDateFormat("d/M/yyyy", java.util.Locale.getDefault()).parse(selectedDate);
java.util.Calendar selectedCal = java.util.Calendar.getInstance();
if (d != null) selectedCal.setTime(d);
int dayOfWeek = selectedCal.get(java.util.Calendar.DAY_OF_WEEK);
com.example.pap_teste.models.ScheduleDay sd = selectedRestaurant.getScheduleForDay(dayOfWeek);
if (sd != null && !sd.isClosed()) {
if (sd.getOpenTime() != null && sd.getCloseTime() != null && !sd.getOpenTime().isEmpty() && !sd.getCloseTime().isEmpty()) {
String open = sd.getOpenTime();
String close = sd.getCloseTime();
if (open.compareTo(close) < 0) {
if (chosenTime.compareTo(open) < 0 || chosenTime.compareTo(close) > 0) {
isOutOfHours = true;
}
} else {
if (chosenTime.compareTo(open) < 0 && chosenTime.compareTo(close) > 0) {
isOutOfHours = true;
}
}
}
}
} catch (Exception e) {}
}
if (isOutOfHours) {
android.widget.Toast.makeText(this, "Por favor selecione uma hora dentro do horário de funcionamento.", android.widget.Toast.LENGTH_LONG).show();
selectedTime = null;
btnTime.setText("Selecionar Hora");
} else {
selectedTime = chosenTime;
btnTime.setText(selectedTime);
}
}, cal.get(java.util.Calendar.HOUR_OF_DAY), cal.get(java.util.Calendar.MINUTE), true).show(); }, cal.get(java.util.Calendar.HOUR_OF_DAY), cal.get(java.util.Calendar.MINUTE), true).show();
}); });

View File

@@ -118,6 +118,7 @@ public class ProfileDashboardActivity extends AppCompatActivity {
private void performLogOut() { private void performLogOut() {
try { try {
stopService(new Intent(this, ReservationNotificationService.class));
FirebaseAuth.getInstance().signOut(); FirebaseAuth.getInstance().signOut();
Toast.makeText(this, "Sessão terminada com sucesso.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Sessão terminada com sucesso.", Toast.LENGTH_SHORT).show();

View File

@@ -0,0 +1,41 @@
package com.example.pap_teste.models;
import java.io.Serializable;
import com.google.firebase.database.PropertyName;
public class OperatingHoursDay implements Serializable {
@PropertyName("isOpen")
private boolean openStatus;
private String openTime;
private String closeTime;
public OperatingHoursDay() {
// Construtor vazio necessário para Firebase
}
@PropertyName("isOpen")
public boolean getOpenStatus() {
return openStatus;
}
@PropertyName("isOpen")
public void setOpenStatus(boolean openStatus) {
this.openStatus = openStatus;
}
public String getOpenTime() {
return openTime;
}
public void setOpenTime(String openTime) {
this.openTime = openTime;
}
public String getCloseTime() {
return closeTime;
}
public void setCloseTime(String closeTime) {
this.closeTime = closeTime;
}
}

View File

@@ -10,6 +10,7 @@ public class Restaurant implements Serializable {
private String logoUrl; private String logoUrl;
private Double ratingAvg; private Double ratingAvg;
private Integer ratingCount; private Integer ratingCount;
private boolean fechadoWebsite;
// No-argument constructor required for Firebase // No-argument constructor required for Firebase
public Restaurant() { public Restaurant() {
@@ -41,9 +42,51 @@ public class Restaurant implements Serializable {
public void setEmail(String email) { this.email = email; } public void setEmail(String email) { this.email = email; }
public void setAvailable(boolean available) { this.available = available; } public void setAvailable(boolean available) { this.available = available; }
public void setLogoUrl(String logoUrl) { this.logoUrl = logoUrl; } public void setLogoUrl(String logoUrl) { this.logoUrl = logoUrl; }
public boolean isFechadoWebsite() { return fechadoWebsite; }
public void setFechadoWebsite(boolean fechadoWebsite) { this.fechadoWebsite = fechadoWebsite; }
public Double getRatingAvg() { return ratingAvg; } public Double getRatingAvg() { return ratingAvg; }
public void setRatingAvg(Double ratingAvg) { this.ratingAvg = ratingAvg; } public void setRatingAvg(Double ratingAvg) { this.ratingAvg = ratingAvg; }
public Integer getRatingCount() { return ratingCount; } public Integer getRatingCount() { return ratingCount; }
public void setRatingCount(Integer ratingCount) { this.ratingCount = ratingCount; } public void setRatingCount(Integer ratingCount) { this.ratingCount = ratingCount; }
private java.util.Map<String, ScheduleDay> schedule;
public java.util.Map<String, ScheduleDay> getSchedule() { return schedule; }
public void setSchedule(java.util.Map<String, ScheduleDay> schedule) { this.schedule = schedule; }
public ScheduleDay getScheduleForDay(int dayOfWeekCalendar) {
if (schedule == null) return null;
String[] dayKeys = {"sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"};
String[] dayKeysPt = {"domingo", "segunda-feira", "terça-feira", "quarta-feira", "quinta-feira", "sexta-feira", "sábado"};
String[] dayKeysPtShort = {"domingo", "segunda", "terça", "quarta", "quinta", "sexta", "sábado"};
int index = dayOfWeekCalendar - 1;
ScheduleDay sd = schedule.get(dayKeys[index]);
if (sd == null) sd = schedule.get(dayKeysPt[index]);
if (sd == null) sd = schedule.get(dayKeysPtShort[index]);
if (sd == null) sd = schedule.get(String.valueOf(index));
if (sd == null) {
for (java.util.Map.Entry<String, ScheduleDay> entry : schedule.entrySet()) {
String k = entry.getKey().toLowerCase(java.util.Locale.ROOT).replace("-", "").replace(" ", "");
String expected1 = dayKeys[index].replace("-", "");
String expected2 = dayKeysPt[index].replace("-", "");
String expected3 = dayKeysPtShort[index].replace("-", "");
if (k.equals(expected1) || k.equals(expected2) || k.equals(expected3) || k.equals(String.valueOf(index))) {
return entry.getValue();
}
}
}
return sd;
}
public boolean isClosedForDay(int dayOfWeekCalendar) {
if (schedule == null || schedule.isEmpty()) return false; // If no schedule is set at all, default to open to avoid locking out old restaurants.
ScheduleDay sd = getScheduleForDay(dayOfWeekCalendar);
if (sd == null) {
return true; // Se o dia não existe no mapa de um restaurante com horário, está fechado.
}
return sd.isClosed();
}
} }

View File

@@ -0,0 +1,77 @@
package com.example.pap_teste.models;
import java.io.Serializable;
public class ScheduleDay implements Serializable {
private boolean closed;
private Boolean isOpen;
private String openTime;
private String closeTime;
public ScheduleDay() {
// No-argument constructor for Firebase
}
public ScheduleDay(boolean closed, String openTime, String closeTime) {
this.closed = closed;
this.isOpen = !closed;
this.openTime = openTime;
this.closeTime = closeTime;
}
public boolean isClosed() {
if (isOpen != null) {
return !isOpen;
}
return closed;
}
public void setClosed(boolean closed) {
this.closed = closed;
if (this.isOpen == null) {
this.isOpen = !closed;
}
}
// Handles potential "isClosed" boolean in Firebase
public void setIsClosed(Boolean isClosed) {
if (isClosed != null) {
this.closed = isClosed;
this.isOpen = !isClosed;
}
}
// Handles potential "fechado" boolean from Portuguese web dashboards
public void setFechado(Boolean fechado) {
if (fechado != null) {
this.closed = fechado;
this.isOpen = !fechado;
}
}
@com.google.firebase.database.PropertyName("isOpen")
public Boolean getIsOpen() {
return isOpen;
}
@com.google.firebase.database.PropertyName("isOpen")
public void setIsOpen(Boolean isOpen) {
this.isOpen = isOpen;
}
public String getOpenTime() {
return openTime;
}
public void setOpenTime(String openTime) {
this.openTime = openTime;
}
public String getCloseTime() {
return closeTime;
}
public void setCloseTime(String closeTime) {
this.closeTime = closeTime;
}
}

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#D4AF37">
<path
android:fillColor="@android:color/white"
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z" />
<path
android:fillColor="@android:color/white"
android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z" />
</vector>

View File

@@ -156,6 +156,22 @@
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:background="@drawable/input_bg" /> android:background="@drawable/input_bg" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Horário de Funcionamento"
android:textColor="@color/colorTextPrimary"
android:textSize="16sp"
android:fontFamily="sans-serif-medium" />
<LinearLayout
android:id="@+id/containerSchedule"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="vertical" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/btnSaveSettings" android:id="@+id/btnSaveSettings"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -71,116 +71,150 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="32dp" android:layout_marginTop="32dp"
android:orientation="horizontal"> android:orientation="vertical">
<!-- MESAS OCUPADAS CARD -->
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/cardReservasHoje" android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="6dp" app:cardBackgroundColor="#1F1D1A"
android:layout_weight="1"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="@color/colorSurface"
app:cardCornerRadius="16dp" app:cardCornerRadius="16dp"
app:cardElevation="2dp"> app:cardElevation="2dp">
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:padding="20dp">
android:orientation="vertical"
android:padding="16dp">
<TextView <TextView
android:id="@+id/lblMesasOcupadas"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Reservas" android:text="MESAS OCUPADAS"
android:textColor="@color/colorTextSecondary" android:textColor="#888175"
android:textSize="13sp" /> android:textSize="14sp"
android:letterSpacing="0.05"
android:fontFamily="serif"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <ImageView
android:id="@+id/txtReservasHojeDash" android:layout_width="24dp"
android:layout_width="wrap_content" android:layout_height="24dp"
android:layout_height="wrap_content" android:src="@drawable/ic_clock_gold"
android:layout_marginTop="6dp" app:layout_constraintEnd_toEndOf="parent"
android:text="00" app:layout_constraintTop_toTopOf="parent"
android:textColor="@color/colorPrimary" app:layout_constraintBottom_toBottomOf="@id/lblMesasOcupadas" />
android:textSize="30sp"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:layout_weight="1"
app:cardBackgroundColor="@color/colorSurface"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Mesas"
android:textColor="@color/colorTextSecondary"
android:textSize="13sp" />
<TextView <TextView
android:id="@+id/txtMesasLivresDash" android:id="@+id/txtMesasLivresDash"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="6dp" android:layout_marginTop="16dp"
android:text="00" android:text="0 / 0"
android:textColor="@color/colorTextPrimary" android:textColor="#FFFFFF"
android:textSize="30sp" android:textSize="38sp"
android:fontFamily="sans-serif-medium" /> android:fontFamily="serif-monospace"
</LinearLayout> android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/lblMesasOcupadas" />
<TextView
android:id="@+id/txtMesasLivresSub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="0 livres"
android:textColor="#D4AF37"
android:textSize="16sp"
android:fontFamily="sans-serif-medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtMesasLivresDash" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView <LinearLayout
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="6dp" android:layout_marginTop="12dp"
android:layout_weight="1" android:orientation="horizontal">
app:cardBackgroundColor="@color/colorSurface"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout <!-- RESERVAS CARD -->
android:layout_width="match_parent" <com.google.android.material.card.MaterialCardView
android:id="@+id/cardReservasHoje"
android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center" android:layout_marginEnd="6dp"
android:orientation="vertical" android:layout_weight="1"
android:padding="16dp"> android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="@color/colorSurface"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<TextView <LinearLayout
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Espera" android:gravity="center"
android:textColor="@color/colorTextSecondary" android:orientation="vertical"
android:textSize="13sp" /> android:padding="16dp">
<TextView <TextView
android:id="@+id/txtListaEsperaDash" android:layout_width="wrap_content"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="Reservas"
android:textColor="@color/colorTextSecondary"
android:textSize="13sp" />
<TextView
android:id="@+id/txtReservasHojeDash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="00"
android:textColor="@color/colorPrimary"
android:textSize="30sp"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- ESPERA CARD -->
<com.google.android.material.card.MaterialCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_weight="1"
app:cardBackgroundColor="@color/colorSurface"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="6dp" android:gravity="center"
android:text="00" android:orientation="vertical"
android:textColor="@color/colorWarning" android:padding="16dp">
android:textSize="30sp"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Espera"
android:textColor="@color/colorTextSecondary"
android:textSize="13sp" />
<TextView
android:id="@+id/txtListaEsperaDash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="00"
android:textColor="@color/colorWarning"
android:textSize="30sp"
android:fontFamily="sans-serif-medium" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout> </LinearLayout>
<TextView <TextView

View File

@@ -122,6 +122,19 @@
android:textStyle="bold" android:textStyle="bold"
android:layout_marginBottom="24dp" /> android:layout_marginBottom="24dp" />
<TextView
android:id="@+id/txtRestauranteFechado"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Restaurante fechado temporariamente. Não é possível efetuar reservas."
android:textColor="@android:color/holo_red_dark"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:gravity="center"
android:padding="8dp" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:paddingVertical="8dp">
<CheckBox
android:id="@+id/cbClosed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Folga"
android:textColor="@color/colorTextSecondary" />
<TextView
android:id="@+id/tvDayName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:text="Segunda-feira"
android:textColor="@color/colorTextPrimary"
android:textSize="14sp"
android:fontFamily="sans-serif-medium" />
<TextView
android:id="@+id/tvOpenTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="09:00"
android:padding="8dp"
android:background="@drawable/input_bg"
android:textColor="@color/colorTextPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="-"
android:layout_marginHorizontal="8dp"
android:textColor="@color/colorTextSecondary" />
<TextView
android:id="@+id/tvCloseTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="22:00"
android:padding="8dp"
android:background="@drawable/input_bg"
android:textColor="@color/colorTextPrimary" />
</LinearLayout>

View File

@@ -1,5 +1,5 @@
[versions] [versions]
agp = "9.1.0" agp = "9.1.1"
junit = "4.13.2" junit = "4.13.2"
junitVersion = "1.3.0" junitVersion = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"