Compare commits

..

5 Commits

Author SHA1 Message Date
995d23ac7a 15/06 2026-06-15 14:12:03 +01:00
eaa3d86fc9 9/06 2026-06-09 17:12:11 +01:00
d192568ed8 9/06 2026-06-09 17:12:07 +01:00
31a7cbb2df ..asd 2026-06-08 14:43:39 +01:00
fe9266263f ..asd 2026-06-08 10:55:02 +01:00
69 changed files with 5806 additions and 3496 deletions

View File

@@ -10,7 +10,7 @@ android {
defaultConfig { defaultConfig {
applicationId = "com.example.pap_teste" applicationId = "com.example.pap_teste"
minSdk = 24 minSdk = 24
targetSdk = 35 targetSdk = 36
versionCode = 1 versionCode = 1
versionName = "1.0" versionName = "1.0"
@@ -44,11 +44,6 @@ 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

@@ -15,6 +15,10 @@
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<!-- Notification permissions -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@@ -24,72 +28,100 @@
android:roundIcon="@drawable/na_mesa" android:roundIcon="@drawable/na_mesa"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Pap_teste"> android:theme="@style/Theme.Pap_teste">
<activity
android:name=".AddStaffActivity"
android:exported="false" />
<activity
android:name=".DetalhesReservasActivity"
android:exported="false"
android:label="Detalhes das reservas"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".GerirMesasActivity"
android:exported="false"
android:label="Editar mesas"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".EstablishmentDashboardActivity"
android:exported="false"
android:label="Área do Estabelecimento"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".ListaEsperaActivity"
android:exported="false"
android:label="Lista de espera"
android:theme="@style/Theme.Pap_teste" />
<activity <activity
android:name=".SplashActivity" android:name=".GestaoStaffActivity"
android:exported="false"
android:label="Gestão de staff"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".ClientDashboardActivity"
android:exported="false"
android:label="Área do Cliente"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".NovaReservaActivity"
android:exported="false"
android:label="Nova reserva"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".ExplorarRestaurantesActivity"
android:exported="false"
android:label="Explorar restaurantes"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".FavoritosActivity"
android:exported="false"
android:label="Favoritos"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".CheckInAntecipadoActivity"
android:exported="false"
android:label="Check-in antecipado"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".PartilharReservaActivity"
android:exported="false"
android:label="Partilhar reserva"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".AccountCreatedActivity"
android:exported="false"
android:label="Conta criada"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".DefinicoesAdminActivity"
android:exported="false"
android:label="Definições"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".ProfileDashboardActivity"
android:exported="false"
android:label="O meu Perfil"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".MinhasReservasActivity"
android:exported="false"
android:label="Minhas Reservas"
android:theme="@style/Theme.Pap_teste" />
<activity
android:name=".MainActivity"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <service
android:name=".OnboardingActivity" android:name=".ReservationNotificationService"
android:exported="false" /> android:exported="false"
android:foregroundServiceType="dataSync" />
<activity
android:name=".MainActivity"
android:exported="false" />
<activity
android:name=".ClientDashboardActivity"
android:exported="false" />
<activity
android:name=".RestaurantDetailActivity"
android:exported="false" />
<activity
android:name=".ReservationActivity"
android:exported="false" />
<activity
android:name=".FavoritosActivity"
android:exported="false" />
<activity
android:name=".EstablishmentDashboardActivity"
android:exported="false" />
<activity
android:name=".AccountCreatedActivity"
android:exported="false" />
<activity
android:name=".NovaReservaActivity"
android:exported="false" />
<activity
android:name=".CheckInAntecipadoActivity"
android:exported="false" />
<activity
android:name=".MinhasReservasActivity"
android:exported="false" />
<activity
android:name=".PartilharReservaActivity"
android:exported="false" />
<activity
android:name=".ProfileDashboardActivity"
android:exported="false" />
<activity
android:name=".ExplorarRestaurantesActivity"
android:exported="false" />
</application> </application>
</manifest> </manifest>

View File

@@ -0,0 +1,135 @@
package com.example.pap_teste;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import java.util.ArrayList;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.text.InputType;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.pap_teste.models.Staff;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
public class AddStaffActivity extends AppCompatActivity {
private Button addButton;
private EditText nameEditText;
private Spinner zonaSpinner;
private Button btnAddZone;
private Spinner mesaSpinner;
private ArrayList<String> zones;
private ArrayList<String> mesas;
private ArrayAdapter<String> adapter;
private ArrayAdapter<String> mesaAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_add_staff);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
addButton = findViewById(R.id.addButton);
nameEditText = findViewById(R.id.nammeEditText);
zonaSpinner = findViewById(R.id.zonaSpinner);
btnAddZone = findViewById(R.id.btnAddZone);
mesaSpinner = findViewById(R.id.mesaSpinner);
Button btnVoltarAddStaff = findViewById(R.id.btnVoltarAddStaff);
if (btnVoltarAddStaff != null) {
btnVoltarAddStaff.setOnClickListener(v -> finish());
}
zones = new ArrayList<>();
zones.add("Sala");
zones.add("Esplanada");
zones.add("Balcão");
adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, zones);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
zonaSpinner.setAdapter(adapter);
mesas = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
mesas.add("Mesa " + i);
}
mesaAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, mesas);
mesaAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mesaSpinner.setAdapter(mesaAdapter);
btnAddZone.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(AddStaffActivity.this);
builder.setTitle("Adicionar Zona");
final EditText input = new EditText(AddStaffActivity.this);
input.setInputType(InputType.TYPE_CLASS_TEXT);
builder.setView(input);
builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String newZone = input.getText().toString();
if (!newZone.isEmpty()) {
zones.add(newZone);
adapter.notifyDataSetChanged();
zonaSpinner.setSelection(zones.size() - 1);
}
}
});
builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});
builder.show();
}
});
addButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = nameEditText.getText().toString();
String zona = "";
if (zonaSpinner.getSelectedItem() != null) {
zona = zonaSpinner.getSelectedItem().toString();
}
String mesa = "";
if (mesaSpinner.getSelectedItem() != null) {
mesa = mesaSpinner.getSelectedItem().toString();
}
if (!name.isEmpty()) {
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("Staff");
String uuid = java.util.UUID.randomUUID().toString();
Staff staff = new Staff(name, zona, mesa, uuid);
databaseReference.child(uuid).setValue(staff);
Toast.makeText(AddStaffActivity.this, "Staff adicionado com sucesso!", Toast.LENGTH_SHORT).show();
finish();
} else {
Toast.makeText(AddStaffActivity.this, "Erro: Preencha todos os campos", Toast.LENGTH_SHORT).show();
}
}
});
}
}

View File

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

View File

@@ -1,46 +1,276 @@
package com.example.pap_teste; package com.example.pap_teste;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment; import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Restaurant;
import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList;
import java.util.List;
public class ClientDashboardActivity extends AppCompatActivity { public class ClientDashboardActivity extends AppCompatActivity {
private String email, displayName;
private TextView txtGreeting;
private ImageView imgProfile;
private EditText etSearch;
private RecyclerView rvFeatured;
private ProgressBar progressBar;
private View layoutFeatured;
private android.widget.LinearLayout categoriesContainer;
private List<Restaurant> allRestaurants = new ArrayList<>();
private List<Restaurant> filteredRestaurants = new ArrayList<>();
private FeaturedRestaurantAdapter featuredAdapter;
private final String[] CATEGORIES = { "Carnes", "Massas", "Sushi", "Pizzas", "Sobremesas" };
private String currentSearchFilter = "";
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client_dashboard_v2); EdgeToEdge.enable(this);
setContentView(R.layout.activity_client_dashboard);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.clientRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME);
initViews();
setupBottomNavigation();
setupSearch();
updateGreeting();
fetchProfilePicture();
fetchRestaurants();
// Iniciar serviço de notificações se for Android O ou superior
Intent serviceIntent = new Intent(this, ReservationNotificationService.class);
startService(serviceIntent);
}
private RestaurantAdapter.OnRestaurantClickListener clickListener;
private void initViews() {
txtGreeting = findViewById(R.id.txtClientGreeting);
imgProfile = findViewById(R.id.imgProfile);
etSearch = findViewById(R.id.etSearch);
rvFeatured = findViewById(R.id.rvFeatured);
progressBar = findViewById(R.id.progressBar);
layoutFeatured = findViewById(R.id.layoutFeatured);
categoriesContainer = findViewById(R.id.categoriesContainer);
rvFeatured.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
// Click listener for restaurants to open booking flow
clickListener = restaurant -> {
Intent intent = new Intent(this, ExplorarRestaurantesActivity.class);
intent.putExtra("restaurant", restaurant);
startActivity(intent);
};
featuredAdapter = new FeaturedRestaurantAdapter(new ArrayList<>(), clickListener);
rvFeatured.setAdapter(featuredAdapter);
// Click listener for profile picture in the header
findViewById(R.id.cardProfile).setOnClickListener(v -> {
Intent intent = new Intent(this, ProfileDashboardActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, email);
intent.putExtra(MainActivity.EXTRA_DISPLAY_NAME, displayName);
startActivity(intent);
});
}
private void setupBottomNavigation() {
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigation); BottomNavigationView bottomNav = findViewById(R.id.bottomNavigation);
// Initial fragment
loadFragment(new HomeFragment());
bottomNav.setOnItemSelectedListener(item -> { bottomNav.setOnItemSelectedListener(item -> {
Fragment fragment = null;
int id = item.getItemId(); int id = item.getItemId();
if (id == R.id.nav_home) { if (id == R.id.nav_home) {
fragment = new HomeFragment();
} else if (id == R.id.nav_reservations) {
fragment = new MyReservationsFragment();
} else if (id == R.id.nav_profile) {
fragment = new ProfileFragment();
}
if (fragment != null) {
loadFragment(fragment);
return true; return true;
} else if (id == R.id.nav_reservations) {
Intent intent = new Intent(this, MinhasReservasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, email);
startActivity(intent);
return false; // Don't highlight if we open new activity
} else if (id == R.id.nav_profile) {
Intent intent = new Intent(this, ProfileDashboardActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, email);
intent.putExtra(MainActivity.EXTRA_DISPLAY_NAME, displayName);
startActivity(intent);
return false;
} }
return false; return false;
}); });
} }
private void loadFragment(Fragment fragment) { private void updateGreeting() {
getSupportFragmentManager() if (displayName != null && !displayName.isEmpty()) {
.beginTransaction() txtGreeting.setText("Olá, " + displayName.split(" ")[0] + "!");
.replace(R.id.fragmentContainer, fragment) } else {
.commit(); txtGreeting.setText("Olá, Visitante!");
}
}
private void fetchProfilePicture() {
if (email == null)
return;
String documentId = email.replace(".", "_").replace("@", "_at_");
FirebaseDatabase.getInstance().getReference().child("Clientes").child(documentId).child("photoUrl")
.get().addOnSuccessListener(snapshot -> {
if (!isDestroyed() && snapshot.exists() && snapshot.getValue(String.class) != null) {
com.bumptech.glide.Glide.with(this).load(snapshot.getValue(String.class)).circleCrop()
.into(imgProfile);
}
});
}
private void fetchRestaurants() {
progressBar.setVisibility(View.VISIBLE);
layoutFeatured.setVisibility(View.GONE);
categoriesContainer.removeAllViews();
DatabaseReference usersRef = FirebaseDatabase.getInstance().getReference("Restaurantes");
usersRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull DataSnapshot snapshot) {
progressBar.setVisibility(View.GONE);
allRestaurants.clear();
for (DataSnapshot ds : snapshot.getChildren()) {
String role = ds.child("role").getValue(String.class);
String accountType = ds.child("accountType").getValue(String.class);
// Aceitar todos os registos na coleção Restaurantes
if (true) {
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);
Double ratingAvg = ds.child("ratingAvg").getValue(Double.class);
Integer ratingCount = ds.child("ratingCount").getValue(Integer.class);
if (name != null && email != null) {
Restaurant r = new Restaurant(name, cat != null ? cat : "Vário", email, false, logoUrl);
if (ratingAvg != null)
r.setRatingAvg(ratingAvg);
if (ratingCount != null)
r.setRatingCount(ratingCount);
allRestaurants.add(r);
}
}
}
applyFilters();
}
@Override
public void onCancelled(@androidx.annotation.NonNull DatabaseError error) {
progressBar.setVisibility(View.GONE);
}
});
}
private void setupSearch() {
etSearch.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
currentSearchFilter = s.toString().toLowerCase().trim();
applyFilters();
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
private void applyFilters() {
filteredRestaurants.clear();
String normalizedSearch = normalizeString(currentSearchFilter);
for (Restaurant r : allRestaurants) {
String normalizedName = normalizeString(r.getName());
boolean matchesSearch = currentSearchFilter.isEmpty() || normalizedName.contains(normalizedSearch);
if (matchesSearch) {
filteredRestaurants.add(r);
}
}
// Update featured carousel with top restaurants
List<Restaurant> featuredList = new ArrayList<>();
for (int i = 0; i < Math.min(3, filteredRestaurants.size()); i++) {
featuredList.add(filteredRestaurants.get(i));
}
featuredAdapter = new FeaturedRestaurantAdapter(featuredList, clickListener);
rvFeatured.setAdapter(featuredAdapter);
layoutFeatured.setVisibility(featuredList.isEmpty() ? View.GONE : View.VISIBLE);
// Update category rows
categoriesContainer.removeAllViews();
for (String category : CATEGORIES) {
List<Restaurant> catList = new ArrayList<>();
for (Restaurant r : filteredRestaurants) {
if (category.equals(r.getCategory())) {
catList.add(r);
}
}
if (!catList.isEmpty()) {
View rowView = android.view.LayoutInflater.from(this).inflate(R.layout.item_category_row, categoriesContainer, false);
TextView txtTitle = rowView.findViewById(R.id.txtCategoryTitle);
RecyclerView rvCategory = rowView.findViewById(R.id.rvCategoryRestaurants);
txtTitle.setText(category);
rvCategory.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
rvCategory.setAdapter(new FeaturedRestaurantAdapter(catList, clickListener));
categoriesContainer.addView(rowView);
}
}
}
private String normalizeString(String str) {
if (str == null)
return "";
// Remove accents and special marks, and convert to lowercase
String normalized = java.text.Normalizer.normalize(str, java.text.Normalizer.Form.NFD);
normalized = normalized.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
return normalized.toLowerCase().trim();
} }
} }

View File

@@ -1,75 +0,0 @@
package com.example.pap_teste;
import com.example.pap_teste.models.Restaurant;
import com.google.firebase.firestore.CollectionReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DataSeeder {
public static void seedRestaurants() {
com.google.firebase.firestore.CollectionReference restaurants = FirestoreManager.getInstance().getRestaurantsCollection();
List<Restaurant> sampleRestaurants = new ArrayList<>();
sampleRestaurants.add(createRestaurant("The Italian Job", "Italian",
"Authentic wood-fired pizzas and homemade pasta in a cozy setting.",
"https://images.unsplash.com/photo-1555396273-367ea4eb4db5", 4.8, "Rua de Itália, 123, Lisboa"));
sampleRestaurants.add(createRestaurant("Sushi Zen", "Japanese",
"Premium sushi experience with fresh ingredients flown in daily.",
"https://images.unsplash.com/photo-1579871494447-9811cf80d66c", 4.9, "Avenida do Japão, 45, Porto"));
sampleRestaurants.add(createRestaurant("Burger Haven", "Burgers",
"Gourmet burgers made with 100% aged beef and unique toppings.",
"https://images.unsplash.com/photo-1571091718767-18b5b1457add", 4.5, "Praça do Hambúrguer, 10, Braga"));
sampleRestaurants.add(createRestaurant("Taco Libre", "Mexican",
"Vibrant street-style Mexican food and craft margaritas.",
"https://images.unsplash.com/photo-1565299585323-38d6b0865b47", 4.6, "Rua do México, 88, Faro"));
sampleRestaurants.add(createRestaurant("Steakhouse Prime", "Steakhouse",
"Expertly grilled premium cuts and an extensive wine list.",
"https://images.unsplash.com/photo-1546241072-48010ad28c2c", 4.7, "Travessa do Bife, 5, Coimbra"));
sampleRestaurants.add(createRestaurant("Le Petit Bistro", "French",
"A slice of Paris serving classic French dishes and fine wine.",
"https://images.unsplash.com/photo-1550966842-28a2a2d3efd1", 4.4, "Rua de França, 22, Évora"));
sampleRestaurants.add(createRestaurant("Green Garden", "Vegetarian",
"Sustainable and delicious plant-based cuisine for everyone.",
"https://images.unsplash.com/photo-1512621776951-a57141f2eefd", 4.6, "Alameda Verde, 15, Aveiro"));
sampleRestaurants.add(createRestaurant("Ocean's Catch", "Seafood",
"The freshest seafood and shellfish with a beautiful ocean view.",
"https://images.unsplash.com/photo-1551248429-42405d2d9cd8", 4.8, "Estrada da Praia, 1, Cascais"));
for (Restaurant r : sampleRestaurants) {
restaurants.document().set(r);
}
}
private static Restaurant createRestaurant(String name, String cuisine, String desc, String url, double rating, String address) {
Restaurant r = new Restaurant();
r.setName(name);
r.setCuisine(cuisine);
r.setDescription(desc);
r.setImageURL(url);
r.setRating(rating);
r.setAddress(address);
Map<String, String> hours = new HashMap<>();
hours.put("open", "12:00");
hours.put("close", "23:00");
r.setOpeningHours(hours);
List<String> slots = new ArrayList<>();
slots.add("12:00"); slots.add("13:00"); slots.add("14:00");
slots.add("19:00"); slots.add("20:00"); slots.add("21:00"); slots.add("22:00");
r.setAvailableSlots(slots);
return r;
}
}

View File

