diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml index b268ef3..a248f76 100644 --- a/.idea/deploymentTargetSelector.xml +++ b/.idea/deploymentTargetSelector.xml @@ -4,6 +4,14 @@ diff --git a/app/src/main/java/com/example/pap_teste/DefinicoesAdminActivity.java b/app/src/main/java/com/example/pap_teste/DefinicoesAdminActivity.java index 61c9584..061cb28 100644 --- a/app/src/main/java/com/example/pap_teste/DefinicoesAdminActivity.java +++ b/app/src/main/java/com/example/pap_teste/DefinicoesAdminActivity.java @@ -22,13 +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, inputAddress; private android.widget.Spinner spinnerCategory; + private ImageView imgLogo; private DatabaseReference databaseReference; private String documentId; - private String[] categories = {"Carnes", "Massas", "Sushi", "Pizzas", "Sobremesas"}; + private String photoUrl; + private ActivityResultLauncher imagePickerLauncher; + private String[] categories = {"Carnes", "Massas", "Sushi", "Pizzas", "Sobremesas", "Italiana", "Moderna"}; @Override protected void onCreate(Bundle savedInstanceState) { @@ -51,6 +64,7 @@ public class DefinicoesAdminActivity extends AppCompatActivity { inputRadius = findViewById(R.id.inputRadius); inputAddress = findViewById(R.id.inputAddress); spinnerCategory = findViewById(R.id.spinnerCategory); + imgLogo = findViewById(R.id.imgRestaurantLogo); android.widget.ArrayAdapter adapter = new android.widget.ArrayAdapter<>(this, android.R.layout.simple_spinner_item, categories); @@ -59,16 +73,79 @@ public class DefinicoesAdminActivity extends AppCompatActivity { 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; @@ -96,6 +173,12 @@ public class DefinicoesAdminActivity extends AppCompatActivity { } } } + 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); + } + } } } @@ -126,6 +209,9 @@ public class DefinicoesAdminActivity extends AppCompatActivity { updates.put("securityDistance", radius); updates.put("address", addressStr); updates.put("category", selectedCategory); + if (photoUrl != null) { + updates.put("logoUrl", photoUrl); + } databaseReference.child(documentId).updateChildren(updates) .addOnSuccessListener(aVoid -> { 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 c14b834..30d007d 100644 --- a/app/src/main/java/com/example/pap_teste/EstablishmentDashboardActivity.java +++ b/app/src/main/java/com/example/pap_teste/EstablishmentDashboardActivity.java @@ -68,5 +68,77 @@ public class EstablishmentDashboardActivity extends AppCompatActivity { btnBack.setOnClickListener(v -> finish()); } + + loadProximasReservas(); + } + + 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; + } + } + + 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 && "Confirmada".equals(r.getEstado())) { + 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); } } diff --git a/app/src/main/java/com/example/pap_teste/ExplorarRestaurantesActivity.java b/app/src/main/java/com/example/pap_teste/ExplorarRestaurantesActivity.java index 068409d..2345a0d 100644 --- a/app/src/main/java/com/example/pap_teste/ExplorarRestaurantesActivity.java +++ b/app/src/main/java/com/example/pap_teste/ExplorarRestaurantesActivity.java @@ -97,11 +97,10 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity { 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 no category filter in query, we might need a client-side filter if the index isn't used - // but here we use the query. Actually if filter is null we get all, but we need to ensure they are ESTAB if (name != null && email != null) { - restaurantsList.add(new com.example.pap_teste.models.Restaurant(name, cat, email, false)); + restaurantsList.add(new com.example.pap_teste.models.Restaurant(name, cat, email, false, logoUrl)); } } } @@ -120,9 +119,13 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity { } }); } + private androidx.activity.result.ActivityResultLauncher photoPickerLauncher; + 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(); @@ -141,6 +144,119 @@ public class ExplorarRestaurantesActivity extends AppCompatActivity { }); findViewById(R.id.btnConfirmarReserva).setOnClickListener(v -> saveReservation()); + + btnVerAvaliacoes.setOnClickListener(v -> showReviewsDialog()); + + if (photoPickerLauncher == null) { + 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); + } + } + }); + } + + 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 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()) { + String userEmail = com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser() != null + ? com.google.firebase.auth.FirebaseAuth.getInstance().getCurrentUser().getEmail() + : "Visitante"; + + String encodedEmail = selectedRestaurant.getEmail().replace(".", "_").replace("@", "_at_"); + java.util.Map review = new java.util.HashMap<>(); + review.put("author", userEmail); + 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(); + } + }); + } + }) + .setNegativeButton("Cancelar", null) + .show(); } private void saveReservation() { diff --git a/app/src/main/java/com/example/pap_teste/ListaEsperaActivity.java b/app/src/main/java/com/example/pap_teste/ListaEsperaActivity.java index be002c2..634962a 100644 --- a/app/src/main/java/com/example/pap_teste/ListaEsperaActivity.java +++ b/app/src/main/java/com/example/pap_teste/ListaEsperaActivity.java @@ -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 reservasPendentes = new ArrayList<>(); + private ArrayAdapter 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,130 @@ 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 -> atualizarEstadoSelecionado("Confirmada")); + btnRecusar.setOnClickListener(v -> showRecusarDialog()); + } + + 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(); + } } diff --git a/app/src/main/java/com/example/pap_teste/RestaurantAdapter.java b/app/src/main/java/com/example/pap_teste/RestaurantAdapter.java index 40b7266..1ef4816 100644 --- a/app/src/main/java/com/example/pap_teste/RestaurantAdapter.java +++ b/app/src/main/java/com/example/pap_teste/RestaurantAdapter.java @@ -46,6 +46,15 @@ public class RestaurantAdapter extends RecyclerView.Adapter { if (listener != null) { listener.onRestaurantClick(restaurant); @@ -99,11 +108,13 @@ public class RestaurantAdapter extends RecyclerView.Adapter + + +