Compare commits

...

7 Commits

Author SHA1 Message Date
404f28da15 ... 2026-04-16 10:34:55 +01:00
740680e9fb ... 2026-04-14 17:13:52 +01:00
fa9ba84d5c ... 2026-04-14 15:28:45 +01:00
a921553f2b ... 2026-03-18 10:40:19 +00:00
88ef4b6796 . 2026-03-17 10:35:41 +00:00
948921a424 ... 2026-03-17 09:17:07 +00:00
2db6544284 ... 2026-03-17 09:16:58 +00:00
45 changed files with 2965 additions and 394 deletions

View File

@@ -4,6 +4,14 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-04-14T16:06:26.670067Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=b93659d0e5dd" />
</handle>
</Target>
</DropdownSelection>
<DialogSelection />
</SelectionState>
</selectionStates>
</component>

View File

@@ -42,6 +42,8 @@ dependencies {
implementation("com.google.firebase:firebase-firestore")
implementation("com.google.firebase:firebase-auth")
implementation(libs.firebase.database)
implementation(libs.glide)
implementation(libs.firebase.storage)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)

View File

@@ -5,15 +5,23 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<!-- Bluetooth permissions for energy/power stats and location accuracy -->
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:icon="@drawable/na_mesa"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:roundIcon="@drawable/na_mesa"
android:supportsRtl="true"
android:theme="@style/Theme.Pap_teste">
<activity
@@ -91,6 +99,11 @@
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">

View File