@@ -0,0 +1,228 @@
package com.example.pap_teste;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import 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.HashMap;
import java.util.Map;
import android.net.Uri;
import android.content.Intent;
import android.widget.ImageView;
import com.bumptech.glide.Glide;
import com.google.firebase.storage.FirebaseStorage;
import com.google.firebase.storage.StorageReference;
import java.util.UUID;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
public class DefinicoesAdminActivity extends AppCompatActivity {
private EditText inputRadius, inputAddress;
private android.widget.Spinner spinnerCategory;
private ImageView imgLogo;
private DatabaseReference databaseReference;
private String documentId;
private String photoUrl;
private ActivityResultLauncher<Intent> imagePickerLauncher;
private String[] categories = {"Carnes", "Massas", "Sushi", "Pizzas", "Sobremesas", "Italiana", "Moderna"};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_definicoes_admin);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.definicoesRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
String email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
if (email != null) {
documentId = email.replace(".", "_").replace("@", "_at_");
}
databaseReference = FirebaseDatabase.getInstance().getReference().child("Restaurantes");
inputRadius = findViewById(R.id.inputRadius);
inputAddress = findViewById(R.id.inputAddress);
spinnerCategory = findViewById(R.id.spinnerCategory);
imgLogo = findViewById(R.id.imgRestaurantLogo);
android.widget.ArrayAdapter<String> adapter = new android.widget.ArrayAdapter<>(this,
android.R.layout.simple_spinner_item, categories);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinnerCategory.setAdapter(adapter);
Button btnSave = findViewById(R.id.btnSaveSettings);
Button btnBack = findViewById(R.id.btnVoltar);
Button btnChangeLogo = findViewById(R.id.btnChangeLogo);
if (btnBack != null) {
btnBack.setOnClickListener(v -> finish());
}
imagePickerLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
Uri imageUri = result.getData().getData();
if (imageUri != null) {
uploadImageToFirebase(imageUri);
}
}
});
btnChangeLogo.setOnClickListener(v -> {
String[] options = {"Galeria", "URL da Imagem"};
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Escolher Logótipo")
.setItems(options, (dialog, which) -> {
if (which == 0) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
imagePickerLauncher.launch(intent);
} else {
showUrlInputDialog();
}
}).show();
});
loadCurrentSettings();
btnSave.setOnClickListener(v -> saveSettings());
}
private void showUrlInputDialog() {
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this);
builder.setTitle("Inserir URL da Imagem");
final EditText input = new EditText(this);
input.setInputType(android.text.InputType.TYPE_TEXT_VARIATION_URI);
builder.setView(input);
builder.setPositiveButton("Confirmar", (dialog, which) -> {
String url = input.getText().toString().trim();
if (!TextUtils.isEmpty(url)) {
this.photoUrl = url;
Glide.with(this).load(photoUrl).circleCrop().into(imgLogo);
Toast.makeText(this, "Logótipo definido!", Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton("Cancelar", (dialog, which) -> dialog.cancel());
builder.show();
}
private void uploadImageToFirebase(Uri imageUri) {
if (documentId == null) return;
StorageReference storageRef = FirebaseStorage.getInstance().getReference()
.child("restaurant_logos/" + UUID.randomUUID().toString());
storageRef.putFile(imageUri).addOnSuccessListener(taskSnapshot -> {
storageRef.getDownloadUrl().addOnSuccessListener(uri -> {
photoUrl = uri.toString();
Glide.with(this).load(photoUrl).circleCrop().into(imgLogo);
});
}).addOnFailureListener(e -> {
Toast.makeText(this, "Falha no upload: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
}
private void loadCurrentSettings() {
if (documentId == null)
return;
databaseReference.child(documentId).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
if (snapshot.exists()) {
if (snapshot.hasChild("securityDistance")) {
Object val = snapshot.child("securityDistance").getValue();
inputRadius.setText(val != null ? val.toString() : "");
}
if (snapshot.hasChild("address")) {
String addr = snapshot.child("address").getValue(String.class);
inputAddress.setText(addr != null ? addr : "");
}
if (snapshot.hasChild("category")) {
String cat = snapshot.child("category").getValue(String.class);
if (cat != null) {
for (int i = 0; i < categories.length; i++) {
if (categories[i].equalsIgnoreCase(cat)) {
spinnerCategory.setSelection(i);
break;
}
}
}
}
if (snapshot.hasChild("logoUrl")) {
photoUrl = snapshot.child("logoUrl").getValue(String.class);
if (photoUrl != null && !photoUrl.isEmpty()) {
Glide.with(DefinicoesAdminActivity.this).load(photoUrl).circleCrop().into(imgLogo);
}
}
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(DefinicoesAdminActivity.this, "Erro ao carregar definições.", Toast.LENGTH_SHORT).show();
}
});
}
private void saveSettings() {
if (documentId == null)
return;
String radiusStr = inputRadius.getText().toString().trim();
String addressStr = inputAddress.getText().toString().trim();
String selectedCategory = spinnerCategory.getSelectedItem().toString();
if (TextUtils.isEmpty(radiusStr) || TextUtils.isEmpty(addressStr)) {
Toast.makeText(this, "Preencha todos os campos.", Toast.LENGTH_SHORT).show();
return;
}
try {
int radius = Integer.parseInt(radiusStr);
Map<String, Object> updates = new HashMap<>();
updates.put("securityDistance", radius);
updates.put("address", addressStr);
updates.put("category", selectedCategory);
if (photoUrl != null) {
updates.put("logoUrl", photoUrl);
}
databaseReference.child(documentId).updateChildren(updates)
.addOnSuccessListener(aVoid -> {
Toast.makeText(this, "Definições guardadas com sucesso!", Toast.LENGTH_SHORT).show();
finish();
})
.addOnFailureListener(
e -> Toast.makeText(this, "Falha ao guardar definições.", Toast.LENGTH_SHORT).show());
} catch (NumberFormatException e) {
Toast.makeText(this, "Raio inválido.", Toast.LENGTH_SHORT).show();
}
}
}

View File

@@ -0,0 +1,365 @@
package com.example.pap_teste;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.ArrayList;
import java.util.List;
public class DetalhesReservasActivity extends AppCompatActivity {
private final List<com.example.pap_teste.models.Reserva> reservas = new ArrayList<>();
private ArrayAdapter<String> adapter;
private ListView listReservas;
private TextView txtInfo;
private TextView txtNotas;
private TextView txtEstado;
private TextView txtMensagem;
private Button btnConfirmar, btnRecusar, btnApagar;
private int selectedIndex = -1;
private String restaurantEmail;
private com.google.firebase.database.DatabaseReference databaseReference;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_detalhes_reservas);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.detalhesReservasRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
restaurantEmail = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
if (restaurantEmail == null) {
if (com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser() != null) {
restaurantEmail = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser().getEmail();
} else {
restaurantEmail = "sabor_arte@restaurante.com";
}
}
bindViews();
setupList();
setupActions();
loadReservas();
}
private void bindViews() {
listReservas = findViewById(R.id.listReservas);
txtInfo = findViewById(R.id.txtReservaInfo);
txtNotas = findViewById(R.id.txtReservaNotas);
txtEstado = findViewById(R.id.txtReservaEstado);
txtMensagem = findViewById(R.id.txtMensagemReserva);
btnConfirmar = findViewById(R.id.btnConfirmarReserva);
btnRecusar = findViewById(R.id.btnCancelarReserva);
btnApagar = findViewById(R.id.btnApagarReserva);
Button back = findViewById(R.id.btnVoltar);
if (back != null) {
back.setOnClickListener(v -> finish());
}
}
private void loadReservas() {
databaseReference = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reservas");
databaseReference.addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(
@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
reservas.clear();
String targetEmail = restaurantEmail != null ? restaurantEmail.trim() : "";
for (com.google.firebase.database.DataSnapshot data : snapshot.getChildren()) {
com.example.pap_teste.models.Reserva r = data
.getValue(com.example.pap_teste.models.Reserva.class);
if (r != null && r.getEstado() != null && !"Arquivada".equals(r.getEstado())) {
String rEmail = r.getRestauranteEmail();
if (rEmail != null && rEmail.trim().equalsIgnoreCase(targetEmail)) {
reservas.add(r);
}
}
}
refreshList();
}
@Override
public void onCancelled(
@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
Toast.makeText(DetalhesReservasActivity.this, "Erro ao carregar reservas.", Toast.LENGTH_SHORT)
.show();
}
});
}
private void setupList() {
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_activated_1);
listReservas.setAdapter(adapter);
listReservas.setOnItemClickListener((parent, view, position, id) -> {
selectedIndex = position;
mostrarDetalhe(reservas.get(position));
});
}
private void setupActions() {
if (btnConfirmar != null) {
btnConfirmar.setOnClickListener(v -> {
com.example.pap_teste.models.Reserva item = reservas.get(selectedIndex);
if ("Pendente".equals(item.getEstado())) {
mostrarMesasDisponiveis();
} else if ("Confirmada".equals(item.getEstado()) || item.getEstado().startsWith("Confirmada (Mesa")) {
showConcluirDialog();
}
});
}
if (btnRecusar != null) {
btnRecusar.setOnClickListener(v -> showRecusarDialog());
}
if (btnApagar != null) {
btnApagar.setOnClickListener(v -> apagarReserva());
}
}
private void mostrarMesasDisponiveis() {
if (selectedIndex < 0 || selectedIndex >= reservas.size()) {
Toast.makeText(this, "Selecione uma reserva.", Toast.LENGTH_SHORT).show();
return;
}
com.example.pap_teste.models.Reserva item = reservas.get(selectedIndex);
com.google.firebase.database.DatabaseReference mesasRef = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("Mesas");
mesasRef.orderByChild("restauranteEmail").equalTo(restaurantEmail).addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
List<com.example.pap_teste.models.Mesa> mesasLivres = new ArrayList<>();
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.getEstado() != null && m.getEstado().equalsIgnoreCase("Livre") && m.getCapacidade() >= item.getPessoas()) {
m.setId(ds.getKey());
mesasLivres.add(m);
}
}
if (mesasLivres.isEmpty()) {
new androidx.appcompat.app.AlertDialog.Builder(DetalhesReservasActivity.this)
.setTitle("Sem mesas disponíveis")
.setMessage("Não há mesas livres com capacidade suficiente para " + item.getPessoas() + " pessoas. Deseja confirmar a reserva mesmo assim (sem mesa atribuída)?")
.setPositiveButton("Sim", (dialog, which) -> atualizarEstadoSelecionado("Confirmada", null))
.setNegativeButton("Não", null)
.show();
return;
}
String[] mesaOptions = new String[mesasLivres.size()];
for (int i = 0; i < mesasLivres.size(); i++) {
com.example.pap_teste.models.Mesa m = mesasLivres.get(i);
mesaOptions[i] = String.format("Mesa %d (%d lugares)", m.getNumero(), m.getCapacidade());
}
new androidx.appcompat.app.AlertDialog.Builder(DetalhesReservasActivity.this)
.setTitle("Atribuir Mesa")
.setItems(mesaOptions, (dialog, which) -> {
com.example.pap_teste.models.Mesa selecionada = mesasLivres.get(which);
confirmarReservaComMesa(item, selecionada);
})
.setNegativeButton("Cancelar", null)
.show();
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
Toast.makeText(DetalhesReservasActivity.this, "Erro ao carregar mesas.", Toast.LENGTH_SHORT).show();
}
});
}
private void confirmarReservaComMesa(com.example.pap_teste.models.Reserva reserva, com.example.pap_teste.models.Mesa mesa) {
String novoEstado = "Confirmada (Mesa " + mesa.getNumero() + ")";
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("estado", novoEstado);
updates.put("motivo", null); // limpar possível motivo antigo
databaseReference.child(reserva.getId()).updateChildren(updates).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("Mesas").child(mesa.getId()).child("estado").setValue("Reservada")
.addOnCompleteListener(t2 -> {
Toast.makeText(this, "Reserva aceite e mesa atribuída com sucesso.", Toast.LENGTH_SHORT).show();
reserva.setEstado(novoEstado);
reserva.setMotivo(null);
mostrarDetalhe(reserva);
});
} else {
Toast.makeText(this, "Erro ao confirmar reserva.", Toast.LENGTH_SHORT).show();
}
});
}
private void showConcluirDialog() {
if (selectedIndex < 0) return;
android.widget.EditText input = new android.widget.EditText(this);
input.setHint("Notas de conclusão (opcional)");
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Concluir Reserva")
.setMessage("Deseja adicionar alguma nota ao concluir esta reserva?")
.setView(input)
.setPositiveButton("Concluir", (dialog, which) -> {
String motivo = input.getText().toString();
atualizarEstadoSelecionado("Concluída", motivo.isEmpty() ? null : motivo);
})
.setNegativeButton("Cancelar", null)
.show();
}
private void showRecusarDialog() {
if (selectedIndex < 0)
return;
String[] motivos = { "Sem espaço no restaurante", "Fora de horas", "Reserva duplicada", "Outro" };
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Motivo da Recusa")
.setItems(motivos, (dialog, which) -> {
atualizarEstadoSelecionado("Recusada", motivos[which]);
})
.setNegativeButton("Voltar", null)
.show();
}
private void apagarReserva() {
if (selectedIndex < 0)
return;
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Apagar/Arquivar Reserva")
.setMessage("Tem a certeza que deseja limpar esta reserva da lista?")
.setPositiveButton("Sim", (dialog, which) -> {
com.example.pap_teste.models.Reserva r = reservas.get(selectedIndex);
String idReserva = r.getId();
if ("Concluída".equals(r.getEstado()) || r.getEstado().startsWith("Confirmada")) {
databaseReference.child(idReserva).child("estado").setValue("Arquivada").addOnCompleteListener(task -> {
if (task.isSuccessful()) {
clearSelectionAndShowMessage();
}
});
} else {
databaseReference.child(idReserva).removeValue().addOnCompleteListener(task -> {
if (task.isSuccessful()) {
clearSelectionAndShowMessage();
}
});
}
})
.setNegativeButton("Voltar", null)
.show();
}
private void clearSelectionAndShowMessage() {
selectedIndex = -1;
txtInfo.setText("Selecione uma reserva");
txtNotas.setText("");
txtEstado.setText("Estado:");
txtMensagem.setText("Reserva processada e apagada da vista.");
toggleButtons(null);
}
private void atualizarEstadoSelecionado(String novoEstado, String motivo) {
if (selectedIndex < 0 || selectedIndex >= reservas.size()) {
Toast.makeText(this, "Selecione uma reserva para atualizar.", Toast.LENGTH_SHORT).show();
return;
}
com.example.pap_teste.models.Reserva item = reservas.get(selectedIndex);
java.util.Map<String, Object> updates = new java.util.HashMap<>();
updates.put("estado", novoEstado);
if (motivo != null && !motivo.isEmpty()) {
updates.put("motivo", motivo);
} else {
updates.put("motivo", null);
}
databaseReference.child(item.getId()).updateChildren(updates).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
txtMensagem
.setText(String.format("Reserva de %s marcada como %s.", item.getClienteEmail(), novoEstado));
item.setEstado(novoEstado);
item.setMotivo(motivo);
mostrarDetalhe(item);
}
});
}
private void mostrarDetalhe(com.example.pap_teste.models.Reserva item) {
txtInfo.setText(String.format("%s • %s • %s • %dp", item.getClienteEmail(), item.getRestauranteName(),
item.getHora(), item.getPessoas()));
txtNotas.setText("Data: " + item.getData());
String estadoTexto = item.getEstado();
if (item.getMotivo() != null && !item.getMotivo().isEmpty()) {
estadoTexto += " (" + item.getMotivo() + ")";
}
txtEstado.setText(String.format("Estado: %s", estadoTexto));
toggleButtons(item);
}
private void toggleButtons(com.example.pap_teste.models.Reserva item) {
if (item == null) {
btnConfirmar.setVisibility(android.view.View.GONE);
btnRecusar.setVisibility(android.view.View.GONE);
btnApagar.setVisibility(android.view.View.GONE);
return;
}
switch (item.getEstado()) {
case "Pendente":
btnConfirmar.setText("Confirmar");
btnConfirmar.setVisibility(android.view.View.VISIBLE);
btnRecusar.setVisibility(android.view.View.VISIBLE);
btnApagar.setVisibility(android.view.View.GONE);
break;
case "Concluída":
btnConfirmar.setVisibility(android.view.View.GONE);
btnRecusar.setVisibility(android.view.View.GONE);
btnApagar.setVisibility(android.view.View.VISIBLE);
break;
default:
if (item.getEstado() != null && item.getEstado().startsWith("Confirmada")) {
btnConfirmar.setText("Concluir");
btnConfirmar.setVisibility(android.view.View.VISIBLE);
btnRecusar.setVisibility(android.view.View.VISIBLE); // Still allow refusal
btnApagar.setVisibility(android.view.View.GONE);
} else {
// Recusada or Cancelada
btnConfirmar.setVisibility(android.view.View.GONE);
btnRecusar.setVisibility(android.view.View.GONE);
btnApagar.setVisibility(android.view.View.VISIBLE); // Allow deleting refused ones as well
}
break;
}
}
private void refreshList() {
adapter.clear();
for (com.example.pap_teste.models.Reserva item : reservas) {
String resumo = String.format("%s - %s (%s) • %s", item.getHora(), item.getData(), item.getEstado(),
item.getClienteEmail());
adapter.add(resumo);
}
adapter.notifyDataSetChanged();
}
}

View File

@@ -1,13 +1,250 @@
package com.example.pap_teste; package com.example.pap_teste;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class EstablishmentDashboardActivity extends AppCompatActivity { public class EstablishmentDashboardActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) { @Override
super.onCreate(savedInstanceState); protected void onCreate(Bundle savedInstanceState) {
// Temporariamente usando o layout do cliente ou um placeholder se necessário super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client_dashboard); EdgeToEdge.enable(this);
} setContentView(R.layout.activity_establishment_dashboard);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.establishmentRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
TextView txtTitle = findViewById(R.id.txtEstabTitle);
TextView txtSubtitle = findViewById(R.id.txtEstabSubtitle);
TextView txtRole = findViewById(R.id.txtEstabRole);
String actionMode = getIntent().getStringExtra(MainActivity.EXTRA_ACTION_MODE);
String displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME);
String role = getIntent().getStringExtra(MainActivity.EXTRA_ROLE);
boolean isNewAccount = "CRIAR".equalsIgnoreCase(actionMode);
txtTitle.setText(displayName != null ? displayName : "Estabelecimento");
txtRole.setText(String.format("Função: %s", role != null ? role : "ADMIN"));
txtSubtitle.setText(isNewAccount
? "Perfil criado. Configure horários, mesas e abra reservas."
: "Dashboard operacional. Acompanhe as reservas em tempo real.");
Button btnOpenWalkIns = findViewById(R.id.btnAbrirEspera);
Button btnStaff = findViewById(R.id.btnGestaoStaff);
Button btnGerirMesas = findViewById(R.id.btnGerirMesas);
Button btnDetails = findViewById(R.id.btnDetalhesReservas);
Button btnSettings = findViewById(R.id.btnDefinicoes);
Button btnBack = findViewById(R.id.btnVoltar);
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; // Can't proceed without email
}
}
final String finalEmail = email;
btnOpenWalkIns.setOnClickListener(v -> {
Intent intent = new Intent(this, ListaEsperaActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, finalEmail);
startActivity(intent);
});
btnStaff.setOnClickListener(v -> {
Intent intent = new Intent(this, GestaoStaffActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, finalEmail);
startActivity(intent);
});
btnGerirMesas.setOnClickListener(v -> {
Intent intent = new Intent(this, GerirMesasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, finalEmail);
startActivity(intent);
});
btnDetails.setOnClickListener(v -> {
Intent intent = new Intent(this, DetalhesReservasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, finalEmail);
startActivity(intent);
});
btnSettings.setOnClickListener(v -> {
Intent intent = new Intent(this, DefinicoesAdminActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, finalEmail);
startActivity(intent);
});
findViewById(R.id.cardReservasHoje).setOnClickListener(v -> {
Intent intent = new Intent(this, DetalhesReservasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, finalEmail);
startActivity(intent);
});
if (btnBack != null) {
btnBack.setOnClickListener(v -> finish());
}
loadProximasReservas();
loadEstatisticas();
}
private void loadProximasReservas() {
android.widget.LinearLayout llProximasReservas = findViewById(R.id.llProximasReservas);
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 targetEmail = email != null ? email.trim() : "";
java.util.Calendar cal = java.util.Calendar.getInstance();
String todayDate = cal.get(java.util.Calendar.DAY_OF_MONTH) + "/" +
(cal.get(java.util.Calendar.MONTH) + 1) + "/" +
cal.get(java.util.Calendar.YEAR);
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reservas")
.addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
llProximasReservas.removeAllViews();
boolean found = false;
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
com.example.pap_teste.models.Reserva r = ds.getValue(com.example.pap_teste.models.Reserva.class);
if (r != null && r.getEstado() != null && (r.getEstado().startsWith("Confirmada") || r.getEstado().equals("Concluída")) && todayDate.equals(r.getData())) {
String rEmail = r.getRestauranteEmail();
if (rEmail != null && rEmail.trim().equalsIgnoreCase(targetEmail)) {
found = true;
addReservaView(llProximasReservas, r);
}
}
}
if (!found) {
TextView empty = new TextView(EstablishmentDashboardActivity.this);
empty.setText("Não há reservas confirmadas para hoje.");
empty.setTextColor(android.graphics.Color.parseColor("#5F5F5F"));
empty.setTextSize(14f);
llProximasReservas.addView(empty);
}
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
}
});
}
private void addReservaView(android.widget.LinearLayout container, com.example.pap_teste.models.Reserva r) {
TextView title = new TextView(this);
title.setText(String.format("%s - %s", r.getHora(), r.getData()));
title.setTextColor(android.graphics.Color.BLACK);
title.setTextSize(16f);
title.setTypeface(null, android.graphics.Typeface.BOLD);
TextView subtitle = new TextView(this);
subtitle.setText(String.format("Cliente: %s • %d pessoas", r.getClienteEmail(), r.getPessoas()));
subtitle.setTextColor(android.graphics.Color.parseColor("#5F5F5F"));
subtitle.setTextSize(14f);
android.widget.LinearLayout.LayoutParams params = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
subtitle.setLayoutParams(params);
android.view.View divider = new android.view.View(this);
android.widget.LinearLayout.LayoutParams divParams = new android.widget.LinearLayout.LayoutParams(
android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 2);
divParams.setMargins(0, 24, 0, 24);
divider.setLayoutParams(divParams);
divider.setBackgroundColor(android.graphics.Color.parseColor("#EEEEEE"));
container.addView(title);
container.addView(subtitle);
container.addView(divider);
}
private void loadEstatisticas() {
TextView txtReservasHoje = findViewById(R.id.txtReservasHojeDash);
TextView txtMesasLivres = findViewById(R.id.txtMesasLivresDash);
TextView txtListaEspera = findViewById(R.id.txtListaEsperaDash);
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() : "";
java.util.Calendar cal = java.util.Calendar.getInstance();
String todayDate = cal.get(java.util.Calendar.DAY_OF_MONTH) + "/" +
(cal.get(java.util.Calendar.MONTH) + 1) + "/" +
cal.get(java.util.Calendar.YEAR);
com.google.firebase.database.DatabaseReference refReservas = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reservas");
refReservas.addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
int reservasHoje = 0;
int listaEspera = 0;
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 (r.getEstado() != null) {
if ((r.getEstado().startsWith("Confirmada") || r.getEstado().equals("Concluída") || r.getEstado().equals("Arquivada")) && todayDate.equals(r.getData())) {
reservasHoje++;
} else if ("Pendente".equals(r.getEstado())) {
listaEspera++;
}
}
}
}
if (txtReservasHoje != null) txtReservasHoje.setText(String.format(java.util.Locale.US, "%02d", reservasHoje));
if (txtListaEspera != null) txtListaEspera.setText(String.format(java.util.Locale.US, "%02d", listaEspera));
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
}
});
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("Mesas")
.addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
int mesasLivres = 0;
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);
if (mesa != null && mesa.getRestauranteEmail() != null && mesa.getRestauranteEmail().trim().equalsIgnoreCase(finalEmail)) {
if ("Livre".equalsIgnoreCase(mesa.getEstado())) {
mesasLivres++;
}
}
}
if (txtMesasLivres != null) txtMesasLivres.setText(String.format(java.util.Locale.US, "%02d", mesasLivres));
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
}
});
}
} }

View File

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

View File

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

View File

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

View File

@@ -1,36 +0,0 @@
package com.example.pap_teste;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.CollectionReference;
public class FirestoreManager {
private static FirestoreManager instance;
private final FirebaseFirestore db;
private FirestoreManager() {
db = FirebaseFirestore.getInstance();
}
public static synchronized FirestoreManager getInstance() {
if (instance == null) {
instance = new FirestoreManager();
}
return instance;
}
public FirebaseFirestore getDb() {
return db;
}
public CollectionReference getUsersCollection() {
return db.collection("Clientes");
}
public CollectionReference getRestaurantsCollection() {
return db.collection("Restaurantes");
}
public CollectionReference getReservationsCollection() {
return db.collection("Reservas");
}
}

View File

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

View File

