diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d92e637..7cab3cb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,9 @@ xmlns:tools="http://schemas.android.com/tools"> + + + - + - + + + diff --git a/app/src/main/java/com/example/pap_teste/BloqueioHorarioActivity.java b/app/src/main/java/com/example/pap_teste/BloqueioHorarioActivity.java deleted file mode 100644 index 627a269..0000000 --- a/app/src/main/java/com/example/pap_teste/BloqueioHorarioActivity.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.example.pap_teste; - -import android.os.Bundle; - -import androidx.activity.EdgeToEdge; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; - -import android.widget.Button; - -public class BloqueioHorarioActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_bloqueio_horario); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.bloqueioRoot), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - - Button back = findViewById(R.id.btnVoltar); - if (back != null) { - back.setOnClickListener(v -> finish()); - } - } -} - - - - - diff --git a/app/src/main/java/com/example/pap_teste/CheckInAntecipadoActivity.java b/app/src/main/java/com/example/pap_teste/CheckInAntecipadoActivity.java index c2e0753..25f826c 100644 --- a/app/src/main/java/com/example/pap_teste/CheckInAntecipadoActivity.java +++ b/app/src/main/java/com/example/pap_teste/CheckInAntecipadoActivity.java @@ -1,16 +1,40 @@ package com.example.pap_teste; +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; import android.os.Bundle; +import android.widget.Button; +import android.widget.TextView; +import android.widget.Toast; import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.ActivityCompat; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; -import android.widget.Button; +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; -public class CheckInAntecipadoActivity extends AppCompatActivity { +public class CheckInAntecipadoActivity extends AppCompatActivity implements LocationListener { + + private TextView txtDistancia, txtStatus; + private Button btnConfirmarChegada; + private LocationManager locationManager; + private DatabaseReference databaseReference; + + private double restaurantLat, restaurantLon; + private int securityDistance = 500; // default 500m + private boolean settingsLoaded = false; @Override protected void onCreate(Bundle savedInstanceState) { @@ -23,14 +47,114 @@ public class CheckInAntecipadoActivity extends AppCompatActivity { return insets; }); + txtDistancia = findViewById(R.id.txtDistancia); + txtStatus = findViewById(R.id.txtStatusDistancia); + btnConfirmarChegada = findViewById(R.id.btnConfirmarChegada); Button back = findViewById(R.id.btnVoltar); + if (back != null) { back.setOnClickListener(v -> finish()); } + + String restaurantEmail = getIntent().getStringExtra("restaurant_email"); + if (restaurantEmail != null) { + String restaurantId = restaurantEmail.replace(".", "_").replace("@", "_at_"); + loadRestaurantSettings(restaurantId); + } else { + txtStatus.setText("Erro: Restaurante não identificado."); + } + + locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); + startLocationUpdates(); + + btnConfirmarChegada.setOnClickListener(v -> { + Toast.makeText(this, "Chegada confirmada com sucesso!", Toast.LENGTH_LONG).show(); + finish(); + }); + } + + private void loadRestaurantSettings(String restaurantId) { + databaseReference = FirebaseDatabase.getInstance().getReference().child("users").child(restaurantId); + databaseReference.addListenerForSingleValueEvent(new ValueEventListener() { + @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); + 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; + } else { + txtStatus.setText("Aviso: Definições do restaurante não encontradas. Usando valores padrão."); + settingsLoaded = true; // Use defaults + } + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + txtStatus.setText("Erro ao carregar definições do restaurante."); + } + }); + } + + 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."); + return; + } + try { + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 5, this); + locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 5000, 5, this); + } catch (Exception e) { + txtStatus.setText("Erro ao iniciar localização: " + e.getMessage()); + } + } + + @Override + public void onLocationChanged(@NonNull Location location) { + if (!settingsLoaded) + return; + + float[] results = new float[1]; + Location.distanceBetween(location.getLatitude(), location.getLongitude(), restaurantLat, restaurantLon, + results); + float distanceInMeters = results[0]; + + txtDistancia.setText(String.format("Distância: %.0f metros", distanceInMeters)); + + if (distanceInMeters <= securityDistance) { + txtStatus.setText("Está no raio de segurança. Pode confirmar a sua chegada."); + txtStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark)); + btnConfirmarChegada.setEnabled(true); + } else { + txtStatus.setText(String.format("Está fora do raio de segurança (%dm). Aproxime-se para fazer check-in.", + securityDistance)); + txtStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark)); + btnConfirmarChegada.setEnabled(false); + } + } + + @Override + protected void onStop() { + super.onStop(); + locationManager.removeUpdates(this); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + @Override + public void onProviderEnabled(@NonNull String provider) { + } + + @Override + public void onProviderDisabled(@NonNull String provider) { } } - - - - - diff --git a/app/src/main/java/com/example/pap_teste/ClientDashboardActivity.java b/app/src/main/java/com/example/pap_teste/ClientDashboardActivity.java index a2ce9d1..05a7c11 100644 --- a/app/src/main/java/com/example/pap_teste/ClientDashboardActivity.java +++ b/app/src/main/java/com/example/pap_teste/ClientDashboardActivity.java @@ -11,70 +11,89 @@ 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.FoodCategory; +import java.util.ArrayList; +import java.util.List; + public class ClientDashboardActivity extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_client_dashboard); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.clientRoot), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); + private String email, displayName, role; + private TextView txtGreeting; + private androidx.activity.result.ActivityResultLauncher profileLauncher; - TextView txtGreeting = findViewById(R.id.txtClientGreeting); - TextView txtStatus = findViewById(R.id.txtClientStatus); - TextView txtRole = findViewById(R.id.txtClientRole); - TextView txtReservationStatus = findViewById(R.id.txtReservationStatus); - TextView txtReservationSubtitle = findViewById(R.id.txtReservationSubtitle); - Button btnBack = findViewById(R.id.btnVoltar); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_client_dashboard); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.clientRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); - String actionMode = getIntent().getStringExtra(MainActivity.EXTRA_ACTION_MODE); - String displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); - String role = getIntent().getStringExtra(MainActivity.EXTRA_ROLE); + txtGreeting = findViewById(R.id.txtClientGreeting); - boolean isNewAccount = "CRIAR".equalsIgnoreCase(actionMode); - txtGreeting.setText(String.format("Olá, %s", displayName != null ? displayName : "convidado")); - txtRole.setText(String.format("Função: %s", role != null ? role : "CLIENTE")); - txtStatus.setText(isNewAccount - ? "Conta criada com sucesso! Configure as suas preferências para começarmos." - : "Bom tê-lo de volta! Já deixámos tudo pronto para a sua próxima reserva."); + email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); + displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); + role = getIntent().getStringExtra(MainActivity.EXTRA_ROLE); - txtReservationStatus.setText("Próxima reserva"); - txtReservationSubtitle.setText("Mesa para 2 • Amanhã às 20h • Sabor & Arte"); + profileLauncher = registerForActivityResult( + new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK) { + android.widget.Toast.makeText(this, + "Perfil atualizado. Reinicie para ver mudanças.", + android.widget.Toast.LENGTH_SHORT).show(); + } + }); - Button btnNewReservation = findViewById(R.id.btnNovaReserva); - Button btnExplore = findViewById(R.id.btnExplorar); - Button btnFavorites = findViewById(R.id.btnFavoritos); - - Button btnCheckIn = findViewById(R.id.btnCheckIn); - Button btnShare = findViewById(R.id.btnPartilhar); - - btnNewReservation.setOnClickListener(v -> - startActivity(new Intent(this, NovaReservaActivity.class)) - ); - - btnExplore.setOnClickListener(v -> - startActivity(new Intent(this, ExplorarRestaurantesActivity.class)) - ); - - btnFavorites.setOnClickListener(v -> - startActivity(new Intent(this, FavoritosActivity.class)) - ); - - btnCheckIn.setOnClickListener(v -> - startActivity(new Intent(this, CheckInAntecipadoActivity.class)) - ); - - btnShare.setOnClickListener(v -> - startActivity(new Intent(this, PartilharReservaActivity.class)) - ); - - if (btnBack != null) { - btnBack.setOnClickListener(v -> finish()); + updateGreeting(); + setupCategories(); + setupActions(); } - } -} + private void updateGreeting() { + txtGreeting.setText(String.format("Olá, %s", displayName != null ? displayName : "convidado")); + } + + private void setupCategories() { + RecyclerView rv = findViewById(R.id.rvCategories); + List 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.ic_launcher_background)); + cats.add(new FoodCategory("Sobremesas", R.drawable.ic_launcher_background)); + + FoodCategoryAdapter adapter = new FoodCategoryAdapter(cats); + rv.setAdapter(adapter); + } + + private void setupActions() { + findViewById(R.id.cardProfile).setOnClickListener(v -> { + Intent intent = new Intent(this, ProfileDashboardActivity.class); + intent.putExtra(MainActivity.EXTRA_EMAIL, email); + intent.putExtra(MainActivity.EXTRA_DISPLAY_NAME, displayName); + profileLauncher.launch(intent); + }); + + 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.btnExplorar).setOnClickListener( + v -> startActivity(new Intent(this, ExplorarRestaurantesActivity.class))); + } +} diff --git a/app/src/main/java/com/example/pap_teste/DefinicoesAdminActivity.java b/app/src/main/java/com/example/pap_teste/DefinicoesAdminActivity.java new file mode 100644 index 0000000..7acce4e --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/DefinicoesAdminActivity.java @@ -0,0 +1,129 @@ +package com.example.pap_teste; + +import android.os.Bundle; +import android.text.TextUtils; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; + +import java.util.HashMap; +import java.util.Map; + +public class DefinicoesAdminActivity extends AppCompatActivity { + + private EditText inputRadius, inputLatitude, inputLongitude; + private DatabaseReference databaseReference; + private String documentId; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_definicoes_admin); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.definicoesRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + String email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); + if (email != null) { + documentId = email.replace(".", "_").replace("@", "_at_"); + } + + databaseReference = FirebaseDatabase.getInstance().getReference().child("users"); + + inputRadius = findViewById(R.id.inputRadius); + inputLatitude = findViewById(R.id.inputLatitude); + inputLongitude = findViewById(R.id.inputLongitude); + Button btnSave = findViewById(R.id.btnSaveSettings); + Button btnBack = findViewById(R.id.btnVoltar); + + if (btnBack != null) { + btnBack.setOnClickListener(v -> finish()); + } + + loadCurrentSettings(); + + btnSave.setOnClickListener(v -> saveSettings()); + } + + private void loadCurrentSettings() { + if (documentId == null) + return; + + databaseReference.child(documentId).addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + if (snapshot.exists()) { + if (snapshot.hasChild("securityDistance")) { + Object val = snapshot.child("securityDistance").getValue(); + inputRadius.setText(val != null ? val.toString() : ""); + } + if (snapshot.hasChild("latitude")) { + Object val = snapshot.child("latitude").getValue(); + inputLatitude.setText(val != null ? val.toString() : ""); + } + if (snapshot.hasChild("longitude")) { + Object val = snapshot.child("longitude").getValue(); + inputLongitude.setText(val != null ? val.toString() : ""); + } + } + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + Toast.makeText(DefinicoesAdminActivity.this, "Erro ao carregar definições.", Toast.LENGTH_SHORT).show(); + } + }); + } + + private void saveSettings() { + if (documentId == null) + return; + + String radiusStr = inputRadius.getText().toString().trim(); + String latStr = inputLatitude.getText().toString().trim(); + String lonStr = inputLongitude.getText().toString().trim(); + + if (TextUtils.isEmpty(radiusStr) || TextUtils.isEmpty(latStr) || TextUtils.isEmpty(lonStr)) { + 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 updates = new HashMap<>(); + updates.put("securityDistance", radius); + updates.put("latitude", lat); + updates.put("longitude", lon); + + databaseReference.child(documentId).updateChildren(updates) + .addOnSuccessListener(aVoid -> { + Toast.makeText(this, "Definições guardadas com sucesso!", Toast.LENGTH_SHORT).show(); + finish(); + }) + .addOnFailureListener( + e -> Toast.makeText(this, "Falha ao guardar definições.", Toast.LENGTH_SHORT).show()); + + } catch (NumberFormatException e) { + Toast.makeText(this, "Valores inválidos.", Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/app/src/main/java/com/example/pap_teste/DetalhesReservasActivity.java b/app/src/main/java/com/example/pap_teste/DetalhesReservasActivity.java index dbb6dcd..2fa0d74 100644 --- a/app/src/main/java/com/example/pap_teste/DetalhesReservasActivity.java +++ b/app/src/main/java/com/example/pap_teste/DetalhesReservasActivity.java @@ -25,6 +25,7 @@ public class DetalhesReservasActivity extends AppCompatActivity { private TextView txtNotas; private TextView txtEstado; private TextView txtMensagem; + private Button btnConfirmar, btnRecusar, btnApagar; private int selectedIndex = -1; @Override @@ -51,6 +52,10 @@ public class DetalhesReservasActivity extends AppCompatActivity { txtEstado = findViewById(R.id.txtReservaEstado); txtMensagem = findViewById(R.id.txtMensagemReserva); + btnConfirmar = findViewById(R.id.btnConfirmarReserva); + btnRecusar = findViewById(R.id.btnCancelarReserva); + btnApagar = findViewById(R.id.btnApagarReserva); + Button back = findViewById(R.id.btnVoltar); if (back != null) { back.setOnClickListener(v -> finish()); @@ -61,6 +66,7 @@ public class DetalhesReservasActivity extends AppCompatActivity { 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 setupList() { @@ -75,16 +81,59 @@ public class DetalhesReservasActivity extends AppCompatActivity { } private void setupActions() { - Button btnConfirmar = findViewById(R.id.btnConfirmarReserva); - Button btnCancelar = findViewById(R.id.btnCancelarReserva); - if (btnConfirmar != null) { - btnConfirmar.setOnClickListener(v -> atualizarEstadoSelecionado("Confirmada")); + btnConfirmar.setOnClickListener(v -> { + ReservaItem item = reservas.get(selectedIndex); + if ("Pendente".equals(item.estado)) { + atualizarEstadoSelecionado("Confirmada"); + } else if ("Confirmada".equals(item.estado)) { + atualizarEstadoSelecionado("Concluída"); + } + }); } - if (btnCancelar != null) { - btnCancelar.setOnClickListener(v -> atualizarEstadoSelecionado("Cancelada")); + if (btnRecusar != null) { + btnRecusar.setOnClickListener(v -> showRecusarDialog()); } + + if (btnApagar != null) { + btnApagar.setOnClickListener(v -> apagarReserva()); + } + } + + private void showRecusarDialog() { + if (selectedIndex < 0) + return; + + String[] motivos = { "Sem espaço no restaurante", "Fora de horas", "Reserva duplicada", "Outro" }; + new androidx.appcompat.app.AlertDialog.Builder(this) + .setTitle("Motivo da Recusa") + .setItems(motivos, (dialog, which) -> { + atualizarEstadoSelecionado("Recusada (" + motivos[which] + ")"); + }) + .setNegativeButton("Voltar", null) + .show(); + } + + private void apagarReserva() { + if (selectedIndex < 0) + return; + + new androidx.appcompat.app.AlertDialog.Builder(this) + .setTitle("Apagar 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(); + }) + .setNegativeButton("Voltar", null) + .show(); } private void atualizarEstadoSelecionado(String novoEstado) { @@ -104,6 +153,41 @@ public class DetalhesReservasActivity extends AppCompatActivity { 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)); + toggleButtons(item); + } + + private void toggleButtons(ReservaItem item) { + if (item == null) { + btnConfirmar.setVisibility(android.view.View.GONE); + btnRecusar.setVisibility(android.view.View.GONE); + btnApagar.setVisibility(android.view.View.GONE); + return; + } + + switch (item.estado) { + case "Pendente": + btnConfirmar.setText("Confirmar"); + btnConfirmar.setVisibility(android.view.View.VISIBLE); + btnRecusar.setVisibility(android.view.View.VISIBLE); + btnApagar.setVisibility(android.view.View.GONE); + break; + case "Confirmada": + btnConfirmar.setText("Concluir"); + btnConfirmar.setVisibility(android.view.View.VISIBLE); + btnRecusar.setVisibility(android.view.View.VISIBLE); // Still allow refusal + btnApagar.setVisibility(android.view.View.GONE); + break; + case "Concluída": + btnConfirmar.setVisibility(android.view.View.GONE); + btnRecusar.setVisibility(android.view.View.GONE); + btnApagar.setVisibility(android.view.View.VISIBLE); + break; + default: // Recusada or Cancelada + btnConfirmar.setVisibility(android.view.View.GONE); + btnRecusar.setVisibility(android.view.View.GONE); + btnApagar.setVisibility(android.view.View.VISIBLE); // Allow deleting refused ones as well + break; + } } private void refreshList() { @@ -133,13 +217,3 @@ public class DetalhesReservasActivity extends AppCompatActivity { } } } - - - - - - - - - - diff --git a/app/src/main/java/com/example/pap_teste/EstablishmentDashboardActivity.java b/app/src/main/java/com/example/pap_teste/EstablishmentDashboardActivity.java index 9043058..af69294 100644 --- a/app/src/main/java/com/example/pap_teste/EstablishmentDashboardActivity.java +++ b/app/src/main/java/com/example/pap_teste/EstablishmentDashboardActivity.java @@ -13,67 +13,56 @@ import androidx.core.view.WindowInsetsCompat; public class EstablishmentDashboardActivity extends AppCompatActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_establishment_dashboard); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.establishmentRoot), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_establishment_dashboard); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.establishmentRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); - TextView txtTitle = findViewById(R.id.txtEstabTitle); - TextView txtSubtitle = findViewById(R.id.txtEstabSubtitle); - TextView txtRole = findViewById(R.id.txtEstabRole); + TextView txtTitle = findViewById(R.id.txtEstabTitle); + TextView txtSubtitle = findViewById(R.id.txtEstabSubtitle); + TextView txtRole = findViewById(R.id.txtEstabRole); - String actionMode = getIntent().getStringExtra(MainActivity.EXTRA_ACTION_MODE); - String displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); - String role = getIntent().getStringExtra(MainActivity.EXTRA_ROLE); + String actionMode = getIntent().getStringExtra(MainActivity.EXTRA_ACTION_MODE); + String displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); + String role = getIntent().getStringExtra(MainActivity.EXTRA_ROLE); - boolean isNewAccount = "CRIAR".equalsIgnoreCase(actionMode); - txtTitle.setText(displayName != null ? displayName : "Estabelecimento"); - txtRole.setText(String.format("Função: %s", role != null ? role : "ADMIN")); - txtSubtitle.setText(isNewAccount - ? "Perfil criado. Configure horários, mesas e abra reservas." - : "Dashboard operacional. Acompanhe as reservas em tempo real."); + boolean isNewAccount = "CRIAR".equalsIgnoreCase(actionMode); + txtTitle.setText(displayName != null ? displayName : "Estabelecimento"); + txtRole.setText(String.format("Função: %s", role != null ? role : "ADMIN")); + txtSubtitle.setText(isNewAccount + ? "Perfil criado. Configure horários, mesas e abra reservas." + : "Dashboard operacional. Acompanhe as reservas em tempo real."); - Button btnOpenWalkIns = findViewById(R.id.btnAbrirEspera); - Button btnBlockTime = findViewById(R.id.btnCriarBloqueio); - Button btnStaff = findViewById(R.id.btnGestaoStaff); - Button btnReports = findViewById(R.id.btnVerRelatorios); - Button btnGerirMesas = findViewById(R.id.btnGerirMesas); - Button btnDetalhesReservas = findViewById(R.id.btnDetalhesReservas); - Button btnBack = findViewById(R.id.btnVoltar); + Button btnOpenWalkIns = findViewById(R.id.btnAbrirEspera); + Button btnStaff = findViewById(R.id.btnGestaoStaff); + Button btnGerirMesas = findViewById(R.id.btnGerirMesas); + Button btnDetails = findViewById(R.id.btnDetalhesReservas); + Button btnSettings = findViewById(R.id.btnDefinicoes); + Button btnBack = findViewById(R.id.btnVoltar); - btnOpenWalkIns.setOnClickListener(v -> - startActivity(new Intent(this, ListaEsperaActivity.class)) - ); + btnOpenWalkIns.setOnClickListener(v -> startActivity(new Intent(this, ListaEsperaActivity.class))); - btnBlockTime.setOnClickListener(v -> - startActivity(new Intent(this, BloqueioHorarioActivity.class)) - ); + btnStaff.setOnClickListener(v -> startActivity(new Intent(this, GestaoStaffActivity.class))); - btnStaff.setOnClickListener(v -> - startActivity(new Intent(this, GestaoStaffActivity.class)) - ); + btnGerirMesas.setOnClickListener(v -> startActivity(new Intent(this, GerirMesasActivity.class))); - btnReports.setOnClickListener(v -> - startActivity(new Intent(this, RelatoriosActivity.class)) - ); + btnDetails.setOnClickListener(v -> startActivity(new Intent(this, DetalhesReservasActivity.class))); - btnGerirMesas.setOnClickListener(v -> - startActivity(new Intent(this, GerirMesasActivity.class)) - ); + btnSettings.setOnClickListener(v -> { + Intent intent = new Intent(this, DefinicoesAdminActivity.class); + intent.putExtra(MainActivity.EXTRA_EMAIL, getIntent().getStringExtra(MainActivity.EXTRA_EMAIL)); + startActivity(intent); + }); - btnDetalhesReservas.setOnClickListener(v -> - startActivity(new Intent(this, DetalhesReservasActivity.class)) - ); + if (btnBack != null) { - if (btnBack != null) { - btnBack.setOnClickListener(v -> finish()); + btnBack.setOnClickListener(v -> finish()); + } } - } } - diff --git a/app/src/main/java/com/example/pap_teste/FoodCategoryAdapter.java b/app/src/main/java/com/example/pap_teste/FoodCategoryAdapter.java new file mode 100644 index 0000000..29acfc0 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/FoodCategoryAdapter.java @@ -0,0 +1,55 @@ +package com.example.pap_teste; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.example.pap_teste.models.FoodCategory; + +import java.util.List; + +public class FoodCategoryAdapter extends RecyclerView.Adapter { + + private final List categories; + + public FoodCategoryAdapter(List categories) { + this.categories = categories; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_food_category, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + FoodCategory category = categories.get(position); + holder.txtName.setText(category.getName()); + if (category.getImageResId() != 0) { + holder.imgCategory.setImageResource(category.getImageResId()); + } + } + + @Override + public int getItemCount() { + return categories.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + ImageView imgCategory; + TextView txtName; + + public ViewHolder(@NonNull View itemView) { + super(itemView); + imgCategory = itemView.findViewById(R.id.imgCategory); + txtName = itemView.findViewById(R.id.txtCategoryName); + } + } +} diff --git a/app/src/main/java/com/example/pap_teste/GerirMesasActivity.java b/app/src/main/java/com/example/pap_teste/GerirMesasActivity.java index 78456ba..3dd8329 100644 --- a/app/src/main/java/com/example/pap_teste/GerirMesasActivity.java +++ b/app/src/main/java/com/example/pap_teste/GerirMesasActivity.java @@ -37,6 +37,7 @@ public class GerirMesasActivity extends AppCompatActivity { private Spinner spinnerEstado; private TextView txtMensagem; private DatabaseReference mDatabase; + private Mesa selectedMesa = null; @Override protected void onCreate(Bundle savedInstanceState) { @@ -88,6 +89,8 @@ public class GerirMesasActivity extends AppCompatActivity { for (DataSnapshot postSnapshot : snapshot.getChildren()) { Mesa mesa = postSnapshot.getValue(Mesa.class); if (mesa != null) { + // Ensure the ID is set from the snapshot key if it's missing in the value + mesa.setId(postSnapshot.getKey()); mesas.add(mesa); String resumo = String.format("Mesa %02d • %d lugares • %s", mesa.getNumero(), mesa.getCapacidade(), mesa.getEstado()); @@ -106,6 +109,7 @@ public class GerirMesasActivity extends AppCompatActivity { listMesas.setOnItemClickListener((parent, view, position, id) -> { Mesa item = mesas.get(position); + selectedMesa = item; inputNumero.setText(String.valueOf(item.getNumero())); inputCapacidade.setText(String.valueOf(item.getCapacidade())); spinnerEstado.setSelection(getEstadoIndex(item.getEstado())); @@ -128,6 +132,34 @@ public class GerirMesasActivity extends AppCompatActivity { if (btnGuardar != null) { btnGuardar.setOnClickListener(v -> guardarMesa()); } + + Button btnRemover = findViewById(R.id.btnRemoverMesa); + if (btnRemover != null) { + btnRemover.setOnClickListener(v -> removerMesa()); + } + } + + private void removerMesa() { + if (selectedMesa == null || selectedMesa.getId() == null) { + Toast.makeText(this, "Selecione uma mesa válida para remover.", Toast.LENGTH_SHORT).show(); + return; + } + + mDatabase.child(selectedMesa.getId()).removeValue() + .addOnSuccessListener(aVoid -> { + Toast.makeText(this, "Mesa removida com sucesso.", Toast.LENGTH_SHORT).show(); + limparCampos(); + }) + .addOnFailureListener(e -> { + Toast.makeText(this, "Erro ao remover mesa: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + + private void limparCampos() { + inputNumero.setText(""); + inputCapacidade.setText(""); + selectedMesa = null; + txtMensagem.setText(""); } private void guardarMesa() { @@ -162,6 +194,10 @@ public class GerirMesasActivity extends AppCompatActivity { txtMensagem.setText(String.format("Mesa %d adicionada.", numero)); } else { mesaId = existente.getId(); + if (mesaId == null) { + Toast.makeText(this, "Erro ao atualizar: ID não encontrado.", Toast.LENGTH_SHORT).show(); + return; + } existente.setCapacidade(capacidade); existente.setEstado(estado); mDatabase.child(mesaId).setValue(existente); @@ -169,8 +205,7 @@ public class GerirMesasActivity extends AppCompatActivity { } // Clearing inputs - inputNumero.setText(""); - inputCapacidade.setText(""); + limparCampos(); } private Mesa findMesa(int numero) { diff --git a/app/src/main/java/com/example/pap_teste/GestaoStaffActivity.java b/app/src/main/java/com/example/pap_teste/GestaoStaffActivity.java index d2075a5..f82deed 100644 --- a/app/src/main/java/com/example/pap_teste/GestaoStaffActivity.java +++ b/app/src/main/java/com/example/pap_teste/GestaoStaffActivity.java @@ -169,6 +169,14 @@ public class GestaoStaffActivity extends AppCompatActivity { } }); } + + Button btnGerirMesas = findViewById(R.id.btnGerirMesasStaff); + if (btnGerirMesas != null) { + btnGerirMesas.setOnClickListener(v -> { + Intent intent = new Intent(GestaoStaffActivity.this, GerirMesasActivity.class); + startActivity(intent); + }); + } } private void guardarAtribuicao() { diff --git a/app/src/main/java/com/example/pap_teste/MainActivity.java b/app/src/main/java/com/example/pap_teste/MainActivity.java index 003a968..fa462b5 100644 --- a/app/src/main/java/com/example/pap_teste/MainActivity.java +++ b/app/src/main/java/com/example/pap_teste/MainActivity.java @@ -15,6 +15,12 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; +import android.Manifest; +import android.content.pm.PackageManager; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import com.google.firebase.FirebaseApp; import com.google.firebase.auth.FirebaseAuth; @@ -62,6 +68,22 @@ public class MainActivity extends AppCompatActivity { private FirebaseAuth firebaseAuth; private DatabaseReference databaseReference; + private final ActivityResultLauncher locationPermissionRequest = 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); + 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(); + } + }); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -81,6 +103,28 @@ public class MainActivity extends AppCompatActivity { setupTypeToggle(); setupActionToggle(); setupPrimaryAction(); + checkLocationPermissions(); + } + + private void checkLocationPermissions() { + if (ContextCompat.checkSelfPermission(this, + Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + 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(); + }) + .show(); + } } private void bindViews() { @@ -341,7 +385,8 @@ public class MainActivity extends AppCompatActivity { DataSnapshot snapshot = task.getResult(); if (snapshot == null || !snapshot.exists()) { - Toast.makeText(this, "Conta sem perfil na cloud. A entrar em modo básico.", Toast.LENGTH_SHORT).show(); + // Toast.makeText(this, "Conta sem perfil na cloud. A entrar em modo básico.", + // Toast.LENGTH_SHORT).show(); navigateToDashboard(email, fallbackName, resolvedRole); return; } diff --git a/app/src/main/java/com/example/pap_teste/ProfileDashboardActivity.java b/app/src/main/java/com/example/pap_teste/ProfileDashboardActivity.java new file mode 100644 index 0000000..55f3d28 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/ProfileDashboardActivity.java @@ -0,0 +1,94 @@ +package com.example.pap_teste; + +import android.os.Bundle; +import android.text.TextUtils; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; + +import java.util.HashMap; +import java.util.Map; + +public class ProfileDashboardActivity extends AppCompatActivity { + + private EditText inputName; + private String email, documentId; + private DatabaseReference databaseReference; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_profile_dashboard); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.profileRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); + String currentName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); + + if (email != null) { + documentId = email.replace(".", "_").replace("@", "_at_"); + } + + databaseReference = FirebaseDatabase.getInstance().getReference().child("users"); + + inputName = findViewById(R.id.inputProfileName); + if (currentName != null) { + inputName.setText(currentName); + } + + Button btnSave = findViewById(R.id.btnSaveProfile); + Button btnBack = findViewById(R.id.btnVoltar); + Button btnFavs = findViewById(R.id.btnFavoritos); + Button btnRes = findViewById(R.id.btnMinhasReservas); + + btnBack.setOnClickListener(v -> finish()); + + btnSave.setOnClickListener(v -> saveProfile()); + + btnFavs.setOnClickListener(v -> { + Toast.makeText(this, "A abrir Favoritos...", Toast.LENGTH_SHORT).show(); + // startActivity(new Intent(this, FavoritosActivity.class)); + }); + + btnRes.setOnClickListener(v -> { + Toast.makeText(this, "A abrir Reservas...", Toast.LENGTH_SHORT).show(); + // startActivity(new Intent(this, DetalhesReservasActivity.class)); + }); + } + + private void saveProfile() { + if (documentId == null) + return; + + String newName = inputName.getText().toString().trim(); + if (TextUtils.isEmpty(newName)) { + Toast.makeText(this, "Indique um nome.", Toast.LENGTH_SHORT).show(); + return; + } + + Map updates = new HashMap<>(); + updates.put("displayName", newName); + + databaseReference.child(documentId).updateChildren(updates) + .addOnSuccessListener(aVoid -> { + Toast.makeText(this, "Perfil atualizado!", Toast.LENGTH_SHORT).show(); + setResult(RESULT_OK); + finish(); + }) + .addOnFailureListener( + e -> Toast.makeText(this, "Falha ao atualizar perfil.", Toast.LENGTH_SHORT).show()); + } +} diff --git a/app/src/main/java/com/example/pap_teste/RelatoriosActivity.java b/app/src/main/java/com/example/pap_teste/RelatoriosActivity.java deleted file mode 100644 index dc401ab..0000000 --- a/app/src/main/java/com/example/pap_teste/RelatoriosActivity.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.example.pap_teste; - -import android.os.Bundle; - -import androidx.activity.EdgeToEdge; -import androidx.appcompat.app.AppCompatActivity; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; - -import android.widget.Button; - -public class RelatoriosActivity extends AppCompatActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - EdgeToEdge.enable(this); - setContentView(R.layout.activity_relatorios); - ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.relatoriosRoot), (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); - return insets; - }); - - Button back = findViewById(R.id.btnVoltar); - if (back != null) { - back.setOnClickListener(v -> finish()); - } - } -} diff --git a/app/src/main/java/com/example/pap_teste/models/FoodCategory.java b/app/src/main/java/com/example/pap_teste/models/FoodCategory.java new file mode 100644 index 0000000..44e491a --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/models/FoodCategory.java @@ -0,0 +1,19 @@ +package com.example.pap_teste.models; + +public class FoodCategory { + private String name; + private int imageResId; + + public FoodCategory(String name, int imageResId) { + this.name = name; + this.imageResId = imageResId; + } + + public String getName() { + return name; + } + + public int getImageResId() { + return imageResId; + } +} diff --git a/app/src/main/res/drawable/cat_carnes.png b/app/src/main/res/drawable/cat_carnes.png new file mode 100644 index 0000000..10f3ba0 Binary files /dev/null and b/app/src/main/res/drawable/cat_carnes.png differ diff --git a/app/src/main/res/drawable/cat_massas.png b/app/src/main/res/drawable/cat_massas.png new file mode 100644 index 0000000..c13db2d Binary files /dev/null and b/app/src/main/res/drawable/cat_massas.png differ diff --git a/app/src/main/res/drawable/cat_sushi.png b/app/src/main/res/drawable/cat_sushi.png new file mode 100644 index 0000000..a61acda Binary files /dev/null and b/app/src/main/res/drawable/cat_sushi.png differ diff --git a/app/src/main/res/layout/activity_bloqueio_horario.xml b/app/src/main/res/layout/activity_bloqueio_horario.xml deleted file mode 100644 index 090e2df..0000000 --- a/app/src/main/res/layout/activity_bloqueio_horario.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - -