From 9058c57c5b47b0279cb81fb3c0ffdadfbd0978c5 Mon Sep 17 00:00:00 2001
From: lucas <230410@epvc.pt>
Date: Sun, 11 Jan 2026 12:22:59 +0000
Subject: [PATCH] Pap atualizada
---
.idea/deploymentTargetSelector.xml | 19 +-
.idea/misc.xml | 1 -
app/src/main/AndroidManifest.xml | 4 +
.../com/example/bem/GenerateCodeActivity.java | 115 +++
.../com/example/bem/InviteCodeActivity.java | 255 ++----
.../java/com/example/bem/LoginActivity.java | 29 +-
.../java/com/example/bem/MainActivity.java | 744 ++++++++----------
.../res/layout/activity_generate_code.xml | 57 ++
.../main/res/layout/activity_invite_code.xml | 150 +---
app/src/main/res/layout/activity_main.xml | 14 +-
app/src/main/res/layout/dialog_intro.xml | 46 ++
app/src/main/res/layout/layout_tab_alarms.xml | 80 +-
.../main/res/layout/layout_tab_reminders.xml | 205 +----
app/src/main/res/values/colors.xml | 1 +
14 files changed, 681 insertions(+), 1039 deletions(-)
create mode 100644 app/src/main/java/com/example/bem/GenerateCodeActivity.java
create mode 100644 app/src/main/res/layout/activity_generate_code.xml
create mode 100644 app/src/main/res/layout/dialog_intro.xml
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
index afe9e82..5400c00 100644
--- a/.idea/deploymentTargetSelector.xml
+++ b/.idea/deploymentTargetSelector.xml
@@ -4,14 +4,27 @@
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 74dd639..b2c751a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 98c733b..9180770 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -36,6 +36,10 @@
android:name=".MainActivity"
android:exported="false" />
+
+
diff --git a/app/src/main/java/com/example/bem/GenerateCodeActivity.java b/app/src/main/java/com/example/bem/GenerateCodeActivity.java
new file mode 100644
index 0000000..3b049b6
--- /dev/null
+++ b/app/src/main/java/com/example/bem/GenerateCodeActivity.java
@@ -0,0 +1,115 @@
+package com.example.bem;
+
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.FirebaseFirestore;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+public class GenerateCodeActivity extends AppCompatActivity {
+
+ private FirebaseFirestore db;
+ private FirebaseAuth mAuth;
+
+ private TextView textCode;
+ private TextView textTimer;
+ private Button btnGenerate;
+ private ProgressBar progressBar;
+
+ private CountDownTimer countDownTimer;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_generate_code);
+
+ db = FirebaseFirestore.getInstance();
+ mAuth = FirebaseAuth.getInstance();
+
+ textCode = findViewById(R.id.text_code);
+ textTimer = findViewById(R.id.text_timer);
+ btnGenerate = findViewById(R.id.btn_generate_code);
+ progressBar = findViewById(R.id.progress_bar);
+
+ btnGenerate.setOnClickListener(v -> generateCode());
+
+ generateCode();
+ }
+
+ private void generateCode() {
+ setLoading(true);
+
+ String userId = mAuth.getCurrentUser().getUid();
+ if (userId == null) {
+ Toast.makeText(this, "Erro: Utilizador não autenticado.", Toast.LENGTH_SHORT).show();
+ setLoading(false);
+ return;
+ }
+
+ String code = String.format("%06d", new Random().nextInt(999999));
+
+ Map codeData = new HashMap<>();
+ codeData.put("userId", userId);
+ codeData.put("createdAt", System.currentTimeMillis());
+
+ db.collection("inviteCodes").document(code)
+ .set(codeData)
+ .addOnSuccessListener(aVoid -> {
+ textCode.setText(code);
+ startTimer();
+ setLoading(false);
+ })
+ .addOnFailureListener(e -> {
+ Toast.makeText(GenerateCodeActivity.this, "Falha ao gerar o código.", Toast.LENGTH_SHORT).show();
+ setLoading(false);
+ });
+ }
+
+ private void startTimer() {
+ if (countDownTimer != null) {
+ countDownTimer.cancel();
+ }
+
+ countDownTimer = new CountDownTimer(30000, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ textTimer.setText(String.format("Expira em %d segundos", millisUntilFinished / 1000));
+ }
+
+ @Override
+ public void onFinish() {
+ textTimer.setText("Código expirado");
+ textCode.setText("------");
+ }
+ }.start();
+ }
+
+ private void setLoading(boolean isLoading) {
+ if (isLoading) {
+ progressBar.setVisibility(View.VISIBLE);
+ btnGenerate.setEnabled(false);
+ } else {
+ progressBar.setVisibility(View.GONE);
+ btnGenerate.setEnabled(true);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ if (countDownTimer != null) {
+ countDownTimer.cancel();
+ }
+ }
+}
diff --git a/app/src/main/java/com/example/bem/InviteCodeActivity.java b/app/src/main/java/com/example/bem/InviteCodeActivity.java
index 9586f48..c5fc693 100644
--- a/app/src/main/java/com/example/bem/InviteCodeActivity.java
+++ b/app/src/main/java/com/example/bem/InviteCodeActivity.java
@@ -2,44 +2,29 @@ package com.example.bem;
import android.content.Intent;
import android.os.Bundle;
-import android.os.CountDownTimer;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
-import android.widget.LinearLayout;
-import android.widget.TextView;
+import android.widget.ProgressBar;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.firestore.FieldValue;
import com.google.firebase.firestore.FirebaseFirestore;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Random;
-
public class InviteCodeActivity extends AppCompatActivity {
+ private EditText inputCode;
+ private Button btnContinue;
+ private ProgressBar progressBar;
+
private FirebaseAuth mAuth;
private FirebaseFirestore db;
- private LinearLayout layoutGenerateCode;
- private LinearLayout layoutEnterCode;
- private TextView textInviteCode;
- private TextView textCountdown;
- private TextView textModeDescription;
- private Button btnGenerateCode;
- private EditText inputInviteCode;
- private Button btnValidateCode;
- private Button btnBack;
-
- private String currentCode = null;
- private CountDownTimer countDownTimer;
- private boolean isGuardianMode = false;
-
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -48,201 +33,77 @@ public class InviteCodeActivity extends AppCompatActivity {
mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance();
- layoutGenerateCode = findViewById(R.id.layoutGenerateCode);
- layoutEnterCode = findViewById(R.id.layoutEnterCode);
- textInviteCode = findViewById(R.id.textInviteCode);
- textCountdown = findViewById(R.id.textCountdown);
- textModeDescription = findViewById(R.id.textModeDescription);
- btnGenerateCode = findViewById(R.id.btnGenerateCode);
- inputInviteCode = findViewById(R.id.inputInviteCode);
- btnValidateCode = findViewById(R.id.btnValidateCode);
- btnBack = findViewById(R.id.btnBack);
+ inputCode = findViewById(R.id.input_invite_code);
+ btnContinue = findViewById(R.id.btn_validate_code);
+ progressBar = findViewById(R.id.progress_bar_invite);
- isGuardianMode = getIntent().getBooleanExtra("is_guardian", false);
-
- if (isGuardianMode) {
- textModeDescription.setText("Insira o código do utilizador para se vincular como responsável");
- layoutGenerateCode.setVisibility(View.GONE);
- layoutEnterCode.setVisibility(View.VISIBLE);
- } else {
- textModeDescription.setText("Gere um código temporário para vincular responsável");
- layoutGenerateCode.setVisibility(View.VISIBLE);
- layoutEnterCode.setVisibility(View.GONE);
- generateCode();
- }
-
- btnGenerateCode.setOnClickListener(v -> generateCode());
- btnValidateCode.setOnClickListener(v -> validateCode());
- btnBack.setOnClickListener(v -> finish());
+ btnContinue.setOnClickListener(v -> validateInviteCode());
}
- private void generateCode() {
- if (mAuth.getCurrentUser() == null) {
- Toast.makeText(this, "Erro: Não autenticado", Toast.LENGTH_SHORT).show();
- return;
- }
-
- String code = String.format("%06d", new Random().nextInt(999999));
- currentCode = code;
- textInviteCode.setText(code);
-
- String userId = mAuth.getCurrentUser().getUid();
- Map codeData = new HashMap<>();
- codeData.put("code", code);
- codeData.put("userId", userId);
- codeData.put("createdAt", System.currentTimeMillis());
- codeData.put("expiresAt", System.currentTimeMillis() + 30000);
- codeData.put("used", false);
-
- db.collection("inviteCodes").document(code)
- .set(codeData)
- .addOnSuccessListener(aVoid -> {
- startCountdown();
- Toast.makeText(this, "✓ Código gerado! Válido por 30 segundos", Toast.LENGTH_SHORT).show();
- })
- .addOnFailureListener(e -> {
- Toast.makeText(this, "Erro ao gerar código: " + e.getMessage(), Toast.LENGTH_LONG).show();
- });
- }
-
- private void startCountdown() {
- if (countDownTimer != null) {
- countDownTimer.cancel();
- }
-
- countDownTimer = new CountDownTimer(30000, 1000) {
- @Override
- public void onTick(long millisUntilFinished) {
- int seconds = (int) (millisUntilFinished / 1000);
- textCountdown.setText("Expira em: " + seconds + "s");
-
- if (seconds <= 10) {
- textCountdown.setTextColor(getResources().getColor(android.R.color.holo_red_dark));
- }
- }
-
- @Override
- public void onFinish() {
- textCountdown.setText("Código expirado");
- textInviteCode.setText("------");
- currentCode = null;
-
- btnGenerateCode.setEnabled(true);
- }
- };
-
- countDownTimer.start();
- btnGenerateCode.setEnabled(false);
- }
-
- private void validateCode() {
- String code = inputInviteCode.getText().toString().trim();
+ private void validateInviteCode() {
+ String code = inputCode.getText().toString().trim();
+ FirebaseUser currentUser = mAuth.getCurrentUser();
if (TextUtils.isEmpty(code) || code.length() != 6) {
- inputInviteCode.setError("Código deve ter 6 dígitos");
+ inputCode.setError("O código deve ter 6 dígitos.");
return;
}
- if (mAuth.getCurrentUser() == null) {
- Toast.makeText(this, "Erro: Não autenticado", Toast.LENGTH_SHORT).show();
+ if (currentUser == null) {
+ Toast.makeText(this, "Erro: Sessão inválida. Faça login novamente.", Toast.LENGTH_SHORT).show();
return;
}
- btnValidateCode.setEnabled(false);
+ setLoading(true);
- db.collection("inviteCodes").document(code)
- .get()
- .addOnSuccessListener(document -> {
- if (!document.exists()) {
- btnValidateCode.setEnabled(true);
- Toast.makeText(this, "❌ Código inválido", Toast.LENGTH_SHORT).show();
- return;
- }
+ // 1. Encontrar o código na coleção 'inviteCodes'
+ db.collection("inviteCodes").document(code).get().addOnSuccessListener(documentSnapshot -> {
+ if (!documentSnapshot.exists()) {
+ showError("Código inválido ou expirado.");
+ return;
+ }
- Boolean used = document.getBoolean("used");
- Long expiresAt = document.getLong("expiresAt");
- String targetUserId = document.getString("userId");
+ String patientId = documentSnapshot.getString("userId");
+ if (patientId == null) {
+ showError("Erro no código. Tente gerar um novo.");
+ return;
+ }
- if (used != null && used) {
- btnValidateCode.setEnabled(true);
- Toast.makeText(this, "❌ Código já utilizado", Toast.LENGTH_SHORT).show();
- return;
- }
+ String guardianId = currentUser.getUid();
- if (expiresAt != null && System.currentTimeMillis() > expiresAt) {
- btnValidateCode.setEnabled(true);
- Toast.makeText(this, "❌ Código expirado", Toast.LENGTH_SHORT).show();
- return;
- }
+ // 2. Atualizar o perfil do paciente para adicionar o ID do responsável
+ db.collection("users").document(patientId)
+ .update("guardianId", guardianId)
+ .addOnSuccessListener(aVoid -> {
- linkGuardianToUser(code, targetUserId);
- })
- .addOnFailureListener(e -> {
- btnValidateCode.setEnabled(true);
- Toast.makeText(this, "Erro: " + e.getMessage(), Toast.LENGTH_LONG).show();
- });
+ // 3. Atualizar o perfil do responsável para adicionar o ID do paciente
+ db.collection("users").document(guardianId)
+ .update("managedUsers", FieldValue.arrayUnion(patientId))
+ .addOnSuccessListener(aVoid1 -> {
+
+ // 4. Apagar o código de convite para que não seja reutilizado
+ db.collection("inviteCodes").document(code).delete();
+
+ Toast.makeText(this, "Utilizador associado com sucesso!", Toast.LENGTH_LONG).show();
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ finish();
+
+ }).addOnFailureListener(e -> showError("Falha ao atualizar o seu perfil. Tente novamente."));
+
+ }).addOnFailureListener(e -> showError("Falha ao associar. Verifique se o utilizador já tem um responsável."));
+
+ }).addOnFailureListener(e -> showError("Erro ao validar o código. Verifique a sua ligação."));
}
- private void linkGuardianToUser(String code, String targetUserId) {
- String guardianId = mAuth.getCurrentUser().getUid();
-
- Map linkData = new HashMap<>();
- linkData.put("guardianId", guardianId);
- linkData.put("linkedAt", System.currentTimeMillis());
-
- // 1. Update the Target User's document (Add guardian to their list)
- db.collection("users").document(targetUserId)
- .update("guardians", FieldValue.arrayUnion(guardianId))
- .addOnSuccessListener(aVoid -> {
-
- // 2. Update the Guardian's document (Add target user to their list)
- db.collection("users").document(guardianId)
- .update("managedUsers", FieldValue.arrayUnion(targetUserId))
- .addOnSuccessListener(aVoid2 -> {
-
- // 3. Mark code as used
- db.collection("inviteCodes").document(code)
- .update("used", true)
- .addOnSuccessListener(aVoid3 -> {
- Toast.makeText(this, "✓ Vinculado com sucesso!", Toast.LENGTH_LONG).show();
-
- Intent intent = new Intent(this, MainActivity.class);
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivity(intent);
- finish();
- })
- .addOnFailureListener(e -> {
- // Even if marking code fails, the link is done.
- // But ideally we should handle this. For now, proceeding.
- Intent intent = new Intent(this, MainActivity.class);
- intent.setFlags(
- Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivity(intent);
- finish();
- });
-
- })
- .addOnFailureListener(e -> {
- // If updating guardian fails, we should probably revert or warn.
- // For MVP/Simple app: Log and warn.
- btnValidateCode.setEnabled(true);
- Toast.makeText(this, "Erro ao atualizar perfil do responsável: " + e.getMessage(),
- Toast.LENGTH_LONG).show();
- });
-
- })
- .addOnFailureListener(e -> {
- btnValidateCode.setEnabled(true);
- Toast.makeText(this, "Erro ao vincular: " + e.getMessage(), Toast.LENGTH_LONG).show();
- });
+ private void setLoading(boolean loading) {
+ progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
+ btnContinue.setEnabled(!loading);
}
- @Override
- protected void onDestroy() {
- super.onDestroy();
- if (countDownTimer != null) {
- countDownTimer.cancel();
- }
+ private void showError(String message) {
+ Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+ setLoading(false);
}
}
diff --git a/app/src/main/java/com/example/bem/LoginActivity.java b/app/src/main/java/com/example/bem/LoginActivity.java
index ffa7c67..8dc32aa 100644
--- a/app/src/main/java/com/example/bem/LoginActivity.java
+++ b/app/src/main/java/com/example/bem/LoginActivity.java
@@ -53,7 +53,6 @@ public class LoginActivity extends AppCompatActivity {
mAuth = FirebaseAuth.getInstance();
db = FirebaseFirestore.getInstance();
- // Forçar login sempre que abrir a app: faz signOut se já existir sessão
if (mAuth.getCurrentUser() != null) {
mAuth.signOut();
}
@@ -242,13 +241,11 @@ public class LoginActivity extends AppCompatActivity {
.apply();
if ("guardian".equals(type)) {
- // Verifies if guardian is already linked to a user
java.util.List managedUsers = (java.util.List) document.get("managedUsers");
if (managedUsers != null && !managedUsers.isEmpty()) {
redirectToApp();
} else {
- // If not linked, redirect to enter access code
Intent intent = new Intent(LoginActivity.this, InviteCodeActivity.class);
intent.putExtra("is_guardian", true);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
@@ -259,37 +256,45 @@ public class LoginActivity extends AppCompatActivity {
redirectToApp();
}
} else {
- // Se o documento não existir (ex.: registo antigo sem salvar), cria um básico
+ // Se o perfil não existe, cria um com base no modo de login atual.
FirebaseUser user = mAuth.getCurrentUser();
String email = user != null ? user.getEmail() : "";
- String name = (email != null && email.contains("@")) ? email.substring(0, email.indexOf("@"))
- : "Utilizador";
+ String name = (email != null && email.contains("@")) ? email.substring(0, email.indexOf("@")) : "Utilizador";
+ String userType = isGuardianMode ? "guardian" : "user";
Map userData = new HashMap<>();
userData.put("email", email);
userData.put("name", name);
userData.put("phone", "");
- userData.put("type", "user");
+ userData.put("type", userType);
userData.put("createdAt", System.currentTimeMillis());
db.collection("users").document(uid)
.set(userData)
.addOnSuccessListener(aVoid -> {
prefs.edit()
- .putString("user_type", "user")
+ .putString("user_type", userType)
.putString("user_name", name)
.apply();
- redirectToApp();
+
+ if ("guardian".equals(userType)) {
+ Intent intent = new Intent(LoginActivity.this, InviteCodeActivity.class);
+ intent.putExtra("is_guardian", true);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ startActivity(intent);
+ finish();
+ } else {
+ redirectToApp();
+ }
})
.addOnFailureListener(e -> {
- Toast.makeText(this, "Erro ao criar perfil: " + e.getMessage(), Toast.LENGTH_SHORT)
- .show();
+ Toast.makeText(this, "Erro ao criar perfil: " + e.getMessage(), Toast.LENGTH_SHORT).show();
btnLogin.setEnabled(true);
});
}
})
.addOnFailureListener(e -> {
- Toast.makeText(this, "Erro ao carregar dados", Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, "Erro ao carregar dados do utilizador: " + e.getMessage(), Toast.LENGTH_SHORT).show();
btnLogin.setEnabled(true);
});
}
diff --git a/app/src/main/java/com/example/bem/MainActivity.java b/app/src/main/java/com/example/bem/MainActivity.java
index 7334e4b..eee52b9 100644
--- a/app/src/main/java/com/example/bem/MainActivity.java
+++ b/app/src/main/java/com/example/bem/MainActivity.java
@@ -13,8 +13,8 @@ import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
@@ -30,18 +30,26 @@ import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
-import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
+import com.google.android.gms.tasks.OnCompleteListener;
+import com.google.android.gms.tasks.Task;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
+import com.google.firebase.firestore.DocumentReference;
import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.QueryDocumentSnapshot;
+import com.google.firebase.firestore.QuerySnapshot;
import java.util.ArrayList;
import java.util.Calendar;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Random;
public class MainActivity extends AppCompatActivity {
@@ -77,9 +85,7 @@ public class MainActivity extends AppCompatActivity {
private FirebaseAuth mAuth;
private FirebaseFirestore db;
private SharedPreferences prefs;
- private static final String PREF_GUARDIAN_PIN = "guardian_pin";
private static final String PREF_DARK_MODE = "dark_mode";
- private static final int REQUEST_NOTIFICATION_PERMISSION = 1001;
private static final int REQUEST_RINGTONE_PICKER = 1002;
private int editingAlarmIndex = -1;
private String currentUserId = null;
@@ -107,6 +113,7 @@ public class MainActivity extends AppCompatActivity {
return;
}
+ currentUserId = currentUser.getUid();
prefs = getSharedPreferences("app_prefs", MODE_PRIVATE);
applyThemeFromPrefs();
@@ -114,7 +121,6 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
inflater = LayoutInflater.from(this);
- currentUserId = currentUser.getUid();
String userType = prefs.getString("user_type", "user");
isGuardian = "guardian".equals(userType);
@@ -143,9 +149,9 @@ public class MainActivity extends AppCompatActivity {
profileButton.setOnClickListener(v -> showProfileMenu());
requestNotificationPermissions();
- seedSampleAlarms();
-
applyUserModeLayout();
+ loadAlarmsFromFirestore();
+ showIntroDialogIfNeeded();
}
private void applyUserModeLayout() {
@@ -153,15 +159,11 @@ public class MainActivity extends AppCompatActivity {
tabAlarms.setVisibility(View.GONE);
tabReminders.setVisibility(View.GONE);
tabCalendar.setVisibility(View.VISIBLE);
-
- // Guardian starts on their panel
switchTab(Tab.GUARDIANS);
} else {
tabAlarms.setVisibility(View.VISIBLE);
tabReminders.setVisibility(View.VISIBLE);
tabCalendar.setVisibility(View.GONE);
-
- // Regular user starts on Alarms
switchTab(Tab.ALARMS);
}
}
@@ -173,41 +175,113 @@ public class MainActivity extends AppCompatActivity {
finish();
}
- // ... existing requestNotificationPermissions ...
-
- private void handleGuardianTabClick() {
- // Guardians are always allowed
- if (isGuardian) {
- switchTab(Tab.GUARDIANS);
- } else {
- checkIfUserHasGuardians();
+ private void requestNotificationPermissions() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
+ notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS);
+ }
}
}
- // ... existing checkIfUserHasGuardians, showAddGuardianDialog,
- // showGuardianOptionsDialog, settings ...
+ private void handleGuardianTabClick() {
+ if (isGuardian) {
+ switchTab(Tab.GUARDIANS);
+ } else {
+ db.collection("users").document(currentUserId).get().addOnSuccessListener(documentSnapshot -> {
+ if (documentSnapshot.exists() && documentSnapshot.contains("guardianId")) {
+ Toast.makeText(this, "Já tem um responsável associado.", Toast.LENGTH_SHORT).show();
+ switchTab(Tab.GUARDIANS);
+ } else {
+ new AlertDialog.Builder(this)
+ .setTitle("Controle Parental")
+ .setMessage("Ainda não tem um responsável associado. Deseja gerar um código de convite para adicionar um?")
+ .setPositiveButton("Gerar Código", (dialog, which) -> {
+ Intent intent = new Intent(MainActivity.this, GenerateCodeActivity.class);
+ startActivity(intent);
+ })
+ .setNegativeButton("Agora Não", null)
+ .show();
+ }
+ });
+ }
+ }
- // ... showSettingsMenu ...
+ private void showProfileMenu() {
+ final CharSequence[] items = { "Ver as minhas informações", "Mudar tema", "Suporte", "Sair" };
- // ... toggleDarkMode, showUserInfo, logout, showProfileMenu,
- // applyThemeFromPrefs, openSupportEmail ...
+ new AlertDialog.Builder(this)
+ .setTitle("Perfil")
+ .setItems(items, (dialog, which) -> {
+ switch (which) {
+ case 0:
+ showUserInfo();
+ break;
+ case 1:
+ toggleDarkMode();
+ break;
+ case 2:
+ openSupportEmail();
+ break;
+ case 3:
+ logout();
+ break;
+ }
+ })
+ .show();
+ }
+
+ private void showUserInfo() {
+ String name = prefs.getString("user_name", "Utilizador");
+ FirebaseUser user = mAuth.getCurrentUser();
+ String email = user != null ? user.getEmail() : "Não disponível";
+
+ new AlertDialog.Builder(this)
+ .setTitle("As minhas informações")
+ .setMessage("Nome: " + name + "\nEmail: " + email)
+ .setPositiveButton("OK", null)
+ .show();
+ }
+
+ private void toggleDarkMode() {
+ boolean isDarkMode = prefs.getBoolean(PREF_DARK_MODE, false);
+ prefs.edit().putBoolean(PREF_DARK_MODE, !isDarkMode).apply();
+ recreate();
+ }
+
+ private void openSupportEmail() {
+ Intent intent = new Intent(Intent.ACTION_SENDTO);
+ intent.setData(Uri.parse("mailto:support@bempap.com"));
+ intent.putExtra(Intent.EXTRA_SUBJECT, "Suporte App Bem+");
+ try {
+ startActivity(intent);
+ } catch (Exception e) {
+ Toast.makeText(this, "Nenhuma app de email encontrada.", Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void logout() {
+ mAuth.signOut();
+ redirectToLogin();
+ }
+
+ private void applyThemeFromPrefs() {
+ if (prefs.getBoolean(PREF_DARK_MODE, false)) {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
+ } else {
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
+ }
+ }
private void switchTab(Tab tab) {
- if (tab == currentTab) {
- return;
- }
+ if (tab == currentTab) return;
currentTab = tab;
View oldContent = tabContainer.getChildCount() > 0 ? tabContainer.getChildAt(0) : null;
if (oldContent != null) {
- oldContent.animate()
- .alpha(0f)
- .setDuration(150)
- .withEndAction(() -> {
- tabContainer.removeAllViews();
- loadAndShowTab(tab);
- })
- .start();
+ oldContent.animate().alpha(0f).setDuration(150).withEndAction(() -> {
+ tabContainer.removeAllViews();
+ loadAndShowTab(tab);
+ }).start();
} else {
loadAndShowTab(tab);
}
@@ -217,40 +291,25 @@ public class MainActivity extends AppCompatActivity {
View content;
switch (tab) {
case REMINDERS:
- if (remindersView == null) {
- remindersView = inflater.inflate(R.layout.layout_tab_reminders, tabContainer, false);
- }
+ if (remindersView == null) remindersView = inflater.inflate(R.layout.layout_tab_reminders, tabContainer, false);
content = remindersView;
break;
case GUARDIANS:
- if (guardiansView == null) {
- guardiansView = inflater.inflate(R.layout.layout_tab_guardians, tabContainer, false);
- }
+ if (guardiansView == null) guardiansView = inflater.inflate(R.layout.layout_tab_guardians, tabContainer, false);
loadGuardianData(guardiansView);
content = guardiansView;
break;
case CALENDAR:
- if (calendarView == null) {
- calendarView = inflater.inflate(R.layout.layout_tab_calendar, tabContainer, false);
- }
+ if (calendarView == null) calendarView = inflater.inflate(R.layout.layout_tab_calendar, tabContainer, false);
setupCalendar(calendarView);
content = calendarView;
break;
case ALARMS:
default:
- if (alarmsView == null) {
- alarmsView = inflater.inflate(R.layout.layout_tab_alarms, tabContainer, false);
- }
- // ... setup alarmsView listeners ...
+ if (alarmsView == null) alarmsView = inflater.inflate(R.layout.layout_tab_alarms, tabContainer, false);
Button addAlarm = alarmsView.findViewById(R.id.btnAddAlarm);
- ImageView btnSettings = alarmsView.findViewById(R.id.btnSettings);
alarmListContainer = alarmsView.findViewById(R.id.alarmListContainer);
- if (addAlarm != null) {
- addAlarm.setOnClickListener(v -> showAddAlarmDialog());
- }
- if (btnSettings != null) {
- btnSettings.setOnClickListener(v -> showSettingsMenu());
- }
+ if (addAlarm != null) addAlarm.setOnClickListener(v -> showAddAlarmDialog());
renderAlarms();
content = alarmsView;
break;
@@ -258,15 +317,12 @@ public class MainActivity extends AppCompatActivity {
content.setAlpha(0f);
tabContainer.addView(content);
- content.animate()
- .alpha(1f)
- .setDuration(120)
- .start();
-
+ content.animate().alpha(1f).setDuration(120).start();
updateTabHighlight(tab);
}
private void setupCalendar(View view) {
+ if (view == null) return;
LinearLayout calendarGrid = view.findViewById(R.id.calendarGrid);
TextView monthTitle = view.findViewById(R.id.textMonthTitle);
LinearLayout legendContainer = view.findViewById(R.id.legendContainer);
@@ -278,86 +334,63 @@ public class MainActivity extends AppCompatActivity {
int currentDay = calendar.get(Calendar.DAY_OF_MONTH);
int currentMonth = calendar.get(Calendar.MONTH);
- String[] months = { "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro",
- "Outubro", "Novembro", "Dezembro" };
- monthTitle.setText(months[currentMonth] + " " + calendar.get(Calendar.YEAR));
+ String[] months = { "Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro" };
+ monthTitle.setText(String.format("%s %d", months[currentMonth], calendar.get(Calendar.YEAR)));
- // Colors for dots
- int[] dotColors = {
- ContextCompat.getColor(this, R.color.primary),
- ContextCompat.getColor(this, R.color.guardian_purple_end),
- ContextCompat.getColor(this, R.color.delete),
- 0xFFFFA500, // Orange
- 0xFF00C853 // Green
- };
-
- // Map medications to colors
- java.util.Map medColors = new java.util.HashMap<>();
+ int[] dotColors = { ContextCompat.getColor(this, R.color.primary), ContextCompat.getColor(this, R.color.guardian_purple_end), ContextCompat.getColor(this, R.color.delete), 0xFFFFA500, 0xFF00C853 };
+ Map medColors = new HashMap<>();
int colorIndex = 0;
for (AlarmItem alarm : alarms) {
- medColors.put(alarm.title, dotColors[colorIndex % dotColors.length]);
- colorIndex++;
-
- // Add to legend
- View legendItem = new TextView(this);
- ((TextView) legendItem).setText("• " + alarm.title);
- ((TextView) legendItem).setTextColor(medColors.get(alarm.title));
- ((TextView) legendItem).setTextSize(14);
- ((TextView) legendItem).setPadding(0, 0, 0, 8);
- legendContainer.addView(legendItem);
+ if (!medColors.containsKey(alarm.title)) {
+ medColors.put(alarm.title, dotColors[colorIndex % dotColors.length]);
+ colorIndex++;
+ TextView legendItem = new TextView(this);
+ legendItem.setText(String.format("• %s", alarm.title));
+ legendItem.setTextColor(medColors.get(alarm.title));
+ legendItem.setTextSize(14);
+ legendItem.setPadding(0, 0, 0, 8);
+ legendContainer.addView(legendItem);
+ }
}
calendar.set(Calendar.DAY_OF_MONTH, 1);
- int firstDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 0=Sunday
+ int firstDayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
int daysInMonth = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
SharedPreferences logPrefs = getSharedPreferences("medication_log", MODE_PRIVATE);
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMdd", java.util.Locale.getDefault());
-
- LinearLayout currentWeekRow = null;
-
- // Empty cells for dias before 1st
- currentWeekRow = new LinearLayout(this);
+ LinearLayout currentWeekRow = new LinearLayout(this);
currentWeekRow.setOrientation(LinearLayout.HORIZONTAL);
calendarGrid.addView(currentWeekRow);
for (int i = 0; i < firstDayOfWeek; i++) {
View empty = new View(this);
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, 50, 1.0f);
- empty.setLayoutParams(params);
+ empty.setLayoutParams(new LinearLayout.LayoutParams(0, 50, 1.0f));
currentWeekRow.addView(empty);
}
int dayOfWeekCounter = firstDayOfWeek;
-
for (int day = 1; day <= daysInMonth; day++) {
if (dayOfWeekCounter == 0) {
currentWeekRow = new LinearLayout(this);
currentWeekRow.setOrientation(LinearLayout.HORIZONTAL);
calendarGrid.addView(currentWeekRow);
}
-
View dayCell = inflater.inflate(R.layout.item_calendar_day, currentWeekRow, false);
TextView numText = dayCell.findViewById(R.id.textDayNumber);
LinearLayout dots = dayCell.findViewById(R.id.dotsContainer);
-
numText.setText(String.valueOf(day));
if (day == currentDay) {
numText.setBackground(ContextCompat.getDrawable(this, R.drawable.bg_calendar_day_selected));
numText.setTextColor(ContextCompat.getColor(this, R.color.primary));
- } else {
- numText.setBackground(null);
}
- // Verify taken meds for this day
calendar.set(Calendar.DAY_OF_MONTH, day);
String dateKey = sdf.format(calendar.getTime());
-
for (AlarmItem alarm : alarms) {
String key = "med_" + alarm.title.replaceAll("\\s+", "_") + "_" + dateKey;
if (logPrefs.getBoolean(key + "_taken", false)) {
- // Add dot
View dot = new View(this);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(12, 12);
params.setMargins(2, 0, 2, 0);
@@ -367,24 +400,14 @@ public class MainActivity extends AppCompatActivity {
dots.addView(dot);
}
}
-
currentWeekRow.addView(dayCell);
-
- dayOfWeekCounter++;
- if (dayOfWeekCounter >= 7) {
- dayOfWeekCounter = 0;
- }
+ dayOfWeekCounter = (dayOfWeekCounter + 1) % 7;
}
-
- // Fill remaining cells
- if (dayOfWeekCounter != 0) {
- while (dayOfWeekCounter < 7) {
- View empty = new View(this);
- LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, 50, 1.0f);
- empty.setLayoutParams(params);
- currentWeekRow.addView(empty);
- dayOfWeekCounter++;
- }
+ while (dayOfWeekCounter != 0 && dayOfWeekCounter < 7) {
+ View empty = new View(this);
+ empty.setLayoutParams(new LinearLayout.LayoutParams(0, 50, 1.0f));
+ currentWeekRow.addView(empty);
+ dayOfWeekCounter++;
}
}
@@ -393,20 +416,11 @@ public class MainActivity extends AppCompatActivity {
resetTab(tabReminders, iconReminders, textReminders);
resetTab(tabGuardians, iconGuardians, textGuardians);
resetTab(tabCalendar, iconCalendar, textCalendar);
-
switch (tab) {
- case ALARMS:
- highlightTab(tabAlarms, iconAlarms, textAlarms);
- break;
- case REMINDERS:
- highlightTab(tabReminders, iconReminders, textReminders);
- break;
- case GUARDIANS:
- highlightTab(tabGuardians, iconGuardians, textGuardians);
- break;
- case CALENDAR:
- highlightTab(tabCalendar, iconCalendar, textCalendar);
- break;
+ case ALARMS: highlightTab(tabAlarms, iconAlarms, textAlarms); break;
+ case REMINDERS: highlightTab(tabReminders, iconReminders, textReminders); break;
+ case GUARDIANS: highlightTab(tabGuardians, iconGuardians, textGuardians); break;
+ case CALENDAR: highlightTab(tabCalendar, iconCalendar, textCalendar); break;
}
}
@@ -423,15 +437,8 @@ public class MainActivity extends AppCompatActivity {
text.setTextColor(neutral);
}
- private void seedSampleAlarms() {
- alarms.add(new AlarmItem("Tomar medicamentos", "08:00 • 100mg", "Seg • Ter • Qua • Qui • Sex", true));
- alarms.add(new AlarmItem("Almoço", "12:30 • 1 comprimido", "Seg • Ter • Qua • Qui • Sex • Sáb • Dom", true));
- }
-
private void renderAlarms() {
- if (alarmListContainer == null) {
- return;
- }
+ if (alarmListContainer == null) return;
alarmListContainer.removeAllViews();
if (alarms.isEmpty()) {
TextView empty = new TextView(this);
@@ -478,25 +485,16 @@ public class MainActivity extends AppCompatActivity {
toggle.setOnCheckedChangeListener((buttonView, isChecked) -> {
item.enabled = isChecked;
- Toast.makeText(this,
- item.title + (isChecked ? " ativado" : " desativado"),
- Toast.LENGTH_SHORT).show();
+ updateAlarmInFirestore(item);
});
edit.setOnClickListener(v -> showEditAlarmDialog(item, index));
- delete.setOnClickListener(v -> {
- new AlertDialog.Builder(this)
- .setTitle("Remover Alarme")
- .setMessage("Tem certeza que deseja remover \"" + item.title + "\"?")
- .setPositiveButton("Remover", (dialog, which) -> {
- alarms.remove(index);
- renderAlarms();
- Toast.makeText(this, "Alarme removido", Toast.LENGTH_SHORT).show();
- })
- .setNegativeButton("Cancelar", null)
- .show();
- });
+ delete.setOnClickListener(v -> new AlertDialog.Builder(this)
+ .setTitle("Remover Alarme")
+ .setMessage("Tem certeza que deseja remover \"" + item.title + "\"?")
+ .setPositiveButton("Remover", (dialog, which) -> deleteAlarmFromFirestore(item, index))
+ .setNegativeButton("Cancelar", null).show());
alarmListContainer.addView(card);
}
@@ -505,42 +503,26 @@ public class MainActivity extends AppCompatActivity {
private void showConfirmationDialog(AlarmItem item) {
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault());
String timeStr = sdf.format(new java.util.Date(item.lastTakenTimestamp));
-
- View dialogView = inflater.inflate(R.layout.dialog_add_alarm, null, false);
-
new AlertDialog.Builder(this)
.setTitle("✓ Medicamento Confirmado")
- .setMessage("Confirmou que tomou:\n\n" +
- "💊 " + item.title + "\n" +
- "🕐 Às " + timeStr + "\n\n" +
- "O responsável será notificado automaticamente.")
+ .setMessage("Confirmou que tomou:\n\n" + "💊 " + item.title + "\n" + "🕐 Às " + timeStr + "\n\n" + "O responsável será notificado automaticamente.")
.setPositiveButton("OK", null)
.show();
-
Toast.makeText(this, "Responsável notificado!", Toast.LENGTH_LONG).show();
}
private void saveMedicationConfirmation(AlarmItem item) {
SharedPreferences confirmPrefs = getSharedPreferences("medication_log", MODE_PRIVATE);
- String key = "med_" + item.title.replaceAll("\\s+", "_") + "_" +
- new java.text.SimpleDateFormat("yyyyMMdd", java.util.Locale.getDefault())
- .format(new java.util.Date());
- confirmPrefs.edit()
- .putLong(key, item.lastTakenTimestamp)
- .putBoolean(key + "_taken", true)
- .putString(key + "_title", item.title)
- .apply();
+ String key = "med_" + item.title.replaceAll("\\s+", "_") + "_" + new java.text.SimpleDateFormat("yyyyMMdd", java.util.Locale.getDefault()).format(new java.util.Date());
+ confirmPrefs.edit().putLong(key, item.lastTakenTimestamp).putBoolean(key + "_taken", true).putString(key + "_title", item.title).apply();
}
private void loadGuardianData(View guardiansView) {
SharedPreferences confirmPrefs = getSharedPreferences("medication_log", MODE_PRIVATE);
- java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat("yyyyMMdd",
- java.util.Locale.getDefault());
+ java.text.SimpleDateFormat dateFormat = new java.text.SimpleDateFormat("yyyyMMdd", java.util.Locale.getDefault());
String today = dateFormat.format(new java.util.Date());
-
int takenCount = 0;
int totalCount = alarms.size();
-
for (AlarmItem alarm : alarms) {
String key = "med_" + alarm.title.replaceAll("\\s+", "_") + "_" + today;
if (confirmPrefs.getBoolean(key + "_taken", false)) {
@@ -551,22 +533,16 @@ public class MainActivity extends AppCompatActivity {
TextView takenText = guardiansView.findViewById(R.id.textTakenCount);
TextView pendingText = guardiansView.findViewById(R.id.textPendingCount);
TextView adherenceText = guardiansView.findViewById(R.id.textAdherenceRate);
-
- if (takenText != null) {
- takenText.setText(String.valueOf(takenCount));
- }
- if (pendingText != null) {
- pendingText.setText(String.valueOf(totalCount - takenCount));
- }
+ if (takenText != null) takenText.setText(String.valueOf(takenCount));
+ if (pendingText != null) pendingText.setText(String.valueOf(totalCount - takenCount));
if (adherenceText != null) {
int adherence = totalCount > 0 ? (takenCount * 100 / totalCount) : 0;
- adherenceText.setText(adherence + "%");
+ adherenceText.setText(String.format("%d%%", adherence));
}
LinearLayout statusContainer = guardiansView.findViewById(R.id.statusContainer);
if (statusContainer != null) {
statusContainer.removeAllViews();
-
for (AlarmItem alarm : alarms) {
String key = "med_" + alarm.title.replaceAll("\\s+", "_") + "_" + today;
boolean taken = confirmPrefs.getBoolean(key + "_taken", false);
@@ -576,22 +552,16 @@ public class MainActivity extends AppCompatActivity {
TextView titleView = statusCard.findViewById(R.id.textAlarmTitle);
TextView timeView = statusCard.findViewById(R.id.textAlarmTime);
TextView daysView = statusCard.findViewById(R.id.textAlarmDays);
- Button confirmBtn = statusCard.findViewById(R.id.btnConfirmMedication);
- Switch toggleSwitch = statusCard.findViewById(R.id.switchAlarm);
- ImageButton deleteBtn = statusCard.findViewById(R.id.buttonDeleteAlarm);
+ statusCard.findViewById(R.id.btnConfirmMedication).setVisibility(View.GONE);
+ statusCard.findViewById(R.id.switchAlarm).setVisibility(View.GONE);
+ statusCard.findViewById(R.id.buttonDeleteAlarm).setVisibility(View.GONE);
titleView.setText(alarm.title);
timeView.setText(alarm.time);
- toggleSwitch.setVisibility(View.GONE);
- deleteBtn.setVisibility(View.GONE);
- confirmBtn.setVisibility(View.GONE);
-
if (taken) {
- java.text.SimpleDateFormat timeFormat = new java.text.SimpleDateFormat("HH:mm",
- java.util.Locale.getDefault());
- String takenTime = timeFormat.format(new java.util.Date(timestamp));
- daysView.setText("✓ Tomado às " + takenTime);
+ String takenTime = new java.text.SimpleDateFormat("HH:mm", java.util.Locale.getDefault()).format(new java.util.Date(timestamp));
+ daysView.setText(String.format("✓ Tomado às %s", takenTime));
daysView.setTextColor(ContextCompat.getColor(this, R.color.primary));
daysView.setBackgroundColor(ContextCompat.getColor(this, R.color.accent));
} else {
@@ -599,7 +569,6 @@ public class MainActivity extends AppCompatActivity {
daysView.setTextColor(ContextCompat.getColor(this, R.color.delete));
daysView.setBackgroundColor(ContextCompat.getColor(this, R.color.background_surface));
}
-
statusContainer.addView(statusCard);
}
}
@@ -614,212 +583,107 @@ public class MainActivity extends AppCompatActivity {
final int[] selectedHour = { 8 };
final int[] selectedMinute = { 0 };
- timeDisplay.setText(String.format("%02d:%02d", selectedHour[0], selectedMinute[0]));
+ timeDisplay.setText(String.format(java.util.Locale.getDefault(), "%02d:%02d", selectedHour[0], selectedMinute[0]));
timeDisplay.setTextColor(ContextCompat.getColor(this, R.color.neutral_dark));
- View timePickerLayout = (View) timeDisplay.getParent();
- if (timePickerLayout != null) {
- timePickerLayout.setOnClickListener(v -> {
- android.app.TimePickerDialog picker = new android.app.TimePickerDialog(
- this,
- (view, hourOfDay, minute) -> {
- selectedHour[0] = hourOfDay;
- selectedMinute[0] = minute;
- timeDisplay.setText(String.format("%02d:%02d", hourOfDay, minute));
- timeDisplay.setTextColor(ContextCompat.getColor(this, R.color.neutral_dark));
- },
- selectedHour[0],
- selectedMinute[0],
- true);
- picker.setTitle("Escolha o horário");
- picker.show();
- });
- }
-
- AlertDialog dialog = new AlertDialog.Builder(this)
- .setView(dialogView)
- .setPositiveButton("Guardar", null)
- .setNegativeButton("Cancelar", null)
- .create();
-
- dialog.setOnShowListener(dialogInterface -> {
- Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
- positiveButton.setTextColor(ContextCompat.getColor(this, R.color.primary));
- positiveButton.setOnClickListener(v -> {
- String title = titleInput.getText().toString().trim();
- String details = detailsInput.getText().toString().trim();
-
- if (TextUtils.isEmpty(title)) {
- titleInput.setError("Insira o nome do medicamento");
- titleInput.requestFocus();
- return;
- }
-
- if (TextUtils.isEmpty(details)) {
- details = "Configurar dias na edição";
- }
-
- String timeFormatted = String.format("%02d:%02d", selectedHour[0], selectedMinute[0]);
- AlarmItem newAlarm = new AlarmItem(title, timeFormatted + " • " + details,
- "Seg • Ter • Qua • Qui • Sex", true);
- newAlarm.hour = selectedHour[0];
- newAlarm.minute = selectedMinute[0];
- alarms.add(newAlarm);
- scheduleAlarm(newAlarm, alarms.size() - 1);
- renderAlarms();
- Toast.makeText(this, "Alarme criado e agendado!", Toast.LENGTH_SHORT).show();
- dialog.dismiss();
- });
-
- Button negativeButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE);
- negativeButton.setTextColor(ContextCompat.getColor(this, R.color.neutral_medium));
+ ((View) timeDisplay.getParent()).setOnClickListener(v -> {
+ android.app.TimePickerDialog picker = new android.app.TimePickerDialog(this,
+ (view, hourOfDay, minute) -> {
+ selectedHour[0] = hourOfDay;
+ selectedMinute[0] = minute;
+ timeDisplay.setText(String.format(java.util.Locale.getDefault(), "%02d:%02d", hourOfDay, minute));
+ }, selectedHour[0], selectedMinute[0], true);
+ picker.setTitle("Escolha o horário");
+ picker.show();
});
+ AlertDialog dialog = new AlertDialog.Builder(this).setView(dialogView).setPositiveButton("Guardar", null).setNegativeButton("Cancelar", null).create();
+ dialog.setOnShowListener(dialogInterface -> {
+ Button positiveButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ positiveButton.setOnClickListener(v -> {
+ String title = titleInput.getText().toString().trim();
+ if (TextUtils.isEmpty(title)) {
+ titleInput.setError("Insira o nome do medicamento");
+ return;
+ }
+ String details = detailsInput.getText().toString().trim();
+ String timeFormatted = String.format(java.util.Locale.getDefault(), "%02d:%02d", selectedHour[0], selectedMinute[0]);
+
+ AlarmItem newAlarm = new AlarmItem(title, timeFormatted + " • " + details, "Seg • Ter • Qua • Qui • Sex", true);
+ newAlarm.hour = selectedHour[0];
+ newAlarm.minute = selectedMinute[0];
+
+ saveAlarmInFirestore(newAlarm);
+ dialog.dismiss();
+ });
+ });
dialog.show();
}
private void showEditAlarmDialog(AlarmItem item, int index) {
editingAlarmIndex = index;
-
- String[] options = {
- "⏰ Alterar horário",
- "🔔 Escolher toque",
- "🎵 Testar toque atual",
- "📝 Editar título"
- };
-
- new AlertDialog.Builder(this)
- .setTitle("Editar: " + item.title)
- .setItems(options, (dialog, which) -> {
- switch (which) {
- case 0:
- showChangeTimeDialog(item, index);
- break;
- case 1:
- openRingtonePicker(item);
- break;
- case 2:
- testRingtone(item);
- break;
- case 3:
- showEditTitleDialog(item, index);
- break;
- }
- })
- .setNegativeButton("Fechar", null)
- .show();
+ String[] options = { "⏰ Alterar horário", "🔔 Escolher toque", "🎵 Testar toque atual", "📝 Editar título" };
+ new AlertDialog.Builder(this).setTitle("Editar: " + item.title).setItems(options, (dialog, which) -> {
+ switch (which) {
+ case 0: showChangeTimeDialog(item); break;
+ case 1: openRingtonePicker(item); break;
+ case 2: testRingtone(item); break;
+ case 3: showEditTitleDialog(item); break;
+ }
+ }).setNegativeButton("Fechar", null).show();
}
- private void showChangeTimeDialog(AlarmItem item, int index) {
- android.app.TimePickerDialog picker = new android.app.TimePickerDialog(
- this,
- (view, hourOfDay, minute) -> {
- item.hour = hourOfDay;
- item.minute = minute;
- item.time = String.format("%02d:%02d • %s", hourOfDay, minute,
- item.time.contains("•") ? item.time.split("•")[1].trim() : "");
- scheduleAlarm(item, index);
- renderAlarms();
- Toast.makeText(this, "Horário atualizado!", Toast.LENGTH_SHORT).show();
- },
- item.hour,
- item.minute,
- true);
- picker.setTitle("Novo horário");
- picker.show();
+ private void showChangeTimeDialog(AlarmItem item) {
+ new android.app.TimePickerDialog(this, (view, hourOfDay, minute) -> {
+ item.hour = hourOfDay;
+ item.minute = minute;
+ String details = item.time.contains("•") ? item.time.split("•")[1].trim() : "";
+ item.time = String.format(java.util.Locale.getDefault(), "%02d:%02d • %s", hourOfDay, minute, details);
+ updateAlarmInFirestore(item);
+ scheduleAlarm(item, alarms.indexOf(item));
+ }, item.hour, item.minute, true).show();
}
- private void showEditTitleDialog(AlarmItem item, int index) {
+ private void showEditTitleDialog(AlarmItem item) {
EditText input = new EditText(this);
input.setText(item.title);
- input.setHint("Nome do medicamento");
input.setPadding(50, 40, 50, 40);
-
- new AlertDialog.Builder(this)
- .setTitle("Editar nome")
- .setView(input)
- .setPositiveButton("Guardar", (dialog, which) -> {
- String newTitle = input.getText().toString().trim();
- if (!TextUtils.isEmpty(newTitle)) {
- item.title = newTitle;
- renderAlarms();
- Toast.makeText(this, "Nome atualizado!", Toast.LENGTH_SHORT).show();
- }
- })
- .setNegativeButton("Cancelar", null)
- .show();
+ new AlertDialog.Builder(this).setTitle("Editar nome").setView(input).setPositiveButton("Guardar", (dialog, which) -> {
+ String newTitle = input.getText().toString().trim();
+ if (!TextUtils.isEmpty(newTitle)) {
+ item.title = newTitle;
+ updateAlarmInFirestore(item);
+ }
+ }).setNegativeButton("Cancelar", null).show();
}
private void openRingtonePicker(AlarmItem item) {
Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_ALARM);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TITLE, "Escolher toque para " + item.title);
- intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
- intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false);
-
- Uri currentUri = null;
- if (item.ringtoneUri != null && !item.ringtoneUri.isEmpty()) {
- try {
- currentUri = Uri.parse(item.ringtoneUri);
- } catch (Exception e) {
- currentUri = null;
- }
- }
-
- if (currentUri == null) {
- currentUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
- }
+ Uri currentUri = item.ringtoneUri != null ? Uri.parse(item.ringtoneUri) : RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentUri);
- try {
- startActivityForResult(intent, REQUEST_RINGTONE_PICKER);
- } catch (Exception e) {
- Toast.makeText(this, "Erro ao abrir seletor de toques", Toast.LENGTH_SHORT).show();
- }
+ startActivityForResult(intent, REQUEST_RINGTONE_PICKER);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
-
- if (requestCode == REQUEST_RINGTONE_PICKER && resultCode == RESULT_OK && editingAlarmIndex >= 0) {
- if (data == null) {
- Toast.makeText(this, "Nenhum toque selecionado", Toast.LENGTH_SHORT).show();
- editingAlarmIndex = -1;
- return;
- }
-
+ if (requestCode == REQUEST_RINGTONE_PICKER && resultCode == RESULT_OK && editingAlarmIndex != -1) {
Uri uri = data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI);
-
- if (uri != null && editingAlarmIndex < alarms.size()) {
- AlarmItem item = alarms.get(editingAlarmIndex);
+ AlarmItem item = alarms.get(editingAlarmIndex);
+ if (uri != null) {
item.ringtoneUri = uri.toString();
-
- try {
- Ringtone ringtone = RingtoneManager.getRingtone(this, uri);
- if (ringtone != null) {
- item.ringtoneName = ringtone.getTitle(this);
- if (item.ringtoneName == null || item.ringtoneName.isEmpty()) {
- item.ringtoneName = "Toque personalizado";
- }
- } else {
- item.ringtoneName = "Toque do sistema";
- }
- } catch (Exception e) {
- item.ringtoneName = "Toque selecionado";
- }
-
- scheduleAlarm(item, editingAlarmIndex);
-
- Toast.makeText(this, "✓ Toque guardado: " + item.ringtoneName + "\nToque em 'Testar' para ouvir",
- Toast.LENGTH_LONG).show();
- } else if (uri == null && editingAlarmIndex < alarms.size()) {
- AlarmItem item = alarms.get(editingAlarmIndex);
+ Ringtone ringtone = RingtoneManager.getRingtone(this, uri);
+ item.ringtoneName = ringtone.getTitle(this);
+ } else {
item.ringtoneUri = null;
item.ringtoneName = "Toque padrão do sistema";
- Toast.makeText(this, "Usando toque padrão", Toast.LENGTH_SHORT).show();
}
+ updateAlarmInFirestore(item);
+ scheduleAlarm(item, editingAlarmIndex);
editingAlarmIndex = -1;
}
}
@@ -830,28 +694,15 @@ public class MainActivity extends AppCompatActivity {
if (currentlyPlayingRingtone != null && currentlyPlayingRingtone.isPlaying()) {
currentlyPlayingRingtone.stop();
currentlyPlayingRingtone = null;
- Toast.makeText(this, "Toque parado", Toast.LENGTH_SHORT).show();
return;
}
-
- Uri ringtoneUri;
- if (item.ringtoneUri != null) {
- ringtoneUri = Uri.parse(item.ringtoneUri);
- } else {
- ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
- }
-
+ Uri ringtoneUri = item.ringtoneUri != null ? Uri.parse(item.ringtoneUri) : RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM);
try {
currentlyPlayingRingtone = RingtoneManager.getRingtone(this, ringtoneUri);
currentlyPlayingRingtone.play();
-
- Toast.makeText(this, "🔊 Tocando: " + item.ringtoneName + "\nToque novamente para parar", Toast.LENGTH_LONG)
- .show();
-
new android.os.Handler().postDelayed(() -> {
if (currentlyPlayingRingtone != null && currentlyPlayingRingtone.isPlaying()) {
currentlyPlayingRingtone.stop();
- currentlyPlayingRingtone = null;
}
}, 5000);
} catch (Exception e) {
@@ -861,62 +712,125 @@ public class MainActivity extends AppCompatActivity {
private void scheduleAlarm(AlarmItem item, int alarmId) {
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
- if (alarmManager == null)
- return;
-
Intent intent = new Intent(this, AlarmReceiver.class);
intent.putExtra("alarm_title", item.title);
- intent.putExtra("alarm_message", "Hora de tomar: " + item.title);
intent.putExtra("ringtone_uri", item.ringtoneUri);
intent.putExtra("alarm_id", alarmId);
-
- PendingIntent pendingIntent = PendingIntent.getBroadcast(
- this,
- alarmId,
- intent,
- PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(this, alarmId, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, item.hour);
calendar.set(Calendar.MINUTE, item.minute);
calendar.set(Calendar.SECOND, 0);
-
if (calendar.getTimeInMillis() < System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
try {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- alarmManager.setExactAndAllowWhileIdle(
- AlarmManager.RTC_WAKEUP,
- calendar.getTimeInMillis(),
- pendingIntent);
- } else {
- alarmManager.setExact(
- AlarmManager.RTC_WAKEUP,
- calendar.getTimeInMillis(),
- pendingIntent);
- }
- Toast.makeText(this, "Alarme programado para " + item.hour + ":" + String.format("%02d", item.minute),
- Toast.LENGTH_SHORT).show();
+ alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);
} catch (SecurityException e) {
Toast.makeText(this, "Erro: Permissão de alarme necessária", Toast.LENGTH_SHORT).show();
}
}
- private static class AlarmItem {
- String title;
- String time;
- String days;
- boolean enabled;
- int hour;
- int minute;
- boolean takenToday;
- long lastTakenTimestamp;
- String ringtoneUri;
- String ringtoneName;
+ private void loadAlarmsFromFirestore() {
+ if (currentUserId == null) return;
+ db.collection("users").document(currentUserId).collection("alarms")
+ .get()
+ .addOnCompleteListener(task -> {
+ if (task.isSuccessful()) {
+ alarms.clear();
+ for (QueryDocumentSnapshot document : task.getResult()) {
+ AlarmItem item = document.toObject(AlarmItem.class);
+ item.documentId = document.getId();
+ alarms.add(item);
+ }
+ renderAlarms();
+ if (calendarView != null) {
+ setupCalendar(calendarView);
+ }
+ } else {
+ Log.e("Firestore", "Error loading alarms: ", task.getException());
+ }
+ });
+ }
- AlarmItem(String title, String time, String days, boolean enabled) {
+ private void saveAlarmInFirestore(AlarmItem item) {
+ if (currentUserId == null) return;
+ db.collection("users").document(currentUserId).collection("alarms")
+ .add(item)
+ .addOnSuccessListener(documentReference -> {
+ item.documentId = documentReference.getId();
+ if (!alarms.contains(item)) {
+ alarms.add(item);
+ }
+ int alarmId = alarms.indexOf(item);
+ scheduleAlarm(item, alarmId);
+ renderAlarms();
+ Toast.makeText(this, "Alarme criado e agendado!", Toast.LENGTH_SHORT).show();
+ })
+ .addOnFailureListener(e -> Toast.makeText(this, "Erro ao guardar alarme", Toast.LENGTH_SHORT).show());
+ }
+
+ private void updateAlarmInFirestore(AlarmItem item) {
+ if (currentUserId == null || item.documentId == null) return;
+ db.collection("users").document(currentUserId).collection("alarms").document(item.documentId)
+ .set(item)
+ .addOnSuccessListener(aVoid -> {
+ renderAlarms();
+ Toast.makeText(this, "Alarme atualizado!", Toast.LENGTH_SHORT).show();
+ })
+ .addOnFailureListener(e -> Toast.makeText(this, "Erro ao atualizar alarme", Toast.LENGTH_SHORT).show());
+ }
+
+ private void deleteAlarmFromFirestore(AlarmItem item, int index) {
+ if (currentUserId == null || item.documentId == null) return;
+ db.collection("users").document(currentUserId).collection("alarms").document(item.documentId)
+ .delete()
+ .addOnSuccessListener(aVoid -> {
+ alarms.remove(index);
+ renderAlarms();
+ Toast.makeText(this, "Alarme removido", Toast.LENGTH_SHORT).show();
+ })
+ .addOnFailureListener(e -> Toast.makeText(this, "Erro ao remover alarme", Toast.LENGTH_SHORT).show());
+ }
+
+ private void showIntroDialogIfNeeded() {
+ boolean hasSeenIntro = prefs.getBoolean("has_seen_intro", false);
+ if (!hasSeenIntro) {
+ View dialogView = inflater.inflate(R.layout.dialog_intro, null);
+ AlertDialog dialog = new AlertDialog.Builder(this)
+ .setView(dialogView)
+ .setCancelable(false)
+ .create();
+
+ Button skipButton = dialogView.findViewById(R.id.btnSkipIntro);
+ skipButton.setOnClickListener(v -> {
+ prefs.edit().putBoolean("has_seen_intro", true).apply();
+ dialog.dismiss();
+ });
+
+ dialog.show();
+ }
+ }
+
+
+ public static class AlarmItem {
+ public String documentId;
+ public String title;
+ public String time;
+ public String days;
+ public boolean enabled;
+ public int hour;
+ public int minute;
+ public boolean takenToday;
+ public long lastTakenTimestamp;
+ public String ringtoneUri;
+ public String ringtoneName;
+
+ public AlarmItem() { }
+
+ public AlarmItem(String title, String time, String days, boolean enabled) {
this.title = title;
this.time = time;
this.days = days;
@@ -929,4 +843,4 @@ public class MainActivity extends AppCompatActivity {
this.ringtoneName = "Toque padrão";
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/res/layout/activity_generate_code.xml b/app/src/main/res/layout/activity_generate_code.xml
new file mode 100644
index 0000000..3db9cbb
--- /dev/null
+++ b/app/src/main/res/layout/activity_generate_code.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_invite_code.xml b/app/src/main/res/layout/activity_invite_code.xml
index 865228a..77a161d 100644
--- a/app/src/main/res/layout/activity_invite_code.xml
+++ b/app/src/main/res/layout/activity_invite_code.xml
@@ -1,148 +1,54 @@
-
-
+ android:padding="24dp"
+ android:background="@color/background_surface"
+ tools:context=".InviteCodeActivity">
+ android:text="Insira o código de 6 dígitos fornecido pelo utilizador para começar a monitorizar os seus alarmes."
+ android:textColor="@color/neutral_medium" />
-
+ android:hint="Código de 6 dígitos"
+ android:inputType="number"
+ android:maxLength="6"
+ android:textAlignment="center"
+ android:textSize="20sp" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_marginTop="16dp"
+ android:visibility="gone" />
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index e2a5712..b6c618e 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -20,16 +20,16 @@
+ app.layout_constraintTop_toTopOf="parent" />
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_intro.xml b/app/src/main/res/layout/dialog_intro.xml
new file mode 100644
index 0000000..0eec54b
--- /dev/null
+++ b/app/src/main/res/layout/dialog_intro.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_tab_alarms.xml b/app/src/main/res/layout/layout_tab_alarms.xml
index 63de103..977f9e7 100644
--- a/app/src/main/res/layout/layout_tab_alarms.xml
+++ b/app/src/main/res/layout/layout_tab_alarms.xml
@@ -45,14 +45,6 @@
android:textColor="@android:color/white"
android:textSize="16sp" />
-
@@ -77,13 +69,6 @@
android:textSize="20sp"
android:textStyle="bold" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/layout_tab_reminders.xml b/app/src/main/res/layout/layout_tab_reminders.xml
index 4f662dc..95b4593 100644
--- a/app/src/main/res/layout/layout_tab_reminders.xml
+++ b/app/src/main/res/layout/layout_tab_reminders.xml
@@ -89,13 +89,6 @@
android:textSize="14sp" />
-
@@ -206,206 +199,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 1b4ea52..405e7c6 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -5,6 +5,7 @@
#00B894
#5FE0C5
+ #E6F8F5
#1E2A28
#5E7671
#EEFFF7