@@ -0,0 +1,227 @@
package com.example.pap_teste;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.example.pap_teste.models.Mesa;
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 com.google.firebase.auth.FirebaseAuth;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
public class GerirMesasActivity extends AppCompatActivity {
private final List<Mesa> mesas = new ArrayList<>();
private ArrayAdapter<String> adapter;
private ListView listMesas;
private EditText inputNumero;
private EditText inputCapacidade;
private Spinner spinnerEstado;
private TextView txtMensagem;
private DatabaseReference mDatabase;
private Mesa selectedMesa = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_gerir_mesas);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.gerirMesasRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
bindViews();
mDatabase = FirebaseDatabase.getInstance().getReference("Mesas");
setupList();
setupFormActions();
}
private void bindViews() {
listMesas = findViewById(R.id.listMesas);
inputNumero = findViewById(R.id.inputMesaNumero);
inputCapacidade = findViewById(R.id.inputMesaCapacidade);
spinnerEstado = findViewById(R.id.spinnerEstadoMesa);
txtMensagem = findViewById(R.id.txtMensagemMesa);
Button back = findViewById(R.id.btnVoltar);
if (back != null) {
back.setOnClickListener(v -> finish());
}
ArrayAdapter<String> estadoAdapter = new ArrayAdapter<>(
this,
android.R.layout.simple_spinner_dropdown_item,
new String[] { "Livre", "Ocupada", "Reservada" });
spinnerEstado.setAdapter(estadoAdapter);
}
private void setupList() {
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_activated_1);
listMesas.setAdapter(adapter);
mDatabase.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
mesas.clear();
adapter.clear();
String currentUserEmail = FirebaseAuth.getInstance().getCurrentUser() != null
? FirebaseAuth.getInstance().getCurrentUser().getEmail()
: "";
for (DataSnapshot postSnapshot : snapshot.getChildren()) {
Mesa mesa = postSnapshot.getValue(Mesa.class);
if (mesa != null && (mesa.getRestauranteEmail() == null || mesa.getRestauranteEmail().equals(currentUserEmail))) {
// Ensure the ID is set from the snapshot key if it's missing in the value
mesa.setId(postSnapshot.getKey());
mesas.add(mesa);
String resumo = String.format("Mesa %02d • %d lugares • %s", mesa.getNumero(),
mesa.getCapacidade(), mesa.getEstado());
adapter.add(resumo);
}
}
adapter.notifyDataSetChanged();
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(GerirMesasActivity.this, "Erro ao carregar mesas: " + error.getMessage(),
Toast.LENGTH_SHORT).show();
}
});
listMesas.setOnItemClickListener((parent, view, position, id) -> {
Mesa item = mesas.get(position);
selectedMesa = item;
inputNumero.setText(String.valueOf(item.getNumero()));
inputCapacidade.setText(String.valueOf(item.getCapacidade()));
spinnerEstado.setSelection(getEstadoIndex(item.getEstado()));
txtMensagem.setText(String.format("Editar mesa %d", item.getNumero()));
});
}
private int getEstadoIndex(String estado) {
if ("Ocupada".equalsIgnoreCase(estado)) {
return 1;
}
if ("Reservada".equalsIgnoreCase(estado)) {
return 2;
}
return 0;
}
private void setupFormActions() {
Button btnGuardar = findViewById(R.id.btnGuardarMesa);
if (btnGuardar != null) {
btnGuardar.setOnClickListener(v -> guardarMesa());
}
Button btnRemover = findViewById(R.id.btnRemoverMesa);
if (btnRemover != null) {
btnRemover.setOnClickListener(v -> removerMesa());
}
}
private void removerMesa() {
if (selectedMesa == null || selectedMesa.getId() == null) {
Toast.makeText(this, "Selecione uma mesa válida para remover.", Toast.LENGTH_SHORT).show();
return;
}
mDatabase.child(selectedMesa.getId()).removeValue()
.addOnSuccessListener(aVoid -> {
Toast.makeText(this, "Mesa removida com sucesso.", Toast.LENGTH_SHORT).show();
limparCampos();
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao remover mesa: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
}
private void limparCampos() {
inputNumero.setText("");
inputCapacidade.setText("");
selectedMesa = null;
txtMensagem.setText("");
}
private void guardarMesa() {
String numeroStr = inputNumero.getText().toString().trim();
String capacidadeStr = inputCapacidade.getText().toString().trim();
String estado = (String) spinnerEstado.getSelectedItem();
if (numeroStr.isEmpty() || capacidadeStr.isEmpty() || estado == null) {
Toast.makeText(this, "Preencha número, capacidade e estado.", Toast.LENGTH_SHORT).show();
return;
}
int numero;
int capacidade;
try {
numero = Integer.parseInt(numeroStr);
capacidade = Integer.parseInt(capacidadeStr);
} catch (NumberFormatException e) {
Toast.makeText(this, "Use apenas números válidos.", Toast.LENGTH_SHORT).show();
return;
}
Mesa existente = findMesa(numero);
String mesaId;
String currentUserEmail = FirebaseAuth.getInstance().getCurrentUser() != null
? FirebaseAuth.getInstance().getCurrentUser().getEmail()
: "";
if (existente == null) {
mesaId = mDatabase.push().getKey();
Mesa novaMesa = new Mesa(mesaId, numero, capacidade, estado, currentUserEmail);
if (mesaId != null) {
mDatabase.child(mesaId).setValue(novaMesa);
}
txtMensagem.setText(String.format("Mesa %d adicionada.", numero));
} else {
mesaId = existente.getId();
if (mesaId == null) {
Toast.makeText(this, "Erro ao atualizar: ID não encontrado.", Toast.LENGTH_SHORT).show();
return;
}
existente.setCapacidade(capacidade);
existente.setEstado(estado);
mDatabase.child(mesaId).setValue(existente);
txtMensagem.setText(String.format("Mesa %d atualizada.", numero));
}
// Clearing inputs
limparCampos();
}
private Mesa findMesa(int numero) {
for (Mesa item : mesas) {
if (item.getNumero() == numero) {
return item;
}
}
return null;
}
}

View File

@@ -0,0 +1,295 @@
package com.example.pap_teste;
import com.example.pap_teste.models.Mesa;
import com.example.pap_teste.models.Staff;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
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 androidx.annotation.NonNull;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import java.util.ArrayList;
import java.util.List;
public class GestaoStaffActivity extends AppCompatActivity {
private final List<Staff> staffList = new ArrayList<>();
private ArrayAdapter<String> staffAdapter;
private ListView listStaffMesas;
private Spinner spinnerNomeStaff;
private Spinner spinnerMesaStaff;
private TextView txtMensagemStaff;
private DatabaseReference staffRef;
private DatabaseReference mesasRef;
private List<String> staffNames = new ArrayList<>();
private List<Mesa> mesasDisponiveis = new ArrayList<>();
private ArrayAdapter<String> staffNameAdapter;
private ArrayAdapter<String> mesaSpinnerAdapter;
private FloatingActionButton floatingActionButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_gestao_staff);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.gestaoStaffRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
staffRef = FirebaseDatabase.getInstance().getReference("Staff");
mesasRef = FirebaseDatabase.getInstance().getReference("Mesas");
Button back = findViewById(R.id.btnVoltar);
if (back != null) {
back.setOnClickListener(v -> finish());
}
bindViews();
setupMesaSpinner();
setupList();
setupFormActions();
}
private void bindViews() {
listStaffMesas = findViewById(R.id.listStaffMesas);
spinnerNomeStaff = findViewById(R.id.spinnerNomeStaff);
spinnerMesaStaff = findViewById(R.id.spinnerMesaStaff);
txtMensagemStaff = findViewById(R.id.txtMensagemStaff);
floatingActionButton = findViewById(R.id.floatingActionButton);
}
/**
* Preenche o spinner com uma lista simples de mesas (120).
* Mais tarde isto pode ser ligado às mesas reais configuradas em "Gerir Mesas".
*/
private void setupMesaSpinner() {
mesaSpinnerAdapter = new ArrayAdapter<>(
this,
android.R.layout.simple_spinner_dropdown_item);
staffNameAdapter = new ArrayAdapter<>(
this,
android.R.layout.simple_spinner_dropdown_item,
staffNames);
spinnerNomeStaff.setAdapter(staffNameAdapter);
loadStaffMembers();
loadMesas();
spinnerMesaStaff.setAdapter(mesaSpinnerAdapter);
}
private void loadMesas() {
mesasRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
mesasDisponiveis.clear();
mesaSpinnerAdapter.clear();
for (DataSnapshot postSnapshot : snapshot.getChildren()) {
Mesa mesa = postSnapshot.getValue(Mesa.class);
if (mesa != null) {
mesasDisponiveis.add(mesa);
mesaSpinnerAdapter.add("Mesa " + mesa.getNumero());
}
}
mesaSpinnerAdapter.notifyDataSetChanged();
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(GestaoStaffActivity.this, "Erro ao carregar mesas.", Toast.LENGTH_SHORT).show();
}
});
}
private void setupList() {
staffAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_activated_1);
listStaffMesas.setAdapter(staffAdapter);
listStaffMesas.setOnItemClickListener((parent, view, position, id) -> {
Staff item = staffList.get(position);
// Select staff in spinner
int staffIndex = staffNames.indexOf(item.getName());
if (staffIndex >= 0) {
spinnerNomeStaff.setSelection(staffIndex);
}
// Select mesa in spinner
// Simple string matching for now since Mesa is stored as String in Staff
String assignedMesa = item.getMesa();
if (assignedMesa != null) {
for (int i = 0; i < mesaSpinnerAdapter.getCount(); i++) {
if (mesaSpinnerAdapter.getItem(i).equals(assignedMesa)) {
spinnerMesaStaff.setSelection(i);
break;
}
}
}
txtMensagemStaff.setText(String.format("A editar: %s", item.getName()));
});
}
private void setupFormActions() {
Button btnAtribuir = findViewById(R.id.btnAtribuirStaff);
if (btnAtribuir != null) {
btnAtribuir.setOnClickListener(v -> guardarAtribuicao());
}
Button btnEliminar = findViewById(R.id.btnEliminarStaff);
if (btnEliminar != null) {
btnEliminar.setOnClickListener(v -> eliminarStaff());
}
if (floatingActionButton != null) {
floatingActionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(GestaoStaffActivity.this, AddStaffActivity.class);
startActivity(intent);
}
});
}
Button btnGerirMesas = findViewById(R.id.btnGerirMesasStaff);
if (btnGerirMesas != null) {
btnGerirMesas.setOnClickListener(v -> {
Intent intent = new Intent(GestaoStaffActivity.this, GerirMesasActivity.class);
startActivity(intent);
});
}
}
private void guardarAtribuicao() {
String nome = "";
if (spinnerNomeStaff.getSelectedItem() != null) {
nome = spinnerNomeStaff.getSelectedItem().toString();
}
if (nome.isEmpty()) {
Toast.makeText(this, "Selecione um funcionário.", Toast.LENGTH_SHORT).show();
return;
}
if (spinnerMesaStaff == null || spinnerMesaStaff.getSelectedItem() == null) {
Toast.makeText(this, "Selecione uma mesa.", Toast.LENGTH_SHORT).show();
return;
}
String mesaSelecionada = spinnerMesaStaff.getSelectedItem().toString();
Staff staffToUpdate = findByNome(nome);
if (staffToUpdate != null) {
staffToUpdate.setMesa(mesaSelecionada);
final String finalNome = nome;
final String finalMesa = mesaSelecionada;
staffRef.child(staffToUpdate.getId()).setValue(staffToUpdate)
.addOnSuccessListener(aVoid -> {
txtMensagemStaff.setText(String.format("%s atribuído à %s.", finalNome, finalMesa));
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao atualizar: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
} else {
Toast.makeText(this, "Erro: Staff não encontrado.", Toast.LENGTH_SHORT).show();
}
}
private void eliminarStaff() {
String nome = "";
if (spinnerNomeStaff.getSelectedItem() != null) {
nome = spinnerNomeStaff.getSelectedItem().toString();
}
if (nome.isEmpty()) {
Toast.makeText(this, "Selecione um funcionário primeiro.", Toast.LENGTH_SHORT).show();
return;
}
Staff staffToDelete = findByNome(nome);
if (staffToDelete != null) {
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Eliminar Staff")
.setMessage("Tem a certeza que deseja eliminar o funcionário " + staffToDelete.getName() + "?")
.setPositiveButton("Eliminar", (dialog, which) -> {
staffRef.child(staffToDelete.getId()).removeValue()
.addOnSuccessListener(aVoid -> {
txtMensagemStaff.setText(staffToDelete.getName() + " foi eliminado.");
})
.addOnFailureListener(e -> {
Toast.makeText(this, "Erro ao eliminar: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
})
.setNegativeButton("Cancelar", null)
.show();
} else {
Toast.makeText(this, "Staff não encontrado.", Toast.LENGTH_SHORT).show();
}
}
private Staff findByNome(String nome) {
for (Staff item : staffList) {
if (item.getName().equalsIgnoreCase(nome)) {
return item;
}
}
return null;
}
private void loadStaffMembers() {
staffRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
staffList.clear();
staffNames.clear();
staffAdapter.clear();
for (DataSnapshot postSnapshot : snapshot.getChildren()) {
Staff staff = postSnapshot.getValue(Staff.class);
if (staff != null && staff.getName() != null) {
staffList.add(staff);
staffNames.add(staff.getName());
String mesaInfo = staff.getMesa() != null ? staff.getMesa() : "Sem Mesa";
String resumo = String.format("%s • %s • %s", staff.getName(), staff.getZona(), mesaInfo);
staffAdapter.add(resumo);
}
}
staffNameAdapter.notifyDataSetChanged();
staffAdapter.notifyDataSetChanged();
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(GestaoStaffActivity.this, "Erro ao carregar staff.", Toast.LENGTH_SHORT).show();
}
});
}
}

View File

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

View File

@@ -0,0 +1,250 @@
package com.example.pap_teste;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.widget.Button;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.pap_teste.models.Reserva;
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.List;
public class ListaEsperaActivity extends AppCompatActivity {
private final List<Reserva> reservasPendentes = new ArrayList<>();
private ArrayAdapter<String> adapter;
private ListView listReservas;
private TextView txtInfo, txtNotas, txtMensagem;
private Button btnConfirmar, btnRecusar;
private int selectedIndex = -1;
private String restaurantEmail;
private DatabaseReference databaseReference;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_lista_espera);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.listaEsperaRoot), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
restaurantEmail = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
if (restaurantEmail == null) {
if (com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser() != null) {
restaurantEmail = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser().getEmail();
} else {
restaurantEmail = ""; // Avoid hardcoded sabor_arte so we don't accidentally load wrong data
}
}
bindViews();
setupList();
setupActions();
loadReservasPendentes();
}
private void bindViews() {
listReservas = findViewById(R.id.listReservasP);
txtInfo = findViewById(R.id.txtReservaInfoP);
txtNotas = findViewById(R.id.txtReservaNotasP);
txtMensagem = findViewById(R.id.txtMensagemReservaP);
btnConfirmar = findViewById(R.id.btnConfirmarReservaP);
btnRecusar = findViewById(R.id.btnRecusarReservaP);
Button back = findViewById(R.id.btnVoltar);
if (back != null) {
back.setOnClickListener(v -> finish());
}
}
private void setupList() {
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_activated_1);
listReservas.setAdapter(adapter);
listReservas.setOnItemClickListener((parent, view, position, id) -> {
selectedIndex = position;
mostrarDetalhe(reservasPendentes.get(position));
});
}
private void setupActions() {
btnConfirmar.setOnClickListener(v -> mostrarMesasDisponiveis());
btnRecusar.setOnClickListener(v -> showRecusarDialog());
}
private void mostrarMesasDisponiveis() {
if (selectedIndex < 0 || selectedIndex >= reservasPendentes.size()) {
Toast.makeText(this, "Selecione uma reserva.", Toast.LENGTH_SHORT).show();
return;
}
Reserva item = reservasPendentes.get(selectedIndex);
DatabaseReference mesasRef = FirebaseDatabase.getInstance().getReference("Mesas");
mesasRef.orderByChild("restauranteEmail").equalTo(restaurantEmail).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull DataSnapshot snapshot) {
List<com.example.pap_teste.models.Mesa> mesasLivres = new ArrayList<>();
for (DataSnapshot ds : snapshot.getChildren()) {
com.example.pap_teste.models.Mesa m = ds.getValue(com.example.pap_teste.models.Mesa.class);
if (m != null && m.getEstado() != null && m.getEstado().equalsIgnoreCase("Livre") && m.getCapacidade() >= item.getPessoas()) {
m.setId(ds.getKey());
mesasLivres.add(m);
}
}
if (mesasLivres.isEmpty()) {
new androidx.appcompat.app.AlertDialog.Builder(ListaEsperaActivity.this)
.setTitle("Sem mesas disponíveis")
.setMessage("Não há mesas livres com capacidade suficiente para " + item.getPessoas() + " pessoas. Deseja confirmar a reserva mesmo assim (sem lugar reservado)?")
.setPositiveButton("Sim", (dialog, which) -> atualizarEstadoSelecionado("Confirmada"))
.setNegativeButton("Não", null)
.show();
return;
}
String[] mesaOptions = new String[mesasLivres.size()];
for (int i = 0; i < mesasLivres.size(); i++) {
com.example.pap_teste.models.Mesa m = mesasLivres.get(i);
mesaOptions[i] = String.format("Mesa %d (%d lugares)", m.getNumero(), m.getCapacidade());
}
new androidx.appcompat.app.AlertDialog.Builder(ListaEsperaActivity.this)
.setTitle("Atribuir Mesa")
.setItems(mesaOptions, (dialog, which) -> {
com.example.pap_teste.models.Mesa selecionada = mesasLivres.get(which);
confirmarReservaComMesa(item, selecionada);
})
.setNegativeButton("Cancelar", null)
.show();
}
@Override
public void onCancelled(@androidx.annotation.NonNull DatabaseError error) {
Toast.makeText(ListaEsperaActivity.this, "Erro ao carregar mesas.", Toast.LENGTH_SHORT).show();
}
});
}
private void confirmarReservaComMesa(Reserva reserva, com.example.pap_teste.models.Mesa mesa) {
String novoEstado = "Confirmada (Mesa " + mesa.getNumero() + ")";
databaseReference.child(reserva.getId()).child("estado").setValue(novoEstado).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
FirebaseDatabase.getInstance().getReference("Mesas").child(mesa.getId()).child("estado").setValue("Reservada")
.addOnCompleteListener(t2 -> {
Toast.makeText(this, "Reserva aceite e mesa atribuída com sucesso.", Toast.LENGTH_SHORT).show();
selectedIndex = -1;
toggleButtons(null);
});
} else {
Toast.makeText(this, "Erro ao confirmar reserva.", Toast.LENGTH_SHORT).show();
}
});
}
private void showRecusarDialog() {
if (selectedIndex < 0) return;
String[] motivos = { "Sem espaço no restaurante", "Fora de horas", "Reserva duplicada", "Outro" };
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Motivo da Recusa")
.setItems(motivos, (dialog, which) -> {
atualizarEstadoSelecionado("Recusada (" + motivos[which] + ")");
})
.setNegativeButton("Voltar", null)
.show();
}
private void atualizarEstadoSelecionado(String novoEstado) {
if (selectedIndex < 0 || selectedIndex >= reservasPendentes.size()) {
Toast.makeText(this, "Selecione uma reserva para avaliar.", Toast.LENGTH_SHORT).show();
return;
}
Reserva item = reservasPendentes.get(selectedIndex);
databaseReference.child(item.getId()).child("estado").setValue(novoEstado).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Toast.makeText(this, "Reserva avaliada com sucesso.", Toast.LENGTH_SHORT).show();
selectedIndex = -1;
toggleButtons(null);
} else {
Toast.makeText(this, "Erro ao alterar estado da reserva.", Toast.LENGTH_SHORT).show();
}
});
}
private void mostrarDetalhe(Reserva item) {
txtInfo.setText(String.format("%s • %s", item.getClienteEmail(), item.getHora()));
txtNotas.setText(String.format("Data: %s • Pessoas: %d", item.getData(), item.getPessoas()));
toggleButtons(item);
}
private void toggleButtons(Reserva item) {
if (item == null) {
btnConfirmar.setVisibility(android.view.View.GONE);
btnRecusar.setVisibility(android.view.View.GONE);
txtInfo.setText("Selecione uma reserva");
txtNotas.setText("");
return;
}
btnConfirmar.setVisibility(android.view.View.VISIBLE);
btnRecusar.setVisibility(android.view.View.VISIBLE);
}
private void loadReservasPendentes() {
databaseReference = FirebaseDatabase.getInstance().getReference("reservas");
databaseReference.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull DataSnapshot snapshot) {
reservasPendentes.clear();
String targetEmail = restaurantEmail != null ? restaurantEmail.trim() : "";
for (DataSnapshot data : snapshot.getChildren()) {
Reserva r = data.getValue(Reserva.class);
if (r != null && "Pendente".equals(r.getEstado())) {
String rEmail = r.getRestauranteEmail();
if (rEmail != null && rEmail.trim().equalsIgnoreCase(targetEmail)) {
reservasPendentes.add(r);
}
}
}
refreshList();
}
@Override
public void onCancelled(@androidx.annotation.NonNull DatabaseError error) {
Toast.makeText(ListaEsperaActivity.this, "Erro ao carregar lista de espera.", Toast.LENGTH_SHORT).show();
}
});
}
private void refreshList() {
adapter.clear();
for (Reserva item : reservasPendentes) {
adapter.add(String.format("%s - %dp • %s", item.getHora(), item.getPessoas(), item.getClienteEmail()));
}
adapter.notifyDataSetChanged();
}
}

View File

