From b65817f36cde7a9d04835087b375a1671fde7614 Mon Sep 17 00:00:00 2001 From: lucas <230410@epvc.pt> Date: Sun, 11 Jan 2026 12:22:59 +0000 Subject: [PATCH] janeiro --- .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 @@ + + + + + + + + + + + + + +