@@ -50,6 +50,11 @@ public class AddStaffActivity extends AppCompatActivity {
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");

View File

@@ -2,10 +2,12 @@ package com.example.pap_teste;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
@@ -25,16 +27,20 @@ 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 CheckInAntecipadoActivity extends AppCompatActivity implements LocationListener {
private TextView txtDistancia, txtStatus;
private Button btnConfirmarChegada;
private TextView txtDistancia, txtStatus, txtEndereco;
private Button btnConfirmarChegada, btnAbrirMapa;
private LocationManager locationManager;
private DatabaseReference databaseReference;
private double restaurantLat, restaurantLon;
private int securityDistance = 500; // default 500m
private boolean settingsLoaded = false;
private String restaurantAddress;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -49,7 +55,9 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
txtDistancia = findViewById(R.id.txtDistancia);
txtStatus = findViewById(R.id.txtStatusDistancia);
txtEndereco = findViewById(R.id.txtEnderecoRestaurante);
btnConfirmarChegada = findViewById(R.id.btnConfirmarChegada);
btnAbrirMapa = findViewById(R.id.btnAbrirMapa);
Button back = findViewById(R.id.btnVoltar);
if (back != null) {
@@ -65,12 +73,37 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
}
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
startLocationUpdates();
if (hasRequiredPermissions()) {
startLocationUpdates();
} else {
txtStatus.setText("Permissões em falta para localização/proximidade.");
}
btnConfirmarChegada.setOnClickListener(v -> {
Toast.makeText(this, "Chegada confirmada com sucesso!", Toast.LENGTH_LONG).show();
finish();
});
btnAbrirMapa.setOnClickListener(v -> {
if (restaurantAddress != null) {
try {
String uri = "geo:0,0?q=" + java.net.URLEncoder.encode(restaurantAddress, "UTF-8");
Intent intent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(uri));
startActivity(intent);
} catch (Exception e) {
Toast.makeText(this, "Erro ao abrir mapa.", Toast.LENGTH_SHORT).show();
}
}
});
}
private boolean hasRequiredPermissions() {
boolean fineLocation = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
boolean btScan = ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED;
return fineLocation && btScan;
}
return fineLocation;
}
private void loadRestaurantSettings(String restaurantId) {
@@ -79,17 +112,26 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
if (snapshot.exists()) {
Double lat = snapshot.child("latitude").getValue(Double.class);
Double lon = snapshot.child("longitude").getValue(Double.class);
restaurantAddress = snapshot.child("address").getValue(String.class);
Integer dist = snapshot.child("securityDistance").getValue(Integer.class);
restaurantLat = lat != null ? lat : 0.0;
restaurantLon = lon != null ? lon : 0.0;
securityDistance = dist != null ? dist : 500;
settingsLoaded = true;
if (restaurantAddress != null) {
txtEndereco.setText("Morada: " + restaurantAddress);
geocodeAddress(restaurantAddress);
} else {
txtEndereco.setText("Morada não disponível.");
// Fallback to old lat/lon if they exist
Double lat = snapshot.child("latitude").getValue(Double.class);
Double lon = snapshot.child("longitude").getValue(Double.class);
if (lat != null && lon != null) {
restaurantLat = lat;
restaurantLon = lon;
settingsLoaded = true;
}
}
} else {
txtStatus.setText("Aviso: Definições do restaurante não encontradas. Usando valores padrão.");
settingsLoaded = true; // Use defaults
txtStatus.setText("Aviso: Definições do restaurante não encontradas.");
}
}
@@ -100,12 +142,26 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
});
}
private void geocodeAddress(String address) {
new Thread(() -> {
try {
android.location.Geocoder geocoder = new android.location.Geocoder(this);
List<android.location.Address> addresses = geocoder.getFromLocationName(address, 1);
if (addresses != null && !addresses.isEmpty()) {
restaurantLat = addresses.get(0).getLatitude();
restaurantLon = addresses.get(0).getLongitude();
settingsLoaded = true;
} else {
runOnUiThread(() -> txtStatus.setText("Erro: Não foi possível localizar a morada no mapa."));
}
} catch (Exception e) {
runOnUiThread(() -> txtStatus.setText("Erro ao obter coordenadas da morada."));
}
}).start();
}
private void startLocationUpdates() {
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
txtStatus.setText("Permissão de localização negada.");
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
try {
@@ -143,7 +199,9 @@ public class CheckInAntecipadoActivity extends AppCompatActivity implements Loca
@Override
protected void onStop() {
super.onStop();
locationManager.removeUpdates(this);
if (locationManager != null) {
locationManager.removeUpdates(this);
}
}
@Override

View File

@@ -53,22 +53,41 @@ public class ClientDashboardActivity extends AppCompatActivity {
});
updateGreeting();
fetchProfilePicture();
setupCategories();
setupActions();
loadNextReservation();
}
private void updateGreeting() {
txtGreeting.setText(String.format("Olá, %s", displayName != null ? displayName : "convidado"));
}
private void fetchProfilePicture() {
if (email == null) return;
String documentId = email.replace(".", "_").replace("@", "_at_");
com.google.firebase.database.FirebaseDatabase.getInstance().getReference()
.child("users").child(documentId).child("photoUrl")
.get().addOnSuccessListener(snapshot -> {
if (!isDestroyed() && snapshot.exists()) {
String photoUrl = snapshot.getValue(String.class);
if (photoUrl != null && !photoUrl.isEmpty()) {
android.widget.ImageView imgProfile = findViewById(R.id.imgProfile);
com.bumptech.glide.Glide.with(this).load(photoUrl).circleCrop().into(imgProfile);
}
}
});
}
private void setupCategories() {
RecyclerView rv = findViewById(R.id.rvCategories);
List<FoodCategory> cats = new ArrayList<>();
cats.add(new FoodCategory("Carnes", R.drawable.cat_carnes));
cats.add(new FoodCategory("Massas", R.drawable.cat_massas));
cats.add(new FoodCategory("Sushi", R.drawable.cat_sushi));
cats.add(new FoodCategory("Pizzas", R.drawable.cat_pizzas));
cats.add(new FoodCategory("Sobremesas", R.drawable.cat_sobremesas));
// Using circle_bg as placeholder for missing images
cats.add(new FoodCategory("Pizzas", R.drawable.circle_bg));
cats.add(new FoodCategory("Sobremesas", R.drawable.circle_bg));
FoodCategoryAdapter adapter = new FoodCategoryAdapter(cats, category -> {
Intent intent = new Intent(this, ExplorarRestaurantesActivity.class);
@@ -88,16 +107,118 @@ public class ClientDashboardActivity extends AppCompatActivity {
findViewById(R.id.btnVoltar).setOnClickListener(v -> finish());
findViewById(R.id.btnCheckIn).setOnClickListener(v -> {
Intent intent = new Intent(this, CheckInAntecipadoActivity.class);
intent.putExtra("restaurant_email", "sabor_arte@restaurante.com");
startActivity(intent);
});
findViewById(R.id.btnPartilhar).setOnClickListener(
v -> startActivity(new Intent(this, PartilharReservaActivity.class)));
findViewById(R.id.btnNovaReserva)
.setOnClickListener(v -> startActivity(new Intent(this, NovaReservaActivity.class)));
findViewById(R.id.btnMinhasReservas)
.setOnClickListener(v -> {
Intent intent = new Intent(this, MinhasReservasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, email);
startActivity(intent);
});
}
private void loadNextReservation() {
if (email == null) return;
com.google.firebase.database.DatabaseReference ref = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reservas");
ref.orderByChild("clienteEmail").equalTo(email).addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(com.google.firebase.database.DataSnapshot snapshot) {
com.example.pap_teste.models.Reserva proxima = null;
long timeProx = Long.MAX_VALUE;
long currentTime = System.currentTimeMillis();
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("dd/MM/yyyy HH:mm", java.util.Locale.getDefault());
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
com.example.pap_teste.models.Reserva r = ds.getValue(com.example.pap_teste.models.Reserva.class);
if (r != null && (r.getEstado().startsWith("Confirmada") || r.getEstado().equals("Pendente"))) {
try {
java.util.Date rDate = sdf.parse(r.getData() + " " + r.getHora());
if (rDate != null) {
long rTime = rDate.getTime();
// Consider reservations from the last 2 hours and in the future
if (rTime >= currentTime - 2L * 60 * 60 * 1000) {
if (rTime < timeProx) {
timeProx = rTime;
proxima = r;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
TextView txtTitle = findViewById(R.id.txtResTitle);
TextView txtTime = findViewById(R.id.txtResTime);
Button btnCheckIn = findViewById(R.id.btnCheckIn);
Button btnPartilhar = findViewById(R.id.btnPartilhar);
if (proxima != null) {
txtTitle.setText(proxima.getRestauranteName());
// Formatting to show "Hoje", "Amanhã" or the date itself
String dateStr = proxima.getData();
try {
java.util.Date rDate = sdf.parse(proxima.getData() + " " + proxima.getHora());
if (rDate != null) {
java.util.Calendar cal = java.util.Calendar.getInstance();
cal.setTime(rDate);
java.util.Calendar today = java.util.Calendar.getInstance();
java.util.Calendar tmr = java.util.Calendar.getInstance();
tmr.add(java.util.Calendar.DAY_OF_YEAR, 1);
if (cal.get(java.util.Calendar.YEAR) == today.get(java.util.Calendar.YEAR) && cal.get(java.util.Calendar.DAY_OF_YEAR) == today.get(java.util.Calendar.DAY_OF_YEAR)) {
dateStr = "Hoje";
} else if (cal.get(java.util.Calendar.YEAR) == tmr.get(java.util.Calendar.YEAR) && cal.get(java.util.Calendar.DAY_OF_YEAR) == tmr.get(java.util.Calendar.DAY_OF_YEAR)) {
dateStr = "Amanhã";
}
}
} catch (Exception e) {
// ignore
}
txtTime.setText(String.format("%s às %s • %d pessoas", dateStr, proxima.getHora(), proxima.getPessoas()));
btnCheckIn.setVisibility(android.view.View.VISIBLE);
btnPartilhar.setVisibility(android.view.View.VISIBLE);
String encodedEmail = proxima.getRestauranteEmail().replace(".", "_").replace("@", "_at_");
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users").child(encodedEmail)
.child("logoUrl").get().addOnSuccessListener(s -> {
if (!isDestroyed() && s.exists() && s.getValue() != null) {
android.widget.ImageView imgResIcon = findViewById(R.id.imgResIcon);
com.bumptech.glide.Glide.with(ClientDashboardActivity.this).load(s.getValue(String.class)).circleCrop().into(imgResIcon);
}
});
com.example.pap_teste.models.Reserva finalProxima = proxima;
btnCheckIn.setOnClickListener(v -> {
boolean hasLocationPermission = androidx.core.app.ActivityCompat.checkSelfPermission(ClientDashboardActivity.this, android.Manifest.permission.ACCESS_FINE_LOCATION) == android.content.pm.PackageManager.PERMISSION_GRANTED;
if (!hasLocationPermission) {
android.widget.Toast.makeText(ClientDashboardActivity.this, "Dê permissões de localização para aceder ao check-in.", android.widget.Toast.LENGTH_SHORT).show();
} else {
Intent intent = new Intent(ClientDashboardActivity.this, CheckInAntecipadoActivity.class);
intent.putExtra("restaurant_email", finalProxima.getRestauranteEmail());
startActivity(intent);
}
});
} else {
txtTitle.setText("Sem reservas ativas");
txtTime.setText("Explore os restaurantes e reserve!");
btnCheckIn.setVisibility(android.view.View.GONE);
btnPartilhar.setVisibility(android.view.View.GONE);
android.widget.ImageView imgResIcon = findViewById(R.id.imgResIcon);
imgResIcon.setImageResource(R.drawable.circle_bg);
}
}
@Override
public void onCancelled(com.google.firebase.database.DatabaseError error) {}
});
}
}

View File

@@ -22,11 +22,26 @@ 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, inputLatitude, inputLongitude;
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) {
@@ -47,20 +62,90 @@ public class DefinicoesAdminActivity extends AppCompatActivity {
databaseReference = FirebaseDatabase.getInstance().getReference().child("users");
inputRadius = findViewById(R.id.inputRadius);
inputLatitude = findViewById(R.id.inputLatitude);
inputLongitude = findViewById(R.id.inputLongitude);
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;
@@ -73,13 +158,26 @@ public class DefinicoesAdminActivity extends AppCompatActivity {
Object val = snapshot.child("securityDistance").getValue();
inputRadius.setText(val != null ? val.toString() : "");
}
if (snapshot.hasChild("latitude")) {
Object val = snapshot.child("latitude").getValue();
inputLatitude.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("longitude")) {
Object val = snapshot.child("longitude").getValue();
inputLongitude.setText(val != null ? val.toString() : "");
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);
}
}
}
}
@@ -96,23 +194,24 @@ public class DefinicoesAdminActivity extends AppCompatActivity {
return;
String radiusStr = inputRadius.getText().toString().trim();
String latStr = inputLatitude.getText().toString().trim();
String lonStr = inputLongitude.getText().toString().trim();
String addressStr = inputAddress.getText().toString().trim();
String selectedCategory = spinnerCategory.getSelectedItem().toString();
if (TextUtils.isEmpty(radiusStr) || TextUtils.isEmpty(latStr) || TextUtils.isEmpty(lonStr)) {
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);
double lat = Double.parseDouble(latStr);
double lon = Double.parseDouble(lonStr);
Map<String, Object> updates = new HashMap<>();
updates.put("securityDistance", radius);
updates.put("latitude", lat);
updates.put("longitude", lon);
updates.put("address", addressStr);
updates.put("category", selectedCategory);
if (photoUrl != null) {
updates.put("logoUrl", photoUrl);
}
databaseReference.child(documentId).updateChildren(updates)
.addOnSuccessListener(aVoid -> {
@@ -123,7 +222,7 @@ public class DefinicoesAdminActivity extends AppCompatActivity {
e -> Toast.makeText(this, "Falha ao guardar definições.", Toast.LENGTH_SHORT).show());
} catch (NumberFormatException e) {
Toast.makeText(this, "Valores inválidos.", Toast.LENGTH_SHORT).show();
Toast.makeText(this, "Raio inválido.", Toast.LENGTH_SHORT).show();
}
}
}

View File

@@ -18,7 +18,7 @@ import java.util.List;
public class DetalhesReservasActivity extends AppCompatActivity {
private final List<ReservaItem> reservas = new ArrayList<>();
private final List<com.example.pap_teste.models.Reserva> reservas = new ArrayList<>();
private ArrayAdapter<String> adapter;
private ListView listReservas;
private TextView txtInfo;
@@ -27,6 +27,8 @@ public class DetalhesReservasActivity extends AppCompatActivity {
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) {
@@ -39,10 +41,14 @@ public class DetalhesReservasActivity extends AppCompatActivity {
return insets;
});
restaurantEmail = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
if (restaurantEmail == null)
restaurantEmail = "sabor_arte@restaurante.com";
bindViews();
seedReservasDemo();
setupList();
setupActions();
loadReservas();
}
private void bindViews() {
@@ -62,17 +68,36 @@ public class DetalhesReservasActivity extends AppCompatActivity {
}
}
private void seedReservasDemo() {
reservas.add(new ReservaItem("Ana Ribeiro", "Mesa 12", "20h00", 4, "Aniversário", "Confirmada"));
reservas.add(new ReservaItem("Bruno Costa", "Mesa 03", "21h15", 2, "Preferência por janela", "Pendente"));
reservas.add(new ReservaItem("Carla Silva", "Mesa 07", "19h30", 3, "Levar bolo para a mesa", "Pendente"));
reservas.add(new ReservaItem("Duarte Neves", "Mesa 01", "22h00", 2, "", "Concluída"));
private void loadReservas() {
databaseReference = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reservas");
databaseReference.orderByChild("restauranteEmail").equalTo(restaurantEmail)
.addValueEventListener(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(
@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
reservas.clear();
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())) {
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);
refreshList();
listReservas.setOnItemClickListener((parent, view, position, id) -> {
selectedIndex = position;
@@ -83,10 +108,10 @@ public class DetalhesReservasActivity extends AppCompatActivity {
private void setupActions() {
if (btnConfirmar != null) {
btnConfirmar.setOnClickListener(v -> {
ReservaItem item = reservas.get(selectedIndex);
if ("Pendente".equals(item.estado)) {
com.example.pap_teste.models.Reserva item = reservas.get(selectedIndex);
if ("Pendente".equals(item.getEstado())) {
atualizarEstadoSelecionado("Confirmada");
} else if ("Confirmada".equals(item.estado)) {
} else if ("Confirmada".equals(item.getEstado())) {
atualizarEstadoSelecionado("Concluída");
}
});
@@ -120,43 +145,64 @@ public class DetalhesReservasActivity extends AppCompatActivity {
return;
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Apagar Reserva")
.setMessage("Tem a certeza que deseja apagar esta reserva?")
.setPositiveButton("Apagar", (dialog, which) -> {
reservas.remove(selectedIndex);
selectedIndex = -1;
txtInfo.setText("Selecione uma reserva");
txtNotas.setText("");
txtEstado.setText("Estado:");
txtMensagem.setText("Reserva apagada com sucesso.");
toggleButtons(null);
refreshList();
.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) {
if (selectedIndex < 0 || selectedIndex >= reservas.size()) {
Toast.makeText(this, "Selecione uma reserva para atualizar.", Toast.LENGTH_SHORT).show();
return;
}
ReservaItem item = reservas.get(selectedIndex);
item.estado = novoEstado;
txtMensagem.setText(String.format("Reserva de %s marcada como %s.", item.nomeCliente, novoEstado));
mostrarDetalhe(item);
refreshList();
com.example.pap_teste.models.Reserva item = reservas.get(selectedIndex);
databaseReference.child(item.getId()).child("estado").setValue(novoEstado).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
txtMensagem
.setText(String.format("Reserva de %s marcada como %s.", item.getClienteEmail(), novoEstado));
mostrarDetalhe(item);
}
});
}
private void mostrarDetalhe(ReservaItem item) {
txtInfo.setText(String.format("%s • %s • %s • %dp", item.nomeCliente, item.mesa, item.hora, item.pessoas));
txtNotas.setText(item.notas);
txtEstado.setText(String.format("Estado: %s", item.estado));
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());
txtEstado.setText(String.format("Estado: %s", item.getEstado()));
toggleButtons(item);
}
private void toggleButtons(ReservaItem 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);
@@ -164,7 +210,7 @@ public class DetalhesReservasActivity extends AppCompatActivity {
return;
}
switch (item.estado) {
switch (item.getEstado()) {
case "Pendente":
btnConfirmar.setText("Confirmar");
btnConfirmar.setVisibility(android.view.View.VISIBLE);
@@ -192,28 +238,11 @@ public class DetalhesReservasActivity extends AppCompatActivity {
private void refreshList() {
adapter.clear();
for (ReservaItem item : reservas) {
String resumo = String.format("%s - %s (%s) • %s", item.hora, item.mesa, item.estado, item.nomeCliente);
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();
}
private static class ReservaItem {
String nomeCliente;
String mesa;
String hora;
int pessoas;
String notas;
String estado;
ReservaItem(String nomeCliente, String mesa, String hora, int pessoas, String notas, String estado) {
this.nomeCliente = nomeCliente;
this.mesa = mesa;
this.hora = hora;
this.pessoas = pessoas;
this.notas = notas;
this.estado = estado;
}
}
}

View File

@@ -52,7 +52,11 @@ public class EstablishmentDashboardActivity extends AppCompatActivity {
btnGerirMesas.setOnClickListener(v -> startActivity(new Intent(this, GerirMesasActivity.class)));
btnDetails.setOnClickListener(v -> startActivity(new Intent(this, DetalhesReservasActivity.class)));
btnDetails.setOnClickListener(v -> {
Intent intent = new Intent(this, DetalhesReservasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, getIntent().getStringExtra(MainActivity.EXTRA_EMAIL));
startActivity(intent);
});
btnSettings.setOnClickListener(v -> {
Intent intent = new Intent(this, DefinicoesAdminActivity.class);
@@ -60,9 +64,160 @@ public class EstablishmentDashboardActivity extends AppCompatActivity {
startActivity(intent);
});
findViewById(R.id.cardReservasHoje).setOnClickListener(v -> {
Intent intent = new Intent(this, DetalhesReservasActivity.class);
intent.putExtra(MainActivity.EXTRA_EMAIL, getIntent().getStringExtra(MainActivity.EXTRA_EMAIL));
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;
}
}
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")
.orderByChild("restauranteEmail").equalTo(email)
.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())) {
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;
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.orderByChild("restauranteEmail").equalTo(finalEmail)
.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.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 && finalEmail.equals(mesa.getRestauranteEmail())) {
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

@@ -12,6 +12,32 @@ import android.widget.Button;
public class ExplorarRestaurantesActivity extends AppCompatActivity {
private enum State {
LIST, DETAILS
}
private State currentState = State.LIST;
private com.example.pap_teste.models.Restaurant selectedRestaurant = null;
private android.view.View scrollReservaDetails;
private androidx.recyclerview.widget.RecyclerView rvRestaurants;
private android.widget.TextView txtTitle;
private String selectedDate = null;
private String selectedTime = null;
private final androidx.activity.result.ActivityResultLauncher<android.content.Intent> photoPickerLauncher = registerForActivityResult(
new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
result -> {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
android.net.Uri imageUri = result.getData().getData();
if (imageUri != null) {
uploadRestaurantPhoto(imageUri);
}
}
});
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -23,44 +49,267 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity {
return insets;
});
rvRestaurants = findViewById(R.id.rvRestaurants);
scrollReservaDetails = findViewById(R.id.scrollReservaDetails);
txtTitle = findViewById(R.id.txtTituloExplorar);
Button back = findViewById(R.id.btnVoltar);
if (back != null) {
back.setOnClickListener(v -> finish());
back.setOnClickListener(v -> handleBackNavigation());
}
setupRestaurantList();
updateViewState();
}
private void handleBackNavigation() {
if (currentState == State.DETAILS) {
currentState = State.LIST;
updateViewState();
} else {
finish();
}
}
private void updateViewState() {
rvRestaurants.setVisibility(currentState == State.LIST ? android.view.View.VISIBLE : android.view.View.GONE);
scrollReservaDetails
.setVisibility(currentState == State.DETAILS ? android.view.View.VISIBLE : android.view.View.GONE);
if (currentState == State.LIST) {
String filter = getIntent().getStringExtra("category_filter");
txtTitle.setText(filter != null ? "Explorar: " + filter : "Explorar restaurantes");
} else {
txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : ""));
setupReservationOptions();
}
}
private void setupRestaurantList() {
String filter = getIntent().getStringExtra("category_filter");
android.widget.TextView txtTitle = findViewById(R.id.txtTituloExplorar);
if (filter != null && txtTitle != null) {
txtTitle.setText("Explorar: " + filter);
}
androidx.recyclerview.widget.RecyclerView rv = findViewById(R.id.rvRestaurants);
java.util.List<com.example.pap_teste.models.Restaurant> allRestaurants = new java.util.ArrayList<>();
allRestaurants.add(new com.example.pap_teste.models.Restaurant("Sabor & Arte", "Tradicional", true));
allRestaurants.add(new com.example.pap_teste.models.Restaurant("Pizzeria Bella", "Pizzas", false));
allRestaurants.add(new com.example.pap_teste.models.Restaurant("Sushi Zen", "Sushi", false));
allRestaurants.add(new com.example.pap_teste.models.Restaurant("O Chuveiro", "Frutos do Mar", true));
allRestaurants.add(new com.example.pap_teste.models.Restaurant("Hamburgueria Real", "Carnes", false));
allRestaurants.add(new com.example.pap_teste.models.Restaurant("Churrascaria Gaúcha", "Carnes", false));
allRestaurants.add(new com.example.pap_teste.models.Restaurant("Sushi House", "Sushi", false));
allRestaurants.add(new com.example.pap_teste.models.Restaurant("Doce Momento", "Sobremesas", false));
java.util.List<com.example.pap_teste.models.Restaurant> filteredList = new java.util.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("users");
com.google.firebase.database.Query query = usersRef;
if (filter != null) {
for (com.example.pap_teste.models.Restaurant r : allRestaurants) {
if (r.getCategory().equalsIgnoreCase(filter)) {
filteredList.add(r);
}
}
} else {
filteredList.addAll(allRestaurants);
query = usersRef.orderByChild("category").equalTo(filter);
}
RestaurantAdapter adapter = new RestaurantAdapter(filteredList);
rv.setAdapter(adapter);
query.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
restaurantsList.clear();
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
String role = ds.child("role").getValue(String.class);
String accountType = ds.child("accountType").getValue(String.class);
if ("ADMIN".equalsIgnoreCase(role) || "ESTABELECIMENTO".equalsIgnoreCase(accountType)) {
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);
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
android.widget.Toast.makeText(ExplorarRestaurantesActivity.this, "Erro ao carregar restaurantes.", android.widget.Toast.LENGTH_SHORT).show();
}
});
}
private void setupReservationOptions() {
android.widget.Button btnDate = findViewById(R.id.btnSelectDate);
android.widget.Button btnTime = findViewById(R.id.btnSelectTime);
android.widget.Button btnVerAvaliacoes = findViewById(R.id.btnVerAvaliacoes);
android.widget.Button btnUploadFoto = findViewById(R.id.btnUploadFoto);
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());
btnVerAvaliacoes.setOnClickListener(v -> showReviewsDialog());
btnUploadFoto.setOnClickListener(v -> {
android.content.Intent intent = new android.content.Intent(android.content.Intent.ACTION_PICK);
intent.setType("image/*");
photoPickerLauncher.launch(intent);
});
}
private void uploadRestaurantPhoto(android.net.Uri imageUri) {
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());
storageRef.putFile(imageUri).addOnSuccessListener(taskSnapshot -> {
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() {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return;
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_");
com.google.firebase.database.DatabaseReference reviewsRef = com.google.firebase.database.FirebaseDatabase.getInstance()
.getReference("reviews").child(encodedEmail);
reviewsRef.addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
java.util.List<String> 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);
if (author != null && text != null) {
reviewsList.add(author + "\n" + text);
}
}
String[] reviewsArray = reviewsList.toArray(new String[0]);
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(ExplorarRestaurantesActivity.this)
.setTitle("Avaliações")
.setItems(reviewsArray, null)
.setPositiveButton("Fechar", null)
.setNeutralButton("Adicionar", (dialog, which) -> addReviewDialog());
if (reviewsList.isEmpty()) {
builder.setMessage("Ainda sem avaliações.");
}
builder.show();
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
android.widget.Toast.makeText(ExplorarRestaurantesActivity.this, "Erro ao carregar avaliações.", android.widget.Toast.LENGTH_SHORT).show();
}
});
}
private void addReviewDialog() {
if (selectedRestaurant == null || selectedRestaurant.getEmail() == null) return;
android.widget.EditText input = new android.widget.EditText(this);
input.setHint("Escreva a sua avaliação aqui...");
new androidx.appcompat.app.AlertDialog.Builder(this)
.setTitle("Adicionar Avaliação")
.setView(input)
.setPositiveButton("Enviar", (dialog, which) -> {
String revText = input.getText().toString().trim();
if (!revText.isEmpty()) {
com.google.firebase.auth.FirebaseUser currentUser = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
if (currentUser != null && currentUser.getEmail() != null) {
String userDoc = currentUser.getEmail().replace(".", "_").replace("@", "_at_");
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users").child(userDoc).get().addOnSuccessListener(snapshot -> {
String authorName = snapshot.exists() && snapshot.child("displayName").getValue(String.class) != null
? snapshot.child("displayName").getValue(String.class)
: "Visitante";
submitReviewToFirebase(authorName, revText);
}).addOnFailureListener(e -> submitReviewToFirebase("Visitante", revText));
} else {
submitReviewToFirebase("Visitante", revText);
}
}
})
.setNegativeButton("Cancelar", null)
.show();
}
private void submitReviewToFirebase(String authorName, String revText) {
String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_");
java.util.Map<String, Object> review = new java.util.HashMap<>();
review.put("author", authorName);
review.put("text", revText);
com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reviews")
.child(encodedEmail).push().setValue(review).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
android.widget.Toast.makeText(this, "Avaliação enviada!", android.widget.Toast.LENGTH_SHORT).show();
}
});
}
private void saveReservation() {
android.widget.EditText etPartySize = findViewById(R.id.etPartySize);
int partySize = 0;
try {
partySize = Integer.parseInt(etPartySize.getText().toString());
} catch (Exception e) {}
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 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,
selectedRestaurant.getName(),
selectedRestaurant.getEmail(),
selectedDate,
selectedTime,
partySize,
"Pendente");
if (id != null) {
ref.child(id).setValue(reserva).addOnCompleteListener(task -> {
if (task.isSuccessful()) {
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

@@ -3,15 +3,33 @@ package com.example.pap_teste;
import android.os.Bundle;
import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.widget.Button;
import android.widget.Toast;
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.ArrayList;
import java.util.List;
public class FavoritosActivity extends AppCompatActivity {
private androidx.recyclerview.widget.RecyclerView rv;
private RestaurantAdapter adapter;
private List<Restaurant> list;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -28,16 +46,39 @@ public class FavoritosActivity extends AppCompatActivity {
back.setOnClickListener(v -> finish());
}
rv = findViewById(R.id.rvFavoritos);
list = new ArrayList<>();
adapter = new RestaurantAdapter(list, null);
rv.setAdapter(adapter);
setupFavoritesList();
}
private void setupFavoritesList() {
androidx.recyclerview.widget.RecyclerView rv = findViewById(R.id.rvFavoritos);
java.util.List<com.example.pap_teste.models.Restaurant> list = new java.util.ArrayList<>();
list.add(new com.example.pap_teste.models.Restaurant("Sabor & Arte", "Tradicional", true));
list.add(new com.example.pap_teste.models.Restaurant("O Chuveiro", "Mariscos", true));
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
if (user != null && user.getEmail() != null) {
String encodedUserEmail = user.getEmail().replace(".", "_").replace("@", "_at_");
DatabaseReference favRef = FirebaseDatabase.getInstance().getReference("users")
.child(encodedUserEmail).child("favorites");
RestaurantAdapter adapter = new RestaurantAdapter(list);
rv.setAdapter(adapter);
favRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
list.clear();
for (DataSnapshot ds : snapshot.getChildren()) {
Restaurant restaurant = ds.getValue(Restaurant.class);
if (restaurant != null) {
list.add(restaurant);
}
}
adapter.notifyDataSetChanged();
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(FavoritosActivity.this, "Erro ao carregar favoritos.", Toast.LENGTH_SHORT).show();
}
});
}
}
}