@@ -1,166 +1,594 @@
package com.example.pap_teste; package com.example.pap_teste;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.View; import android.view.View;
import android.widget.ProgressBar; import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AlertDialog;
import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Build;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import com.example.pap_teste.models.User; import com.google.firebase.FirebaseApp;
import com.google.android.material.button.MaterialButton;
import com.google.android.material.button.MaterialButtonToggleGroup;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore; import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MainActivity extends AppCompatActivity { public class MainActivity extends AppCompatActivity {
public static final String EXTRA_ACTION_MODE = "extra_action_mode";
public static final String EXTRA_DISPLAY_NAME = "extra_display_name"; public static final String EXTRA_DISPLAY_NAME = "extra_display_name";
public static final String EXTRA_EMAIL = "extra_email"; public static final String EXTRA_EMAIL = "extra_email";
public static final String EXTRA_ACCOUNT_TYPE = "extra_account_type"; public static final String EXTRA_ACCOUNT_TYPE = "extra_account_type";
public static final String EXTRA_ROLE = "extra_role"; public static final String EXTRA_ROLE = "extra_role";
public static final String EXTRA_ACTION_MODE = "extra_action_mode"; private static final String PREFS_NAME = "pap_prefs";
public enum AccountAction { CRIAR, ENTRAR } private static final String KEY_HAS_CREATED_ACCOUNT = "has_created_account";
private FirebaseAuth mAuth; public enum AccountType {
private FirebaseFirestore db; CLIENTE, ESTABELECIMENTO
private boolean isLoginMode = true; }
private TextInputLayout layoutName; public enum AccountAction {
private TextInputEditText inputName, inputEmail, inputPassword; ENTRAR, CRIAR
private MaterialButton btnPrimaryAction; }
private ProgressBar progressBar;
private AccountType selectedAccountType = AccountType.CLIENTE;
private AccountAction selectedAccountAction = AccountAction.ENTRAR;
private Button btnEntrar;
private Button btnCriarConta;
private Button btnPrimaryAction;
private EditText inputName;
private EditText inputEmail;
private EditText inputPassword;
private EditText inputOwnerPhone;
private EditText inputEstablishmentName;
private EditText inputEstablishmentEmail;
private EditText inputEstablishmentPhone;
private android.widget.TextView txtForgotPassword;
private android.widget.ImageView iconPasswordVisibility;
private boolean isPasswordVisible = false;
private boolean hasCreatedAccount;
private FirebaseAuth firebaseAuth;
private DatabaseReference databaseReference;
private final ActivityResultLauncher<String[]> permissionRequest = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(), result -> {
Boolean fineLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false);
Boolean bluetoothScanGranted = true;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
bluetoothScanGranted = result.getOrDefault(Manifest.permission.BLUETOOTH_SCAN, false);
}
if (fineLocationGranted != null && fineLocationGranted) {
// Precise location access granted.
} else {
Toast.makeText(this, "A permissão de localização é necessária para o check-in.", Toast.LENGTH_LONG)
.show();
}
});
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
mAuth = FirebaseAuth.getInstance(); selectedAccountType = AccountType.CLIENTE;
db = FirebaseFirestore.getInstance();
progressBar = findViewById(R.id.progressBarLogin);
if (mAuth.getCurrentUser() != null) { FirebaseApp.initializeApp(this);
checkUserRoleAndRedirect(mAuth.getCurrentUser().getUid()); firebaseAuth = FirebaseAuth.getInstance();
databaseReference = FirebaseDatabase.getInstance().getReference();
bindViews();
setupActionToggle();
setupPrimaryAction();
checkPermissions();
migrateUsersNodeToSeparateCollections();
}
private void migrateUsersNodeToSeparateCollections() {
if (databaseReference == null) return;
databaseReference.child("users").get().addOnCompleteListener(task -> {
if (task.isSuccessful() && task.getResult() != null && task.getResult().exists()) {
for (DataSnapshot userSnapshot : task.getResult().getChildren()) {
String accountType = userSnapshot.child("accountType").getValue(String.class);
String role = userSnapshot.child("role").getValue(String.class);
String targetCollection = "Clientes";
if ("ESTABELECIMENTO".equalsIgnoreCase(accountType) || "ADMIN".equalsIgnoreCase(role)) {
targetCollection = "Restaurantes";
}
databaseReference.child(targetCollection).child(userSnapshot.getKey())
.setValue(userSnapshot.getValue());
}
}
});
}
private void checkPermissions() {
List<String> permissionsNeeded = new ArrayList<>();
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.BLUETOOTH_SCAN);
}
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.BLUETOOTH_CONNECT);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.READ_MEDIA_IMAGES);
}
} else { } else {
initViews(); if (ContextCompat.checkSelfPermission(this,
Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.POST_NOTIFICATIONS);
}
}
if (!permissionsNeeded.isEmpty()) {
new AlertDialog.Builder(this)
.setTitle("Permissões Necessárias")
.setMessage(
"Para o correto funcionamento do check-in, receber notificações de reservas e acesso a fotos, precisamos de algumas permissões.")
.setPositiveButton("Configurar", (dialog, which) -> {
permissionRequest.launch(permissionsNeeded.toArray(new String[0]));
})
.setNegativeButton("Agora Não", null)
.show();
} }
} }
private void initViews() { private void bindViews() {
layoutName = findViewById(R.id.layoutName); btnEntrar = findViewById(R.id.btnEntrar);
btnCriarConta = findViewById(R.id.btnCriarConta);
btnPrimaryAction = findViewById(R.id.btnFinalCriarConta);
inputName = findViewById(R.id.inputName); inputName = findViewById(R.id.inputName);
inputEmail = findViewById(R.id.inputEmail); inputEmail = findViewById(R.id.inputEmail);
inputPassword = findViewById(R.id.inputPassword); inputPassword = findViewById(R.id.inputPassword);
btnPrimaryAction = findViewById(R.id.btnPrimaryAction); inputOwnerPhone = findViewById(R.id.inputOwnerPhone);
MaterialButtonToggleGroup toggleGroup = findViewById(R.id.toggleGroup); inputEstablishmentName = findViewById(R.id.inputEstablishmentName);
inputEstablishmentEmail = findViewById(R.id.inputEstablishmentEmail);
inputEstablishmentPhone = findViewById(R.id.inputEstablishmentPhone);
txtForgotPassword = findViewById(R.id.txtForgotPassword);
iconPasswordVisibility = findViewById(R.id.iconPasswordVisibility);
toggleGroup.addOnButtonCheckedListener((group, checkedId, isChecked) -> { setupPasswordFeatures();
if (isChecked) {
isLoginMode = checkedId == R.id.btnEntrar;
layoutName.setVisibility(isLoginMode ? View.GONE : View.VISIBLE);
btnPrimaryAction.setText(isLoginMode ? "Entrar" : "Criar Conta");
}
});
btnPrimaryAction.setOnClickListener(v -> handleAuth());
} }
private void checkUserRoleAndRedirect(String uid) { private void setupPasswordFeatures() {
if (progressBar != null) progressBar.setVisibility(View.VISIBLE); iconPasswordVisibility.setOnClickListener(v -> {
isPasswordVisible = !isPasswordVisible;
// 1. Try Clientes if (isPasswordVisible) {
db.collection("Clientes").document(uid).get().addOnSuccessListener(docClient -> { inputPassword.setInputType(android.text.InputType.TYPE_CLASS_TEXT
if (docClient.exists()) { | android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
if (progressBar != null) progressBar.setVisibility(View.GONE); iconPasswordVisibility.setImageResource(R.drawable.ic_visibility);
startActivity(new Intent(this, ClientDashboardActivity.class));
finish();
} else { } else {
// 2. Try legacy users collection inputPassword.setInputType(
db.collection("users").document(uid).get().addOnSuccessListener(docLegacy -> { android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
if (docLegacy.exists()) { iconPasswordVisibility.setImageResource(R.drawable.ic_visibility_off);
// Migrate to Clientes }
User legacyUser = docLegacy.toObject(User.class); inputPassword.setSelection(inputPassword.getText().length());
if (legacyUser != null && "CLIENTE".equalsIgnoreCase(legacyUser.getRole())) { });
db.collection("Clientes").document(uid).set(legacyUser)
.addOnSuccessListener(aVoid -> { txtForgotPassword.setOnClickListener(v -> {
if (progressBar != null) progressBar.setVisibility(View.GONE); String email = inputEmail.getText().toString().trim();
startActivity(new Intent(this, ClientDashboardActivity.class)); if (TextUtils.isEmpty(email)) {
finish(); Toast.makeText(this, "Por favor, introduza o seu email primeiro.", Toast.LENGTH_SHORT).show();
return;
}
new AlertDialog.Builder(this)
.setTitle("Recuperar palavra-passe")
.setMessage("Deseja enviar um email de recuperação para " + email + "?")
.setPositiveButton("Sim", (dialog, which) -> {
if (firebaseAuth != null) {
firebaseAuth.sendPasswordResetEmail(email)
.addOnCompleteListener(task -> {
if (task.isSuccessful()) {
Toast.makeText(MainActivity.this, "Email de recuperação enviado!",
Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MainActivity.this,
"Falha ao enviar email. Verifique se o endereço está correto.",
Toast.LENGTH_LONG).show();
}
}); });
} else {
handleNoClientAccess(uid);
} }
} else { })
handleNoClientAccess(uid); .setNegativeButton("Não", null)
} .show();
});
}
}).addOnFailureListener(e -> {
if (progressBar != null) progressBar.setVisibility(View.GONE);
Toast.makeText(this, "Erro de rede ou permissões. Verifique a sua ligação.", Toast.LENGTH_LONG).show();
initViews();
});
initViews();
}
private void handleNoClientAccess(String uid) {
// Check if it's a Restaurant
db.collection("Restaurantes").document(uid).get().addOnSuccessListener(docRest -> {
if (progressBar != null) progressBar.setVisibility(View.GONE);
if (docRest.exists()) {
Toast.makeText(this, "Contas de Restaurante devem usar o Dashboard Web.", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "Utilizador não encontrado. Por favor, crie uma conta.", Toast.LENGTH_LONG).show();
}
mAuth.signOut();
initViews();
}); });
} }
private void handleAuth() {
private void setupActionToggle() {
btnEntrar.setOnClickListener(v -> {
selectedAccountAction = AccountAction.ENTRAR;
updateActionButtons();
});
btnCriarConta.setOnClickListener(v -> {
selectedAccountAction = AccountAction.CRIAR;
updateActionButtons();
});
updateActionButtons();
}
private void setupPrimaryAction() {
btnPrimaryAction.setOnClickListener(v -> handlePrimaryAction());
updatePrimaryActionState();
}
private void updateActionButtons() {
setSelectedState(btnEntrar, selectedAccountAction == AccountAction.ENTRAR);
setSelectedState(btnCriarConta, selectedAccountAction == AccountAction.CRIAR);
updatePrimaryActionState();
}
private void setSelectedState(Button button, boolean isSelected) {
int selectedTextColor = Color.WHITE;
int defaultTextColor = Color.parseColor("#231F1F");
button.setBackgroundResource(isSelected ? R.drawable.tab_selected : R.drawable.tab_unselected);
button.setTextColor(isSelected ? selectedTextColor : defaultTextColor);
}
private void updatePrimaryActionState() {
boolean creatingAccount = selectedAccountAction == AccountAction.CRIAR;
btnPrimaryAction.setText(creatingAccount ? "Criar Conta" : "Entrar");
updateInputVisibility();
}
private void updateInputVisibility() {
boolean creatingAccount = selectedAccountAction == AccountAction.CRIAR;
boolean isEstablishment = selectedAccountType == AccountType.ESTABELECIMENTO;
inputName.setHint(isEstablishment ? "Nome do proprietário" : "O seu nome");
inputEmail.setHint(isEstablishment ? "Email do proprietário" : "Email");
inputName.setVisibility(creatingAccount ? View.VISIBLE : View.GONE);
inputOwnerPhone.setVisibility(creatingAccount && isEstablishment ? View.VISIBLE : View.GONE);
inputEstablishmentName.setVisibility(creatingAccount && isEstablishment ? View.VISIBLE : View.GONE);
inputEstablishmentEmail.setVisibility(creatingAccount && isEstablishment ? View.VISIBLE : View.GONE);
inputEstablishmentPhone.setVisibility(creatingAccount && isEstablishment ? View.VISIBLE : View.GONE);
if (txtForgotPassword != null) {
txtForgotPassword.setVisibility(creatingAccount ? View.GONE : View.VISIBLE);
}
}
private void handlePrimaryAction() {
String email = inputEmail.getText().toString().trim(); String email = inputEmail.getText().toString().trim();
String password = inputPassword.getText().toString().trim(); String password = inputPassword.getText().toString().trim();
String name = inputName.getText().toString().trim(); String providedName = inputName.getText().toString().trim();
String ownerPhone = inputOwnerPhone.getText().toString().trim();
String establishmentName = inputEstablishmentName.getText().toString().trim();
String establishmentEmail = inputEstablishmentEmail.getText().toString().trim();
String establishmentPhone = inputEstablishmentPhone.getText().toString().trim();
if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password)) { if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password)) {
Toast.makeText(this, "Preencha todos os campos.", Toast.LENGTH_SHORT).show(); Toast.makeText(this, "Preencha email e palavra-passe.", Toast.LENGTH_SHORT).show();
return; return;
} }
if (progressBar != null) progressBar.setVisibility(View.VISIBLE); boolean creatingAccount = selectedAccountAction == AccountAction.CRIAR;
if (isLoginMode) { if (creatingAccount && !isValidPassword(password)) {
mAuth.signInWithEmailAndPassword(email, password) return;
.addOnSuccessListener(authResult -> checkUserRoleAndRedirect(authResult.getUser().getUid())) }
.addOnFailureListener(e -> {
if (progressBar != null) progressBar.setVisibility(View.GONE); if (creatingAccount) {
Toast.makeText(this, "Login falhou: " + e.getMessage(), Toast.LENGTH_LONG).show(); if (selectedAccountType == AccountType.CLIENTE && TextUtils.isEmpty(providedName)) {
}); Toast.makeText(this, "Indique o seu nome para criar conta.", Toast.LENGTH_SHORT).show();
} else {
if (TextUtils.isEmpty(name)) {
if (progressBar != null) progressBar.setVisibility(View.GONE);
Toast.makeText(this, "Insira o seu nome.", Toast.LENGTH_SHORT).show();
return; return;
} }
mAuth.createUserWithEmailAndPassword(email, password)
.addOnSuccessListener(authResult -> { if (selectedAccountType == AccountType.ESTABELECIMENTO) {
String uid = authResult.getUser().getUid(); boolean missingOwner = TextUtils.isEmpty(providedName) || TextUtils.isEmpty(ownerPhone);
User newUser = new User(uid, name, email, "CLIENTE"); boolean missingEstablishment = TextUtils.isEmpty(establishmentName)
db.collection("Clientes").document(uid).set(newUser) || TextUtils.isEmpty(establishmentEmail)
.addOnSuccessListener(aVoid -> { || TextUtils.isEmpty(establishmentPhone);
if (progressBar != null) progressBar.setVisibility(View.GONE);
startActivity(new Intent(this, ClientDashboardActivity.class)); if (missingOwner || missingEstablishment) {
finish(); Toast.makeText(this, "Preencha os dados do proprietário e do estabelecimento.", Toast.LENGTH_SHORT)
}); .show();
}) return;
.addOnFailureListener(e -> { }
if (progressBar != null) progressBar.setVisibility(View.GONE); }
Toast.makeText(this, "Registo falhou: " + e.getMessage(), Toast.LENGTH_LONG).show();
});
} }
String fallbackName = !TextUtils.isEmpty(providedName) ? providedName : deriveNameFromEmail(email);
String resolvedRole = getRoleForSelectedType();
if (creatingAccount) {
createAccountInFirebase(
email,
password,
providedName,
ownerPhone,
establishmentName,
establishmentEmail,
establishmentPhone,
fallbackName,
resolvedRole);
return;
}
signInExistingAccount(email, password, fallbackName, resolvedRole);
}
private String deriveNameFromEmail(String email) {
if (!email.contains("@")) {
return "Utilizador";
}
String candidate = email.substring(0, email.indexOf("@"));
if (candidate.isEmpty()) {
return "Utilizador";
}
String firstLetter = candidate.substring(0, 1).toUpperCase();
String rest = candidate.length() > 1 ? candidate.substring(1) : "";
return firstLetter + rest;
}
private void markAccountCreated() {
hasCreatedAccount = true;
SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE);
prefs.edit().putBoolean(KEY_HAS_CREATED_ACCOUNT, true).apply();
}
private String getRoleForSelectedType() {
return selectedAccountType == AccountType.ESTABELECIMENTO ? "ADMIN" : "CLIENTE";
}
private String buildDocumentId(String email) {
return email.replace(".", "_").replace("@", "_at_");
}
private boolean ensureFirebaseReady() {
boolean ready = firebaseAuth != null && databaseReference != null;
if (!ready) {
Toast.makeText(this, "Ligue-se ao Firebase para continuar.", Toast.LENGTH_SHORT).show();
}
return ready;
}
private void signInExistingAccount(String email, String password, String fallbackName, String resolvedRole) {
if (!ensureFirebaseReady()) {
return;
}
firebaseAuth.signInWithEmailAndPassword(email, password)
.addOnSuccessListener(authResult -> fetchAccountAndNavigate(email, fallbackName, resolvedRole))
.addOnFailureListener(e -> {
android.util.Log.e("LoginError", "SignIn failed", e);
Toast.makeText(this, "Não foi possível iniciar sessão: " + e.getMessage(), Toast.LENGTH_SHORT)
.show();
});
}
private void createAccountInFirebase(
String email,
String password,
String providedName,
String ownerPhone,
String establishmentName,
String establishmentEmail,
String establishmentPhone,
String fallbackName,
String resolvedRole) {
if (!ensureFirebaseReady()) {
return;
}
firebaseAuth.createUserWithEmailAndPassword(email, password)
.addOnSuccessListener(result -> {
if (!hasCreatedAccount) {
markAccountCreated();
}
String finalDisplayName = selectedAccountType == AccountType.ESTABELECIMENTO
&& !TextUtils.isEmpty(establishmentName)
? establishmentName
: fallbackName;
String uid = result.getUser() != null ? result.getUser().getUid() : null;
persistAccountInFirebase(
email,
providedName,
resolvedRole,
ownerPhone,
establishmentName,
establishmentEmail,
establishmentPhone,
uid,
() -> {
Toast.makeText(this, "Conta criada com sucesso! Carregue em Entrar.", Toast.LENGTH_LONG)
.show();
selectedAccountAction = AccountAction.ENTRAR;
updateActionButtons();
});
})
.addOnFailureListener(e -> {
android.util.Log.e("LoginError", "CreateUser failed", e);
Toast.makeText(this, "Falha ao criar conta: " + e.getMessage(), Toast.LENGTH_SHORT).show();
});
}
private void fetchAccountAndNavigate(String email, String fallbackName, String resolvedRole) {
if (databaseReference == null) {
Toast.makeText(this, "Firebase indisponível.", Toast.LENGTH_SHORT).show();
if (firebaseAuth != null) firebaseAuth.signOut();
return;
}
String documentId = buildDocumentId(email);
String collectionPath = selectedAccountType == AccountType.ESTABELECIMENTO ? "Restaurantes" : "Clientes";
databaseReference.child(collectionPath).child(documentId).get().addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
android.util.Log.e("LoginError", "Database check failed", task.getException());
Toast.makeText(this, "Falha ao validar perfil. Tente novamente.", Toast.LENGTH_SHORT)
.show();
if (firebaseAuth != null) firebaseAuth.signOut();
return;
}
DataSnapshot snapshot = task.getResult();
if (snapshot == null || !snapshot.exists()) {
String tipo = selectedAccountType == AccountType.ESTABELECIMENTO ? "Estabelecimento" : "Cliente";
Toast.makeText(this, "Conta não encontrada como " + tipo + ".\nPor favor, crie uma conta nova.", Toast.LENGTH_LONG).show();
if (firebaseAuth != null) firebaseAuth.signOut();
return;
}
String accountTypeInFirebase = snapshot.child("accountType").getValue(String.class);
if (accountTypeInFirebase != null
&& !accountTypeInFirebase.equalsIgnoreCase(selectedAccountType.name())) {
Toast.makeText(this, "Tipo de conta não corresponde ao registo.\nPor favor verifique se escolheu a opção correta.", Toast.LENGTH_LONG)
.show();
if (firebaseAuth != null) firebaseAuth.signOut();
return;
}
String displayNameFromDb = snapshot.child("displayName").getValue(String.class);
String establishmentName = snapshot.child("establishmentName").getValue(String.class);
String ownerName = snapshot.child("ownerName").getValue(String.class);
String roleFromDb = snapshot.child("role").getValue(String.class);
String finalDisplayName = !TextUtils.isEmpty(establishmentName)
? establishmentName
: !TextUtils.isEmpty(displayNameFromDb) ? displayNameFromDb
: !TextUtils.isEmpty(ownerName) ? ownerName
: fallbackName;
String finalRole = !TextUtils.isEmpty(roleFromDb) ? roleFromDb : resolvedRole;
navigateToDashboard(email, finalDisplayName, finalRole);
});
}
private void navigateToDashboard(String email, String displayName, String role) {
Intent nextScreen = selectedAccountType == AccountType.CLIENTE
? new Intent(this, ClientDashboardActivity.class)
: new Intent(this, EstablishmentDashboardActivity.class);
nextScreen.putExtra(EXTRA_ACTION_MODE, selectedAccountAction.name());
nextScreen.putExtra(EXTRA_DISPLAY_NAME, displayName);
nextScreen.putExtra(EXTRA_EMAIL, email);
nextScreen.putExtra(EXTRA_ACCOUNT_TYPE, selectedAccountType.name());
nextScreen.putExtra(EXTRA_ROLE, role);
startActivity(nextScreen);
}
private void persistAccountInFirebase(
String ownerEmail,
String ownerName,
String role,
String ownerPhone,
String establishmentName,
String establishmentEmail,
String establishmentPhone,
String uid,
Runnable onSuccess) {
if (databaseReference == null) {
Toast.makeText(this, "Firebase indisponível.", Toast.LENGTH_SHORT).show();
return;
}
String documentId = buildDocumentId(ownerEmail);
Map<String, Object> payload = new HashMap<>();
if (!TextUtils.isEmpty(uid)) {
payload.put("uid", uid);
}
payload.put("email", ownerEmail);
payload.put("displayName", ownerName);
payload.put("role", role);
payload.put("accountType", selectedAccountType.name());
payload.put("createdAt", System.currentTimeMillis());
if (selectedAccountType == AccountType.ESTABELECIMENTO) {
payload.put("ownerName", ownerName);
payload.put("ownerEmail", ownerEmail);
payload.put("ownerPhone", ownerPhone);
payload.put("establishmentName", establishmentName);
payload.put("establishmentEmail", establishmentEmail);
payload.put("establishmentPhone", establishmentPhone);
}
String collectionPath = selectedAccountType == AccountType.ESTABELECIMENTO ? "Restaurantes" : "Clientes";
databaseReference.child(collectionPath).child(documentId).updateChildren(payload)
.addOnSuccessListener(unused -> {
Toast.makeText(this, "Conta guardada na cloud.", Toast.LENGTH_SHORT).show();
if (onSuccess != null) {
onSuccess.run();
}
})
.addOnFailureListener(
e -> Toast.makeText(this, "Não foi possível guardar na cloud.", Toast.LENGTH_SHORT).show());
}
private boolean isValidPassword(String password) {
if (password.length() < 6) {
Toast.makeText(this, "A palavra-passe deve ter pelo menos 6 caracteres.", Toast.LENGTH_SHORT).show();
return false;
}
boolean hasLower = false;
boolean hasDigit = false;
boolean hasSpecial = false;
for (char c : password.toCharArray()) {
if (Character.isLowerCase(c))
hasLower = true;
else if (Character.isDigit(c))
hasDigit = true;
else if (!Character.isLetterOrDigit(c))
hasSpecial = true;
}
if (!hasLower || !hasDigit || !hasSpecial) {
Toast.makeText(this, "A palavra-passe deve conter minúsculas, números e símbolos.", Toast.LENGTH_LONG)
.show();
return false;
}
return true;
} }
} }

