commit f08e661796a9b2a815759681835edae063d1e86c Author: 230409 <230409@epvc.pt> Date: Mon Apr 27 15:06:38 2026 +0100 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/appInsightsSettings.xml b/.idea/appInsightsSettings.xml new file mode 100644 index 0000000..3a40abc --- /dev/null +++ b/.idea/appInsightsSettings.xml @@ -0,0 +1,40 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..b86273d --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..a248f76 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,18 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/deviceManager.xml b/.idea/deviceManager.xml new file mode 100644 index 0000000..91f9558 --- /dev/null +++ b/.idea/deviceManager.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..639c779 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/markdown.xml b/.idea/markdown.xml new file mode 100644 index 0000000..c61ea33 --- /dev/null +++ b/.idea/markdown.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..b2c751a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/studiobot.xml b/.idea/studiobot.xml new file mode 100644 index 0000000..539e3b8 --- /dev/null +++ b/.idea/studiobot.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..6e5597a --- /dev/null +++ b/Readme.md @@ -0,0 +1,3 @@ +EstabelecimentoPap@gmail.com + +PaP@P.1 \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..ad20d54 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,51 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.google.services) +} + +android { + namespace = "com.example.pap_teste" + compileSdk = 36 + + defaultConfig { + applicationId = "com.example.pap_teste" + minSdk = 24 + targetSdk = 36 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +dependencies { + + implementation(libs.appcompat) + implementation(libs.material) + implementation(libs.activity) + implementation(libs.constraintlayout) + implementation(platform(libs.firebase.bom)) + 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) + androidTestImplementation(libs.test.core) +} \ No newline at end of file diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..995669b --- /dev/null +++ b/app/google-services.json @@ -0,0 +1,29 @@ +{ + "project_info": { + "project_number": "476421715902", + "project_id": "namesa-429c1", + "storage_bucket": "namesa-429c1.firebasestorage.app" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:476421715902:android:4147ab5f1cde601e1aebef", + "android_client_info": { + "package_name": "com.example.pap_teste" + } + }, + "oauth_client": [], + "api_key": [ + { + "current_key": "AIzaSyCPz7Pd3tJj3QkF7fV_vudCJythNsyR57k" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/example/pap_teste/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/example/pap_teste/ExampleInstrumentedTest.java new file mode 100644 index 0000000..bf50fcc --- /dev/null +++ b/app/src/androidTest/java/com/example/pap_teste/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.example.pap_teste; + +import android.content.Context; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("com.example.pap_teste", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b1b9e33 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/pap_teste/AccountCreatedActivity.java b/app/src/main/java/com/example/pap_teste/AccountCreatedActivity.java new file mode 100644 index 0000000..e23c3c9 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/AccountCreatedActivity.java @@ -0,0 +1,67 @@ +package com.example.pap_teste; + +import android.content.Intent; +import android.os.Bundle; +import android.widget.Button; +import android.widget.TextView; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +/** + * Tela de confirmação apresentada logo após a criação de conta. + * Exibe os dados básicos e direciona para a área correspondente + * (estabelecimento como admin, cliente como cliente). + */ +public class AccountCreatedActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_account_created); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.accountCreatedRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + TextView txtWelcome = findViewById(R.id.txtWelcome); + TextView txtEmail = findViewById(R.id.txtEmail); + TextView txtRole = findViewById(R.id.txtRole); + Button btnContinue = findViewById(R.id.btnContinuar); + Button btnBack = findViewById(R.id.btnVoltar); + + String displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); + String email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); + String accountType = getIntent().getStringExtra(MainActivity.EXTRA_ACCOUNT_TYPE); + String role = getIntent().getStringExtra(MainActivity.EXTRA_ROLE); + + txtWelcome.setText(displayName != null ? displayName : "Novo utilizador"); + txtEmail.setText(email != null ? email : "Email não informado"); + txtRole.setText(role != null ? role : "CLIENTE"); + + btnContinue.setOnClickListener(v -> { + boolean isEstablishment = "ESTABELECIMENTO".equalsIgnoreCase(accountType); + Intent nextScreen = new Intent( + this, + isEstablishment ? EstablishmentDashboardActivity.class : ClientDashboardActivity.class + ); + nextScreen.putExtra(MainActivity.EXTRA_ACTION_MODE, MainActivity.AccountAction.CRIAR.name()); + nextScreen.putExtra(MainActivity.EXTRA_DISPLAY_NAME, displayName); + nextScreen.putExtra(MainActivity.EXTRA_EMAIL, email); + nextScreen.putExtra(MainActivity.EXTRA_ACCOUNT_TYPE, accountType); + nextScreen.putExtra(MainActivity.EXTRA_ROLE, role); + startActivity(nextScreen); + finish(); + }); + + if (btnBack != null) { + btnBack.setOnClickListener(v -> finish()); + } + } +} + diff --git a/app/src/main/java/com/example/pap_teste/AddStaffActivity.java b/app/src/main/java/com/example/pap_teste/AddStaffActivity.java new file mode 100644 index 0000000..dba5c09 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/AddStaffActivity.java @@ -0,0 +1,135 @@ +package com.example.pap_teste; + +import android.os.Bundle; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.ArrayAdapter; +import android.widget.Toast; +import java.util.ArrayList; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.text.InputType; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.example.pap_teste.models.Staff; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; + +public class AddStaffActivity extends AppCompatActivity { + + private Button addButton; + private EditText nameEditText; + private Spinner zonaSpinner; + private Button btnAddZone; + private Spinner mesaSpinner; + private ArrayList zones; + private ArrayList mesas; + private ArrayAdapter adapter; + private ArrayAdapter mesaAdapter; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_add_staff); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + addButton = findViewById(R.id.addButton); + nameEditText = findViewById(R.id.nammeEditText); + zonaSpinner = findViewById(R.id.zonaSpinner); + btnAddZone = findViewById(R.id.btnAddZone); + mesaSpinner = findViewById(R.id.mesaSpinner); + + Button btnVoltarAddStaff = findViewById(R.id.btnVoltarAddStaff); + if (btnVoltarAddStaff != null) { + btnVoltarAddStaff.setOnClickListener(v -> finish()); + } + + zones = new ArrayList<>(); + zones.add("Sala"); + zones.add("Esplanada"); + zones.add("Balcão"); + + adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, zones); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + zonaSpinner.setAdapter(adapter); + + mesas = new ArrayList<>(); + for (int i = 1; i <= 10; i++) { + mesas.add("Mesa " + i); + } + mesaAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, mesas); + mesaAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mesaSpinner.setAdapter(mesaAdapter); + + btnAddZone.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog.Builder builder = new AlertDialog.Builder(AddStaffActivity.this); + builder.setTitle("Adicionar Zona"); + + final EditText input = new EditText(AddStaffActivity.this); + input.setInputType(InputType.TYPE_CLASS_TEXT); + builder.setView(input); + + builder.setPositiveButton("OK", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + String newZone = input.getText().toString(); + if (!newZone.isEmpty()) { + zones.add(newZone); + adapter.notifyDataSetChanged(); + zonaSpinner.setSelection(zones.size() - 1); + } + } + }); + builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + builder.show(); + } + }); + + addButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String name = nameEditText.getText().toString(); + String zona = ""; + if (zonaSpinner.getSelectedItem() != null) { + zona = zonaSpinner.getSelectedItem().toString(); + } + String mesa = ""; + if (mesaSpinner.getSelectedItem() != null) { + mesa = mesaSpinner.getSelectedItem().toString(); + } + + if (!name.isEmpty()) { + DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference("Staff"); + String uuid = java.util.UUID.randomUUID().toString(); + Staff staff = new Staff(name, zona, mesa, uuid); + databaseReference.child(uuid).setValue(staff); + + Toast.makeText(AddStaffActivity.this, "Staff adicionado com sucesso!", Toast.LENGTH_SHORT).show(); + finish(); + } else { + Toast.makeText(AddStaffActivity.this, "Erro: Preencha todos os campos", Toast.LENGTH_SHORT).show(); + } + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap_teste/CheckInAntecipadoActivity.java b/app/src/main/java/com/example/pap_teste/CheckInAntecipadoActivity.java new file mode 100644 index 0000000..982bb43 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/CheckInAntecipadoActivity.java @@ -0,0 +1,218 @@ +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; +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 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 CheckInAntecipadoActivity extends AppCompatActivity implements LocationListener { + + 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) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_checkin_antecipado); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.checkinRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + txtDistancia = findViewById(R.id.txtDistancia); + 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) { + 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); + 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) { + databaseReference = FirebaseDatabase.getInstance().getReference().child("users").child(restaurantId); + databaseReference.addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + if (snapshot.exists()) { + restaurantAddress = snapshot.child("address").getValue(String.class); + Integer dist = snapshot.child("securityDistance").getValue(Integer.class); + securityDistance = dist != null ? dist : 500; + + 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."); + } + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + txtStatus.setText("Erro ao carregar definições do restaurante."); + } + }); + } + + private void geocodeAddress(String address) { + new Thread(() -> { + try { + android.location.Geocoder geocoder = new android.location.Geocoder(this); + List 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) { + 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(); + if (locationManager != null) { + 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 new file mode 100644 index 0000000..70a901d --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/ClientDashboardActivity.java @@ -0,0 +1,223 @@ +package com.example.pap_teste; + +import android.content.Intent; +import android.os.Bundle; +import android.widget.Button; +import android.widget.TextView; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import androidx.recyclerview.widget.RecyclerView; +import com.example.pap_teste.models.FoodCategory; +import java.util.ArrayList; +import java.util.List; + +public class ClientDashboardActivity extends AppCompatActivity { + + private String email, displayName, role; + private TextView txtGreeting; + private androidx.activity.result.ActivityResultLauncher profileLauncher; + + @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; + }); + + txtGreeting = findViewById(R.id.txtClientGreeting); + + email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); + displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); + role = getIntent().getStringExtra(MainActivity.EXTRA_ROLE); + + profileLauncher = registerForActivityResult( + new androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK && result.getData() != null) { + String updatedName = result.getData() + .getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); + if (updatedName != null) { + displayName = updatedName; + updateGreeting(); + } + } + }); + + 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 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)); + + FoodCategoryAdapter adapter = new FoodCategoryAdapter(cats, category -> { + Intent intent = new Intent(this, ExplorarRestaurantesActivity.class); + intent.putExtra("category_filter", category.getName()); + startActivity(intent); + }); + 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.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) {} + }); + } +} 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..ce9640e --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/DefinicoesAdminActivity.java @@ -0,0 +1,228 @@ +package com.example.pap_teste; + +import android.os.Bundle; +import android.text.TextUtils; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.activity.EdgeToEdge; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; + +import java.util.HashMap; +import java.util.Map; + +import android.net.Uri; +import android.content.Intent; +import android.widget.ImageView; +import com.bumptech.glide.Glide; +import com.google.firebase.storage.FirebaseStorage; +import com.google.firebase.storage.StorageReference; +import java.util.UUID; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; + +public class DefinicoesAdminActivity extends AppCompatActivity { + + private EditText inputRadius, inputAddress; + private android.widget.Spinner spinnerCategory; + private ImageView imgLogo; + private DatabaseReference databaseReference; + private String documentId; + private String photoUrl; + private ActivityResultLauncher imagePickerLauncher; + private String[] categories = {"Carnes", "Massas", "Sushi", "Pizzas", "Sobremesas", "Italiana", "Moderna"}; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_definicoes_admin); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.definicoesRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + String email = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); + if (email != null) { + documentId = email.replace(".", "_").replace("@", "_at_"); + } + + databaseReference = FirebaseDatabase.getInstance().getReference().child("users"); + + 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); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinnerCategory.setAdapter(adapter); + + Button btnSave = findViewById(R.id.btnSaveSettings); + Button btnBack = findViewById(R.id.btnVoltar); + Button btnChangeLogo = findViewById(R.id.btnChangeLogo); + + if (btnBack != null) { + btnBack.setOnClickListener(v -> finish()); + } + + imagePickerLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == RESULT_OK && result.getData() != null) { + Uri imageUri = result.getData().getData(); + if (imageUri != null) { + uploadImageToFirebase(imageUri); + } + } + }); + + btnChangeLogo.setOnClickListener(v -> { + String[] options = {"Galeria", "URL da Imagem"}; + new androidx.appcompat.app.AlertDialog.Builder(this) + .setTitle("Escolher Logótipo") + .setItems(options, (dialog, which) -> { + if (which == 0) { + Intent intent = new Intent(Intent.ACTION_PICK); + intent.setType("image/*"); + imagePickerLauncher.launch(intent); + } else { + showUrlInputDialog(); + } + }).show(); + }); + + loadCurrentSettings(); + + btnSave.setOnClickListener(v -> saveSettings()); + } + + private void showUrlInputDialog() { + androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(this); + builder.setTitle("Inserir URL da Imagem"); + + final EditText input = new EditText(this); + input.setInputType(android.text.InputType.TYPE_TEXT_VARIATION_URI); + builder.setView(input); + + builder.setPositiveButton("Confirmar", (dialog, which) -> { + String url = input.getText().toString().trim(); + if (!TextUtils.isEmpty(url)) { + this.photoUrl = url; + Glide.with(this).load(photoUrl).circleCrop().into(imgLogo); + Toast.makeText(this, "Logótipo definido!", Toast.LENGTH_SHORT).show(); + } + }); + builder.setNegativeButton("Cancelar", (dialog, which) -> dialog.cancel()); + builder.show(); + } + + private void uploadImageToFirebase(Uri imageUri) { + if (documentId == null) return; + + StorageReference storageRef = FirebaseStorage.getInstance().getReference() + .child("restaurant_logos/" + UUID.randomUUID().toString()); + + storageRef.putFile(imageUri).addOnSuccessListener(taskSnapshot -> { + storageRef.getDownloadUrl().addOnSuccessListener(uri -> { + photoUrl = uri.toString(); + Glide.with(this).load(photoUrl).circleCrop().into(imgLogo); + }); + }).addOnFailureListener(e -> { + Toast.makeText(this, "Falha no upload: " + e.getMessage(), Toast.LENGTH_LONG).show(); + }); + } + + private void loadCurrentSettings() { + if (documentId == null) + return; + + databaseReference.child(documentId).addListenerForSingleValueEvent(new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + if (snapshot.exists()) { + if (snapshot.hasChild("securityDistance")) { + Object val = snapshot.child("securityDistance").getValue(); + inputRadius.setText(val != null ? val.toString() : ""); + } + if (snapshot.hasChild("address")) { + String addr = snapshot.child("address").getValue(String.class); + inputAddress.setText(addr != null ? addr : ""); + } + if (snapshot.hasChild("category")) { + String cat = snapshot.child("category").getValue(String.class); + if (cat != null) { + for (int i = 0; i < categories.length; i++) { + if (categories[i].equalsIgnoreCase(cat)) { + spinnerCategory.setSelection(i); + break; + } + } + } + } + if (snapshot.hasChild("logoUrl")) { + photoUrl = snapshot.child("logoUrl").getValue(String.class); + if (photoUrl != null && !photoUrl.isEmpty()) { + Glide.with(DefinicoesAdminActivity.this).load(photoUrl).circleCrop().into(imgLogo); + } + } + } + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + Toast.makeText(DefinicoesAdminActivity.this, "Erro ao carregar definições.", Toast.LENGTH_SHORT).show(); + } + }); + } + + private void saveSettings() { + if (documentId == null) + return; + + String radiusStr = inputRadius.getText().toString().trim(); + String addressStr = inputAddress.getText().toString().trim(); + String selectedCategory = spinnerCategory.getSelectedItem().toString(); + + if (TextUtils.isEmpty(radiusStr) || TextUtils.isEmpty(addressStr)) { + Toast.makeText(this, "Preencha todos os campos.", Toast.LENGTH_SHORT).show(); + return; + } + + try { + int radius = Integer.parseInt(radiusStr); + + Map updates = new HashMap<>(); + updates.put("securityDistance", radius); + updates.put("address", addressStr); + updates.put("category", selectedCategory); + if (photoUrl != null) { + updates.put("logoUrl", photoUrl); + } + + databaseReference.child(documentId).updateChildren(updates) + .addOnSuccessListener(aVoid -> { + Toast.makeText(this, "Definições guardadas com sucesso!", Toast.LENGTH_SHORT).show(); + finish(); + }) + .addOnFailureListener( + e -> Toast.makeText(this, "Falha ao guardar definições.", Toast.LENGTH_SHORT).show()); + + } catch (NumberFormatException e) { + Toast.makeText(this, "Raio inválido.", Toast.LENGTH_SHORT).show(); + } + } +} diff --git a/app/src/main/java/com/example/pap_teste/DetalhesReservasActivity.java b/app/src/main/java/com/example/pap_teste/DetalhesReservasActivity.java new file mode 100644 index 0000000..51d223e --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/DetalhesReservasActivity.java @@ -0,0 +1,248 @@ +package com.example.pap_teste; + +import android.os.Bundle; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import java.util.ArrayList; +import java.util.List; + +public class DetalhesReservasActivity extends AppCompatActivity { + + private final List reservas = new ArrayList<>(); + private ArrayAdapter adapter; + private ListView listReservas; + private TextView txtInfo; + private TextView txtNotas; + private TextView txtEstado; + private TextView txtMensagem; + private Button btnConfirmar, btnRecusar, btnApagar; + private int selectedIndex = -1; + private String restaurantEmail; + private com.google.firebase.database.DatabaseReference databaseReference; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_detalhes_reservas); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.detalhesReservasRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + restaurantEmail = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); + if (restaurantEmail == null) + restaurantEmail = "sabor_arte@restaurante.com"; + + bindViews(); + setupList(); + setupActions(); + loadReservas(); + } + + private void bindViews() { + listReservas = findViewById(R.id.listReservas); + txtInfo = findViewById(R.id.txtReservaInfo); + txtNotas = findViewById(R.id.txtReservaNotas); + txtEstado = findViewById(R.id.txtReservaEstado); + txtMensagem = findViewById(R.id.txtMensagemReserva); + + btnConfirmar = findViewById(R.id.btnConfirmarReserva); + btnRecusar = findViewById(R.id.btnCancelarReserva); + btnApagar = findViewById(R.id.btnApagarReserva); + + Button back = findViewById(R.id.btnVoltar); + if (back != null) { + back.setOnClickListener(v -> finish()); + } + } + + private void loadReservas() { + databaseReference = com.google.firebase.database.FirebaseDatabase.getInstance().getReference("reservas"); + databaseReference.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); + + listReservas.setOnItemClickListener((parent, view, position, id) -> { + selectedIndex = position; + mostrarDetalhe(reservas.get(position)); + }); + } + + private void setupActions() { + if (btnConfirmar != null) { + btnConfirmar.setOnClickListener(v -> { + com.example.pap_teste.models.Reserva item = reservas.get(selectedIndex); + if ("Pendente".equals(item.getEstado())) { + atualizarEstadoSelecionado("Confirmada"); + } else if ("Confirmada".equals(item.getEstado())) { + atualizarEstadoSelecionado("Concluída"); + } + }); + } + + 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/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; + } + + 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(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(com.example.pap_teste.models.Reserva item) { + if (item == null) { + btnConfirmar.setVisibility(android.view.View.GONE); + btnRecusar.setVisibility(android.view.View.GONE); + btnApagar.setVisibility(android.view.View.GONE); + return; + } + + switch (item.getEstado()) { + case "Pendente": + btnConfirmar.setText("Confirmar"); + btnConfirmar.setVisibility(android.view.View.VISIBLE); + btnRecusar.setVisibility(android.view.View.VISIBLE); + btnApagar.setVisibility(android.view.View.GONE); + break; + case "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() { + adapter.clear(); + for (com.example.pap_teste.models.Reserva item : reservas) { + String resumo = String.format("%s - %s (%s) • %s", item.getHora(), item.getData(), item.getEstado(), + item.getClienteEmail()); + adapter.add(resumo); + } + adapter.notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/com/example/pap_teste/EstablishmentDashboardActivity.java b/app/src/main/java/com/example/pap_teste/EstablishmentDashboardActivity.java new file mode 100644 index 0000000..927dc96 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/EstablishmentDashboardActivity.java @@ -0,0 +1,223 @@ +package com.example.pap_teste; + +import android.content.Intent; +import android.os.Bundle; +import android.widget.Button; +import android.widget.TextView; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +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; + }); + + TextView txtTitle = findViewById(R.id.txtEstabTitle); + TextView txtSubtitle = findViewById(R.id.txtEstabSubtitle); + TextView txtRole = findViewById(R.id.txtEstabRole); + + String actionMode = getIntent().getStringExtra(MainActivity.EXTRA_ACTION_MODE); + String displayName = getIntent().getStringExtra(MainActivity.EXTRA_DISPLAY_NAME); + String role = getIntent().getStringExtra(MainActivity.EXTRA_ROLE); + + boolean isNewAccount = "CRIAR".equalsIgnoreCase(actionMode); + txtTitle.setText(displayName != null ? displayName : "Estabelecimento"); + txtRole.setText(String.format("Função: %s", role != null ? role : "ADMIN")); + txtSubtitle.setText(isNewAccount + ? "Perfil criado. Configure horários, mesas e abra reservas." + : "Dashboard operacional. Acompanhe as reservas em tempo real."); + + Button btnOpenWalkIns = findViewById(R.id.btnAbrirEspera); + Button btnStaff = findViewById(R.id.btnGestaoStaff); + Button btnGerirMesas = findViewById(R.id.btnGerirMesas); + Button btnDetails = findViewById(R.id.btnDetalhesReservas); + Button btnSettings = findViewById(R.id.btnDefinicoes); + Button btnBack = findViewById(R.id.btnVoltar); + + btnOpenWalkIns.setOnClickListener(v -> startActivity(new Intent(this, ListaEsperaActivity.class))); + + btnStaff.setOnClickListener(v -> startActivity(new Intent(this, GestaoStaffActivity.class))); + + btnGerirMesas.setOnClickListener(v -> startActivity(new Intent(this, GerirMesasActivity.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); + intent.putExtra(MainActivity.EXTRA_EMAIL, getIntent().getStringExtra(MainActivity.EXTRA_EMAIL)); + 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) { + } + }); + } +} diff --git a/app/src/main/java/com/example/pap_teste/ExplorarRestaurantesActivity.java b/app/src/main/java/com/example/pap_teste/ExplorarRestaurantesActivity.java new file mode 100644 index 0000000..568eded --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/ExplorarRestaurantesActivity.java @@ -0,0 +1,315 @@ +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 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 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); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_explorar_restaurantes); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.explorarRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + rvRestaurants = findViewById(R.id.rvRestaurants); + scrollReservaDetails = findViewById(R.id.scrollReservaDetails); + txtTitle = findViewById(R.id.txtTituloExplorar); + + Button back = findViewById(R.id.btnVoltar); + if (back != null) { + 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"); + java.util.List 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) { + query = usersRef.orderByChild("category").equalTo(filter); + } + + 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 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 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(); + } + }); + } + } +} diff --git a/app/src/main/java/com/example/pap_teste/FavoritosActivity.java b/app/src/main/java/com/example/pap_teste/FavoritosActivity.java new file mode 100644 index 0000000..a10be70 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/FavoritosActivity.java @@ -0,0 +1,84 @@ +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 list; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_favoritos); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.favoritosRoot), (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()); + } + + rv = findViewById(R.id.rvFavoritos); + list = new ArrayList<>(); + adapter = new RestaurantAdapter(list, null); + rv.setAdapter(adapter); + + setupFavoritesList(); + } + + private void setupFavoritesList() { + 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"); + + 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(); + } + }); + } + } +} 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..0ffa27b --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/FoodCategoryAdapter.java @@ -0,0 +1,70 @@ +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 { + + public interface OnCategoryClickListener { + void onCategoryClick(FoodCategory category); + } + + private final List categories; + private final OnCategoryClickListener listener; + + public FoodCategoryAdapter(List categories, OnCategoryClickListener listener) { + this.categories = categories; + this.listener = listener; + } + + @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) { + com.bumptech.glide.Glide.with(holder.itemView.getContext()) + .load(category.getImageResId()) + .centerCrop() + .into(holder.imgCategory); + } + + holder.itemView.setOnClickListener(v -> { + if (listener != null) { + listener.onCategoryClick(category); + } + }); + } + + @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 new file mode 100644 index 0000000..9a5ebe1 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/GerirMesasActivity.java @@ -0,0 +1,227 @@ +package com.example.pap_teste; + +import android.os.Bundle; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import com.example.pap_teste.models.Mesa; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; +import com.google.firebase.auth.FirebaseAuth; + +import java.util.ArrayList; +import java.util.List; + +import androidx.annotation.NonNull; + +public class GerirMesasActivity extends AppCompatActivity { + + private final List mesas = new ArrayList<>(); + private ArrayAdapter adapter; + private ListView listMesas; + private EditText inputNumero; + private EditText inputCapacidade; + private Spinner spinnerEstado; + private TextView txtMensagem; + private DatabaseReference mDatabase; + private Mesa selectedMesa = null; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_gerir_mesas); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.gerirMesasRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + bindViews(); + + mDatabase = FirebaseDatabase.getInstance().getReference("Mesas"); + + setupList(); + setupFormActions(); + } + + private void bindViews() { + listMesas = findViewById(R.id.listMesas); + inputNumero = findViewById(R.id.inputMesaNumero); + inputCapacidade = findViewById(R.id.inputMesaCapacidade); + spinnerEstado = findViewById(R.id.spinnerEstadoMesa); + txtMensagem = findViewById(R.id.txtMensagemMesa); + + Button back = findViewById(R.id.btnVoltar); + if (back != null) { + back.setOnClickListener(v -> finish()); + } + + ArrayAdapter estadoAdapter = new ArrayAdapter<>( + this, + android.R.layout.simple_spinner_dropdown_item, + new String[] { "Livre", "Ocupada", "Reservada" }); + spinnerEstado.setAdapter(estadoAdapter); + } + + private void setupList() { + adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_activated_1); + listMesas.setAdapter(adapter); + + mDatabase.addValueEventListener(new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + mesas.clear(); + adapter.clear(); + String currentUserEmail = FirebaseAuth.getInstance().getCurrentUser() != null + ? FirebaseAuth.getInstance().getCurrentUser().getEmail() + : ""; + for (DataSnapshot postSnapshot : snapshot.getChildren()) { + Mesa mesa = postSnapshot.getValue(Mesa.class); + if (mesa != null && (mesa.getRestauranteEmail() == null || mesa.getRestauranteEmail().equals(currentUserEmail))) { + // Ensure the ID is set from the snapshot key if it's missing in the value + mesa.setId(postSnapshot.getKey()); + mesas.add(mesa); + String resumo = String.format("Mesa %02d • %d lugares • %s", mesa.getNumero(), + mesa.getCapacidade(), mesa.getEstado()); + adapter.add(resumo); + } + } + adapter.notifyDataSetChanged(); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + Toast.makeText(GerirMesasActivity.this, "Erro ao carregar mesas: " + error.getMessage(), + Toast.LENGTH_SHORT).show(); + } + }); + + listMesas.setOnItemClickListener((parent, view, position, id) -> { + Mesa item = mesas.get(position); + selectedMesa = item; + inputNumero.setText(String.valueOf(item.getNumero())); + inputCapacidade.setText(String.valueOf(item.getCapacidade())); + spinnerEstado.setSelection(getEstadoIndex(item.getEstado())); + txtMensagem.setText(String.format("Editar mesa %d", item.getNumero())); + }); + } + + private int getEstadoIndex(String estado) { + if ("Ocupada".equalsIgnoreCase(estado)) { + return 1; + } + if ("Reservada".equalsIgnoreCase(estado)) { + return 2; + } + return 0; + } + + private void setupFormActions() { + Button btnGuardar = findViewById(R.id.btnGuardarMesa); + if (btnGuardar != null) { + btnGuardar.setOnClickListener(v -> guardarMesa()); + } + + Button btnRemover = findViewById(R.id.btnRemoverMesa); + if (btnRemover != null) { + btnRemover.setOnClickListener(v -> removerMesa()); + } + } + + private void removerMesa() { + if (selectedMesa == null || selectedMesa.getId() == null) { + Toast.makeText(this, "Selecione uma mesa válida para remover.", Toast.LENGTH_SHORT).show(); + return; + } + + mDatabase.child(selectedMesa.getId()).removeValue() + .addOnSuccessListener(aVoid -> { + Toast.makeText(this, "Mesa removida com sucesso.", Toast.LENGTH_SHORT).show(); + limparCampos(); + }) + .addOnFailureListener(e -> { + Toast.makeText(this, "Erro ao remover mesa: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + + private void limparCampos() { + inputNumero.setText(""); + inputCapacidade.setText(""); + selectedMesa = null; + txtMensagem.setText(""); + } + + private void guardarMesa() { + String numeroStr = inputNumero.getText().toString().trim(); + String capacidadeStr = inputCapacidade.getText().toString().trim(); + String estado = (String) spinnerEstado.getSelectedItem(); + + if (numeroStr.isEmpty() || capacidadeStr.isEmpty() || estado == null) { + Toast.makeText(this, "Preencha número, capacidade e estado.", Toast.LENGTH_SHORT).show(); + return; + } + + int numero; + int capacidade; + try { + numero = Integer.parseInt(numeroStr); + capacidade = Integer.parseInt(capacidadeStr); + } catch (NumberFormatException e) { + Toast.makeText(this, "Use apenas números válidos.", Toast.LENGTH_SHORT).show(); + return; + } + + Mesa existente = findMesa(numero); + String mesaId; + String currentUserEmail = FirebaseAuth.getInstance().getCurrentUser() != null + ? FirebaseAuth.getInstance().getCurrentUser().getEmail() + : ""; + + if (existente == null) { + mesaId = mDatabase.push().getKey(); + Mesa novaMesa = new Mesa(mesaId, numero, capacidade, estado, currentUserEmail); + if (mesaId != null) { + mDatabase.child(mesaId).setValue(novaMesa); + } + txtMensagem.setText(String.format("Mesa %d adicionada.", numero)); + } else { + mesaId = existente.getId(); + if (mesaId == null) { + Toast.makeText(this, "Erro ao atualizar: ID não encontrado.", Toast.LENGTH_SHORT).show(); + return; + } + existente.setCapacidade(capacidade); + existente.setEstado(estado); + mDatabase.child(mesaId).setValue(existente); + txtMensagem.setText(String.format("Mesa %d atualizada.", numero)); + } + + // Clearing inputs + limparCampos(); + } + + private Mesa findMesa(int numero) { + for (Mesa item : mesas) { + if (item.getNumero() == numero) { + return item; + } + } + return null; + } + +} diff --git a/app/src/main/java/com/example/pap_teste/GestaoStaffActivity.java b/app/src/main/java/com/example/pap_teste/GestaoStaffActivity.java new file mode 100644 index 0000000..2082f37 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/GestaoStaffActivity.java @@ -0,0 +1,295 @@ +package com.example.pap_teste; + +import com.example.pap_teste.models.Mesa; +import com.example.pap_teste.models.Staff; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; +import androidx.annotation.NonNull; + +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.Button; + +import android.widget.ListView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import java.util.ArrayList; +import java.util.List; + +public class GestaoStaffActivity extends AppCompatActivity { + + private final List staffList = new ArrayList<>(); + private ArrayAdapter staffAdapter; + private ListView listStaffMesas; + + private Spinner spinnerNomeStaff; + private Spinner spinnerMesaStaff; + private TextView txtMensagemStaff; + + private DatabaseReference staffRef; + private DatabaseReference mesasRef; + + private List staffNames = new ArrayList<>(); + private List mesasDisponiveis = new ArrayList<>(); + private ArrayAdapter staffNameAdapter; + private ArrayAdapter mesaSpinnerAdapter; + + private FloatingActionButton floatingActionButton; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_gestao_staff); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.gestaoStaffRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + staffRef = FirebaseDatabase.getInstance().getReference("Staff"); + mesasRef = FirebaseDatabase.getInstance().getReference("Mesas"); + + Button back = findViewById(R.id.btnVoltar); + if (back != null) { + back.setOnClickListener(v -> finish()); + } + + bindViews(); + setupMesaSpinner(); + setupList(); + setupFormActions(); + } + + private void bindViews() { + listStaffMesas = findViewById(R.id.listStaffMesas); + spinnerNomeStaff = findViewById(R.id.spinnerNomeStaff); + spinnerMesaStaff = findViewById(R.id.spinnerMesaStaff); + txtMensagemStaff = findViewById(R.id.txtMensagemStaff); + floatingActionButton = findViewById(R.id.floatingActionButton); + } + + /** + * Preenche o spinner com uma lista simples de mesas (1–20). + * Mais tarde isto pode ser ligado às mesas reais configuradas em "Gerir Mesas". + */ + private void setupMesaSpinner() { + mesaSpinnerAdapter = new ArrayAdapter<>( + this, + android.R.layout.simple_spinner_dropdown_item); + + staffNameAdapter = new ArrayAdapter<>( + this, + android.R.layout.simple_spinner_dropdown_item, + staffNames); + spinnerNomeStaff.setAdapter(staffNameAdapter); + + loadStaffMembers(); + loadMesas(); + + spinnerMesaStaff.setAdapter(mesaSpinnerAdapter); + } + + private void loadMesas() { + mesasRef.addValueEventListener(new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + mesasDisponiveis.clear(); + mesaSpinnerAdapter.clear(); + for (DataSnapshot postSnapshot : snapshot.getChildren()) { + Mesa mesa = postSnapshot.getValue(Mesa.class); + if (mesa != null) { + mesasDisponiveis.add(mesa); + mesaSpinnerAdapter.add("Mesa " + mesa.getNumero()); + } + } + mesaSpinnerAdapter.notifyDataSetChanged(); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + Toast.makeText(GestaoStaffActivity.this, "Erro ao carregar mesas.", Toast.LENGTH_SHORT).show(); + } + }); + } + + private void setupList() { + staffAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_activated_1); + listStaffMesas.setAdapter(staffAdapter); + + listStaffMesas.setOnItemClickListener((parent, view, position, id) -> { + Staff item = staffList.get(position); + // Select staff in spinner + int staffIndex = staffNames.indexOf(item.getName()); + if (staffIndex >= 0) { + spinnerNomeStaff.setSelection(staffIndex); + } + // Select mesa in spinner + // Simple string matching for now since Mesa is stored as String in Staff + String assignedMesa = item.getMesa(); + if (assignedMesa != null) { + for (int i = 0; i < mesaSpinnerAdapter.getCount(); i++) { + if (mesaSpinnerAdapter.getItem(i).equals(assignedMesa)) { + spinnerMesaStaff.setSelection(i); + break; + } + } + } + + txtMensagemStaff.setText(String.format("A editar: %s", item.getName())); + }); + } + + private void setupFormActions() { + Button btnAtribuir = findViewById(R.id.btnAtribuirStaff); + if (btnAtribuir != null) { + btnAtribuir.setOnClickListener(v -> guardarAtribuicao()); + } + + Button btnEliminar = findViewById(R.id.btnEliminarStaff); + if (btnEliminar != null) { + btnEliminar.setOnClickListener(v -> eliminarStaff()); + } + + if (floatingActionButton != null) { + floatingActionButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(GestaoStaffActivity.this, AddStaffActivity.class); + startActivity(intent); + } + }); + } + + Button btnGerirMesas = findViewById(R.id.btnGerirMesasStaff); + if (btnGerirMesas != null) { + btnGerirMesas.setOnClickListener(v -> { + Intent intent = new Intent(GestaoStaffActivity.this, GerirMesasActivity.class); + startActivity(intent); + }); + } + } + + private void guardarAtribuicao() { + String nome = ""; + if (spinnerNomeStaff.getSelectedItem() != null) { + nome = spinnerNomeStaff.getSelectedItem().toString(); + } + + if (nome.isEmpty()) { + Toast.makeText(this, "Selecione um funcionário.", Toast.LENGTH_SHORT).show(); + return; + } + + if (spinnerMesaStaff == null || spinnerMesaStaff.getSelectedItem() == null) { + Toast.makeText(this, "Selecione uma mesa.", Toast.LENGTH_SHORT).show(); + return; + } + + String mesaSelecionada = spinnerMesaStaff.getSelectedItem().toString(); + + Staff staffToUpdate = findByNome(nome); + if (staffToUpdate != null) { + staffToUpdate.setMesa(mesaSelecionada); + + final String finalNome = nome; + final String finalMesa = mesaSelecionada; + + staffRef.child(staffToUpdate.getId()).setValue(staffToUpdate) + .addOnSuccessListener(aVoid -> { + txtMensagemStaff.setText(String.format("%s atribuído à %s.", finalNome, finalMesa)); + }) + .addOnFailureListener(e -> { + Toast.makeText(this, "Erro ao atualizar: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } else { + Toast.makeText(this, "Erro: Staff não encontrado.", Toast.LENGTH_SHORT).show(); + } + } + + private void eliminarStaff() { + String nome = ""; + if (spinnerNomeStaff.getSelectedItem() != null) { + nome = spinnerNomeStaff.getSelectedItem().toString(); + } + + if (nome.isEmpty()) { + Toast.makeText(this, "Selecione um funcionário primeiro.", Toast.LENGTH_SHORT).show(); + return; + } + + Staff staffToDelete = findByNome(nome); + if (staffToDelete != null) { + new androidx.appcompat.app.AlertDialog.Builder(this) + .setTitle("Eliminar Staff") + .setMessage("Tem a certeza que deseja eliminar o funcionário " + staffToDelete.getName() + "?") + .setPositiveButton("Eliminar", (dialog, which) -> { + staffRef.child(staffToDelete.getId()).removeValue() + .addOnSuccessListener(aVoid -> { + txtMensagemStaff.setText(staffToDelete.getName() + " foi eliminado."); + }) + .addOnFailureListener(e -> { + Toast.makeText(this, "Erro ao eliminar: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + }) + .setNegativeButton("Cancelar", null) + .show(); + } else { + Toast.makeText(this, "Staff não encontrado.", Toast.LENGTH_SHORT).show(); + } + } + + private Staff findByNome(String nome) { + for (Staff item : staffList) { + if (item.getName().equalsIgnoreCase(nome)) { + return item; + } + } + return null; + } + + private void loadStaffMembers() { + staffRef.addValueEventListener(new ValueEventListener() { + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + staffList.clear(); + staffNames.clear(); + staffAdapter.clear(); + + for (DataSnapshot postSnapshot : snapshot.getChildren()) { + Staff staff = postSnapshot.getValue(Staff.class); + if (staff != null && staff.getName() != null) { + staffList.add(staff); + staffNames.add(staff.getName()); + + String mesaInfo = staff.getMesa() != null ? staff.getMesa() : "Sem Mesa"; + String resumo = String.format("%s • %s • %s", staff.getName(), staff.getZona(), mesaInfo); + staffAdapter.add(resumo); + } + } + + staffNameAdapter.notifyDataSetChanged(); + staffAdapter.notifyDataSetChanged(); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + Toast.makeText(GestaoStaffActivity.this, "Erro ao carregar staff.", Toast.LENGTH_SHORT).show(); + } + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap_teste/ListaEsperaActivity.java b/app/src/main/java/com/example/pap_teste/ListaEsperaActivity.java new file mode 100644 index 0000000..40c2e79 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/ListaEsperaActivity.java @@ -0,0 +1,248 @@ +package com.example.pap_teste; + +import android.os.Bundle; + +import androidx.activity.EdgeToEdge; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.graphics.Insets; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + +import android.widget.Button; + +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.example.pap_teste.models.Reserva; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; + +import java.util.ArrayList; +import java.util.List; + +public class ListaEsperaActivity extends AppCompatActivity { + + private final List 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); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_lista_espera); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.listaEsperaRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + restaurantEmail = getIntent().getStringExtra(MainActivity.EXTRA_EMAIL); + if (restaurantEmail == null) { + // 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 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(); + } +} + + + + + diff --git a/app/src/main/java/com/example/pap_teste/MainActivity.java b/app/src/main/java/com/example/pap_teste/MainActivity.java new file mode 100644 index 0000000..2e10c54 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/MainActivity.java @@ -0,0 +1,572 @@ +package com.example.pap_teste; + +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Color; +import android.os.Bundle; +import android.text.TextUtils; +import android.view.View; +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 androidx.core.content.ContextCompat; +import androidx.appcompat.app.AlertDialog; +import android.Manifest; +import android.content.pm.PackageManager; +import android.os.Build; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; + +import com.google.firebase.FirebaseApp; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MainActivity extends AppCompatActivity { + + public static final String EXTRA_ACTION_MODE = "extra_action_mode"; + public static final String EXTRA_DISPLAY_NAME = "extra_display_name"; + public static final String EXTRA_EMAIL = "extra_email"; + public static final String EXTRA_ACCOUNT_TYPE = "extra_account_type"; + public static final String EXTRA_ROLE = "extra_role"; + private static final String PREFS_NAME = "pap_prefs"; + private static final String KEY_HAS_CREATED_ACCOUNT = "has_created_account"; + + public enum AccountType { + CLIENTE, ESTABELECIMENTO + } + + public enum AccountAction { + ENTRAR, CRIAR + } + + private AccountType selectedAccountType = AccountType.CLIENTE; + private AccountAction selectedAccountAction = AccountAction.ENTRAR; + + private Button btnCliente; + private Button btnEstabelecimento; + private Button btnEntrar; + private Button btnCriarConta; + private Button btnPrimaryAction; + private EditText inputName; + private EditText inputEmail; + private EditText inputPassword; + private EditText inputOwnerPhone; + private EditText inputEstablishmentName; + private EditText inputEstablishmentEmail; + private EditText inputEstablishmentPhone; + private android.widget.TextView txtForgotPassword; + private android.widget.ImageView iconPasswordVisibility; + private boolean isPasswordVisible = false; + private boolean hasCreatedAccount; + private FirebaseAuth firebaseAuth; + private DatabaseReference databaseReference; + + private final ActivityResultLauncher permissionRequest = registerForActivityResult( + new ActivityResultContracts.RequestMultiplePermissions(), result -> { + Boolean fineLocationGranted = result.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false); + Boolean bluetoothScanGranted = true; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + bluetoothScanGranted = result.getOrDefault(Manifest.permission.BLUETOOTH_SCAN, false); + } + + if (fineLocationGranted != null && fineLocationGranted) { + // Precise location access granted. + } else { + Toast.makeText(this, "A permissão de localização é necessária para o check-in.", Toast.LENGTH_LONG) + .show(); + } + }); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_main); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + FirebaseApp.initializeApp(this); + firebaseAuth = FirebaseAuth.getInstance(); + databaseReference = FirebaseDatabase.getInstance().getReference(); + + bindViews(); + setupTypeToggle(); + setupActionToggle(); + setupPrimaryAction(); + checkPermissions(); + } + + private void checkPermissions() { + List 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("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(); + } + } + + private void bindViews() { + btnCliente = findViewById(R.id.btnCliente); + btnEstabelecimento = findViewById(R.id.btnEstabelecimento); + btnEntrar = findViewById(R.id.btnEntrar); + btnCriarConta = findViewById(R.id.btnCriarConta); + btnPrimaryAction = findViewById(R.id.btnFinalCriarConta); + inputName = findViewById(R.id.inputName); + inputEmail = findViewById(R.id.inputEmail); + inputPassword = findViewById(R.id.inputPassword); + inputOwnerPhone = findViewById(R.id.inputOwnerPhone); + 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() { + btnCliente.setOnClickListener(v -> { + selectedAccountType = AccountType.CLIENTE; + updateTypeButtons(); + }); + btnEstabelecimento.setOnClickListener(v -> { + selectedAccountType = AccountType.ESTABELECIMENTO; + updateTypeButtons(); + }); + updateTypeButtons(); + } + + private void setupActionToggle() { + btnEntrar.setOnClickListener(v -> { + selectedAccountAction = AccountAction.ENTRAR; + updateActionButtons(); + }); + btnCriarConta.setOnClickListener(v -> { + selectedAccountAction = AccountAction.CRIAR; + updateActionButtons(); + }); + updateActionButtons(); + } + + private void setupPrimaryAction() { + btnPrimaryAction.setOnClickListener(v -> handlePrimaryAction()); + updatePrimaryActionState(); + } + + private void updateTypeButtons() { + setSelectedState(btnCliente, selectedAccountType == AccountType.CLIENTE); + setSelectedState(btnEstabelecimento, selectedAccountType == AccountType.ESTABELECIMENTO); + updateInputVisibility(); + } + + private void updateActionButtons() { + setSelectedState(btnEntrar, selectedAccountAction == AccountAction.ENTRAR); + setSelectedState(btnCriarConta, selectedAccountAction == AccountAction.CRIAR); + updatePrimaryActionState(); + } + + private void setSelectedState(Button button, boolean isSelected) { + int selectedTextColor = Color.WHITE; + int defaultTextColor = Color.parseColor("#231F1F"); + button.setBackgroundResource(isSelected ? R.drawable.tab_selected : R.drawable.tab_unselected); + button.setTextColor(isSelected ? selectedTextColor : defaultTextColor); + } + + private void updatePrimaryActionState() { + boolean creatingAccount = selectedAccountAction == AccountAction.CRIAR; + btnPrimaryAction.setText(creatingAccount ? "Criar Conta" : "Entrar"); + updateInputVisibility(); + } + + private void updateInputVisibility() { + boolean creatingAccount = selectedAccountAction == AccountAction.CRIAR; + boolean isEstablishment = selectedAccountType == AccountType.ESTABELECIMENTO; + + inputName.setHint(isEstablishment ? "Nome do proprietário" : "O seu nome"); + inputEmail.setHint(isEstablishment ? "Email do proprietário" : "Email"); + + inputName.setVisibility(creatingAccount ? View.VISIBLE : View.GONE); + inputOwnerPhone.setVisibility(creatingAccount && isEstablishment ? View.VISIBLE : View.GONE); + inputEstablishmentName.setVisibility(creatingAccount && isEstablishment ? View.VISIBLE : View.GONE); + inputEstablishmentEmail.setVisibility(creatingAccount && isEstablishment ? View.VISIBLE : View.GONE); + inputEstablishmentPhone.setVisibility(creatingAccount && isEstablishment ? View.VISIBLE : View.GONE); + + if (txtForgotPassword != null) { + txtForgotPassword.setVisibility(creatingAccount ? View.GONE : View.VISIBLE); + } + } + + private void handlePrimaryAction() { + String email = inputEmail.getText().toString().trim(); + String password = inputPassword.getText().toString().trim(); + String providedName = inputName.getText().toString().trim(); + String ownerPhone = inputOwnerPhone.getText().toString().trim(); + String establishmentName = inputEstablishmentName.getText().toString().trim(); + String establishmentEmail = inputEstablishmentEmail.getText().toString().trim(); + String establishmentPhone = inputEstablishmentPhone.getText().toString().trim(); + + if (TextUtils.isEmpty(email) || TextUtils.isEmpty(password)) { + Toast.makeText(this, "Preencha email e palavra-passe.", Toast.LENGTH_SHORT).show(); + return; + } + + boolean creatingAccount = selectedAccountAction == AccountAction.CRIAR; + + if (creatingAccount && !isValidPassword(password)) { + return; + } + + if (creatingAccount) { + if (selectedAccountType == AccountType.CLIENTE && TextUtils.isEmpty(providedName)) { + Toast.makeText(this, "Indique o seu nome para criar conta.", Toast.LENGTH_SHORT).show(); + return; + } + + if (selectedAccountType == AccountType.ESTABELECIMENTO) { + boolean missingOwner = TextUtils.isEmpty(providedName) || TextUtils.isEmpty(ownerPhone); + boolean missingEstablishment = TextUtils.isEmpty(establishmentName) + || TextUtils.isEmpty(establishmentEmail) + || TextUtils.isEmpty(establishmentPhone); + + if (missingOwner || missingEstablishment) { + Toast.makeText(this, "Preencha os dados do proprietário e do estabelecimento.", Toast.LENGTH_SHORT) + .show(); + return; + } + } + } + + String fallbackName = !TextUtils.isEmpty(providedName) ? providedName : deriveNameFromEmail(email); + String resolvedRole = getRoleForSelectedType(); + + if (creatingAccount) { + createAccountInFirebase( + email, + password, + providedName, + ownerPhone, + establishmentName, + establishmentEmail, + establishmentPhone, + fallbackName, + resolvedRole); + return; + } + + signInExistingAccount(email, password, fallbackName, resolvedRole); + } + + private String deriveNameFromEmail(String email) { + if (!email.contains("@")) { + return "Utilizador"; + } + String candidate = email.substring(0, email.indexOf("@")); + if (candidate.isEmpty()) { + return "Utilizador"; + } + String firstLetter = candidate.substring(0, 1).toUpperCase(); + String rest = candidate.length() > 1 ? candidate.substring(1) : ""; + return firstLetter + rest; + } + + private void markAccountCreated() { + hasCreatedAccount = true; + SharedPreferences prefs = getSharedPreferences(PREFS_NAME, MODE_PRIVATE); + prefs.edit().putBoolean(KEY_HAS_CREATED_ACCOUNT, true).apply(); + } + + private String getRoleForSelectedType() { + return selectedAccountType == AccountType.ESTABELECIMENTO ? "ADMIN" : "CLIENTE"; + } + + private String buildDocumentId(String email) { + return email.replace(".", "_").replace("@", "_at_"); + } + + private boolean ensureFirebaseReady() { + boolean ready = firebaseAuth != null && databaseReference != null; + if (!ready) { + Toast.makeText(this, "Ligue-se ao Firebase para continuar.", Toast.LENGTH_SHORT).show(); + } + return ready; + } + + private void signInExistingAccount(String email, String password, String fallbackName, String resolvedRole) { + if (!ensureFirebaseReady()) { + return; + } + + firebaseAuth.signInWithEmailAndPassword(email, password) + .addOnSuccessListener(authResult -> fetchAccountAndNavigate(email, fallbackName, resolvedRole)) + .addOnFailureListener(e -> { + android.util.Log.e("LoginError", "SignIn failed", e); + Toast.makeText(this, "Não foi possível iniciar sessão: " + e.getMessage(), Toast.LENGTH_SHORT) + .show(); + }); + } + + private void createAccountInFirebase( + String email, + String password, + String providedName, + String ownerPhone, + String establishmentName, + String establishmentEmail, + String establishmentPhone, + String fallbackName, + String resolvedRole) { + if (!ensureFirebaseReady()) { + return; + } + + firebaseAuth.createUserWithEmailAndPassword(email, password) + .addOnSuccessListener(result -> { + if (!hasCreatedAccount) { + markAccountCreated(); + } + String finalDisplayName = selectedAccountType == AccountType.ESTABELECIMENTO + && !TextUtils.isEmpty(establishmentName) + ? establishmentName + : fallbackName; + String uid = result.getUser() != null ? result.getUser().getUid() : null; + + persistAccountInFirebase( + email, + providedName, + resolvedRole, + ownerPhone, + establishmentName, + establishmentEmail, + establishmentPhone, + uid, + () -> { + Toast.makeText(this, "Conta criada com sucesso! Carregue em Entrar.", Toast.LENGTH_LONG) + .show(); + selectedAccountAction = AccountAction.ENTRAR; + updateActionButtons(); + }); + }) + .addOnFailureListener(e -> { + android.util.Log.e("LoginError", "CreateUser failed", e); + Toast.makeText(this, "Falha ao criar conta: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + }); + } + + private void fetchAccountAndNavigate(String email, String fallbackName, String resolvedRole) { + if (databaseReference == null) { + Toast.makeText(this, "Firebase indisponível.", Toast.LENGTH_SHORT).show(); + navigateToDashboard(email, fallbackName, resolvedRole); + return; + } + + String documentId = buildDocumentId(email); + databaseReference.child("users").child(documentId).get().addOnCompleteListener(task -> { + if (!task.isSuccessful()) { + android.util.Log.e("LoginError", "Database check failed", task.getException()); + Toast.makeText(this, "Falha ao validar perfil na cloud. A entrar em modo básico.", Toast.LENGTH_SHORT) + .show(); + navigateToDashboard(email, fallbackName, resolvedRole); + return; + } + + 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(); + navigateToDashboard(email, fallbackName, resolvedRole); + return; + } + + String accountTypeInFirebase = snapshot.child("accountType").getValue(String.class); + if (accountTypeInFirebase != null + && !accountTypeInFirebase.equalsIgnoreCase(selectedAccountType.name())) { + Toast.makeText(this, "Tipo de conta não corresponde ao registo no Firebase.", Toast.LENGTH_SHORT) + .show(); + return; + } + + String displayNameFromDb = snapshot.child("displayName").getValue(String.class); + String establishmentName = snapshot.child("establishmentName").getValue(String.class); + String ownerName = snapshot.child("ownerName").getValue(String.class); + String roleFromDb = snapshot.child("role").getValue(String.class); + + String finalDisplayName = !TextUtils.isEmpty(establishmentName) + ? establishmentName + : !TextUtils.isEmpty(displayNameFromDb) ? displayNameFromDb + : !TextUtils.isEmpty(ownerName) ? ownerName + : fallbackName; + + String finalRole = !TextUtils.isEmpty(roleFromDb) ? roleFromDb : resolvedRole; + navigateToDashboard(email, finalDisplayName, finalRole); + }); + } + + private void navigateToDashboard(String email, String displayName, String role) { + Intent nextScreen = selectedAccountType == AccountType.CLIENTE + ? new Intent(this, ClientDashboardActivity.class) + : new Intent(this, EstablishmentDashboardActivity.class); + + nextScreen.putExtra(EXTRA_ACTION_MODE, selectedAccountAction.name()); + nextScreen.putExtra(EXTRA_DISPLAY_NAME, displayName); + nextScreen.putExtra(EXTRA_EMAIL, email); + nextScreen.putExtra(EXTRA_ACCOUNT_TYPE, selectedAccountType.name()); + nextScreen.putExtra(EXTRA_ROLE, role); + startActivity(nextScreen); + } + + private void persistAccountInFirebase( + String ownerEmail, + String ownerName, + String role, + String ownerPhone, + String establishmentName, + String establishmentEmail, + String establishmentPhone, + String uid, + Runnable onSuccess) { + if (databaseReference == null) { + Toast.makeText(this, "Firebase indisponível.", Toast.LENGTH_SHORT).show(); + return; + } + + String documentId = buildDocumentId(ownerEmail); + Map payload = new HashMap<>(); + if (!TextUtils.isEmpty(uid)) { + payload.put("uid", uid); + } + payload.put("email", ownerEmail); + payload.put("displayName", ownerName); + payload.put("role", role); + payload.put("accountType", selectedAccountType.name()); + payload.put("createdAt", System.currentTimeMillis()); + + if (selectedAccountType == AccountType.ESTABELECIMENTO) { + payload.put("ownerName", ownerName); + payload.put("ownerEmail", ownerEmail); + payload.put("ownerPhone", ownerPhone); + payload.put("establishmentName", establishmentName); + payload.put("establishmentEmail", establishmentEmail); + payload.put("establishmentPhone", establishmentPhone); + } + + databaseReference.child("users").child(documentId).updateChildren(payload) + .addOnSuccessListener(unused -> { + Toast.makeText(this, "Conta guardada na cloud.", Toast.LENGTH_SHORT).show(); + if (onSuccess != null) { + onSuccess.run(); + } + }) + .addOnFailureListener( + e -> Toast.makeText(this, "Não foi possível guardar na cloud.", Toast.LENGTH_SHORT).show()); + } + + private boolean isValidPassword(String password) { + if (password.length() < 6) { + Toast.makeText(this, "A palavra-passe deve ter pelo menos 6 caracteres.", Toast.LENGTH_SHORT).show(); + return false; + } + boolean hasLower = false; + boolean hasDigit = false; + boolean hasSpecial = false; + + for (char c : password.toCharArray()) { + if (Character.isLowerCase(c)) + hasLower = true; + else if (Character.isDigit(c)) + hasDigit = true; + else if (!Character.isLetterOrDigit(c)) + hasSpecial = true; + } + + if (!hasLower || !hasDigit || !hasSpecial) { + Toast.makeText(this, "A palavra-passe deve conter minúsculas, números e símbolos.", Toast.LENGTH_LONG) + .show(); + return false; + } + return true; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/pap_teste/MinhasReservasActivity.java b/app/src/main/java/com/example/pap_teste/MinhasReservasActivity.java new file mode 100644 index 0000000..a2fa358 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/MinhasReservasActivity.java @@ -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 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(); + } + }); + } +} diff --git a/app/src/main/java/com/example/pap_teste/NovaReservaActivity.java b/app/src/main/java/com/example/pap_teste/NovaReservaActivity.java new file mode 100644 index 0000000..435e2ab --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/NovaReservaActivity.java @@ -0,0 +1,291 @@ +package com.example.pap_teste; + +import android.os.Bundle; +import androidx.recyclerview.widget.RecyclerView; + +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 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); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_nova_reserva); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.novaReservaRoot), (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); + return insets; + }); + + 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 -> handleBackNavigation()); + } + + 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 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 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); + String logoUrl = ds.child("logoUrl").getValue(String.class); + + if (name != null && email != null) { + filteredList.add(new com.example.pap_teste.models.Restaurant(name, cat, email, false, logoUrl)); + } + } + } + + 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() { + android.widget.Button btnDate = findViewById(R.id.btnSelectDate); + android.widget.Button btnTime = findViewById(R.id.btnSelectTime); + + btnDate.setOnClickListener(v -> { + java.util.Calendar cal = java.util.Calendar.getInstance(); + new android.app.DatePickerDialog(this, (view, year, month, dayOfMonth) -> { + selectedDate = dayOfMonth + "/" + (month + 1) + "/" + year; + btnDate.setText(selectedDate); + }, cal.get(java.util.Calendar.YEAR), cal.get(java.util.Calendar.MONTH), cal.get(java.util.Calendar.DAY_OF_MONTH)).show(); + }); + + btnTime.setOnClickListener(v -> { + java.util.Calendar cal = java.util.Calendar.getInstance(); + new android.app.TimePickerDialog(this, (view, hourOfDay, minute) -> { + selectedTime = String.format(java.util.Locale.getDefault(), "%02d:%02d", hourOfDay, minute); + btnTime.setText(selectedTime); + }, cal.get(java.util.Calendar.HOUR_OF_DAY), cal.get(java.util.Calendar.MINUTE), true).show(); + }); + + findViewById(R.id.btnConfirmarReserva).setOnClickListener(v -> saveReservation()); + } + + private void saveReservation() { + android.widget.EditText etPartySize = findViewById(R.id.etPartySize); + int val = 0; + try { + val = Integer.parseInt(etPartySize.getText().toString()); + } catch (Exception e) {} + final int partySize = val; + + if (selectedDate == null || selectedTime == null || partySize == 0) { + android.widget.Toast.makeText(this, "Por favor, selecione data, hora e número de pessoas.", + android.widget.Toast.LENGTH_SHORT).show(); + return; + } + + String restEmail = selectedRestaurant.getEmail(); + + 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 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(); + } + }); + } + } +} diff --git a/app/src/main/java/com/example/pap_teste/PartilharReservaActivity.java b/app/src/main/java/com/example/pap_teste/PartilharReservaActivity.java new file mode 100644 index 0000000..7545020 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/PartilharReservaActivity.java @@ -0,0 +1,31 @@ +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 PartilharReservaActivity extends AppCompatActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + EdgeToEdge.enable(this); + setContentView(R.layout.activity_partilhar_reserva); + ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.partilharRoot), (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/ProfileDashboardActivity.java b/app/src/main/java/com/example/pap_teste/ProfileDashboardActivity.java new file mode 100644 index 0000000..8b4a96e --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/ProfileDashboardActivity.java @@ -0,0 +1,221 @@ +package com.example.pap_teste; + +import android.content.Intent; +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 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; + +import java.util.HashMap; +import java.util.Map; + +public class ProfileDashboardActivity extends AppCompatActivity { + + private EditText inputName, inputPhone, inputEmailEdit; + private String email, documentId, photoUrl; + private DatabaseReference databaseReference; + private ImageView imgProfile; + private androidx.activity.result.ActivityResultLauncher imagePickerLauncher; + + @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); + inputPhone = findViewById(R.id.inputProfilePhone); + inputEmailEdit = findViewById(R.id.inputProfileEmail); + imgProfile = findViewById(R.id.imgProfile); + + if (currentName != null) { + inputName.setText(currentName); + } + if (email != null) { + inputEmailEdit.setText(email); + } + + fetchAdditionalProfileData(); + + imagePickerLauncher = 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) { + uploadImageToFirebase(imageUri); + } + } + }); + + findViewById(R.id.cardProfileBig).setOnClickListener(v -> { + 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); + 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 -> { + startActivity(new Intent(this, FavoritosActivity.class)); + }); + + btnRes.setOnClickListener(v -> { + 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; + databaseReference.child(documentId).get().addOnSuccessListener(snapshot -> { + if (snapshot.exists()) { + String phone = snapshot.child("ownerPhone").getValue(String.class); + if (phone == null) + phone = snapshot.child("phone").getValue(String.class); + if (phone != null) + inputPhone.setText(phone); + + String dbEmail = snapshot.child("email").getValue(String.class); + if (dbEmail != null) + inputEmailEdit.setText(dbEmail); + + photoUrl = snapshot.child("photoUrl").getValue(String.class); + if (photoUrl != null && !photoUrl.isEmpty()) { + 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 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; + + String newName = inputName.getText().toString().trim(); + String newPhone = inputPhone.getText().toString().trim(); + String newEmail = inputEmailEdit.getText().toString().trim(); + + if (TextUtils.isEmpty(newName)) { + Toast.makeText(this, "Indique um nome.", Toast.LENGTH_SHORT).show(); + return; + } + + Map updates = new HashMap<>(); + 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 -> { + Toast.makeText(this, "Perfil atualizado!", Toast.LENGTH_SHORT).show(); + Intent resultIntent = new Intent(); + resultIntent.putExtra(MainActivity.EXTRA_DISPLAY_NAME, newName); + setResult(RESULT_OK, resultIntent); + finish(); + }) + .addOnFailureListener( + e -> Toast.makeText(this, "Falha ao atualizar perfil.", Toast.LENGTH_SHORT).show()); + } +} diff --git a/app/src/main/java/com/example/pap_teste/ReservaAdapter.java b/app/src/main/java/com/example/pap_teste/ReservaAdapter.java new file mode 100644 index 0000000..9dddf7d --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/ReservaAdapter.java @@ -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 { + + public interface OnReservaActionListener { + void onCheckIn(Reserva reserva); + + void onCancel(Reserva reserva); + } + + private final List reservas; + private final OnReservaActionListener listener; + + public ReservaAdapter(List 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); + } + } +} diff --git a/app/src/main/java/com/example/pap_teste/ReservationOptionAdapter.java b/app/src/main/java/com/example/pap_teste/ReservationOptionAdapter.java new file mode 100644 index 0000000..fc29b13 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/ReservationOptionAdapter.java @@ -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 { + private List options; + private OnOptionClickListener listener; + + public interface OnOptionClickListener { + void onOptionClick(String option); + } + + public ReservationOptionAdapter(List 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); + } + } +} diff --git a/app/src/main/java/com/example/pap_teste/RestaurantAdapter.java b/app/src/main/java/com/example/pap_teste/RestaurantAdapter.java new file mode 100644 index 0000000..1ef4816 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/RestaurantAdapter.java @@ -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 { + private List restaurants; + private OnRestaurantClickListener listener; + + public interface OnRestaurantClickListener { + void onRestaurantClick(Restaurant restaurant); + } + + public RestaurantAdapter(List 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); + } + } +} 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/java/com/example/pap_teste/models/Mesa.java b/app/src/main/java/com/example/pap_teste/models/Mesa.java new file mode 100644 index 0000000..28b226f --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/models/Mesa.java @@ -0,0 +1,66 @@ +package com.example.pap_teste.models; + +public class Mesa { + private String id; + 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, String restauranteEmail) { + this.id = id; + this.numero = numero; + this.capacidade = capacidade; + this.estado = estado; + this.restauranteEmail = restauranteEmail; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getNumero() { + return numero; + } + + public void setNumero(int numero) { + this.numero = numero; + } + + public int getCapacidade() { + return capacidade; + } + + public void setCapacidade(int capacidade) { + this.capacidade = capacidade; + } + + public String getEstado() { + return estado; + } + + public void setEstado(String estado) { + this.estado = estado; + } + + public String getRestauranteEmail() { + return restauranteEmail; + } + + public void setRestauranteEmail(String restauranteEmail) { + this.restauranteEmail = restauranteEmail; + } + + @Override + public String toString() { + return "Mesa " + numero; + } +} diff --git a/app/src/main/java/com/example/pap_teste/models/Reserva.java b/app/src/main/java/com/example/pap_teste/models/Reserva.java new file mode 100644 index 0000000..be20023 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/models/Reserva.java @@ -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; + } +} diff --git a/app/src/main/java/com/example/pap_teste/models/Restaurant.java b/app/src/main/java/com/example/pap_teste/models/Restaurant.java new file mode 100644 index 0000000..eb27198 --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/models/Restaurant.java @@ -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; } +} diff --git a/app/src/main/java/com/example/pap_teste/models/Staff.java b/app/src/main/java/com/example/pap_teste/models/Staff.java new file mode 100644 index 0000000..dc9a98b --- /dev/null +++ b/app/src/main/java/com/example/pap_teste/models/Staff.java @@ -0,0 +1,51 @@ +package com.example.pap_teste.models; + +public class Staff { + private String name; + private String zona; + private String mesa; + + private String id; + + public Staff(String name, String zona, String mesa, String id) { + this.name = name; + this.zona = zona; + this.mesa = mesa; + this.id = id; + } + + public Staff() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getZona() { + return zona; + } + + public void setZona(String zona) { + this.zona = zona; + } + + public String getMesa() { + return mesa; + } + + public void setMesa(String mesa) { + this.mesa = mesa; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/app/src/main/res/drawable/bg_tabs.xml b/app/src/main/res/drawable/bg_tabs.xml new file mode 100644 index 0000000..3508ae9 --- /dev/null +++ b/app/src/main/res/drawable/bg_tabs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_light.xml b/app/src/main/res/drawable/btn_light.xml new file mode 100644 index 0000000..9748faa --- /dev/null +++ b/app/src/main/res/drawable/btn_light.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_light_border.xml b/app/src/main/res/drawable/btn_light_border.xml new file mode 100644 index 0000000..6ece8e5 --- /dev/null +++ b/app/src/main/res/drawable/btn_light_border.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/btn_primary.xml b/app/src/main/res/drawable/btn_primary.xml new file mode 100644 index 0000000..012165d --- /dev/null +++ b/app/src/main/res/drawable/btn_primary.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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_pizzas.png b/app/src/main/res/drawable/cat_pizzas.png new file mode 100644 index 0000000..7e058b8 Binary files /dev/null and b/app/src/main/res/drawable/cat_pizzas.png differ diff --git a/app/src/main/res/drawable/cat_sobremesas.png b/app/src/main/res/drawable/cat_sobremesas.png new file mode 100644 index 0000000..404ac6d Binary files /dev/null and b/app/src/main/res/drawable/cat_sobremesas.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/drawable/circle_bg.xml b/app/src/main/res/drawable/circle_bg.xml new file mode 100644 index 0000000..60f802b --- /dev/null +++ b/app/src/main/res/drawable/circle_bg.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_visibility.xml b/app/src/main/res/drawable/ic_visibility.xml new file mode 100644 index 0000000..97580c0 --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_visibility_off.xml b/app/src/main/res/drawable/ic_visibility_off.xml new file mode 100644 index 0000000..ef3c1bc --- /dev/null +++ b/app/src/main/res/drawable/ic_visibility_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/input_bg.xml b/app/src/main/res/drawable/input_bg.xml new file mode 100644 index 0000000..ae065a3 --- /dev/null +++ b/app/src/main/res/drawable/input_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/na_mesa.png b/app/src/main/res/drawable/na_mesa.png new file mode 100644 index 0000000..16cacc7 Binary files /dev/null and b/app/src/main/res/drawable/na_mesa.png differ diff --git a/app/src/main/res/drawable/tab_selected.xml b/app/src/main/res/drawable/tab_selected.xml new file mode 100644 index 0000000..96c13ef --- /dev/null +++ b/app/src/main/res/drawable/tab_selected.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_unselected.xml b/app/src/main/res/drawable/tab_unselected.xml new file mode 100644 index 0000000..4deeebe --- /dev/null +++ b/app/src/main/res/drawable/tab_unselected.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_account_created.xml b/app/src/main/res/layout/activity_account_created.xml new file mode 100644 index 0000000..49e5e99 --- /dev/null +++ b/app/src/main/res/layout/activity_account_created.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_add_staff.xml b/app/src/main/res/layout/activity_add_staff.xml new file mode 100644 index 0000000..be2fff6 --- /dev/null +++ b/app/src/main/res/layout/activity_add_staff.xml @@ -0,0 +1,164 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_checkin_antecipado.xml b/app/src/main/res/layout/activity_checkin_antecipado.xml new file mode 100644 index 0000000..1b8d659 --- /dev/null +++ b/app/src/main/res/layout/activity_checkin_antecipado.xml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_client_dashboard.xml b/app/src/main/res/layout/activity_client_dashboard.xml new file mode 100644 index 0000000..1f973fc --- /dev/null +++ b/app/src/main/res/layout/activity_client_dashboard.xml @@ -0,0 +1,290 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_definicoes_admin.xml b/app/src/main/res/layout/activity_definicoes_admin.xml new file mode 100644 index 0000000..74ea0c8 --- /dev/null +++ b/app/src/main/res/layout/activity_definicoes_admin.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_detalhes_reservas.xml b/app/src/main/res/layout/activity_detalhes_reservas.xml new file mode 100644 index 0000000..485e326 --- /dev/null +++ b/app/src/main/res/layout/activity_detalhes_reservas.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_establishment_dashboard.xml b/app/src/main/res/layout/activity_establishment_dashboard.xml new file mode 100644 index 0000000..913f8c3 --- /dev/null +++ b/app/src/main/res/layout/activity_establishment_dashboard.xml @@ -0,0 +1,329 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_explorar_restaurantes.xml b/app/src/main/res/layout/activity_explorar_restaurantes.xml new file mode 100644 index 0000000..c6d38df --- /dev/null +++ b/app/src/main/res/layout/activity_explorar_restaurantes.xml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_favoritos.xml b/app/src/main/res/layout/activity_favoritos.xml new file mode 100644 index 0000000..cfce089 --- /dev/null +++ b/app/src/main/res/layout/activity_favoritos.xml @@ -0,0 +1,52 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_gerir_mesas.xml b/app/src/main/res/layout/activity_gerir_mesas.xml new file mode 100644 index 0000000..3d8f7d9 --- /dev/null +++ b/app/src/main/res/layout/activity_gerir_mesas.xml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_gestao_staff.xml b/app/src/main/res/layout/activity_gestao_staff.xml new file mode 100644 index 0000000..89032ac --- /dev/null +++ b/app/src/main/res/layout/activity_gestao_staff.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_lista_espera.xml b/app/src/main/res/layout/activity_lista_espera.xml new file mode 100644 index 0000000..7198404 --- /dev/null +++ b/app/src/main/res/layout/activity_lista_espera.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..e77998a --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,259 @@ + + + + + + + + + + + + + + + + + + + +