View File

@@ -21,6 +21,7 @@ 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;
@@ -86,9 +87,12 @@ public class GerirMesasActivity extends AppCompatActivity {
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) {
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);
@@ -184,10 +188,13 @@ public class GerirMesasActivity extends AppCompatActivity {
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);
Mesa novaMesa = new Mesa(mesaId, numero, capacidade, estado, currentUserEmail);
if (mesaId != null) {
mDatabase.child(mesaId).setValue(novaMesa);
}

View File

@@ -160,6 +160,11 @@ public class GestaoStaffActivity extends AppCompatActivity {
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
@@ -216,6 +221,38 @@ public class GestaoStaffActivity extends AppCompatActivity {
}
}
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)) {

View File

@@ -10,8 +10,32 @@ 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);
@@ -23,11 +47,199 @@ public class ListaEsperaActivity extends AppCompatActivity {
return insets;
});
restaurantEmail = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
if (restaurantEmail == null) {
// Se o extra não chegou, falha segura
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();
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.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 registadas. 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.orderByChild("restauranteEmail").equalTo(restaurantEmail)
.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull DataSnapshot snapshot) {
reservasPendentes.clear();
for (DataSnapshot data : snapshot.getChildren()) {
Reserva r = data.getValue(Reserva.class);
if (r != null && "Pendente".equals(r.getEstado())) {
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

@@ -19,6 +19,7 @@ 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;
@@ -28,7 +29,9 @@ 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 {
@@ -64,20 +67,23 @@ public class MainActivity extends AppCompatActivity {
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[]> locationPermissionRequest = registerForActivityResult(
private final ActivityResultLauncher<String[]> permissionRequest = registerForActivityResult(
new ActivityResultContracts.RequestMultiplePermissions(), result -> {
Boolean fineLocationGranted = result.getOrDefault(
Manifest.permission.ACCESS_FINE_LOCATION, false);
Boolean coarseLocationGranted = result.getOrDefault(
Manifest.permission.ACCESS_COARSE_LOCATION, false);
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 if (coarseLocationGranted != null && coarseLocationGranted) {
// Only approximate location access granted.
} else {
Toast.makeText(this, "A permissão de localização é necessária para o check-in.", Toast.LENGTH_LONG)
.show();
@@ -103,26 +109,42 @@ public class MainActivity extends AppCompatActivity {
setupTypeToggle();
setupActionToggle();
setupPrimaryAction();
checkLocationPermissions();
checkPermissions();
}
private void checkLocationPermissions() {
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
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 {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
permissionsNeeded.add(Manifest.permission.READ_EXTERNAL_STORAGE);
}
}
if (!permissionsNeeded.isEmpty()) {
new AlertDialog.Builder(this)
.setTitle("Partilha de localização")
.setMessage(
"Para permitir o check-in antecipado, precisamos de saber a sua distância ao restaurante. Deseja permitir a partilha de localização?")
.setPositiveButton("Sim", (dialog, which) -> {
locationPermissionRequest.launch(new String[] {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
});
})
.setNegativeButton("Não", (dialog, which) -> {
Toast.makeText(this, "A localização foi recusada. O check-in poderá não funcionar.",
Toast.LENGTH_SHORT).show();
.setTitle("Permissões Necessárias")
.setMessage("Para o correto funcionamento do check-in, serviços de proximidade e fotos da galeria, precisamos de algumas permissões.")
.setPositiveButton("Configurar", (dialog, which) -> {
permissionRequest.launch(permissionsNeeded.toArray(new String[0]));
})
.setNegativeButton("Agora Não", null)
.show();
}
}
@@ -140,6 +162,50 @@ public class MainActivity extends AppCompatActivity {
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);
setupPasswordFeatures();
}
private void setupPasswordFeatures() {
iconPasswordVisibility.setOnClickListener(v -> {
isPasswordVisible = !isPasswordVisible;
if (isPasswordVisible) {
inputPassword.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD);
iconPasswordVisibility.setImageResource(R.drawable.ic_visibility);
} else {
inputPassword.setInputType(android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_VARIATION_PASSWORD);
iconPasswordVisibility.setImageResource(R.drawable.ic_visibility_off);
}
inputPassword.setSelection(inputPassword.getText().length());
});
txtForgotPassword.setOnClickListener(v -> {
String email = inputEmail.getText().toString().trim();
if (TextUtils.isEmpty(email)) {
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();
}
});
}
})
.setNegativeButton("Não", null)
.show();
});
}
private void setupTypeToggle() {
@@ -208,6 +274,10 @@ public class MainActivity extends AppCompatActivity {
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() {

View File

@@ -0,0 +1,117 @@
package com.example.pap_teste;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Button;
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 androidx.recyclerview.widget.RecyclerView;
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 MinhasReservasActivity extends AppCompatActivity {
private RecyclerView rvMinhasReservas;
private ReservaAdapter adapter;
private final List<Reserva> reservaList = new ArrayList<>();
private DatabaseReference databaseReference;
private String clientEmail;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
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;
});
clientEmail = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL);
if (clientEmail == null) {
com.google.firebase.auth.FirebaseUser user = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser();
if (user != null) {
clientEmail = user.getEmail();
}
}
// Fallback for testing if still null
if (clientEmail == null) {
clientEmail = "cliente@teste.com";
}
rvMinhasReservas = findViewById(R.id.rvMinhasReservas);
Button btnVoltar = findViewById(R.id.btnVoltar);
btnVoltar.setOnClickListener(v -> finish());
setupAdapter();
loadReservations();
}
private void setupAdapter() {
adapter = new ReservaAdapter(reservaList, new ReservaAdapter.OnReservaActionListener() {
@Override
public void onCheckIn(Reserva reserva) {
Intent intent = new Intent(MinhasReservasActivity.this, CheckInAntecipadoActivity.class);
intent.putExtra("restaurant_email", reserva.getRestauranteEmail());
startActivity(intent);
}
@Override
public void onCancel(Reserva reserva) {
cancelReservation(reserva);
}
});
rvMinhasReservas.setAdapter(adapter);
}
private void loadReservations() {
databaseReference = FirebaseDatabase.getInstance().getReference("reservas");
databaseReference.orderByChild("clienteEmail").equalTo(clientEmail)
.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
reservaList.clear();
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
Reserva reserva = dataSnapshot.getValue(Reserva.class);
if (reserva != null) {
reservaList.add(reserva);
}
}
adapter.notifyDataSetChanged();
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Toast.makeText(MinhasReservasActivity.this, "Erro ao carregar reservas.", Toast.LENGTH_SHORT)
.show();
}
});
}
private void cancelReservation(Reserva reserva) {
databaseReference.child(reserva.getId()).child("estado").setValue("Cancelada")
.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();
}
});
}
}