View File

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

View File

@@ -1,100 +0,0 @@
package com.example.pap_teste;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Reserva;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.Query;
import com.google.firebase.firestore.QueryDocumentSnapshot;
import java.util.ArrayList;
import java.util.List;
public class MyReservationsFragment extends Fragment {
private RecyclerView rvReservations;
private ReservaAdapter adapter;
private List<Reserva> reservationList = new ArrayList<>();
private ProgressBar progressBar;
private View emptyState;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_my_reservations, container, false);
rvReservations = view.findViewById(R.id.rvReservations);
progressBar = view.findViewById(R.id.progressBar);
emptyState = view.findViewById(R.id.emptyState);
setupAdapter();
loadReservationsFromFirestore();
return view;
}
private void setupAdapter() {
adapter = new ReservaAdapter(reservationList, new ReservaAdapter.OnReservaActionListener() {
@Override
public void onCheckIn(Reserva reserva) {
Toast.makeText(getContext(), "Funcionalidade de Check-in em breve", Toast.LENGTH_SHORT).show();
}
@Override
public void onCancel(Reserva reserva) {
cancelReservation(reserva);
}
});
rvReservations.setLayoutManager(new LinearLayoutManager(getContext()));
rvReservations.setAdapter(adapter);
}
private void cancelReservation(Reserva reserva) {
if (reserva.getId() == null) return;
FirestoreManager.getInstance().getReservationsCollection().document(reserva.getId())
.update("estado", "Cancelada")
.addOnSuccessListener(aVoid -> Toast.makeText(getContext(), "Reserva cancelada.", Toast.LENGTH_SHORT).show());
}
private void loadReservationsFromFirestore() {
String email = FirebaseAuth.getInstance().getCurrentUser() != null
? FirebaseAuth.getInstance().getCurrentUser().getEmail() : null;
if (email == null) {
progressBar.setVisibility(View.GONE);
emptyState.setVisibility(View.VISIBLE);
return;
}
progressBar.setVisibility(View.VISIBLE);
FirestoreManager.getInstance().getReservationsCollection()
.whereEqualTo("clienteEmail", email)
.orderBy("createdAt", Query.Direction.DESCENDING)
.addSnapshotListener((value, error) -> {
progressBar.setVisibility(View.GONE);
if (value == null) return;
reservationList.clear();
for (QueryDocumentSnapshot doc : value) {
Reserva r = doc.toObject(Reserva.class);
r.setId(doc.getId());
reservationList.add(r);
}
adapter.notifyDataSetChanged();
emptyState.setVisibility(reservationList.isEmpty() ? View.VISIBLE : View.GONE);
});
}
}

View File

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

View File

@@ -1,47 +0,0 @@
package com.example.pap_teste;
import android.content.Intent;
import android.os.Bundle;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.button.MaterialButton;
public class OnboardingActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_onboarding);
ViewCompat.setOnApplyWindowInsetsListener(findViewById(android.R.id.content), (v, insets) -> {
Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
return insets;
});
ImageView logo = findViewById(R.id.logo);
TextView tagline = findViewById(R.id.tagline);
MaterialButton btnGetStarted = findViewById(R.id.btnGetStarted);
// Simple animation
Animation fadeIn = new AlphaAnimation(0, 1);
fadeIn.setDuration(1500);
logo.startAnimation(fadeIn);
tagline.startAnimation(fadeIn);
btnGetStarted.setOnClickListener(v -> {
android.app.ActivityOptions options = android.app.ActivityOptions.makeCustomAnimation(this, android.R.anim.fade_in, android.R.anim.fade_out);
startActivity(new Intent(OnboardingActivity.this, MainActivity.class), options.toBundle());
finish();
});
}
}

View File

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

View File

@@ -1,51 +0,0 @@
package com.example.pap_teste;
import android.content.Intent;
import android.os.Bundle;
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.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.widget.Toast;
import com.bumptech.glide.Glide;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
public class ProfileFragment extends Fragment {
private ImageView imgProfile;
private TextView txtName, txtEmail;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_profile, container, false);
imgProfile = view.findViewById(R.id.imgProfile);
txtName = view.findViewById(R.id.txtProfileName);
txtEmail = view.findViewById(R.id.txtProfileEmail);
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
txtName.setText(user.getDisplayName() != null ? user.getDisplayName() : "Utilizador");
txtEmail.setText(user.getEmail());
if (user.getPhotoUrl() != null) {
Glide.with(this).load(user.getPhotoUrl()).circleCrop().into(imgProfile);
}
}
view.findViewById(R.id.btnLogout).setOnClickListener(v -> {
FirebaseAuth.getInstance().signOut();
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
});
return view;
}
}

View File

@@ -5,24 +5,24 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Reserva; import com.example.pap_teste.models.Reserva;
import com.google.android.material.chip.Chip;
import java.text.SimpleDateFormat;
import java.util.List; import java.util.List;
import java.util.Locale;
public class ReservaAdapter extends RecyclerView.Adapter<ReservaAdapter.ViewHolder> { public class ReservaAdapter extends RecyclerView.Adapter<ReservaAdapter.ViewHolder> {
public interface OnReservaActionListener { public interface OnReservaActionListener {
void onCheckIn(Reserva reserva); void onCheckIn(Reserva reserva);
void onCancel(Reserva reserva); void onCancel(Reserva reserva);
} }
private final List<Reserva> reservas; private final List<Reserva> reservas;
private final OnReservaActionListener listener; private final OnReservaActionListener listener;
private final SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy", Locale.getDefault());
public ReservaAdapter(List<Reserva> reservas, OnReservaActionListener listener) { public ReservaAdapter(List<Reserva> reservas, OnReservaActionListener listener) {
this.reservas = reservas; this.reservas = reservas;
@@ -39,40 +39,64 @@ public class ReservaAdapter extends RecyclerView.Adapter<ReservaAdapter.ViewHold
@Override @Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) { public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Reserva reserva = reservas.get(position); Reserva reserva = reservas.get(position);
holder.txtRestaurante.setText(reserva.getRestauranteName());
holder.txtDataHora.setText(reserva.getData() + " às " + reserva.getHora() + "" + reserva.getPessoas() + "p");
holder.txtRestaurante.setText(reserva.getRestaurantName()); if (holder.chipStatus != null) {
String state = reserva.getEstado() != null ? reserva.getEstado() : "Pendente";
String dateStr = reserva.getDate() != null ? dateFormat.format(reserva.getDate().toDate()) : "Data N/A"; holder.chipStatus.setText(state);
holder.txtDataHora.setText(String.format("%s às %s • %d pessoas", dateStr, reserva.getTimeSlot(), reserva.getGuests())); int colorBg, colorText;
android.content.Context ctx = holder.itemView.getContext();
String status = reserva.getStatus() != null ? reserva.getStatus() : "pending"; if ("Confirmada".equals(state)) {
holder.chipStatus.setText(status.toUpperCase()); colorBg = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipConfirmed);
colorText = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipConfirmedText);
// Status Colors } else if ("Cancelada".equals(state) || "Recusada".equals(state)) {
int colorBg, colorText; colorBg = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipCancelled);
android.content.Context ctx = holder.itemView.getContext(); colorText = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipCancelledText);
if ("confirmed".equals(status)) { } else {
colorBg = ctx.getColor(R.color.colorChipConfirmed); colorBg = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipPending);
colorText = ctx.getColor(R.color.colorChipConfirmedText); colorText = androidx.core.content.ContextCompat.getColor(ctx, R.color.colorChipPendingText);
holder.btnCheckIn.setVisibility(View.VISIBLE); }
} else if ("cancelled".equals(status) || "rejected".equals(status)) { holder.chipStatus.setChipBackgroundColor(android.content.res.ColorStateList.valueOf(colorBg));
colorBg = ctx.getColor(R.color.colorChipCancelled); holder.chipStatus.setTextColor(colorText);
colorText = ctx.getColor(R.color.colorChipCancelledText);
holder.btnCheckIn.setVisibility(View.GONE);
} else {
colorBg = ctx.getColor(R.color.colorChipPending);
colorText = ctx.getColor(R.color.colorChipPendingText);
holder.btnCheckIn.setVisibility(View.GONE);
} }
holder.chipStatus.setChipBackgroundColor(android.content.res.ColorStateList.valueOf(colorBg)); // Enable check-in only if confirmed
holder.chipStatus.setTextColor(colorText); boolean isConfirmed = "Confirmada".equals(reserva.getEstado());
boolean hasLocationPermission = androidx.core.app.ActivityCompat.checkSelfPermission(holder.itemView.getContext(), android.Manifest.permission.ACCESS_FINE_LOCATION) == android.content.pm.PackageManager.PERMISSION_GRANTED;
boolean canCancel = "pending".equals(status) || "confirmed".equals(status); if (isConfirmed) {
if (hasLocationPermission) {
holder.btnCheckIn.setEnabled(true);
if (holder.txtLocationWarning != null) {
holder.txtLocationWarning.setVisibility(View.GONE);
}
} else {
holder.btnCheckIn.setEnabled(false);
if (holder.txtLocationWarning != null) {
holder.txtLocationWarning.setVisibility(View.VISIBLE);
}
}
} else {
holder.btnCheckIn.setEnabled(false);
if (holder.txtLocationWarning != null) {
holder.txtLocationWarning.setVisibility(View.GONE);
}
}
// Show cancel only if not concluded or already cancelled/refused
boolean canCancel = "Pendente".equals(reserva.getEstado()) || "Confirmada".equals(reserva.getEstado());
holder.btnCancelar.setVisibility(canCancel ? View.VISIBLE : View.GONE); holder.btnCancelar.setVisibility(canCancel ? View.VISIBLE : View.GONE);
holder.btnCheckIn.setOnClickListener(v -> listener.onCheckIn(reserva)); holder.btnCheckIn.setOnClickListener(v -> {
holder.btnCancelar.setOnClickListener(v -> listener.onCancel(reserva)); if (listener != null)
listener.onCheckIn(reserva);
});
holder.btnCancelar.setOnClickListener(v -> {
if (listener != null)
listener.onCancel(reserva);
});
} }
@Override @Override
@@ -81,18 +105,19 @@ public class ReservaAdapter extends RecyclerView.Adapter<ReservaAdapter.ViewHold
} }
public static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
TextView txtRestaurante, txtDataHora; TextView txtRestaurante, txtDataHora, txtStatus, txtLocationWarning;
Button btnCheckIn, btnCancelar; Button btnCheckIn, btnCancelar;
Chip chipStatus; com.google.android.material.chip.Chip chipStatus;
public ViewHolder(@NonNull View itemView) { public ViewHolder(@NonNull View itemView) {
super(itemView); super(itemView);
txtRestaurante = itemView.findViewById(R.id.txtReservaRestaurante); txtRestaurante = itemView.findViewById(R.id.txtReservaRestaurante);
txtDataHora = itemView.findViewById(R.id.txtReservaDataHora); txtDataHora = itemView.findViewById(R.id.txtReservaDataHora);
txtStatus = itemView.findViewById(R.id.txtReservaStatus);
btnCheckIn = itemView.findViewById(R.id.btnCheckIn); btnCheckIn = itemView.findViewById(R.id.btnCheckIn);
btnCancelar = itemView.findViewById(R.id.btnCancelar); btnCancelar = itemView.findViewById(R.id.btnCancelar);
txtLocationWarning = itemView.findViewById(R.id.txtLocationWarning);
chipStatus = itemView.findViewById(R.id.chipReservaStatus); chipStatus = itemView.findViewById(R.id.chipReservaStatus);
} }
} }
} }

View File

@@ -1,135 +0,0 @@
package com.example.pap_teste;
import android.app.DatePickerDialog;
import android.os.Bundle;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Reserva;
import com.example.pap_teste.models.Restaurant;
import com.google.firebase.Timestamp;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
public class ReservationActivity extends AppCompatActivity {
private String restaurantId, restaurantName;
private int guestCount = 2;
private Calendar selectedDate = Calendar.getInstance();
private String selectedTimeSlot = null;
private TextView txtGuestCount, txtSelectedDate;
private RecyclerView rvTimeSlots;
private TimeSlotAdapter adapter;
private List<String> timeSlots = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_reservation);
restaurantId = getIntent().getStringExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_ID);
restaurantName = getIntent().getStringExtra(RestaurantDetailActivity.EXTRA_RESTAURANT_NAME);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("Reservar: " + restaurantName);
}
txtGuestCount = findViewById(R.id.txtGuestCount);
txtSelectedDate = findViewById(R.id.txtSelectedDate);
rvTimeSlots = findViewById(R.id.rvTimeSlots);
EditText etSpecialRequests = findViewById(R.id.etSpecialRequests);
findViewById(R.id.btnDecreaseGuests).setOnClickListener(v -> {
if (guestCount > 1) {
guestCount--;
txtGuestCount.setText(String.valueOf(guestCount));
}
});
findViewById(R.id.btnIncreaseGuests).setOnClickListener(v -> {
guestCount++;
txtGuestCount.setText(String.valueOf(guestCount));
});
findViewById(R.id.cardDatePicker).setOnClickListener(v -> showDatePicker());
setupTimeSlots();
loadRestaurantSlots();
findViewById(R.id.btnConfirmReservation).setOnClickListener(v -> {
if (selectedTimeSlot == null) {
Toast.makeText(this, "Por favor, selecione um horário.", Toast.LENGTH_SHORT).show();
return;
}
confirmReservation(etSpecialRequests.getText().toString());
});
toolbar.setNavigationOnClickListener(v -> onBackPressed());
}
private void showDatePicker() {
new DatePickerDialog(this, (view, year, month, dayOfMonth) -> {
selectedDate.set(year, month, dayOfMonth);
txtSelectedDate.setText(String.format("%02d/%02d/%d", dayOfMonth, month + 1, year));
}, selectedDate.get(Calendar.YEAR), selectedDate.get(Calendar.MONTH), selectedDate.get(Calendar.DAY_OF_MONTH)).show();
}
private void setupTimeSlots() {
adapter = new TimeSlotAdapter(timeSlots, slot -> selectedTimeSlot = slot);
rvTimeSlots.setAdapter(adapter);
rvTimeSlots.setLayoutManager(new GridLayoutManager(this, 3));
}
private void loadRestaurantSlots() {
// In a real app, we would fetch from Firestore based on the selected date
// For now, let's use the static slots from the restaurant model
FirebaseFirestore.getInstance().collection("restaurants").document(restaurantId).get()
.addOnSuccessListener(documentSnapshot -> {
Restaurant r = documentSnapshot.toObject(Restaurant.class);
if (r != null && r.getAvailableSlots() != null) {
timeSlots.clear();
timeSlots.addAll(r.getAvailableSlots());
adapter.notifyDataSetChanged();
}
});
}
private void confirmReservation(String specialRequests) {
String userId = FirebaseAuth.getInstance().getUid();
if (userId == null) {
Toast.makeText(this, "Erro de autenticação.", Toast.LENGTH_SHORT).show();
return;
}
Reserva reserva = new Reserva();
reserva.setUserId(userId);
reserva.setRestaurantId(restaurantId);
reserva.setRestaurantName(restaurantName);
reserva.setGuests(guestCount);
reserva.setDate(new Timestamp(selectedDate.getTime()));
reserva.setTimeSlot(selectedTimeSlot);
reserva.setSpecialRequests(specialRequests);
reserva.setStatus("confirmed");
reserva.setCreatedAt(new Timestamp(new Date()));
FirebaseFirestore.getInstance().collection("reservations").add(reserva)
.addOnSuccessListener(documentReference -> {
Toast.makeText(this, "Reserva confirmada com sucesso!", Toast.LENGTH_LONG).show();
finish();
})
.addOnFailureListener(e -> Toast.makeText(this, "Erro ao confirmar reserva.", Toast.LENGTH_SHORT).show());
}
}

View File

@@ -0,0 +1,146 @@
package com.example.pap_teste;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.IBinder;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import com.example.pap_teste.models.Reserva;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
public class ReservationNotificationService extends Service {
private static final String CHANNEL_ID = "ReservaNotifications";
private DatabaseReference reservasRef;
private ChildEventListener childEventListener;
private String currentUserEmail;
@Override
public void onCreate() {
super.onCreate();
createNotificationChannel();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null && user.getEmail() != null) {
currentUserEmail = user.getEmail();
startListeningForReservations();
} else {
stopSelf();
}
return START_STICKY;
}
private void startListeningForReservations() {
if (reservasRef != null && childEventListener != null) {
return; // Already listening
}
reservasRef = FirebaseDatabase.getInstance().getReference("reservas");
childEventListener = new ChildEventListener() {
@Override
public void onChildAdded(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {
// Not needed for new reservations created by the user
}
@Override
public void onChildChanged(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {
Reserva reserva = snapshot.getValue(Reserva.class);
if (reserva != null && currentUserEmail.equals(reserva.getClienteEmail())) {
String estado = reserva.getEstado();
if (estado != null && (estado.startsWith("Confirmada") || estado.equals("Recusada") || estado.equals("Concluída") || estado.equals("Cancelada"))) {
sendNotification(reserva);
}
}
}
@Override
public void onChildRemoved(@NonNull DataSnapshot snapshot) {}
@Override
public void onChildMoved(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {}
@Override
public void onCancelled(@NonNull DatabaseError error) {}
};
reservasRef.addChildEventListener(childEventListener);
}
private void sendNotification(Reserva reserva) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
return; // Permission not granted
}
Intent intent = new Intent(this, MainActivity.class); // Or deep link to reservations
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
String title = "Atualização de Reserva";
String message = "A sua reserva no " + reserva.getRestauranteName() + " foi " + reserva.getEstado().toLowerCase() + ".";
if (reserva.getMotivo() != null && !reserva.getMotivo().isEmpty()) {
message += " Motivo: " + reserva.getMotivo();
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.na_mesa) // Assuming na_mesa is a valid drawable
.setContentTitle(title)
.setContentText(message)
.setStyle(new NotificationCompat.BigTextStyle().bigText(message))
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
if (notificationManager != null) {
notificationManager.notify(reserva.getId().hashCode(), builder.build());
}
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Notificações de Reservas";
String description = "Notificações sobre o estado das suas reservas";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance);
channel.setDescription(description);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
if (notificationManager != null) {
notificationManager.createNotificationChannel(channel);
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (reservasRef != null && childEventListener != null) {
reservasRef.removeEventListener(childEventListener);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

View File

@@ -3,54 +3,132 @@ package com.example.pap_teste;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.Button;
import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.example.pap_teste.models.Restaurant; import com.example.pap_teste.models.Restaurant;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
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.List; import java.util.List;
public class RestaurantAdapter extends RecyclerView.Adapter<RestaurantAdapter.ViewHolder> { public class RestaurantAdapter extends RecyclerView.Adapter<RestaurantAdapter.ViewHolder> {
private List<Restaurant> restaurants;
private final List<Restaurant> restaurants; private OnRestaurantClickListener listener;
private final boolean isFeatured;
private final OnRestaurantClickListener listener;
public interface OnRestaurantClickListener { public interface OnRestaurantClickListener {
void onRestaurantClick(Restaurant restaurant); void onRestaurantClick(Restaurant restaurant);
} }
public RestaurantAdapter(List<Restaurant> restaurants, OnRestaurantClickListener listener) { public RestaurantAdapter(List<Restaurant> restaurants, OnRestaurantClickListener listener) {
this(restaurants, false, listener);
}
public RestaurantAdapter(List<Restaurant> restaurants, boolean isFeatured, OnRestaurantClickListener listener) {
this.restaurants = restaurants; this.restaurants = restaurants;
this.isFeatured = isFeatured;
this.listener = listener; this.listener = listener;
} }
@NonNull @NonNull
@Override @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
int layoutId = isFeatured ? R.layout.item_restaurant_featured : R.layout.item_restaurant_card; View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_restaurant, parent, false);
View view = LayoutInflater.from(parent.getContext()).inflate(layoutId, parent, false);
return new ViewHolder(view); return new ViewHolder(view);
} }
@Override @Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) { public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Restaurant restaurant = restaurants.get(position); Restaurant restaurant = restaurants.get(position);
holder.name.setText(restaurant.getName()); holder.text1.setText(restaurant.getName());
holder.cuisine.setText(String.format("%s • %.1f ★", restaurant.getCuisine(), restaurant.getRating())); holder.text2
.setText(restaurant.getCategory() + (restaurant.isAvailable() ? " - Disponível" : " - Indisponível"));
Glide.with(holder.itemView.getContext()) if (holder.txtRating != null) {
.load(restaurant.getImageURL()) if (restaurant.getRatingAvg() != null && restaurant.getRatingAvg() > 0) {
.placeholder(R.drawable.na_mesa) holder.txtRating
.into(holder.image); .setText(String.format(java.util.Locale.getDefault(), "%.1f", restaurant.getRatingAvg()));
} else {
holder.txtRating.setText("Novo");
}
}
holder.itemView.setOnClickListener(v -> listener.onRestaurantClick(restaurant)); if (restaurant.getLogoUrl() != null && !restaurant.getLogoUrl().isEmpty()) {
com.bumptech.glide.Glide.with(holder.itemView.getContext())
.load(restaurant.getLogoUrl())
.circleCrop()
.into(holder.imgThumb);
} else {
holder.imgThumb.setImageResource(R.mipmap.ic_launcher);
}
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onRestaurantClick(restaurant);
}
});
if (holder.btnReservar != null) {
holder.btnReservar.setOnClickListener(v -> {
if (listener != null) {
listener.onRestaurantClick(restaurant);
}
});
}
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null && user.getEmail() != null && restaurant.getEmail() != null) {
String encodedUserEmail = user.getEmail().replace(".", "_").replace("@", "_at_");
String encodedRestEmail = restaurant.getEmail().replace(".", "_").replace("@", "_at_");
DatabaseReference favRef = FirebaseDatabase.getInstance().getReference("Clientes")
.child(encodedUserEmail).child("favorites").child(encodedRestEmail);
// Read initial state
favRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
holder.btnFavorite.setTag(snapshot.exists());
if (snapshot.exists()) {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on);
holder.btnFavorite.setColorFilter(androidx.core.content.ContextCompat
.getColor(holder.itemView.getContext(), R.color.colorPrimary));
} else {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off);
holder.btnFavorite.clearColorFilter();
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
holder.btnFavorite.setOnClickListener(v -> {
// Optimistic UI update
boolean isFav = holder.btnFavorite.getTag() != null && (Boolean) holder.btnFavorite.getTag();
boolean newFavState = !isFav;
holder.btnFavorite.setTag(newFavState);
if (newFavState) {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on);
holder.btnFavorite.setColorFilter(androidx.core.content.ContextCompat
.getColor(holder.itemView.getContext(), R.color.colorPrimary));
favRef.setValue(restaurant);
com.google.android.material.snackbar.Snackbar.make(v, "Adicionado aos Favoritos!",
com.google.android.material.snackbar.Snackbar.LENGTH_SHORT).show();
} else {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off);
holder.btnFavorite.clearColorFilter();
favRef.removeValue();
com.google.android.material.snackbar.Snackbar.make(v, "Removido dos Favoritos.",
com.google.android.material.snackbar.Snackbar.LENGTH_SHORT).show();
}
});
}
} }
@Override @Override
@@ -58,16 +136,20 @@ public class RestaurantAdapter extends RecyclerView.Adapter<RestaurantAdapter.Vi
return restaurants.size(); return restaurants.size();
} }
static class ViewHolder extends RecyclerView.ViewHolder { public static class ViewHolder extends RecyclerView.ViewHolder {
ImageView image; TextView text1, text2, txtRating;
TextView name; ImageButton btnFavorite;
TextView cuisine; android.widget.ImageView imgThumb;
Button btnReservar;
ViewHolder(View itemView) { public ViewHolder(@NonNull View itemView) {
super(itemView); super(itemView);
image = itemView.findViewById(R.id.restaurantImage); text1 = itemView.findViewById(R.id.txtRestaurantName);
name = itemView.findViewById(R.id.restaurantName); text2 = itemView.findViewById(R.id.txtRestaurantCategory);
cuisine = itemView.findViewById(R.id.restaurantCuisine); txtRating = itemView.findViewById(R.id.txtRestaurantRating);
btnFavorite = itemView.findViewById(R.id.btnFavorite);
imgThumb = itemView.findViewById(R.id.imgRestaurantThumb);
btnReservar = itemView.findViewById(R.id.btnReservarCard);
} }
} }
} }

