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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_minhas_reservas.xml b/app/src/main/res/layout/activity_minhas_reservas.xml
new file mode 100644
index 0000000..b113504
--- /dev/null
+++ b/app/src/main/res/layout/activity_minhas_reservas.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_nova_reserva.xml b/app/src/main/res/layout/activity_nova_reserva.xml
new file mode 100644
index 0000000..9734967
--- /dev/null
+++ b/app/src/main/res/layout/activity_nova_reserva.xml
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_partilhar_reserva.xml b/app/src/main/res/layout/activity_partilhar_reserva.xml
new file mode 100644
index 0000000..eaaf5e3
--- /dev/null
+++ b/app/src/main/res/layout/activity_partilhar_reserva.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_profile_dashboard.xml b/app/src/main/res/layout/activity_profile_dashboard.xml
new file mode 100644
index 0000000..3b9dcbb
--- /dev/null
+++ b/app/src/main/res/layout/activity_profile_dashboard.xml
@@ -0,0 +1,193 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_food_category.xml b/app/src/main/res/layout/item_food_category.xml
new file mode 100644
index 0000000..df5dfea
--- /dev/null
+++ b/app/src/main/res/layout/item_food_category.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_reserva_cliente.xml b/app/src/main/res/layout/item_reserva_cliente.xml
new file mode 100644
index 0000000..767fb97
--- /dev/null
+++ b/app/src/main/res/layout/item_reserva_cliente.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_restaurant.xml b/app/src/main/res/layout/item_restaurant.xml
new file mode 100644
index 0000000..67118eb
--- /dev/null
+++ b/app/src/main/res/layout/item_restaurant.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..6f3b755
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000..c209e78
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..b2dfe3d
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000..4f0f1d6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..62b611d
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000..948a307
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..1b9a695
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..28d4b77
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9287f50
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000..aa7d642
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000..9126ae3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000..2a185df
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,7 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..b961208
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,24 @@
+
+
+
+ #06C167
+ #05A357
+ #06C167
+
+
+ #FF000000
+ #FFFFFFFF
+ #F8FAFC
+ #FFFFFF
+ #1E293B
+ #64748B
+
+
+ #10B981
+ #EF4444
+ #F59E0B
+ #E2E8F0
+
+
+ #BE1F13
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..365b1b0
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Pap_teste
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..d260148
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000..4df9255
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..9ee9997
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/com/example/pap_teste/ExampleUnitTest.java b/app/src/test/java/com/example/pap_teste/ExampleUnitTest.java
new file mode 100644
index 0000000..3f01eba
--- /dev/null
+++ b/app/src/test/java/com/example/pap_teste/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.example.pap_teste;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..92eedfa
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,5 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+ alias(libs.plugins.android.application) apply false
+ alias(libs.plugins.google.services) apply false
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..d7d8e66
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,31 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. For more details, visit
+# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
+android.defaults.buildfeatures.resvalues=true
+android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
+android.enableAppCompileTimeRClass=false
+android.usesSdkInManifest.disallowed=false
+android.uniquePackageNames=false
+android.dependency.useConstraints=true
+android.r8.strictFullModeForKeepRules=false
+android.r8.optimizedResourceShrinking=false
+android.builtInKotlin=false
+android.newDsl=false
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..57bf325
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,34 @@
+[versions]
+agp = "9.1.0"
+junit = "4.13.2"
+junitVersion = "1.3.0"
+espressoCore = "3.7.0"
+testCore = "1.6.1"
+appcompat = "1.7.1"
+material = "1.13.0"
+activity = "1.11.0"
+constraintlayout = "2.2.1"
+firebaseBom = "33.7.0"
+googleServices = "4.4.2"
+firebaseDatabase = "22.0.1"
+glide = "4.16.0"
+firebaseStorage = "21.0.1"
+
+[libraries]
+junit = { group = "junit", name = "junit", version.ref = "junit" }
+ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
+espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
+test-core = { group = "androidx.test", name = "core", version.ref = "testCore" }
+appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
+constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
+firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebaseBom" }
+firebase-database = { group = "com.google.firebase", name = "firebase-database", version.ref = "firebaseDatabase" }
+glide = { group = "com.github.bumptech.glide", name = "glide", version.ref = "glide" }
+firebase-storage = { group = "com.google.firebase", name = "firebase-storage", version.ref = "firebaseStorage" }
+
+[plugins]
+android-application = { id = "com.android.application", version.ref = "agp" }
+google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..e708b1c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..235468c
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+#Tue Feb 24 17:05:40 WET 2026
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionSha256Sum=b266d5ff6b90eada6dc3b20cb090e3731302e553a27c5d3e4df1f0d76beaff06
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..4f906e0
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..ac1b06f
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..69ecc14
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,23 @@
+pluginManagement {
+ repositories {
+ google {
+ content {
+ includeGroupByRegex("com\\.android.*")
+ includeGroupByRegex("com\\.google.*")
+ includeGroupByRegex("androidx.*")
+ }
+ }
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "Pap_teste"
+include(":app")