View File

@@ -13,6 +13,18 @@ import android.widget.Button;
public class NovaReservaActivity extends AppCompatActivity {
private enum State {
CATEGORIES, RESTAURANTS, DETAILS
}
private State currentState = State.CATEGORIES;
private String selectedCategory = null;
private com.example.pap_teste.models.Restaurant selectedRestaurant = null;
private androidx.recyclerview.widget.RecyclerView rvCategories, rvRestaurants;
private android.view.View scrollNovaReserva;
private android.widget.TextView txtTitle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -24,45 +36,255 @@ public class NovaReservaActivity extends AppCompatActivity {
return insets;
});
rvCategories = findViewById(R.id.rvCategories);
rvRestaurants = findViewById(R.id.rvRestaurants);
scrollNovaReserva = findViewById(R.id.scrollNovaReserva);
txtTitle = findViewById(R.id.txtTituloNovaReserva);
Button back = findViewById(R.id.btnVoltar);
if (back != null) {
back.setOnClickListener(v -> finish());
back.setOnClickListener(v -> handleBackNavigation());
}
setupReservationOptions();
setupCategories();
updateViewState();
}
private void handleBackNavigation() {
if (currentState == State.RESTAURANTS) {
currentState = State.CATEGORIES;
updateViewState();
} else if (currentState == State.DETAILS) {
currentState = State.RESTAURANTS;
updateViewState();
} else {
finish();
}
}
private void updateViewState() {
rvCategories
.setVisibility(currentState == State.CATEGORIES ? android.view.View.VISIBLE : android.view.View.GONE);
rvRestaurants
.setVisibility(currentState == State.RESTAURANTS ? android.view.View.VISIBLE : android.view.View.GONE);
scrollNovaReserva
.setVisibility(currentState == State.DETAILS ? android.view.View.VISIBLE : android.view.View.GONE);
if (currentState == State.CATEGORIES) {
txtTitle.setText("Escolha o tema");
} else if (currentState == State.RESTAURANTS) {
txtTitle.setText("Restaurantes: " + selectedCategory);
} else {
txtTitle.setText("Reserva: " + (selectedRestaurant != null ? selectedRestaurant.getName() : ""));
setupReservationOptions();
}
}
private void setupCategories() {
java.util.List<com.example.pap_teste.models.FoodCategory> cats = new java.util.ArrayList<>();
cats.add(new com.example.pap_teste.models.FoodCategory("Carnes", R.drawable.cat_carnes));
cats.add(new com.example.pap_teste.models.FoodCategory("Massas", R.drawable.cat_massas));
cats.add(new com.example.pap_teste.models.FoodCategory("Sushi", R.drawable.cat_sushi));
cats.add(new com.example.pap_teste.models.FoodCategory("Pizzas", R.drawable.cat_pizzas));
cats.add(new com.example.pap_teste.models.FoodCategory("Sobremesas", R.drawable.cat_sobremesas));
rvCategories.setLayoutManager(new androidx.recyclerview.widget.LinearLayoutManager(this));
rvCategories.setAdapter(new FoodCategoryAdapter(cats, category -> {
selectedCategory = category.getName();
currentState = State.RESTAURANTS;
setupRestaurants();
updateViewState();
}));
}
private void setupRestaurants() {
java.util.List<com.example.pap_teste.models.Restaurant> filteredList = new java.util.ArrayList<>();
com.google.firebase.database.DatabaseReference usersRef = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("users");
usersRef.orderByChild("category").equalTo(selectedCategory).addListenerForSingleValueEvent(new com.google.firebase.database.ValueEventListener() {
@Override
public void onDataChange(@androidx.annotation.NonNull com.google.firebase.database.DataSnapshot snapshot) {
filteredList.clear();
for (com.google.firebase.database.DataSnapshot ds : snapshot.getChildren()) {
String role = ds.child("role").getValue(String.class);
String accountType = ds.child("accountType").getValue(String.class);
if ("ADMIN".equalsIgnoreCase(role) || "ESTABELECIMENTO".equalsIgnoreCase(accountType)) {
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);
if (name != null && email != null) {
filteredList.add(new com.example.pap_teste.models.Restaurant(name, cat, email, false));
}
}
}
rvRestaurants.setLayoutManager(new androidx.recyclerview.widget.LinearLayoutManager(NovaReservaActivity.this));
rvRestaurants.setAdapter(new RestaurantAdapter(filteredList, restaurant -> {
selectedRestaurant = restaurant;
currentState = State.DETAILS;
updateViewState();
}));
}
@Override
public void onCancelled(@androidx.annotation.NonNull com.google.firebase.database.DatabaseError error) {
android.widget.Toast.makeText(NovaReservaActivity.this, "Erro ao carregar restaurantes.", android.widget.Toast.LENGTH_SHORT).show();
}
});
}
private String selectedDate = null;
private String selectedTime = null;
private void setupReservationOptions() {
// Dates
RecyclerView rvDates = findViewById(R.id.rvDates);
java.util.List<String> dates = new java.util.ArrayList<>();
dates.add("Hoje");
dates.add("Amanhã");
dates.add("Quarta, 12 Mar");
dates.add("Quinta, 13 Mar");
dates.add("Sexta, 14 Mar");
rvDates.setAdapter(new ReservationOptionAdapter(dates));
android.widget.Button btnDate = findViewById(R.id.btnSelectDate);
android.widget.Button btnTime = findViewById(R.id.btnSelectTime);
// Times
RecyclerView rvTimes = findViewById(R.id.rvTimes);
java.util.List<String> times = new java.util.ArrayList<>();
times.add("12:00");
times.add("13:00");
times.add("19:00");
times.add("20:00");
times.add("21:00");
times.add("22:00");
rvTimes.setAdapter(new ReservationOptionAdapter(times));
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();
});
// Party Size
RecyclerView rvParty = findViewById(R.id.rvPartySize);
java.util.List<String> party = new java.util.ArrayList<>();
party.add("1 pessoa");
party.add("2 pessoas");
party.add("3 pessoas");
party.add("4 pessoas");
party.add("5 pessoas");
party.add("6+ pessoas");
rvParty.setAdapter(new ReservationOptionAdapter(party));
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();
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 && restEmail.equals(m.getRestauranteEmail())) {
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.orderByChild("restauranteEmail").equalTo(restEmail).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 && 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,
selectedRestaurant.getName(),
selectedRestaurant.getEmail(),
selectedDate,
selectedTime,
partySize,
"Pendente");
if (id != null) {
ref.child(id).setValue(reserva).addOnCompleteListener(task -> {
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

@@ -13,6 +13,13 @@ import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import android.view.View;
import android.net.Uri;
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 com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
@@ -22,8 +29,9 @@ import java.util.Map;
public class ProfileDashboardActivity extends AppCompatActivity {
private EditText inputName, inputPhone, inputEmailEdit;
private String email, documentId;
private String email, documentId, photoUrl;
private DatabaseReference databaseReference;
private ImageView imgProfile;
private androidx.activity.result.ActivityResultLauncher<Intent> imagePickerLauncher;
@Override
@@ -49,6 +57,7 @@ public class ProfileDashboardActivity extends AppCompatActivity {
inputName = findViewById(R.id.inputProfileName);
inputPhone = findViewById(R.id.inputProfilePhone);
inputEmailEdit = findViewById(R.id.inputProfileEmail);
imgProfile = findViewById(R.id.imgProfile);
if (currentName != null) {
inputName.setText(currentName);
@@ -65,17 +74,25 @@ public class ProfileDashboardActivity extends AppCompatActivity {
if (result.getResultCode() == RESULT_OK && result.getData() != null) {
android.net.Uri imageUri = result.getData().getData();
if (imageUri != null) {
Toast.makeText(this, "Foto selecionada (Simulação: Seria feito o upload)",
Toast.LENGTH_SHORT).show();
// In a real app, you'd upload this to Firebase Storage.
uploadImageToFirebase(imageUri);
}
}
});
findViewById(R.id.cardProfileBig).setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
imagePickerLauncher.launch(intent);
String[] options = {"Galeria", "URL da Imagem"};
androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this);
builder.setTitle("Escolher Foto de Perfil");
builder.setItems(options, (dialog, which) -> {
if (which == 0) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
imagePickerLauncher.launch(intent);
} else {
showUrlInputDialog();
}
});
builder.show();
});
Button btnSave = findViewById(R.id.btnSaveProfile);
@@ -91,10 +108,32 @@ public class ProfileDashboardActivity extends AppCompatActivity {
});
btnRes.setOnClickListener(v -> {
startActivity(new Intent(this, DetalhesReservasActivity.class));
startActivity(new Intent(this, MinhasReservasActivity.class));
});
}
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);
input.setHint("https://exemplo.com/foto.jpg");
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(imgProfile);
Toast.makeText(this, "URL definida!", Toast.LENGTH_SHORT).show();
}
});
builder.setNegativeButton("Cancelar", (dialog, which) -> dialog.cancel());
builder.show();
}
private void fetchAdditionalProfileData() {
if (documentId == null)
return;
@@ -109,10 +148,44 @@ public class ProfileDashboardActivity extends AppCompatActivity {
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()) {
Glide.with(this).load(photoUrl).circleCrop().into(imgProfile);
}
}
});
}
private void uploadImageToFirebase(Uri imageUri) {
if (documentId == null)
return;
StorageReference storageRef = FirebaseStorage.getInstance().getReference()
.child("profile_pics/" + UUID.randomUUID().toString());
storageRef.putFile(imageUri).addOnSuccessListener(taskSnapshot -> {
storageRef.getDownloadUrl().addOnSuccessListener(uri -> {
photoUrl = uri.toString();
Glide.with(this).load(photoUrl).circleCrop().into(imgProfile);
// 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() {
if (documentId == null)
return;
@@ -130,6 +203,9 @@ public class ProfileDashboardActivity extends AppCompatActivity {
updates.put("displayName", newName);
updates.put("phone", newPhone);
updates.put("email", newEmail);
if (photoUrl != null) {
updates.put("photoUrl", photoUrl);
}
databaseReference.child(documentId).updateChildren(updates)
.addOnSuccessListener(aVoid -> {

View File

@@ -0,0 +1,103 @@
package com.example.pap_teste;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.example.pap_teste.models.Reserva;
import java.util.List;
public class ReservaAdapter extends RecyclerView.Adapter<ReservaAdapter.ViewHolder> {
public interface OnReservaActionListener {
void onCheckIn(Reserva reserva);
void onCancel(Reserva reserva);
}
private final List<Reserva> reservas;
private final OnReservaActionListener listener;
public ReservaAdapter(List<Reserva> reservas, OnReservaActionListener listener) {
this.reservas = reservas;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_reserva_cliente, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Reserva reserva = reservas.get(position);
holder.txtRestaurante.setText(reserva.getRestauranteName());
holder.txtDataHora.setText(reserva.getData() + " às " + reserva.getHora() + "" + reserva.getPessoas() + "p");
holder.txtStatus.setText("Estado: " + reserva.getEstado());
// Enable check-in only if confirmed
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;
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.btnCheckIn.setOnClickListener(v -> {
if (listener != null)
listener.onCheckIn(reserva);
});
holder.btnCancelar.setOnClickListener(v -> {
if (listener != null)
listener.onCancel(reserva);
});
}
@Override
public int getItemCount() {
return reservas.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView txtRestaurante, txtDataHora, txtStatus, txtLocationWarning;
Button btnCheckIn, btnCancelar;
public ViewHolder(@NonNull View itemView) {
super(itemView);
txtRestaurante = itemView.findViewById(R.id.txtReservaRestaurante);
txtDataHora = itemView.findViewById(R.id.txtReservaDataHora);
txtStatus = itemView.findViewById(R.id.txtReservaStatus);
btnCheckIn = itemView.findViewById(R.id.btnCheckIn);
btnCancelar = itemView.findViewById(R.id.btnCancelar);
txtLocationWarning = itemView.findViewById(R.id.txtLocationWarning);
}
}
}

View File

@@ -0,0 +1,54 @@
package com.example.pap_teste;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
public class ReservationOptionAdapter extends RecyclerView.Adapter<ReservationOptionAdapter.ViewHolder> {
private List<String> options;
private OnOptionClickListener listener;
public interface OnOptionClickListener {
void onOptionClick(String option);
}
public ReservationOptionAdapter(List<String> options, OnOptionClickListener listener) {
this.options = options;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
String option = options.get(position);
holder.text1.setText(option);
holder.itemView.setOnClickListener(v -> {
if (listener != null) {
listener.onOptionClick(option);
}
});
}
@Override
public int getItemCount() {
return options.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView text1;
public ViewHolder(@NonNull View itemView) {
super(itemView);
text1 = itemView.findViewById(android.R.id.text1);
}
}
}

View File

@@ -0,0 +1,120 @@
package com.example.pap_teste;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
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;
public class RestaurantAdapter extends RecyclerView.Adapter<RestaurantAdapter.ViewHolder> {
private List<Restaurant> restaurants;
private OnRestaurantClickListener listener;
public interface OnRestaurantClickListener {
void onRestaurantClick(Restaurant restaurant);
}
public RestaurantAdapter(List<Restaurant> restaurants, OnRestaurantClickListener listener) {
this.restaurants = restaurants;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_restaurant, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
Restaurant restaurant = restaurants.get(position);
holder.text1.setText(restaurant.getName());
holder.text2.setText(restaurant.getCategory() + (restaurant.isAvailable() ? " - Disponível" : " - Indisponível"));
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);
}
});
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("users")
.child(encodedUserEmail).child("favorites").child(encodedRestEmail);
favRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
if (snapshot.exists()) {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_on);
} else {
holder.btnFavorite.setImageResource(android.R.drawable.btn_star_big_off);
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) { }
});
holder.btnFavorite.setOnClickListener(v -> {
favRef.addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
if (snapshot.exists()) {
favRef.removeValue();
} else {
favRef.setValue(restaurant);
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) { }
});
});
}
}
@Override
public int getItemCount() {
return restaurants.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView text1, text2;
ImageButton btnFavorite;
android.widget.ImageView imgThumb;
public ViewHolder(@NonNull View itemView) {
super(itemView);
text1 = itemView.findViewById(R.id.txtRestaurantName);
text2 = itemView.findViewById(R.id.txtRestaurantCategory);
btnFavorite = itemView.findViewById(R.id.btnFavorite);
imgThumb = itemView.findViewById(R.id.imgRestaurantThumb);
}
}
}