View File

@@ -1,67 +0,0 @@
package com.example.pap_teste;
import android.content.Intent;
import android.os.Bundle;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.bumptech.glide.Glide;
import com.google.android.material.button.MaterialButton;
public class RestaurantDetailActivity extends AppCompatActivity {
public static final String EXTRA_RESTAURANT_ID = "extra_restaurant_id";
public static final String EXTRA_RESTAURANT_NAME = "extra_restaurant_name";
public static final String EXTRA_RESTAURANT_IMAGE = "extra_restaurant_image";
public static final String EXTRA_RESTAURANT_CUISINE = "extra_restaurant_cuisine";
public static final String EXTRA_RESTAURANT_RATING = "extra_restaurant_rating";
public static final String EXTRA_RESTAURANT_DESC = "extra_restaurant_desc";
public static final String EXTRA_RESTAURANT_ADDRESS = "extra_restaurant_address";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_restaurant_detail);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle("");
}
ImageView coverImage = findViewById(R.id.restaurantCover);
TextView nameText = findViewById(R.id.detailName);
TextView cuisineText = findViewById(R.id.detailCuisine);
TextView ratingText = findViewById(R.id.detailRating);
TextView descText = findViewById(R.id.detailDescription);
TextView addressText = findViewById(R.id.detailAddress);
MaterialButton btnReserve = findViewById(R.id.btnReserveNow);
// Get data from intent
String name = getIntent().getStringExtra(EXTRA_RESTAURANT_NAME);
String image = getIntent().getStringExtra(EXTRA_RESTAURANT_IMAGE);
String cuisine = getIntent().getStringExtra(EXTRA_RESTAURANT_CUISINE);
double rating = getIntent().getDoubleExtra(EXTRA_RESTAURANT_RATING, 0.0);
String desc = getIntent().getStringExtra(EXTRA_RESTAURANT_DESC);
String address = getIntent().getStringExtra(EXTRA_RESTAURANT_ADDRESS);
nameText.setText(name);
cuisineText.setText(cuisine);
ratingText.setText(String.format("%.1f ★", rating));
descText.setText(desc);
addressText.setText(address);
Glide.with(this).load(image).into(coverImage);
btnReserve.setOnClickListener(v -> {
Intent intent = new Intent(this, ReservationActivity.class);
intent.putExtra(EXTRA_RESTAURANT_ID, getIntent().getStringExtra(EXTRA_RESTAURANT_ID));
intent.putExtra(EXTRA_RESTAURANT_NAME, getIntent().getStringExtra(EXTRA_RESTAURANT_NAME));
startActivity(intent);
});
toolbar.setNavigationOnClickListener(v -> onBackPressed());
}
}

View File

@@ -1,36 +0,0 @@
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

@@ -1,87 +0,0 @@
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

@@ -1,76 +0,0 @@
package com.example.pap_teste;
import android.graphics.Color;
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.google.android.material.card.MaterialCardView;
import java.util.List;
public class TimeSlotAdapter extends RecyclerView.Adapter<TimeSlotAdapter.ViewHolder> {
private final List<String> timeSlots;
private final OnTimeSlotClickListener listener;
private int selectedPosition = -1;
public interface OnTimeSlotClickListener {
void onTimeSlotClick(String slot);
}
public TimeSlotAdapter(List<String> timeSlots, OnTimeSlotClickListener listener) {
this.timeSlots = timeSlots;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_time_slot, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
String slot = timeSlots.get(position);
holder.txtTime.setText(slot);
boolean isSelected = selectedPosition == position;
holder.card.setStrokeColor(isSelected ? holder.itemView.getContext().getColor(R.color.colorPrimary) : holder.itemView.getContext().getColor(R.color.colorBorder));
holder.card.setStrokeWidth(isSelected ? 4 : 2);
holder.card.setCardBackgroundColor(isSelected ? holder.itemView.getContext().getColor(R.color.colorChip) : Color.WHITE);
holder.txtTime.setTextColor(isSelected ? holder.itemView.getContext().getColor(R.color.colorPrimary) : holder.itemView.getContext().getColor(R.color.colorTextPrimary));
holder.itemView.setOnClickListener(v -> {
int previousSelected = selectedPosition;
int currentPos = holder.getAdapterPosition();
if (currentPos != RecyclerView.NO_POSITION) {
selectedPosition = currentPos;
if (previousSelected != -1) {
notifyItemChanged(previousSelected);
}
notifyItemChanged(selectedPosition);
listener.onTimeSlotClick(slot);
}
});
}
@Override
public int getItemCount() {
return timeSlots.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
MaterialCardView card;
TextView txtTime;
ViewHolder(View itemView) {
super(itemView);
card = itemView.findViewById(R.id.cardTimeSlot);
txtTime = itemView.findViewById(R.id.txtTimeSlot);
}
}
}

View File

@@ -51,9 +51,6 @@ public class InteractiveRatingBar extends LinearLayout {
int sizePx = (int) (starSizeDp * context.getResources().getDisplayMetrics().density); int sizePx = (int) (starSizeDp * context.getResources().getDisplayMetrics().density);
int paddingPx = (int) (starPaddingDp * context.getResources().getDisplayMetrics().density); int paddingPx = (int) (starPaddingDp * context.getResources().getDisplayMetrics().density);
stars.clear();
removeAllViews();
for (int i = 0; i < numStars; i++) { for (int i = 0; i < numStars; i++) {
ImageView star = new ImageView(context); ImageView star = new ImageView(context);
LayoutParams params = new LayoutParams(sizePx, sizePx); LayoutParams params = new LayoutParams(sizePx, sizePx);
@@ -94,12 +91,11 @@ public class InteractiveRatingBar extends LinearLayout {
} }
private void calculateRatingFromTouch(float x) { private void calculateRatingFromTouch(float x) {
int count = stars.size(); if (stars.isEmpty()) return;
if (count == 0) return;
double newRating = 0; double newRating = 0;
for (int i = 0; i < count; i++) { for (int i = 0; i < numStars; i++) {
ImageView star = stars.get(i); ImageView star = stars.get(i);
float starLeft = star.getLeft(); float starLeft = star.getLeft();
float starRight = star.getRight(); float starRight = star.getRight();
@@ -120,7 +116,7 @@ public class InteractiveRatingBar extends LinearLayout {
} }
// Clamp values // Clamp values
newRating = Math.max(0.5, Math.min(newRating, (double) count)); newRating = Math.max(0.5, Math.min(newRating, numStars));
if (newRating != currentRating) { if (newRating != currentRating) {
currentRating = newRating; currentRating = newRating;
@@ -132,8 +128,7 @@ public class InteractiveRatingBar extends LinearLayout {
} }
private void updateStars(double rating, boolean animate) { private void updateStars(double rating, boolean animate) {
int count = stars.size(); for (int i = 0; i < numStars; i++) {
for (int i = 0; i < count; i++) {
ImageView star = stars.get(i); ImageView star = stars.get(i);
int previousResId = (Integer) (star.getTag() != null ? star.getTag() : 0); int previousResId = (Integer) (star.getTag() != null ? star.getTag() : 0);
int newResId; int newResId;

View File

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

View File

@@ -1,8 +1,5 @@
package com.example.pap_teste.models; package com.example.pap_teste.models;
import com.google.firebase.Timestamp;
import java.util.Date;
public class Reserva { public class Reserva {
private String id; private String id;
private String clienteEmail; private String clienteEmail;
@@ -10,18 +7,16 @@ public class Reserva {
private String restauranteEmail; private String restauranteEmail;
private String data; private String data;
private String hora; private String hora;
private com.google.firebase.Timestamp date;
private int pessoas; private int pessoas;
private int tableNumber; private String estado; // Pendente, Confirmada, Concluída, Cancelada, Recusada
private String estado; // "Pendente", "Confirmada", "Cancelada" private String motivo;
private String specialRequests;
private Timestamp createdAt;
public Reserva() { public Reserva() {
// Required for Firebase
} }
public Reserva(String id, String clienteEmail, String restauranteName, String restauranteEmail, public Reserva(String id, String clienteEmail, String restauranteName, String restauranteEmail, String data,
String data, String hora, int pessoas, String estado) { String hora, int pessoas, String estado) {
this.id = id; this.id = id;
this.clienteEmail = clienteEmail; this.clienteEmail = clienteEmail;
this.restauranteName = restauranteName; this.restauranteName = restauranteName;
@@ -30,56 +25,77 @@ public class Reserva {
this.hora = hora; this.hora = hora;
this.pessoas = pessoas; this.pessoas = pessoas;
this.estado = estado; this.estado = estado;
this.createdAt = new Timestamp(new Date());
} }
public String getId() { return id; } public String getId() {
public void setId(String id) { this.id = id; } return id;
}
public String getClienteEmail() { return clienteEmail; } public void setId(String id) {
public void setClienteEmail(String clienteEmail) { this.clienteEmail = clienteEmail; } this.id = id;
}
public String getRestauranteName() { return restauranteName; } public String getClienteEmail() {
public void setRestauranteName(String restauranteName) { this.restauranteName = restauranteName; } return clienteEmail;
}
public String getRestauranteEmail() { return restauranteEmail; } public void setClienteEmail(String clienteEmail) {
public void setRestauranteEmail(String restauranteEmail) { this.restauranteEmail = restauranteEmail; } this.clienteEmail = clienteEmail;
}
public String getData() { return data; } public String getRestauranteName() {
public void setData(String data) { this.data = data; } return restauranteName;
}
public String getHora() { return hora; } public void setRestauranteName(String restauranteName) {
public void setHora(String hora) { this.hora = hora; } this.restauranteName = restauranteName;
}
public com.google.firebase.Timestamp getDate() { return date; } public String getRestauranteEmail() {
public void setDate(com.google.firebase.Timestamp date) { this.date = date; } return restauranteEmail;
}
public int getPessoas() { return pessoas; } public void setRestauranteEmail(String restauranteEmail) {
public void setPessoas(int pessoas) { this.pessoas = pessoas; } this.restauranteEmail = restauranteEmail;
}
public int getTableNumber() { return tableNumber; } public String getData() {
public void setTableNumber(int tableNumber) { this.tableNumber = tableNumber; } return data;
}
public String getEstado() { return estado; } public void setData(String data) {
public void setEstado(String estado) { this.estado = estado; } this.data = data;
}
public String getSpecialRequests() { return specialRequests; } public String getHora() {
public void setSpecialRequests(String specialRequests) { this.specialRequests = specialRequests; } return hora;
}
public Timestamp getCreatedAt() { return createdAt; } public void setHora(String hora) {
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; } this.hora = hora;
}
// Compatibility mappings for ReservationActivity public int getPessoas() {
public String getUserId() { return clienteEmail; } return pessoas;
public void setUserId(String userId) { this.clienteEmail = userId; } }
public String getRestaurantId() { return restauranteEmail; }
public void setRestaurantId(String restaurantId) { this.restauranteEmail = restaurantId; } public void setPessoas(int pessoas) {
public String getRestaurantName() { return restauranteName; } this.pessoas = pessoas;
public void setRestaurantName(String restaurantName) { this.restauranteName = restaurantName; } }
public int getGuests() { return pessoas; }
public void setGuests(int guests) { this.pessoas = guests; } public String getEstado() {
public String getStatus() { return estado; } return estado;
public void setStatus(String status) { this.estado = status; } }
public String getTimeSlot() { return hora; }
public void setTimeSlot(String timeSlot) { this.hora = timeSlot; } public void setEstado(String estado) {
this.estado = estado;
}
public String getMotivo() {
return motivo;
}
public void setMotivo(String motivo) {
this.motivo = motivo;
}
} }

View File

@@ -1,75 +1,49 @@
package com.example.pap_teste.models; package com.example.pap_teste.models;
import java.util.List; import java.io.Serializable;
import java.util.Map;
public class Restaurant { public class Restaurant implements Serializable {
private String id;
private String name; private String name;
private String category; private String category;
private String email; private String email;
private String description; private boolean available;
private String imageURL; private String logoUrl;
private double rating;
private Double ratingAvg; private Double ratingAvg;
private Integer ratingCount; private Integer ratingCount;
private String address;
private Map<String, String> openingHours;
private List<String> availableSlots;
private boolean favorite;
public Restaurant() {} // No-argument constructor required for Firebase
public Restaurant() {
}
public Restaurant(String name, String category, String email, boolean favorite, String imageURL) { public Restaurant(String name, String category, String email, boolean available) {
this.name = name; this.name = name;
this.category = category; this.category = category;
this.email = email; this.email = email;
this.favorite = favorite; this.available = available;
this.imageURL = imageURL;
} }
public String getId() { return id; } public Restaurant(String name, String category, String email, boolean available, String logoUrl) {
public void setId(String id) { this.id = id; } this.name = name;
this.category = category;
this.email = email;
this.available = available;
this.logoUrl = logoUrl;
}
public String getName() { return name; } public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCategory() { return category; } public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public String getEmail() { return email; } public String getEmail() { return email; }
public boolean isAvailable() { return available; }
public String getLogoUrl() { return logoUrl; }
public void setName(String name) { this.name = name; }
public void setCategory(String category) { this.category = category; }
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 setLogoUrl(String logoUrl) { this.logoUrl = logoUrl; }
public String getDescription() { return description; } public Double getRatingAvg() { return ratingAvg; }
public void setDescription(String description) { this.description = description; }
public String getImageURL() { return imageURL; }
public void setImageURL(String imageURL) { this.imageURL = imageURL; }
public double getRating() { return rating; }
public void setRating(double rating) { this.rating = rating; }
public Double getRatingAvg() { return ratingAvg != null ? ratingAvg : rating; }
public void setRatingAvg(Double ratingAvg) { this.ratingAvg = ratingAvg; } public void setRatingAvg(Double ratingAvg) { this.ratingAvg = ratingAvg; }
public Integer getRatingCount() { return ratingCount; }
public Integer getRatingCount() { return ratingCount != null ? ratingCount : 0; }
public void setRatingCount(Integer ratingCount) { this.ratingCount = ratingCount; } public void setRatingCount(Integer ratingCount) { this.ratingCount = ratingCount; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
public Map<String, String> getOpeningHours() { return openingHours; }
public void setOpeningHours(Map<String, String> openingHours) { this.openingHours = openingHours; }
public List<String> getAvailableSlots() { return availableSlots; }
public void setAvailableSlots(List<String> availableSlots) { this.availableSlots = availableSlots; }
public boolean isFavorite() { return favorite; }
public void setFavorite(boolean favorite) { this.favorite = favorite; }
// Compatibility mappings
public String getCuisine() { return category; }
public void setCuisine(String cuisine) { this.category = cuisine; }
public String getLogoUrl() { return imageURL; }
public void setLogoUrl(String logoUrl) { this.imageURL = logoUrl; }
} }

View File

@@ -1,36 +0,0 @@
package com.example.pap_teste.models;
import com.google.firebase.Timestamp;
import java.util.Date;
public class User {
private String id;
private String displayName;
private String email;
private String photoURL;
private String role; // "CLIENTE" or "RESTAURANTE"
private Timestamp createdAt;
public User() {}
public User(String id, String displayName, String email, String role) {
this.id = id;
this.displayName = displayName;
this.email = email;
this.role = role;
this.createdAt = new Timestamp(new Date());
}
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getDisplayName() { return displayName; }
public void setDisplayName(String displayName) { this.displayName = displayName; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getPhotoURL() { return photoURL; }
public void setPhotoURL(String photoURL) { this.photoURL = photoURL; }
public String getRole() { return role; }
public void setRole(String role) { this.role = role; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:angle="270"
android:endColor="#00000000"
android:startColor="#CC000000" />
</shape>

View File

@@ -0,0 +1,164 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
tools:context=".AddStaffActivity">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltarAddStaff"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="← Voltar"
android:textAllCaps="false"
android:textColor="@color/colorError"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtAdicionarTitulo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Novo Funcionário"
android:textColor="@color/colorTextPrimary"
android:textSize="28sp"
android:fontFamily="sans-serif-medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnVoltarAddStaff" />
<TextView
android:id="@+id/txtAdicionarDesc"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="8dp"
android:text="Introduza os dados e atribuições deste membro:"
android:textColor="@color/colorTextSecondary"
android:textAlignment="center"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtAdicionarTitulo" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/colorSurface"
app:layout_constraintTop_toBottomOf="@id/txtAdicionarDesc">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome do Funcionário"
android:textColor="@color/colorTextPrimary"
android:textSize="15sp"
android:fontFamily="sans-serif-medium" />
<EditText
android:id="@+id/nammeEditText"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="10dp"
android:background="@drawable/input_bg"
android:hint="Ex: João Silva"
android:inputType="textPersonName"
android:paddingHorizontal="16dp"
android:textColor="@color/colorTextPrimary" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Zona de Trabalho"
android:textColor="@color/colorTextPrimary"
android:textSize="15sp"
android:fontFamily="sans-serif-medium" />
<LinearLayout
android:id="@+id/zoneLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<Spinner
android:id="@+id/zonaSpinner"
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_weight="1"
android:background="@drawable/input_bg" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAddZone"
android:layout_width="54dp"
android:layout_height="54dp"
android:layout_marginStart="12dp"
app:cornerRadius="12dp"
app:backgroundTint="@color/colorPrimary"
android:insetTop="0dp"
android:insetBottom="0dp"
android:paddingTop="0dp"
android:paddingBottom="0dp"
android:text="+"
android:textColor="@color/white"
android:textSize="24sp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Mesa a Atribuir"
android:textColor="@color/colorTextPrimary"
android:textSize="15sp"
android:fontFamily="sans-serif-medium" />
<LinearLayout
android:id="@+id/mesaLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<Spinner
android:id="@+id/mesaSpinner"
android:layout_width="match_parent"
android:layout_height="54dp"
android:background="@drawable/input_bg" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/addButton"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="36dp"
app:cornerRadius="14dp"
app:backgroundTint="@color/colorPrimary"
android:text="Adicionar Membro"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -8,7 +8,7 @@
android:background="@color/colorBackground" android:background="@color/colorBackground"
tools:context=".ClientDashboardActivity"> tools:context=".ClientDashboardActivity">
<androidx.core.widget.NestedScrollView <ScrollView
android:id="@+id/clientScroll" android:id="@+id/clientScroll"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
@@ -36,15 +36,13 @@
android:id="@+id/txtClientGreeting" android:id="@+id/txtClientGreeting"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:text="Olá, convidado!" android:text="Olá, convidado!"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextPrimary"
android:textSize="28sp" android:textSize="28sp"
android:textStyle="bold" android:textStyle="bold"
android:fontFamily="sans-serif"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/imgNotification" app:layout_constraintTop_toTopOf="parent" />
android:layout_marginEnd="16dp" />
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
@@ -52,21 +50,8 @@
android:text="Encontre a sua próxima mesa." android:text="Encontre a sua próxima mesa."
android:textColor="@color/colorTextSecondary" android:textColor="@color/colorTextSecondary"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintTop_toBottomOf="@id/txtClientGreeting"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/imgNotification" /> app:layout_constraintTop_toBottomOf="@id/txtClientGreeting" />
<ImageButton
android:id="@+id/imgNotification"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/bg_circle_white"
android:src="@android:drawable/ic_popup_reminder"
app:tint="@color/colorTextPrimary"
android:layout_marginEnd="12dp"
app:layout_constraintTop_toTopOf="@id/cardProfile"
app:layout_constraintBottom_toBottomOf="@id/cardProfile"
app:layout_constraintEnd_toStartOf="@id/cardProfile" />
<com.google.android.material.card.MaterialCardView <com.google.android.material.card.MaterialCardView
android:id="@+id/cardProfile" android:id="@+id/cardProfile"
@@ -127,24 +112,6 @@
</LinearLayout> </LinearLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<!-- Category Pills -->
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:scrollbars="none"
android:clipToPadding="false"
android:paddingHorizontal="20dp">
<com.google.android.material.chip.ChipGroup
android:id="@+id/chipGroupCategories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true"
app:singleSelection="true">
<!-- Chips will be added programmatically -->
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<!-- Loading Spinner --> <!-- Loading Spinner -->
<ProgressBar <ProgressBar
@@ -156,7 +123,7 @@
android:visibility="visible" android:visibility="visible"
android:indeterminateTint="@color/colorPrimary" /> android:indeterminateTint="@color/colorPrimary" />
<!-- Featured / Destaques --> <!-- Featured Carousel -->
<LinearLayout <LinearLayout
android:id="@+id/layoutFeatured" android:id="@+id/layoutFeatured"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -169,7 +136,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:text="Destaques" android:text="Restaurantes Populares"
android:textColor="@color/colorTextPrimary" android:textColor="@color/colorTextPrimary"
android:textSize="20sp" android:textSize="20sp"
android:textStyle="bold" android:textStyle="bold"
@@ -188,201 +155,17 @@
tools:listitem="@layout/item_restaurant_featured" /> tools:listitem="@layout/item_restaurant_featured" />
</LinearLayout> </LinearLayout>
<!-- Melhor Avaliados --> <!-- Dynamic Categories Container -->
<LinearLayout <LinearLayout
android:id="@+id/layoutTopRated" android:id="@+id/categoriesContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:visibility="gone" android:layout_marginTop="8dp"
android:layout_marginTop="32dp"> android:paddingBottom="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="Melhor Avaliados"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTopRated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_featured" />
</LinearLayout>
<!-- Pizzas -->
<LinearLayout
android:id="@+id/layoutPizzas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="32dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="Pizzas"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvPizzas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_featured" />
</LinearLayout>
<!-- Sushi -->
<LinearLayout
android:id="@+id/layoutSushi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="32dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="Sushi"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSushi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_featured" />
</LinearLayout>
<!-- Carnes -->
<LinearLayout
android:id="@+id/layoutCarnes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="32dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="Carnes"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCarnes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_featured" />
</LinearLayout>
<!-- Massas -->
<LinearLayout
android:id="@+id/layoutMassas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="32dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="Massas"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMassas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_featured" />
</LinearLayout>
<!-- Sobremesas -->
<LinearLayout
android:id="@+id/layoutSobremesas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
android:layout_marginTop="32dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:text="Sobremesas"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSobremesas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:paddingBottom="16dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_featured" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </ScrollView>
<!-- Bottom Navigation Bar --> <!-- Bottom Navigation Bar -->
<com.google.android.material.bottomnavigation.BottomNavigationView <com.google.android.material.bottomnavigation.BottomNavigationView

View File

@@ -1,27 +0,0 @@
<?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/white">
<FrameLayout
android:id="@+id/fragmentContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/bottomNavigation"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
app:elevation="8dp"
app:itemIconTint="@color/colorPrimary"
app:itemTextColor="@color/colorPrimary"
app:labelVisibilityMode="labeled"
app:layout_constraintBottom_toBottomOf="parent"
app:menu="@menu/bottom_nav_menu" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,176 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/definicoesRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
tools:context=".DefinicoesAdminActivity">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="← Voltar"
android:textAllCaps="false"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
android:fillViewport="true"
android:scrollbars="none"
app:layout_constraintTop_toBottomOf="@id/btnVoltar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingTop="8dp"
android:paddingBottom="48dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Definições do Estabelecimento"
android:textColor="@color/colorTextPrimary"
android:textSize="28sp"
android:fontFamily="sans-serif-medium"
android:textAlignment="center" />
<!-- Improved Logo using MaterialCardView -->
<com.google.android.material.card.MaterialCardView
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="32dp"
app:cardCornerRadius="60dp"
app:cardElevation="2dp"
app:strokeWidth="1dp"
app:strokeColor="@color/colorDivider">
<ImageView
android:id="@+id/imgRestaurantLogo"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/circle_bg" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton
android:id="@+id/btnChangeLogo"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:text="Alterar Logótipo"
android:textAllCaps="false" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Configure o raio de segurança para check-in antecipado e regras associadas."
android:textColor="@color/colorTextSecondary"
android:textAlignment="center"
android:textSize="14sp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
app:cardBackgroundColor="@color/colorSurface"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:strokeWidth="1dp"
app:strokeColor="@color/colorDivider">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Raio de Segurança (metros)"
android:textColor="@color/colorTextPrimary"
android:textSize="16sp"
android:fontFamily="sans-serif-medium" />
<EditText
android:id="@+id/inputRadius"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="12dp"
android:hint="Ex: 500"
android:inputType="number"
android:paddingHorizontal="16dp"
android:background="@drawable/input_bg"
android:textColor="@color/colorTextPrimary" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Informação do Restaurante"
android:textColor="@color/colorTextPrimary"
android:textSize="16sp"
android:fontFamily="sans-serif-medium" />
<EditText
android:id="@+id/inputAddress"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="12dp"
android:hint="Morada (Ex: Rua Estreita, 12)"
android:inputType="textPostalAddress"
android:paddingHorizontal="16dp"
android:background="@drawable/input_bg"
android:textColor="@color/colorTextPrimary" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Categoria"
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
<Spinner
android:id="@+id/spinnerCategory"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="8dp"
android:background="@drawable/input_bg" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnSaveSettings"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="32dp"
app:cornerRadius="14dp"
app:backgroundTint="@color/colorPrimary"
android:text="Guardar Definições"
android:textAllCaps="false"
android:textSize="16sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,183 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/detalhesReservasRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
tools:context=".DetalhesReservasActivity">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="← Voltar"
android:textAllCaps="false"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtTituloDetalhes"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Detalhes das reservas"
android:textColor="@color/colorTextPrimary"
android:textSize="26sp"
android:fontFamily="sans-serif-medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnVoltar"
android:layout_marginTop="20dp" />
<TextView
android:id="@+id/txtDescricaoDetalhes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="8dp"
android:text="Selecione uma reserva para ver os detalhes e atualizar o estado."
android:textColor="@color/colorTextSecondary"
android:textSize="14sp"
android:textAlignment="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloDetalhes" />
<ListView
android:id="@+id/listReservas"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
android:divider="@color/colorDivider"
android:dividerHeight="1dp"
android:scrollbars="none"
android:clipToPadding="false"
app:layout_constraintTop_toBottomOf="@id/txtDescricaoDetalhes"
app:layout_constraintBottom_toTopOf="@id/cardDetalhes"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardDetalhes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="32dp"
app:cardBackgroundColor="@color/colorSurface"
app:cardCornerRadius="20dp"
app:cardElevation="10dp"
app:strokeWidth="1dp"
app:strokeColor="@color/colorDivider"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Reserva selecionada"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp"
android:fontFamily="sans-serif-medium" />
<TextView
android:id="@+id/txtReservaInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="Selecione uma reserva"
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
<TextView
android:id="@+id/txtReservaNotas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text=""
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
<TextView
android:id="@+id/txtReservaEstado"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Estado:"
android:textColor="@color/colorSuccess"
android:textSize="14sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirmarReserva"
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginEnd="4dp"
android:layout_weight="1"
app:cornerRadius="12dp"
app:backgroundTint="@color/colorPrimary"
android:text="Confirmar"
android:textAllCaps="false"
android:textColor="@color/white" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnCancelarReserva"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginHorizontal="4dp"
android:layout_weight="1"
app:cornerRadius="12dp"
android:text="Recusar"
android:textAllCaps="false"
android:textColor="@color/colorTextPrimary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnApagarReserva"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginStart="4dp"
android:layout_weight="1"
app:cornerRadius="12dp"
app:strokeColor="@color/colorError"
android:text="Apagar"
android:textAllCaps="false"
android:textColor="@color/colorError"
android:visibility="gone" />
</LinearLayout>
<TextView
android:id="@+id/txtMensagemReserva"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text=""
android:textColor="@color/colorSuccess"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,329 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/establishmentRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
tools:context=".EstablishmentDashboardActivity">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="← Voltar"
android:textAllCaps="false"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
android:fillViewport="true"
android:scrollbars="none"
app:layout_constraintTop_toBottomOf="@id/btnVoltar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/txtEstabTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Estabelecimento"
android:textColor="@color/colorTextPrimary"
android:textSize="28sp"
android:fontFamily="sans-serif-medium" />
<TextView
android:id="@+id/txtEstabSubtitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Veja as reservas ao vivo e controle a ocupação."
android:textColor="@color/colorTextSecondary"
android:textSize="15sp"
android:lineSpacingExtra="2dp"/>
<TextView
android:id="@+id/txtEstabRole"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Função: ADMIN"
android:textColor="@color/colorSuccess"
android:textSize="13sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardReservasHoje"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="6dp"
android:layout_weight="1"
android:foreground="?attr/selectableItemBackground"
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="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>
<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
android:id="@+id/txtMesasLivresDash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="00"
android:textColor="@color/colorTextPrimary"
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_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="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>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="Próximas reservas"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:fontFamily="sans-serif-medium" />
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:cardBackgroundColor="@color/colorSurface"
app:cardCornerRadius="20dp"
app:cardElevation="4dp">
<LinearLayout
android:id="@+id/llProximasReservas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="A procurar próximas reservas..."
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:text="Operações rápidas"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:fontFamily="sans-serif-medium" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="vertical">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAbrirEspera"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="12dp"
app:cornerRadius="14dp"
android:text="Abrir lista de espera"
android:textAllCaps="false"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnGerirMesas"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="12dp"
app:cornerRadius="14dp"
android:text="Editar mesas"
android:textAllCaps="false"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDetalhesReservas"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="12dp"
app:cornerRadius="14dp"
android:text="Detalhes das reservas"
android:textAllCaps="false"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnGestaoStaff"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="12dp"
app:cornerRadius="14dp"
android:text="Gestão de staff"
android:textAllCaps="false"
android:textSize="16sp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnDefinicoes"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginBottom="24dp"
app:cornerRadius="14dp"
android:text="Definições"
android:textAllCaps="false"
android:textSize="16sp" />
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
app:cardBackgroundColor="@color/colorSurface"
app:cardCornerRadius="20dp"
app:strokeWidth="1dp"
app:strokeColor="@color/colorWarning"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Alertas operacionais"
android:textColor="@color/colorTextPrimary"
android:textSize="16sp"
android:fontFamily="sans-serif-medium" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="• Mesa 07 aguarda confirmação há 10 min\n• 2 clientes aguardam resposta no chat"
android:textColor="@color/colorTextSecondary"
android:lineSpacingExtra="4dp"
android:textSize="14sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,172 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/gerirMesasRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
tools:context=".GerirMesasActivity">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="← Voltar"
android:textAllCaps="false"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtTituloGerirMesas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Editar mesas"
android:textColor="@color/colorTextPrimary"
android:textSize="28sp"
android:fontFamily="sans-serif-medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnVoltar"
android:layout_marginTop="20dp" />
<TextView
android:id="@+id/txtDescricaoGerirMesas"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="8dp"
android:text="Atualize rapidamente as mesas disponíveis, capacidade e estado."
android:textColor="@color/colorTextSecondary"
android:textSize="14sp"
android:textAlignment="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloGerirMesas" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/formMesas"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="24dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/colorSurface"
app:strokeWidth="1dp"
app:strokeColor="@color/colorDivider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtDescricaoGerirMesas">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Editar ou adicionar mesa"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp"
android:fontFamily="sans-serif-medium" />
<EditText
android:id="@+id/inputMesaNumero"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/input_bg"
android:hint="Número da mesa"
android:inputType="number"
android:paddingHorizontal="16dp" />
<EditText
android:id="@+id/inputMesaCapacidade"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="12dp"
android:background="@drawable/input_bg"
android:hint="Capacidade (lugares)"
android:inputType="number"
android:paddingHorizontal="16dp" />
<Spinner
android:id="@+id/spinnerEstadoMesa"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="12dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnGuardarMesa"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="16dp"
app:cornerRadius="12dp"
app:backgroundTint="@color/colorPrimary"
android:text="Guardar"
android:textAllCaps="false"
android:textColor="@color/white" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnRemoverMesa"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="8dp"
app:cornerRadius="12dp"
app:strokeColor="@color/colorError"
android:text="Remover"
android:textAllCaps="false"
android:textColor="@color/colorError" />
<TextView
android:id="@+id/txtMensagemMesa"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text=""
android:textColor="@color/colorSuccess"
android:textSize="13sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/txtListaTitulo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="24dp"
android:text="Mesas registadas"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp"
android:fontFamily="sans-serif-medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/formMesas" />
<ListView
android:id="@+id/listMesas"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="16dp"
android:divider="@color/colorDivider"
android:dividerHeight="1dp"
android:scrollbars="none"
android:clipToPadding="false"
app:layout_constraintTop_toBottomOf="@id/txtListaTitulo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,188 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/gestaoStaffRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
tools:context=".GestaoStaffActivity">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="← Voltar"
android:textAllCaps="false"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtTituloGestaoStaff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Gestão de staff"
android:textColor="@color/colorTextPrimary"
android:textSize="28sp"
android:fontFamily="sans-serif-medium"
app:layout_constraintTop_toBottomOf="@id/btnVoltar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="20dp" />
<TextView
android:id="@+id/txtDescricaoGestaoStaff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="12dp"
android:text="Zona para configurar turnos, equipas e disponibilidade do staff."
android:textColor="@color/colorTextSecondary"
android:textSize="14sp"
android:textAlignment="center"
app:layout_constraintTop_toBottomOf="@id/txtTituloGestaoStaff"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/formStaff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="24dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp"
app:cardBackgroundColor="@color/colorSurface"
app:strokeWidth="1dp"
app:strokeColor="@color/colorDivider"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtDescricaoGestaoStaff">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Atribuir funcionário a uma mesa"
android:textColor="@color/colorTextPrimary"
android:textSize="16sp"
android:fontFamily="sans-serif-medium" />
<Spinner
android:id="@+id/spinnerNomeStaff"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="16dp"
android:background="@drawable/input_bg"
android:padding="0dp" />
<Spinner
android:id="@+id/spinnerMesaStaff"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="12dp"
android:background="@drawable/input_bg"
android:padding="0dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnAtribuirStaff"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="16dp"
app:cornerRadius="12dp"
app:backgroundTint="@color/colorPrimary"
android:text="Guardar atribuição"
android:textAllCaps="false"
android:textColor="@color/white" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnEliminarStaff"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:text="Eliminar Funcionário"
android:textAllCaps="false"
android:textColor="@color/colorError" />
<TextView
android:id="@+id/txtMensagemStaff"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text=""
android:textColor="@color/colorSuccess"
android:textSize="13sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/txtListaStaffTitulo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="24dp"
android:text="Funcionários e mesas"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp"
android:fontFamily="sans-serif-medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/formStaff" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnGerirMesasStaff"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:layout_marginEnd="24dp"
app:cornerRadius="12dp"
android:text="Gerir Mesas"
android:textAllCaps="false"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="@id/txtListaStaffTitulo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/txtListaStaffTitulo" />
<ListView
android:id="@+id/listStaffMesas"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:divider="@color/colorDivider"
android:dividerHeight="1dp"
android:scrollbars="none"
android:clipToPadding="false"
app:layout_constraintTop_toBottomOf="@id/txtListaStaffTitulo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
android:clickable="true"
app:backgroundTint="@color/colorPrimary"
app:tint="@color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:srcCompat="@tools:sample/avatars" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,159 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/listaEsperaRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
tools:context=".ListaEsperaActivity">
<!-- Header Section -->
<com.google.android.material.button.MaterialButton
android:id="@+id/btnVoltar"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="← Voltar"
android:textAllCaps="false"
android:textColor="@color/colorPrimary"
android:textSize="16sp"
android:fontFamily="sans-serif-medium"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtTituloListaEspera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lista de espera"
android:textSize="28sp"
android:fontFamily="sans-serif-medium"
android:textColor="@color/colorTextPrimary"
app:layout_constraintTop_toBottomOf="@id/btnVoltar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="24dp" />
<TextView
android:id="@+id/txtDescricaoListaEspera"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="8dp"
android:text="Aqui o estabelecimento irá gerir a lista de espera em tempo real."
android:textSize="15sp"
android:textAlignment="center"
android:lineSpacingExtra="4dp"
android:textColor="@color/colorTextSecondary"
app:layout_constraintTop_toBottomOf="@id/txtTituloListaEspera"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<!-- List Section -->
<ListView
android:id="@+id/listReservasP"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
android:background="@android:color/transparent"
android:divider="@color/colorDivider"
android:dividerHeight="1dp"
android:scrollbars="none"
android:clipToPadding="false"
android:paddingBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/txtDescricaoListaEspera"
app:layout_constraintBottom_toTopOf="@id/detalhesCardP" />
<!-- Selection/Details Card Bottom -->
<com.google.android.material.card.MaterialCardView
android:id="@+id/detalhesCardP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="32dp"
app:cardCornerRadius="20dp"
app:cardElevation="12dp"
app:strokeWidth="1dp"
app:strokeColor="#E2E8F0"
app:cardBackgroundColor="@color/colorSurface"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/txtReservaInfoP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Selecione uma reserva"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp"
android:fontFamily="sans-serif-medium" />
<TextView
android:id="@+id/txtReservaNotasP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text=""
android:textColor="@color/colorTextSecondary"
android:lineSpacingExtra="2dp"
android:textSize="14sp" />
<TextView
android:id="@+id/txtMensagemReservaP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text=""
android:textColor="@color/colorPrimary"
android:textSize="14sp"
android:textStyle="italic" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:orientation="horizontal"
android:weightSum="2">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnRecusarReservaP"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
app:cornerRadius="14dp"
app:backgroundTint="#FEE2E2"
android:text="Recusar"
android:textAllCaps="false"
android:textColor="@color/colorError"
android:visibility="gone" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirmarReservaP"
android:layout_width="0dp"
android:layout_height="54dp"
android:layout_marginStart="8dp"
android:layout_weight="1"
app:cornerRadius="14dp"
app:backgroundTint="@color/colorSuccess"
android:text="Aceitar"
android:textAllCaps="false"
android:textColor="@color/white"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -2,174 +2,229 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/colorPrimary" android:id="@+id/main"
android:background="@color/colorBackground"
tools:context=".MainActivity"> tools:context=".MainActivity">
<ImageView <ScrollView
android:id="@+id/imgBg"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scaleType="centerCrop" android:fillViewport="true">
android:alpha="0.3"
android:src="@drawable/na_mesa" />
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="80dp"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:src="@drawable/na_mesa" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-black"
android:text="NaMesa"
android:textColor="@color/white"
android:textSize="32sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Premium Dining Reservations"
android:textColor="@color/white"
android:alpha="0.7"
android:textSize="14sp" />
</LinearLayout>
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardContainer"
style="@style/Widget.NaMesa.Card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginBottom="48dp"
app:cardBackgroundColor="@color/white"
app:cardElevation="12dp"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:orientation="vertical"
android:padding="24dp">
<com.google.android.material.button.MaterialButtonToggleGroup <ImageView
android:id="@+id/toggleGroup" android:id="@+id/logoNaMesa"
android:layout_width="match_parent" android:layout_width="160dp"
android:layout_height="wrap_content" android:layout_height="156dp"
android:layout_marginBottom="24dp" android:layout_marginTop="124dp"
app:singleSelection="true" android:src="@drawable/na_mesa"
app:checkedButton="@+id/btnEntrar"> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.508"
<com.google.android.material.button.MaterialButton app:layout_constraintStart_toStartOf="parent"
android:id="@+id/btnEntrar" app:layout_constraintTop_toTopOf="parent" />
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Entrar" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnCriarConta"
style="@style/Widget.Material3.Button.OutlinedButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Registar" />
</com.google.android.material.button.MaterialButtonToggleGroup>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/layoutName"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="Nome Completo"
android:visibility="gone"
app:startIconDrawable="@android:drawable/ic_menu_edit">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPersonName" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:hint="Email"
app:startIconDrawable="@android:drawable/ic_dialog_email">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputEmail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:hint="Palavra-passe"
app:endIconMode="password_toggle"
app:startIconDrawable="@android:drawable/ic_lock_idle_lock">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/inputPassword"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<TextView <TextView
android:id="@+id/txtForgotPassword" android:id="@+id/txtTitle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_marginTop="16dp"
android:layout_marginBottom="24dp" android:text="NaMesa"
android:text="Esqueceu-se da senha?" android:textColor="@color/colorTextPrimary"
android:textColor="@color/colorPrimary" android:textSize="28sp"
android:textSize="12sp" android:fontFamily="sans-serif-medium"
android:textStyle="bold" /> app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/logoNaMesa" />
<com.google.android.material.button.MaterialButton <TextView
android:id="@+id/btnPrimaryAction" android:id="@+id/txtSub"
style="@style/Widget.NaMesa.Button" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="Sistema de Reserva de Mesas"
android:textColor="@color/colorTextSecondary"
android:textSize="15sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTitle" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="60dp" android:layout_height="wrap_content"
android:text="Entrar" android:layout_marginHorizontal="24dp"
android:textSize="16sp" /> android:layout_marginTop="32dp"
android:layout_marginBottom="40dp"
app:cardCornerRadius="24dp"
app:cardElevation="8dp"
app:cardBackgroundColor="@color/colorSurface"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtSub"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintVertical_bias="0.0">
</LinearLayout> <LinearLayout
</com.google.android.material.card.MaterialCardView> android:layout_width="match_parent"
android:layout_height="387dp"
android:orientation="vertical"
android:padding="24dp">
<ProgressBar <LinearLayout
android:id="@+id/progressBarLogin" android:layout_width="match_parent"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginTop="16dp"
android:visibility="gone" android:background="@drawable/bg_tabs"
android:indeterminateTint="@color/white" android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent" android:padding="3dp">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btnEntrar"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/tab_selected"
android:text="Entrar"
android:textAllCaps="false"
android:textColor="#231F1F"
android:textSize="14sp" />
<Button
android:id="@+id/btnCriarConta"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@android:color/transparent"
android:text="Criar Conta"
android:textAllCaps="false"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
</LinearLayout>
<EditText
android:id="@+id/inputName"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="20dp"
android:background="@drawable/input_bg"
android:hint="O seu nome"
android:inputType="textPersonName"
android:paddingHorizontal="16dp"
android:visibility="gone" />
<EditText
android:id="@+id/inputEmail"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="20dp"
android:background="@drawable/input_bg"
android:hint="Email"
android:inputType="textEmailAddress"
android:paddingHorizontal="16dp" />
<EditText
android:id="@+id/inputOwnerPhone"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="16dp"
android:background="@drawable/input_bg"
android:hint="Telefone do proprietário"
android:inputType="phone"
android:paddingHorizontal="16dp"
android:visibility="gone" />
<EditText
android:id="@+id/inputEstablishmentName"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="16dp"
android:background="@drawable/input_bg"
android:hint="Nome do estabelecimento"
android:inputType="textCapWords"
android:paddingHorizontal="16dp"
android:visibility="gone" />
<EditText
android:id="@+id/inputEstablishmentEmail"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="16dp"
android:background="@drawable/input_bg"
android:hint="Email do estabelecimento"
android:inputType="textEmailAddress"
android:paddingHorizontal="16dp"
android:visibility="gone" />
<EditText
android:id="@+id/inputEstablishmentPhone"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_marginTop="16dp"
android:background="@drawable/input_bg"
android:hint="Telefone do estabelecimento"
android:inputType="phone"
android:paddingHorizontal="16dp"
android:visibility="gone" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<EditText
android:id="@+id/inputPassword"
android:layout_width="match_parent"
android:layout_height="54dp"
android:background="@drawable/input_bg"
android:hint="Palavra-passe"
android:inputType="textPassword"
android:paddingStart="16dp"
android:paddingEnd="48dp" />
<ImageView
android:id="@+id/iconPasswordVisibility"
android:layout_width="48dp"
android:layout_height="54dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="true"
android:focusable="true"
android:padding="12dp"
android:src="@drawable/ic_visibility_off" />
</RelativeLayout>
<TextView
android:id="@+id/txtForgotPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="12dp"
android:clickable="true"
android:focusable="true"
android:text="Esqueceu-se da palavra-passe?"
android:textColor="@color/colorPrimary"
android:textSize="14sp"
android:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnFinalCriarConta"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="24dp"
android:text="Entrar"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
app:backgroundTint="@color/colorPrimary"
app:cornerRadius="14dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -161,24 +161,6 @@
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"
@@ -198,28 +180,6 @@
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

@@ -1,46 +0,0 @@
<?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/white">
<ImageView
android:id="@+id/logo"
android:layout_width="120dp"
android:layout_height="120dp"
android:src="@drawable/na_mesa"
app:layout_constraintBottom_toTopOf="@+id/tagline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/tagline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-medium"
android:text="A sua mesa, à sua espera."
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/btnGetStarted"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/logo" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnGetStarted"
style="@style/Widget.NaMesa.Button"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginHorizontal="32dp"
android:layout_marginBottom="48dp"
android:text="Começar"
android:textSize="18sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,203 +0,0 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/white"
app:layout_constraintTop_toTopOf="parent"
app:navigationIcon="@android:drawable/ic_menu_revert"
app:title="Reservar Mesa"
app:titleTextColor="@color/colorTextPrimary" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:fillViewport="true"
app:layout_constraintBottom_toTopOf="@+id/confirmContainer"
app:layout_constraintTop_toBottomOf="@+id/toolbar">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<!-- Number of People -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text="Número de Pessoas"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.NaMesa.Card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:cardElevation="0dp"
app:strokeColor="@color/colorBorder"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="64dp"
android:gravity="center"
android:orientation="horizontal">
<ImageButton
android:id="@+id/btnDecreaseGuests"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_delete"
app:tint="@color/colorPrimary" />
<TextView
android:id="@+id/txtGuestCount"
android:layout_width="80dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="2"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold" />
<ImageButton
android:id="@+id/btnIncreaseGuests"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_add"
app:tint="@color/colorPrimary" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Date Selection -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-black"
android:text="Data da Reserva"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardDatePicker"
style="@style/Widget.NaMesa.Card"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginTop="12dp"
app:cardElevation="0dp"
app:strokeColor="@color/colorBorder"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@android:drawable/ic_menu_my_calendar"
app:tint="@color/colorTextSecondary" />
<TextView
android:id="@+id/txtSelectedDate"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:text="Selecionar Data"
android:textColor="@color/colorTextPrimary"
android:textSize="16sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Time Slots -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-black"
android:text="Horários Disponíveis"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTimeSlots"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="3"
tools:listitem="@layout/item_time_slot" />
<!-- Special Requests -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-black"
android:text="Pedidos Especiais (Opcional)"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.NaMesa.Card"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_marginTop="12dp"
app:cardElevation="0dp"
app:strokeColor="@color/colorBorder"
app:strokeWidth="1dp">
<EditText
android:id="@+id/etSpecialRequests"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@null"
android:gravity="top"
android:hint="Ex: Cadeira para bebé, alergias..."
android:padding="16dp"
android:textColor="@color/colorTextPrimary"
android:textColorHint="@color/colorTextHint"
android:textSize="15sp" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/confirmContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="0dp"
app:cardElevation="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:strokeWidth="0dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnConfirmReservation"
style="@style/Widget.NaMesa.Button"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_margin="20dp"
android:text="Confirmar Reserva"
android:textSize="18sp" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,163 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@color/white"
android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsingToolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:contentScrim="@color/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:titleEnabled="false">
<ImageView
android:id="@+id/restaurantCover"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/na_mesa"
app:layout_collapseMode="parallax" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/gradient_overlay" />
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:navigationIcon="@android:drawable/ic_menu_revert" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/detailName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text="Nome do Restaurante"
android:textColor="@color/colorTextPrimary"
android:textSize="28sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/detailCuisine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Italiano"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:text="•"
android:textColor="@color/colorTextSecondary" />
<TextView
android:id="@+id/detailRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="4.8 ★"
android:textColor="@color/colorWarning"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:fontFamily="sans-serif-black"
android:text="Sobre"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp" />
<TextView
android:id="@+id/detailDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:lineSpacingExtra="4dp"
android:text="Descrição do restaurante aparecerá aqui com todos os detalhes premium."
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:fontFamily="sans-serif-black"
android:text="Localização"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp" />
<TextView
android:id="@+id/detailAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Rua Principal, 123, Lisboa"
android:textColor="@color/colorTextSecondary"
android:textSize="15sp" />
<!-- Spacer for bottom button -->
<View
android:layout_width="match_parent"
android:layout_height="100dp" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:cardBackgroundColor="@color/white"
app:cardCornerRadius="0dp"
app:cardElevation="16dp"
app:strokeWidth="0dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnReserveNow"
style="@style/Widget.NaMesa.Button"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_margin="20dp"
android:text="Reservar Mesa"
android:textSize="18sp" />
</com.google.android.material.card.MaterialCardView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,47 +0,0 @@
<?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

@@ -1,174 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="80dp">
<!-- Header -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:paddingTop="32dp">
<TextView
android:id="@+id/greeting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text="Olá, Explorador"
android:textColor="@color/colorTextPrimary"
android:textSize="32sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Onde gostaria de jantar hoje?"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp" />
</LinearLayout>
<!-- Search Bar -->
<com.google.android.material.card.MaterialCardView
style="@style/Widget.NaMesa.Card"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="24dp"
app:cardElevation="0dp"
app:strokeColor="@color/colorBorder"
app:strokeWidth="1dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@android:drawable/ic_menu_search"
app:tint="@color/colorTextSecondary" />
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:background="@null"
android:hint="Procurar restaurantes, cozinhas..."
android:inputType="text"
android:textColor="@color/colorTextPrimary"
android:textColorHint="@color/colorTextHint"
android:textSize="15sp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- Categories -->
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:clipToPadding="false"
android:paddingHorizontal="24dp"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/categoryChips"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:singleLine="true"
app:singleSelection="true">
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tudo"
app:chipBackgroundColor="@color/colorChip"
app:chipStrokeWidth="0dp" />
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Italiano"
app:chipBackgroundColor="@color/colorChip"
app:chipStrokeWidth="0dp" />
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sushi"
app:chipBackgroundColor="@color/colorChip"
app:chipStrokeWidth="0dp" />
<com.google.android.material.chip.Chip
style="@style/Widget.Material3.Chip.Filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hambúrgueres"
app:chipBackgroundColor="@color/colorChip"
app:chipStrokeWidth="0dp" />
</com.google.android.material.chip.ChipGroup>
</HorizontalScrollView>
<!-- Featured Section -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-black"
android:text="Destaques"
android:textColor="@color/colorTextPrimary"
android:textSize="22sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvFeatured"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_featured" />
<!-- Near You Section -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-black"
android:text="Perto de si"
android:textColor="@color/colorTextPrimary"
android:textSize="22sp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvNearYou"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:paddingHorizontal="24dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_card" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1,71 +0,0 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white">
<TextView
android:id="@+id/txtTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="32dp"
android:fontFamily="sans-serif-black"
android:text="Minhas Reservas"
android:textColor="@color/colorTextPrimary"
android:textSize="28sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvReservations"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:paddingBottom="80dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtTitle"
tools:listitem="@layout/item_reserva_cliente" />
<ProgressBar
android:id="@+id/progressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/emptyState"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="120dp"
android:layout_height="120dp"
android:alpha="0.5"
android:src="@drawable/na_mesa" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Ainda não tem reservas."
android:textColor="@color/colorTextSecondary"
android:textSize="16sp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,121 +0,0 @@
<?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/white">
<View
android:id="@+id/headerBg"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.card.MaterialCardView
android:id="@+id/cardProfile"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_marginTop="140dp"
app:cardCornerRadius="60dp"
app:cardElevation="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:strokeColor="@color/white"
app:strokeWidth="4dp">
<ImageView
android:id="@+id/imgProfile"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/na_mesa" />
</com.google.android.material.card.MaterialCardView>
<TextView
android:id="@+id/txtProfileName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fontFamily="sans-serif-black"
android:text="Nome do Utilizador"
android:textColor="@color/colorTextPrimary"
android:textSize="24sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardProfile" />
<TextView
android:id="@+id/txtProfileEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="email@exemplo.com"
android:textColor="@color/colorTextSecondary"
android:textSize="16sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtProfileName" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:orientation="vertical"
android:paddingHorizontal="24dp"
app:layout_constraintTop_toBottomOf="@+id/txtProfileEmail">
<com.google.android.material.button.MaterialButton
android:id="@+id/btnEditProfile"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="start|center_vertical"
android:text="Editar Perfil"
android:textColor="@color/colorTextPrimary"
android:textSize="16sp"
app:icon="@android:drawable/ic_menu_edit"
app:iconPadding="16dp"
app:iconTint="@color/colorPrimary" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorBorder" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnNotifications"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="60dp"
android:gravity="start|center_vertical"
android:text="Notificações"
android:textColor="@color/colorTextPrimary"
android:textSize="16sp"
app:icon="@android:drawable/ic_popup_reminder"
app:iconPadding="16dp"
app:iconTint="@color/colorPrimary" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorBorder" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnLogout"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="32dp"
android:gravity="start|center_vertical"
android:text="Sair da Conta"
android:textColor="@color/colorError"
android:textSize="16sp"
app:icon="@android:drawable/ic_lock_power_off"
app:iconPadding="16dp"
app:iconTint="@color/colorError" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginTop="24dp">
<TextView
android:id="@+id/txtCategoryTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:textColor="@color/colorTextPrimary"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
tools:text="Sushi" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCategoryRestaurants"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:clipToPadding="false"
android:paddingStart="24dp"
android:paddingEnd="8dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant_featured" />
</LinearLayout>

View File

@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/Widget.NaMesa.Card"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp" android:layout_marginHorizontal="20dp"
android:layout_marginVertical="8dp" android:layout_marginVertical="8dp"
app:cardCornerRadius="20dp"
app:cardElevation="2dp" app:cardElevation="2dp"
app:cardBackgroundColor="@color/colorSurface"
app:strokeWidth="0dp"> app:strokeWidth="0dp">
<LinearLayout <LinearLayout

View File

@@ -1,96 +0,0 @@
<?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

@@ -1,67 +0,0 @@
<?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"
style="@style/Widget.NaMesa.Card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardElevation="2dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp">
<com.google.android.material.card.MaterialCardView
android:layout_width="100dp"
android:layout_height="100dp"
app:cardCornerRadius="12dp"
app:cardElevation="0dp"
app:strokeWidth="0dp">
<ImageView
android:id="@+id/restaurantImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/na_mesa" />
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/restaurantName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-black"
android:text="Nome do Restaurante"
android:textColor="@color/colorTextPrimary"
android:textSize="18sp" />
<TextView
android:id="@+id/restaurantCuisine"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Cozinha • 4.9 ★"
android:textColor="@color/colorTextSecondary"
android:textSize="14sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Ver detalhes"
android:textColor="@color/colorPrimary"
android:textSize="12sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android" <com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
style="@style/Widget.NaMesa.Card"
android:layout_width="280dp" android:layout_width="280dp"
android:layout_height="200dp" android:layout_height="180dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp" android:layout_marginVertical="8dp"
app:cardCornerRadius="24dp"
app:cardElevation="4dp" app:cardElevation="4dp"
app:strokeWidth="0dp"> app:strokeWidth="0dp">
@@ -14,40 +14,112 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<ImageView <ImageView
android:id="@+id/restaurantImage" android:id="@+id/imgFeaturedCover"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="0dp"
android:scaleType="centerCrop" android:scaleType="centerCrop"
android:src="@drawable/na_mesa" /> android:src="@mipmap/ic_launcher"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<View <View
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="80dp"
android:background="@drawable/gradient_overlay" /> android:background="@drawable/gradient_shadow_up"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/txtFeaturedName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:text="Restaurante"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold"
android:fontFamily="sans-serif"
app:layout_constraintBottom_toTopOf="@id/layoutRating"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageButton
android:id="@+id/btnFavorite"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:background="@drawable/bg_circle_white"
android:padding="8dp"
android:scaleType="fitCenter"
android:src="@android:drawable/btn_star_big_off"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:tint="@color/colorTextPrimary" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:id="@+id/layoutRating"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="horizontal"
android:padding="16dp" android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"> android:layout_marginBottom="16dp"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ImageView
android:layout_width="14dp"
android:layout_height="14dp"
android:src="@android:drawable/star_on"
app:tint="@color/colorWarning" />
<TextView <TextView
android:id="@+id/restaurantName" android:id="@+id/txtFeaturedRating"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="sans-serif-black" android:layout_marginStart="4dp"
android:text="Nome do Restaurante" android:text="4.8"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="18sp" /> android:textSize="14sp"
android:textStyle="bold" />
<TextView <TextView
android:id="@+id/restaurantCuisine" android:id="@+id/txtFeaturedCategory"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Cozinha • 4.9 ★" android:layout_marginStart="6dp"
android:textColor="@color/white" android:text="• Sushi"
android:textColor="#E0E0E0"
android:textSize="14sp" /> android:textSize="14sp" />
</LinearLayout> </LinearLayout>
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginStart="12dp"
app:cardCornerRadius="12dp"
app:cardBackgroundColor="#CCFFFFFF"
app:strokeWidth="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingHorizontal="12dp"
android:paddingVertical="6dp"
android:text="Destaque"
android:textSize="12sp"
android:textStyle="bold"
android:textColor="@color/colorTextPrimary" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>

View File

@@ -1,48 +0,0 @@
<?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

@@ -1,22 +0,0 @@
<?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/cardTimeSlot"
style="@style/Widget.NaMesa.Card"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_margin="4dp"
app:cardElevation="0dp"
app:strokeColor="@color/colorBorder"
app:strokeWidth="1dp">
<TextView
android:id="@+id/txtTimeSlot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="19:30"
android:textColor="@color/colorTextPrimary"
android:textSize="15sp"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>

View File

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

View File

@@ -1,33 +1,31 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Brand Colors - Midnight Blue Premium Palette --> <!-- Brand Colors - Warm Premium Palette -->
<color name="colorPrimary">#1A237E</color> <color name="colorPrimary">#FF5A5F</color> <!-- Airbnb/UberEats Coral -->
<color name="colorPrimaryVariant">#000051</color> <color name="colorPrimaryVariant">#FF3B30</color>
<color name="colorSecondary">#3F51B5</color> <color name="colorSecondary">#FF5A5F</color>
<!-- Neutral Colors --> <!-- Neutral Colors -->
<color name="black">#121212</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="colorBackground">#FFFFFF</color> <color name="colorBackground">#F7F7F9</color> <!-- Soft grey for modern background -->
<color name="colorSurface">#FFFFFF</color> <color name="colorSurface">#FFFFFF</color>
<color name="colorTextPrimary">#121212</color> <color name="colorTextPrimary">#1A1A1A</color> <!-- Dark but not pure black -->
<color name="colorTextSecondary">#666666</color> <color name="colorTextSecondary">#757575</color>
<color name="colorTextHint">#A0A0A0</color> <color name="colorTextHint">#BDBDBD</color>
<color name="colorBorder">#EEEEEE</color>
<!-- Semantic Colors --> <!-- Semantic Colors -->
<color name="colorSuccess">#00897B</color> <color name="colorSuccess">#34C759</color> <!-- iOS green -->
<color name="colorError">#D32F2F</color> <color name="colorError">#FF3B30</color>
<color name="colorWarning">#FFA000</color> <color name="colorWarning">#FFCC00</color>
<color name="colorDivider">#F5F5F5</color> <color name="colorDivider">#E0E0E0</color>
<color name="colorChip">#F8F9FA</color> <color name="colorChipPending">#FFE0B2</color>
<color name="colorAccent">#1A237E</color>
<!-- Status Colors (Restored for compatibility and new design) -->
<color name="colorChipPending">#FFF3E0</color>
<color name="colorChipPendingText">#E65100</color> <color name="colorChipPendingText">#E65100</color>
<color name="colorChipConfirmed">#E8F5E9</color> <color name="colorChipConfirmed">#C8E6C9</color>
<color name="colorChipConfirmedText">#2E7D32</color> <color name="colorChipConfirmedText">#1B5E20</color>
<color name="colorChipCancelled">#FFEBEE</color> <color name="colorChipCancelled">#FFCDD2</color>
<color name="colorChipCancelledText">#C62828</color> <color name="colorChipCancelledText">#B71C1C</color>
<!-- Legacy compatibility -->
<color name="my_light_primary">#BE1F13</color>
</resources> </resources>

View File

@@ -1,45 +1,41 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="Base.Theme.Pap_teste" parent="Theme.Material3.Light.NoActionBar"> <style name="Base.Theme.Pap_teste" parent="Theme.Material3.DayNight.NoActionBar">
<!-- Core Colors --> <!-- Core Colors -->
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryVariant">@color/colorPrimaryVariant</item> <item name="colorPrimaryVariant">@color/colorPrimaryVariant</item>
<item name="colorSecondary">@color/colorSecondary</item> <item name="colorSecondary">@color/colorSecondary</item>
<item name="colorAccent">@color/colorAccent</item>
<!-- Background and Surface Colors --> <!-- Background and Surface Colors -->
<item name="android:colorBackground">@color/colorBackground</item> <item name="android:colorBackground">@color/colorBackground</item>
<item name="colorSurface">@color/colorSurface</item> <item name="colorSurface">@color/colorSurface</item>
<item name="colorError">@color/colorError</item> <item name="colorError">@color/colorError</item>
<!-- Status Bar --> <!-- Typography base generic configuration -->
<item name="android:statusBarColor">@color/white</item> <item name="android:fontFamily">sans-serif</item>
<item name="android:windowLightStatusBar">true</item>
<!-- Typography --> <!-- Modern Shape Appearance (Rounded Corners) -->
<item name="android:fontFamily">sans-serif-medium</item> <item name="shapeAppearanceSmallComponent">@style/ShapeAppearance.App.SmallComponent</item>
<item name="shapeAppearanceMediumComponent">@style/ShapeAppearance.App.MediumComponent</item>
<item name="shapeAppearanceLargeComponent">@style/ShapeAppearance.App.LargeComponent</item>
</style> </style>
<style name="Theme.Pap_teste" parent="Base.Theme.Pap_teste" /> <style name="Theme.Pap_teste" parent="Base.Theme.Pap_teste" />
<!-- Premium Button Style --> <!-- Shape Styles -->
<style name="Widget.NaMesa.Button" parent="Widget.Material3.Button"> <style name="ShapeAppearance.App.SmallComponent" parent="ShapeAppearance.Material3.SmallComponent">
<item name="shapeAppearanceOverlay">@style/ShapeAppearance.NaMesa.Rounded</item> <item name="cornerFamily">rounded</item>
<item name="android:insetTop">0dp</item> <item name="cornerSize">12dp</item>
<item name="android:insetBottom">0dp</item>
<item name="android:textAllCaps">false</item>
<item name="android:letterSpacing">0.02</item>
</style> </style>
<!-- Premium Card Style --> <style name="ShapeAppearance.App.MediumComponent" parent="ShapeAppearance.Material3.MediumComponent">
<style name="Widget.NaMesa.Card" parent="Widget.Material3.CardView.Elevated"> <item name="cornerFamily">rounded</item>
<item name="cardCornerRadius">16dp</item>
<item name="cardElevation">2dp</item>
<item name="cardBackgroundColor">@color/white</item>
</style>
<style name="ShapeAppearance.NaMesa.Rounded" parent="">
<item name="cornerSize">16dp</item> <item name="cornerSize">16dp</item>
</style> </style>
<style name="ShapeAppearance.App.LargeComponent" parent="ShapeAppearance.Material3.LargeComponent">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">24dp</item>
</style>
</resources> </resources>

View File

@@ -13,7 +13,6 @@ 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" }
@@ -28,9 +27,6 @@ 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" }

View File