View File

@@ -5,16 +5,18 @@ public class Mesa {
private int numero;
private int capacidade;
private String estado;
private String restauranteEmail;
public Mesa() {
// Default constructor required for calls to DataSnapshot.getValue(Mesa.class)
}
public Mesa(String id, int numero, int capacidade, String estado) {
public Mesa(String id, int numero, int capacidade, String estado, String restauranteEmail) {
this.id = id;
this.numero = numero;
this.capacidade = capacidade;
this.estado = estado;
this.restauranteEmail = restauranteEmail;
}
public String getId() {
@@ -49,6 +51,14 @@ public class Mesa {
this.estado = estado;
}
public String getRestauranteEmail() {
return restauranteEmail;
}
public void setRestauranteEmail(String restauranteEmail) {
this.restauranteEmail = restauranteEmail;
}
@Override
public String toString() {
return "Mesa " + numero;

View File

@@ -0,0 +1,92 @@
package com.example.pap_teste.models;
public class Reserva {
private String id;
private String clienteEmail;
private String restauranteName;
private String restauranteEmail;
private String data;
private String hora;
private int pessoas;
private String estado; // Pendente, Confirmada, Concluída, Cancelada, Recusada
public Reserva() {
// Required for Firebase
}
public Reserva(String id, String clienteEmail, String restauranteName, String restauranteEmail, String data,
String hora, int pessoas, String estado) {
this.id = id;
this.clienteEmail = clienteEmail;
this.restauranteName = restauranteName;
this.restauranteEmail = restauranteEmail;
this.data = data;
this.hora = hora;
this.pessoas = pessoas;
this.estado = estado;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getClienteEmail() {
return clienteEmail;
}
public void setClienteEmail(String clienteEmail) {
this.clienteEmail = clienteEmail;
}
public String getRestauranteName() {
return restauranteName;
}
public void setRestauranteName(String restauranteName) {
this.restauranteName = restauranteName;
}
public String getRestauranteEmail() {
return restauranteEmail;
}
public void setRestauranteEmail(String restauranteEmail) {
this.restauranteEmail = restauranteEmail;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getHora() {
return hora;
}
public void setHora(String hora) {
this.hora = hora;
}
public int getPessoas() {
return pessoas;
}
public void setPessoas(int pessoas) {
this.pessoas = pessoas;
}
public String getEstado() {
return estado;
}
public void setEstado(String estado) {
this.estado = estado;
}
}

View File

@@ -0,0 +1,40 @@
package com.example.pap_teste.models;
public class Restaurant {
private String name;
private String category;
private String email;
private boolean available;
private String logoUrl;
// No-argument constructor required for Firebase
public Restaurant() {
}
public Restaurant(String name, String category, String email, boolean available) {
this.name = name;
this.category = category;
this.email = email;
this.available = available;
}
public Restaurant(String name, String category, String email, boolean available, String logoUrl) {
this.name = name;
this.category = category;
this.email = email;
this.available = available;
this.logoUrl = logoUrl;
}
public String getName() { return name; }
public String getCategory() { return category; }
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 setAvailable(boolean available) { this.available = available; }
public void setLogoUrl(String logoUrl) { this.logoUrl = logoUrl; }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 732 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 KiB

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#757575"
android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 5.99,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#757575"
android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 5.99,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
</vector>

View File

@@ -5,69 +5,147 @@
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7F7F7"
tools:context=".AddStaffActivity">
<EditText
android:id="@+id/nammeEditText"
android:layout_width="0dp"
<!-- Top Bar with Back Button -->
<Button
android:id="@+id/btnVoltarAddStaff"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:ems="10"
android:hint="Name"
android:inputType="text"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
android:background="@android:color/transparent"
android:text="Voltar"
android:textAllCaps="false"
android:textColor="#FF5252"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/zoneLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="@+id/nammeEditText"
app:layout_constraintStart_toStartOf="@+id/nammeEditText"
app:layout_constraintTop_toBottomOf="@+id/nammeEditText">
<Spinner
android:id="@+id/zonaSpinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="48dp" />
<Button
android:id="@+id/btnAddZone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="+" />
</LinearLayout>
<LinearLayout
android:id="@+id/mesaLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="@+id/zoneLayout"
app:layout_constraintStart_toStartOf="@+id/zoneLayout"
app:layout_constraintTop_toBottomOf="@+id/zoneLayout">
<Spinner
android:id="@+id/mesaSpinner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:minHeight="48dp" />
</LinearLayout>
<Button
android:id="@+id/addButton"
<TextView
android:id="@+id/txtAdicionarTitulo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
android:layout_marginTop="20dp"
android:text="Novo Funcionário"
android:textColor="#000"
android:textSize="24sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnVoltarAddStaff" />
<TextView
android:id="@+id/txtAdicionarDesc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Introduza os dados e atribuições deste membro:"
android:textColor="#666666"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtAdicionarTitulo" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp"
android:background="#FFFFFF"
android:elevation="2dp"
android:orientation="vertical"
android:padding="20dp"
app:layout_constraintTop_toBottomOf="@id/txtAdicionarDesc">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome do Funcionário"
android:textColor="#333"
android:textSize="14sp"
android:textStyle="bold" />
<EditText
android:id="@+id/nammeEditText"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:background="@drawable/input_bg"
android:hint="Ex: João Silva"
android:inputType="textPersonName"
android:paddingHorizontal="16dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Zona de Trabalho"
android:textColor="#333"
android:textSize="14sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/zoneLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal"
android:gravity="center_vertical">
<Spinner
android:id="@+id/zonaSpinner"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_weight="1"
android:background="@drawable/input_bg" />
<Button
android:id="@+id/btnAddZone"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:background="@drawable/btn_primary"
android:text="+"
android:textColor="#FFFFFF"
android:textSize="20sp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Mesa a Atribuir"
android:textColor="#333"
android:textSize="14sp"
android:textStyle="bold" />
<LinearLayout
android:id="@+id/mesaLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<Spinner
android:id="@+id/mesaSpinner"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/input_bg" />
</LinearLayout>
<Button
android:id="@+id/addButton"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="32dp"
android:background="@drawable/btn_primary"
android:text="Adicionar Membro"
android:textAllCaps="false"
android:textColor="#FFFFFF"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -73,16 +73,42 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/txtEnderecoRestaurante"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="16dp"
android:text="A carregar morada..."
android:textAlignment="center"
android:textSize="14sp"
android:textColor="#333"
app:layout_constraintTop_toBottomOf="@id/txtStatusDistancia"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/btnAbrirMapa"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Ver no Mapa"
android:textAllCaps="false"
style="@style/Widget.MaterialComponents.Button.TextButton"
app:layout_constraintTop_toBottomOf="@id/txtEnderecoRestaurante"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<Button
android:id="@+id/btnConfirmarChegada"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="32dp"
android:layout_marginTop="16dp"
android:text="Confirmar Chegada"
android:textAllCaps="false"
android:enabled="false"
app:layout_constraintTop_toBottomOf="@id/txtStatusDistancia"
app:layout_constraintTop_toBottomOf="@id/btnAbrirMapa"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />

View File

@@ -25,8 +25,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/circle_bg"
android:padding="8dp" />
android:src="@drawable/circle_bg" />
</androidx.cardview.widget.CardView>
<TextView
@@ -234,6 +233,30 @@
</LinearLayout>
</androidx.cardview.widget.CardView>
<androidx.cardview.widget.CardView
android:id="@+id/cardMinhasReservas"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_margin="8dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
android:id="@+id/btnMinhasReservas"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:background="?attr/selectableItemBackground">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Minhas Reservas"
android:textColor="#000"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</androidx.cardview.widget.CardView>
</GridLayout>
<!-- Status Section -->

View File

@@ -43,6 +43,26 @@
android:textSize="24sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/imgRestaurantLogo"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="24dp"
android:src="@android:drawable/ic_menu_gallery"
android:background="#E0E0E0" />
<Button
android:id="@+id/btnChangeLogo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="12dp"
android:text="Alterar Logótipo"
android:textAllCaps="false"
android:backgroundTint="#2E7D32"
android:textColor="#FFFFFF" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -87,30 +107,35 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="Localização do Restaurante"
android:text="Informação do Restaurante"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<EditText
android:id="@+id/inputLatitude"
android:id="@+id/inputAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Latitude (Ex: 38.7223)"
android:inputType="numberDecimal|numberSigned"
android:hint="Morada (Ex: Rua Estreita, 12)"
android:inputType="textPostalAddress"
android:padding="12dp"
android:background="@android:drawable/editbox_background_normal" />
<EditText
android:id="@+id/inputLongitude"
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:hint="Longitude (Ex: -9.1393)"
android:inputType="numberDecimal|numberSigned"
android:padding="12dp"
android:background="@android:drawable/editbox_background_normal" />
android:layout_marginTop="12dp"
android:text="Categoria"
android:textColor="#5F5F5F"
android:textSize="14sp" />
<Spinner
android:id="@+id/spinnerCategory"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:minHeight="48dp" />
<Button
android:id="@+id/btnSaveSettings"

View File

@@ -69,10 +69,12 @@
android:orientation="horizontal">
<androidx.cardview.widget.CardView
android:id="@+id/cardReservasHoje"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:foreground="?attr/selectableItemBackground"
app:cardBackgroundColor="#FFFFFF"
app:cardCornerRadius="16dp"
app:cardElevation="3dp">
@@ -92,10 +94,11 @@
android:textSize="12sp" />
<TextView
android:id="@+id/txtReservasHojeDash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="32"
android:text="00"
android:textColor="#000"
android:textSize="28sp"
android:textStyle="bold" />
@@ -127,10 +130,11 @@
android:textSize="12sp" />
<TextView
android:id="@+id/txtMesasLivresDash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="08"
android:text="00"
android:textColor="#000"
android:textSize="28sp"
android:textStyle="bold" />
@@ -161,10 +165,11 @@
android:textSize="12sp" />
<TextView
android:id="@+id/txtListaEsperaDash"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="05"
android:text="00"
android:textColor="#000"
android:textSize="28sp"
android:textStyle="bold" />
@@ -191,6 +196,7 @@
app:cardElevation="3dp">
<LinearLayout
android:id="@+id/llProximasReservas"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
@@ -199,38 +205,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="20h00 - Mesa 14"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Cliente: Ana Ribeiro • 4 pessoas"
android:textColor="#5F5F5F"
android:textSize="14sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginVertical="12dp"
android:background="#EEEEEE" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="21h15 - Mesa 03"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Cliente: Bruno Costa • 2 pessoas"
android:text="A procurar próximas reservas..."
android:textColor="#5F5F5F"
android:textSize="14sp" />
</LinearLayout>

View File

@@ -39,6 +39,7 @@
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:paddingBottom="24dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -46,6 +47,139 @@
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_restaurant" />
<ScrollView
android:id="@+id/scrollReservaDetails"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:fillViewport="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloExplorar">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="32dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:layout_marginTop="16dp">
<Button
android:id="@+id/btnVerAvaliacoes"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="8dp"
android:text="Avaliações"
android:textAllCaps="false"
android:backgroundTint="#FFFFFF"
android:textColor="#1976D2"
app:strokeColor="#1976D2"
app:strokeWidth="1dp" />
<Button
android:id="@+id/btnUploadFoto"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:text="Adicionar Foto"
android:textAllCaps="false"
android:backgroundTint="#FFFFFF"
android:textColor="#E53935"
app:strokeColor="#E53935"
app:strokeWidth="1dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:layout_marginTop="16dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Data da reserva"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/btnSelectDate"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:background="@drawable/btn_light_border"
android:text="Selecionar Data"
android:textAllCaps="false"
android:textColor="#5F5F5F" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Hora da reserva"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="@+id/btnSelectTime"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:background="@drawable/btn_light_border"
android:text="Selecionar Hora"
android:textAllCaps="false"
android:textColor="#5F5F5F" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Número de pessoas"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<EditText
android:id="@+id/etPartySize"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:background="@drawable/btn_light_border"
android:inputType="number"
android:paddingHorizontal="16dp"
android:hint="Ex: 2"
android:textColor="#000" />
</LinearLayout>
<Button
android:id="@+id/btnConfirmarReserva"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="32dp"
android:background="@drawable/btn_primary"
android:text="Confirmar Reserva"
android:textAllCaps="false"
android:textColor="#FFF"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -91,6 +91,16 @@
android:textAllCaps="false"
android:textColor="#FFFFFF" />
<Button
android:id="@+id/btnEliminarStaff"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="8dp"
android:background="@android:color/transparent"
android:text="Eliminar Funcionário"
android:textAllCaps="false"
android:textColor="#FF5252" />
<TextView
android:id="@+id/txtMensagemStaff"
android:layout_width="match_parent"

View File

@@ -45,6 +45,92 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ListView
android:id="@+id/listReservasP"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_margin="16dp"
android:background="#FFFFFF"
android:elevation="2dp"
app:layout_constraintTop_toBottomOf="@id/txtDescricaoListaEspera"
app:layout_constraintBottom_toTopOf="@id/detalhesCardP" />
<androidx.cardview.widget.CardView
android:id="@+id/detalhesCardP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:cardCornerRadius="12dp"
app:cardElevation="4dp"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/txtReservaInfoP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Selecione uma reserva"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtReservaNotasP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text=""
android:textColor="#5F5F5F"
android:textSize="14sp" />
<TextView
android:id="@+id/txtMensagemReservaP"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text=""
android:textColor="#1976D2"
android:textSize="14sp"
android:textStyle="italic" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnConfirmarReservaP"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:backgroundTint="#4CAF50"
android:text="Aceitar"
android:textAllCaps="false"
android:textColor="#FFF"
android:visibility="gone" />
<Button
android:id="@+id/btnRecusarReservaP"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_weight="1"
android:backgroundTint="#E53935"
android:text="Recusar"
android:textAllCaps="false"
android:textColor="#FFF"
android:visibility="gone" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -153,8 +153,7 @@
android:background="@drawable/input_bg"
android:hint="Email"
android:inputType="textEmailAddress"
android:padding="12dp"
android:text="EstabelecimentoPap@gmail.com" />
android:padding="12dp" />
<EditText
android:id="@+id/inputOwnerPhone"
@@ -200,16 +199,46 @@
android:padding="12dp"
android:visibility="gone" />
<EditText
android:id="@+id/inputPassword"
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_marginTop="12dp"
android:background="@drawable/input_bg"
android:hint="Palavra-passe"
android:inputType="textPassword"
android:padding="12dp"
android:text="PaP@P.1" />
android:layout_height="wrap_content"
android:layout_marginTop="12dp">
<EditText
android:id="@+id/inputPassword"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/input_bg"
android:hint="Palavra-passe"
android:inputType="textPassword"
android:paddingStart="12dp"
android:paddingEnd="48dp" />
<ImageView
android:id="@+id/iconPasswordVisibility"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:padding="12dp"
android:src="@drawable/ic_visibility_off"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless" />
</RelativeLayout>
<TextView
android:id="@+id/txtForgotPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_gravity="end"
android:text="Esqueceu-se da palavra-passe?"
android:textColor="#1976D2"
android:textSize="14sp"
android:visibility="visible"
android:clickable="true"
android:focusable="true"/>
<Button
android:id="@+id/btnFinalCriarConta"

View File

@@ -0,0 +1,48 @@
<?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/minhasReservasRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#F7F7F7"
tools:context=".MinhasReservasActivity">
<Button
android:id="@+id/btnVoltar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="Voltar"
android:textAllCaps="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtTituloMinhasReservas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Minhas Reservas"
android:textColor="#000"
android:textSize="22sp"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnVoltar"
android:layout_marginTop="24dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMinhasReservas"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:clipToPadding="false"
android:paddingBottom="16dp"
app:layout_constraintTop_toBottomOf="@id/txtTituloMinhasReservas"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -32,12 +32,37 @@
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="32dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvCategories"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloNovaReserva"
tools:listitem="@layout/item_food_category" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvRestaurants"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/txtTituloNovaReserva"
tools:listitem="@layout/item_restaurant" />
<ScrollView
android:id="@+id/scrollNovaReserva"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="24dp"
android:fillViewport="true"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -49,71 +74,70 @@
android:orientation="vertical"
android:paddingBottom="32dp">
<!-- Section: Date -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="16dp"
android:text="Escolha a data"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvDates"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
android:orientation="vertical"
android:paddingHorizontal="24dp"
android:layout_marginTop="16dp">
<!-- Section: Time -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:text="Escolha o horário"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Data da reserva"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvTimes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<Button
android:id="@+id/btnSelectDate"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:background="@drawable/btn_light_border"
android:text="Selecionar Data"
android:textAllCaps="false"
android:textColor="#5F5F5F" />
<!-- Section: Party size -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:text="Número de pessoas"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Hora da reserva"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvPartySize"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingStart="16dp"
android:paddingEnd="16dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<Button
android:id="@+id/btnSelectTime"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:background="@drawable/btn_light_border"
android:text="Selecionar Hora"
android:textAllCaps="false"
android:textColor="#5F5F5F" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="Número de pessoas"
android:textColor="#000"
android:textSize="16sp"
android:textStyle="bold" />
<EditText
android:id="@+id/etPartySize"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="8dp"
android:background="@drawable/btn_light_border"
android:inputType="number"
android:paddingHorizontal="16dp"
android:hint="Ex: 2"
android:textColor="#000" />
</LinearLayout>
<Button
android:id="@+id/btnConfirmarReserva"

View File

@@ -43,11 +43,11 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:id="@+id/imgProfile"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/circle_bg"
android:padding="20dp" />
android:src="@drawable/circle_bg" />
</androidx.cardview.widget.CardView>
<LinearLayout

View File

@@ -1,34 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="120dp"
android:layout_height="140dp"
android:layout_margin="8dp"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginHorizontal="16dp"
android:layout_marginVertical="8dp"
app:cardCornerRadius="16dp"
app:cardElevation="2dp">
<LinearLayout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:padding="8dp">
<ImageView
android:id="@+id/imgCategory"
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerCrop"
android:src="@drawable/ic_launcher_background" />
android:padding="16dp">
<TextView
android:id="@+id/txtCategoryName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_toStartOf="@id/imgCategory"
android:text="Categoria"
android:textColor="#000"
android:textSize="14sp"
android:textSize="20sp"
android:textStyle="bold" />
</LinearLayout>
<ImageView
android:id="@+id/imgCategory"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:scaleType="centerCrop"
android:src="@drawable/ic_launcher_background" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/txtReservaRestaurante"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Nome do Restaurante"
android:textColor="#000"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtReservaDataHora"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Hoje às 20:00"
android:textColor="#757575"
android:textSize="14sp" />
<TextView
android:id="@+id/txtReservaStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Estado: Pendente"
android:textSize="14sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnCheckIn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginEnd="4dp"
android:text="Check-in"
android:textAllCaps="false"
android:background="@drawable/btn_primary"
android:textColor="#FFF" />
<Button
android:id="@+id/btnCancelar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="4dp"
android:text="Cancelar"
android:textAllCaps="false"
android:background="@drawable/btn_light_border"
android:textColor="#C62828" />
</LinearLayout>
<TextView
android:id="@+id/txtLocationWarning"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Dê permissões de localização para aceder ao check-in."
android:textAlignment="center"
android:textColor="#D32F2F"
android:textSize="12sp"
android:visibility="gone" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView 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="2dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<ImageView
android:id="@+id/imgRestaurantThumb"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_alignParentStart="true"
android:layout_centerVertical="true"
android:layout_marginEnd="12dp"
android:scaleType="centerCrop"
android:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/imgRestaurantThumb"
android:layout_toStartOf="@id/btnFavorite"
android:layout_centerVertical="true"
android:orientation="vertical">
<TextView
android:id="@+id/txtRestaurantName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Restaurant Name"
android:textColor="#000"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/txtRestaurantCategory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="Category"
android:textColor="#757575"
android:textSize="14sp" />
</LinearLayout>
<ImageButton
android:id="@+id/btnFavorite"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
android:scaleType="fitCenter"
android:src="@android:drawable/btn_star_big_off" />
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@@ -1,5 +1,5 @@
[versions]
agp = "9.0.1"
agp = "9.1.0"
junit = "4.13.2"
junitVersion = "1.3.0"
espressoCore = "3.7.0"
@@ -11,6 +11,8 @@ constraintlayout = "2.2.1"
firebaseBom = "33.7.0"
googleServices = "4.4.2"
firebaseDatabase = "22.0.1"
glide = "4.16.0"
firebaseStorage = "21.0.1"
[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
@@ -23,6 +25,8 @@ activity = { group = "androidx.activity", name = "activity", version.ref = "acti
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
firebase-database = { group = "com.google.firebase", name = "firebase-database", version.ref = "firebaseDatabase" }
glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }
firebase-storage = { group = "com.google.firebase", name = "firebase-storage", version.ref = "firebaseStorage" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

View File

@@ -1,7 +1,7 @@
#Tue Feb 24 17:05:40 WET 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists