From 84e4a7f8b0d2ffc8b3fdddb09fc1345e59a1f98c Mon Sep 17 00:00:00 2001 From: MeuNome Date: Tue, 28 Apr 2026 17:14:36 +0100 Subject: [PATCH] erro ao realizar a tarefa --- app/src/main/AndroidManifest.xml | 1 + .../com/fluxup/app/FindFriendsActivity.java | 5 + .../java/com/fluxup/app/FirestoreManager.java | 24 + .../java/com/fluxup/app/InicioFragment.java | 506 +- .../java/com/fluxup/app/ProfileFragment.java | 26 +- .../java/com/fluxup/app/SearchFragment.java | 6 +- .../java/com/fluxup/app/SettingsActivity.java | 82 +- .../java/com/fluxup/app/TrophiesActivity.java | 12 +- app/src/main/java/com/fluxup/app/Usuario.java | 1 + .../java/com/fluxup/app/UsuariosService.java | 2 +- app/src/main/res/layout/activity_settings.xml | 1 + app/src/main/res/layout/fragment_inicio.xml | 59 +- app/src/main/res/layout/fragment_search.xml | 2 +- app/src/main/res/values-night/themes.xml | 2 +- app/src/main/res/values/ids.xml | 8 +- app/src/main/res/values/themes.xml | 2 +- build_output.txt | 5145 +---------------- gradle.properties | 9 - 18 files changed, 591 insertions(+), 5302 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7492b45..8b3bdae 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -30,6 +30,7 @@ + diff --git a/app/src/main/java/com/fluxup/app/FindFriendsActivity.java b/app/src/main/java/com/fluxup/app/FindFriendsActivity.java index a556f9f..1662b4a 100644 --- a/app/src/main/java/com/fluxup/app/FindFriendsActivity.java +++ b/app/src/main/java/com/fluxup/app/FindFriendsActivity.java @@ -4,9 +4,14 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; + import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; diff --git a/app/src/main/java/com/fluxup/app/FirestoreManager.java b/app/src/main/java/com/fluxup/app/FirestoreManager.java index 0b66ef6..2cdb4b1 100644 --- a/app/src/main/java/com/fluxup/app/FirestoreManager.java +++ b/app/src/main/java/com/fluxup/app/FirestoreManager.java @@ -24,6 +24,18 @@ public class FirestoreManager { return instance; } + /** + * Obtém os dados do utilizador uma única vez. + */ + public void getUser(String uid, Consumer callback) { + db.collection("users").document(uid).get().addOnSuccessListener(snapshot -> { + if (snapshot.exists()) { + Usuario user = snapshot.toObject(Usuario.class); + if (user != null) callback.accept(user); + } + }); + } + /** * Observa as mudanças no documento do utilizador em tempo real. */ @@ -83,4 +95,16 @@ public class FirestoreManager { public void updateUserStats(String uid, Map updates) { db.collection("users").document(uid).update(updates); } + + /** + * Regista um log de XP. + */ + public void addXpLog(String uid, int amount, String type) { + Map log = new java.util.HashMap<>(); + log.put("userId", uid); + log.put("amount", amount); + log.put("type", type); + log.put("created_at", com.google.firebase.firestore.FieldValue.serverTimestamp()); + db.collection("xp_logs").add(log); + } } diff --git a/app/src/main/java/com/fluxup/app/InicioFragment.java b/app/src/main/java/com/fluxup/app/InicioFragment.java index 3382cc3..275eeb4 100644 --- a/app/src/main/java/com/fluxup/app/InicioFragment.java +++ b/app/src/main/java/com/fluxup/app/InicioFragment.java @@ -12,6 +12,7 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Button; +import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.firebase.auth.FirebaseUser; import com.google.firebase.firestore.ListenerRegistration; import java.util.HashMap; @@ -28,6 +29,7 @@ import java.util.Calendar; import java.util.Locale; import java.util.ArrayList; import java.util.List; +import androidx.core.content.ContextCompat; public class InicioFragment extends Fragment { @@ -36,7 +38,9 @@ public class InicioFragment extends Fragment { private TextView tvTodayStreak, tvTodayXP, tvTodayTasksCount, tvNoTasksIncentive; private TextView tvDailyRewardGoal; private ProgressBar pbDailyTasksProgress; - private LinearLayout tasksContainer, miniRankingContainer; + private androidx.recyclerview.widget.RecyclerView rvTasks; + private TasksAdapter tasksAdapter; + private LinearLayout miniRankingContainer, layoutTasksSection; private Button btnStartFocus, btnAddTasks, btnClaimReward; private CountDownTimer countDownTimer; @@ -56,7 +60,10 @@ public class InicioFragment extends Fragment { "Um passo de cada vez.", "A consistência é a chave.", "Foca no progresso, não na perfeição.", - "Hoje é um ótimo dia para ser produtivo!" + "Hoje é um ótimo dia para ser produtivo!", + "Vence a procrastinação agora.", + "A tua versão do futuro vai agradecer.", + "Pequenos passos, grandes resultados." }; @@ -70,7 +77,10 @@ public class InicioFragment extends Fragment { tvMotivationalSubtitle = view.findViewById(R.id.tvMotivationalSubtitle); view.findViewById(R.id.cardProfileAvatar).setOnClickListener(v -> { if (getActivity() instanceof MainActivity) { - ((MainActivity) getActivity()).findViewById(R.id.nav_profile).performClick(); + BottomNavigationView nav = ((MainActivity) getActivity()).findViewById(R.id.bottom_navigation); + if (nav != null) { + nav.setSelectedItemId(R.id.nav_profile); + } } }); @@ -81,7 +91,17 @@ public class InicioFragment extends Fragment { pbDailyTasksProgress = view.findViewById(R.id.pbDailyTasksProgress); // Tasks - tasksContainer = view.findViewById(R.id.tasksContainer); + layoutTasksSection = view.findViewById(R.id.layoutTasksSection); + rvTasks = view.findViewById(R.id.rvTasks); + rvTasks.setLayoutManager(new androidx.recyclerview.widget.LinearLayoutManager(getContext())); + tasksAdapter = new TasksAdapter(currentTasks, new TasksAdapter.OnTaskClickListener() { + @Override public void onTaskFocus(Task task) { selectTaskForFocus(task); } + @Override public void onTaskDelete(Task task) { showDeleteConfirmDialog(task); } + @Override public void onTaskEdit(Task task) { showEditTaskDialog(task); } + }); + rvTasks.setAdapter(tasksAdapter); + setupSwipeToDelete(); + tvNoTasksIncentive = view.findViewById(R.id.tvNoTasksIncentive); btnAddTasks = view.findViewById(R.id.btnAddTasks); btnAddTasks.setOnClickListener(v -> showAddTaskDialog()); @@ -92,7 +112,9 @@ public class InicioFragment extends Fragment { btnStartFocus.setOnClickListener(v -> { if (!isTimerRunning) { if (selectedTaskForFocus == null) { - Toast.makeText(getContext(), "Seleciona uma tarefa primeiro!", Toast.LENGTH_SHORT).show(); + if (getContext() != null) { + Toast.makeText(getContext(), "Seleciona uma tarefa primeiro!", Toast.LENGTH_SHORT).show(); + } return; } startTimer(); @@ -137,7 +159,11 @@ public class InicioFragment extends Fragment { FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); if (currentUser != null) { tasksListener = FirestoreManager.getInstance().observeTasks(currentUser.getUid(), tasks -> { - currentTasks = tasks; + List activeTasks = new ArrayList<>(); + for (Task t : tasks) { + if (!t.completed) activeTasks.add(t); + } + currentTasks = activeTasks; updateTasksUI(); updateTodayCard(); }); @@ -159,131 +185,157 @@ public class InicioFragment extends Fragment { } // Update Reward Button State - if (user.tasks_concluidas_hoje >= user.meta_diaria_tarefas) { + String today = new java.text.SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new java.util.Date()); + boolean alreadyClaimed = today.equals(user.last_reward_claim_date); + + if (user.tasks_concluidas_hoje >= user.meta_diaria_tarefas && !alreadyClaimed) { btnClaimReward.setEnabled(true); btnClaimReward.setAlpha(1.0f); + btnClaimReward.setText("Resgatar"); + } else if (alreadyClaimed) { + btnClaimReward.setEnabled(false); + btnClaimReward.setAlpha(0.5f); + btnClaimReward.setText("Resgatado"); } else { btnClaimReward.setEnabled(false); btnClaimReward.setAlpha(0.5f); + btnClaimReward.setText("Resgatar"); } - - checkDailyReset(user); + + updateTodayCard(user); + checkDailyResetAndStreak(user); }); } } private void updateTasksUI() { - if (getContext() == null || tasksContainer == null) return; - tasksContainer.removeAllViews(); - - for (Task task : currentTasks) { - View taskView = LayoutInflater.from(getContext()).inflate(R.layout.item_task_home, tasksContainer, false); - - TextView tvTitle = taskView.findViewById(R.id.tvTaskTitle); - TextView tvDuration = taskView.findViewById(R.id.tvTaskDuration); - CheckBox cb = taskView.findViewById(R.id.cbTask); - Button btnStart = taskView.findViewById(R.id.btnStartTaskFocus); - - tvTitle.setText(task.title); - tvDuration.setText(task.duration + " min"); - cb.setChecked(task.completed); - - if (task.completed) { - tvTitle.setPaintFlags(tvTitle.getPaintFlags() | android.graphics.Paint.STRIKE_THRU_TEXT_FLAG); - tvTitle.setTextColor(getResources().getColor(R.color.text_secondary)); - btnStart.setEnabled(false); - btnStart.setAlpha(0.5f); - } - - cb.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (isChecked && !task.completed) { - completeTask(task); - } - }); - - btnStart.setOnClickListener(v -> { - selectedTaskForFocus = task; - timeLeftInMillis = task.duration * 60 * 1000; - updateCountDownText(); - Toast.makeText(getContext(), "Tarefa selecionada: " + task.title, Toast.LENGTH_SHORT).show(); - - // Highlight selection - for (int i = 0; i < tasksContainer.getChildCount(); i++) { - tasksContainer.getChildAt(i).setBackground(null); - } - taskView.setBackgroundResource(R.drawable.task_selected_bg); - }); - - taskView.setOnLongClickListener(v -> { - showDeleteConfirmDialog(task); - return true; - }); - - tasksContainer.addView(taskView); + if (tasksAdapter != null) { + tasksAdapter.setTasks(currentTasks); } updateTodayCard(); } + private void selectTaskForFocus(Task task) { + selectedTaskForFocus = task; + timeLeftInMillis = task.duration * 60 * 1000; + updateCountDownText(); + if (getContext() != null) { + Toast.makeText(getContext(), "Modo Foco: " + task.title, Toast.LENGTH_SHORT).show(); + } + } + + private void setupSwipeToDelete() { + new androidx.recyclerview.widget.ItemTouchHelper(new androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback(0, androidx.recyclerview.widget.ItemTouchHelper.LEFT | androidx.recyclerview.widget.ItemTouchHelper.RIGHT) { + @Override + public boolean onMove(@NonNull androidx.recyclerview.widget.RecyclerView recyclerView, @NonNull androidx.recyclerview.widget.RecyclerView.ViewHolder viewHolder, @NonNull androidx.recyclerview.widget.RecyclerView.ViewHolder target) { + return false; + } + + @Override + public void onSwiped(@NonNull androidx.recyclerview.widget.RecyclerView.ViewHolder viewHolder, int direction) { + int position = viewHolder.getAdapterPosition(); + Task task = currentTasks.get(position); + showDeleteConfirmDialog(task); + tasksAdapter.notifyItemChanged(position); // Revert swipe if canceled? Firestore update will refresh anyway. + } + }).attachToRecyclerView(rvTasks); + } + private void addXP(int amount) { + if (!isAdded()) return; FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); if (currentUser != null) { + String uid = currentUser.getUid(); Map updates = new HashMap<>(); updates.put("xp", com.google.firebase.firestore.FieldValue.increment(amount)); updates.put("xp_hoje", com.google.firebase.firestore.FieldValue.increment(amount)); + updates.put("xp_semanal", com.google.firebase.firestore.FieldValue.increment(amount)); - // Check Level Up logic inside observe once - com.google.firebase.firestore.FirebaseFirestore.getInstance() - .collection("users").document(currentUser.getUid()).get() - .addOnSuccessListener(snapshot -> { - Usuario user = snapshot.toObject(Usuario.class); - if (user != null) { - int currentLevel = user.level; - int nextLevelThreshold = currentLevel * 500; - if (user.xp + amount >= nextLevelThreshold) { - updates.put("level", currentLevel + 1); - showLevelUpAnimation(currentLevel + 1); - } - FirestoreManager.getInstance().updateUserStats(currentUser.getUid(), updates); + FirestoreManager.getInstance().getUser(uid, user -> { + if (user != null && isAdded()) { + int currentLevel = user.level; + int threshold = (currentLevel * (currentLevel + 1) / 2) * 100; + if (user.xp + amount >= threshold) { + updates.put("level", currentLevel + 1); + showLevelUpAnimation(currentLevel + 1); } - }); + FirestoreManager.getInstance().updateUserStats(uid, updates); + } + }); } } private void updateTodayCard() { - int total = currentTasks.size(); - int completed = 0; - for (Task task : currentTasks) { - if (task.completed) completed++; - } - - if (tvTodayTasksCount != null) { - tvTodayTasksCount.setText(completed + "/" + (total == 0 ? 3 : total)); - } - - if (pbDailyTasksProgress != null) { - int progress = (total == 0) ? 0 : (completed * 100) / total; - pbDailyTasksProgress.setProgress(progress); - } - - if (total == 0) { - tvNoTasksIncentive.setVisibility(View.VISIBLE); - } else { - tvNoTasksIncentive.setVisibility(View.GONE); + FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); + if (currentUser != null) { + FirestoreManager.getInstance().getUser(currentUser.getUid(), this::updateTodayCard); } } - private void checkDailyReset(Usuario user) { + private void updateTodayCard(Usuario user) { + int completed = user.tasks_concluidas_hoje; + int goal = user.meta_diaria_tarefas; + if (goal <= 0) goal = 3; // Default fallback + + if (tvTodayTasksCount != null) { + tvTodayTasksCount.setText(completed + "/" + goal); + } + + if (pbDailyTasksProgress != null) { + int progress = (completed * 100) / goal; + if (progress > 100) progress = 100; + pbDailyTasksProgress.setProgress(progress); + updatePathProgress(progress); + } + + if (currentTasks != null && tvNoTasksIncentive != null && layoutTasksSection != null) { + if (currentTasks.isEmpty()) { + tvNoTasksIncentive.setVisibility(View.VISIBLE); + layoutTasksSection.setVisibility(View.GONE); + } else { + tvNoTasksIncentive.setVisibility(View.GONE); + layoutTasksSection.setVisibility(View.VISIBLE); + } + } + } + + private void checkDailyResetAndStreak(Usuario user) { String today = new java.text.SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new java.util.Date()); if (!today.equals(user.last_active_date)) { Map updates = new HashMap<>(); - if (user.tasks_concluidas_hoje == 0 && !user.last_active_date.isEmpty()) { - updates.put("streak", 0); + + // Streak logic + if (!user.last_active_date.isEmpty()) { + java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + try { + java.util.Date lastDate = sdf.parse(user.last_active_date); + java.util.Date todayDate = sdf.parse(today); + long diff = (todayDate.getTime() - lastDate.getTime()) / (1000 * 60 * 60 * 24); + + if (diff == 1) { + // Successive day, but wait for first task completion to increment streak? + // Or increment now if they at least logged in? User said "Completar pelo menos 1 tarefa por dia -> streak +1" + // So we don't increment here, we just check if we should reset. + } else if (diff > 1) { + // Failed a day + updates.put("streak", 0); + } + } catch (Exception e) { + e.printStackTrace(); + } } + updates.put("xp_hoje", 0); updates.put("tasks_concluidas_hoje", 0); updates.put("tempo_foco_hoje", 0); updates.put("last_active_date", today); FirestoreManager.getInstance().updateUserStats(user.id_usuario, updates); + + // Login reward + addXP(10); + if (getContext() != null) { + Toast.makeText(getContext(), "Recompensa de login: +10 XP!", Toast.LENGTH_SHORT).show(); + } } } @@ -306,14 +358,23 @@ public class InicioFragment extends Fragment { } private void addMiniRankingItem(int rank, String name, int xp) { - View item = getLayoutInflater().inflate(android.R.layout.simple_list_item_2, miniRankingContainer, false); - TextView tv1 = item.findViewById(android.R.id.text1); - TextView tv2 = item.findViewById(android.R.id.text2); - tv1.setText(rank + ". " + name); - tv1.setTextSize(14); - tv1.setTextColor(getResources().getColor(R.color.text_primary)); - tv2.setText(xp + " XP hoje"); - tv2.setTextSize(12); + if (getContext() == null || miniRankingContainer == null) return; + + View item = getLayoutInflater().inflate(R.layout.item_ranking_user, miniRankingContainer, false); + TextView tvRank = item.findViewById(R.id.tvRankPosition); + TextView tvName = item.findViewById(R.id.tvRankingName); + TextView tvXP = item.findViewById(R.id.tvRankingXP); + + tvRank.setText("#" + rank); + tvName.setText(name); + tvXP.setText(xp + " XP"); + + // Remove elevation for mini ranking to keep it clean + if (item instanceof androidx.cardview.widget.CardView) { + ((androidx.cardview.widget.CardView) item).setCardElevation(0); + ((androidx.cardview.widget.CardView) item).setCardBackgroundColor(android.graphics.Color.TRANSPARENT); + } + miniRankingContainer.addView(item); } @@ -353,15 +414,19 @@ public class InicioFragment extends Fragment { if (i < currentDayIndex) { // Past day - Assume completed for demo nodeCircle.setBackgroundResource(R.drawable.node_circle_bg); - nodeCircle.getBackground().setTint(getResources().getColor(R.color.success_green)); - nodeDayInitial.setTextColor(getResources().getColor(R.color.white)); + if (getContext() != null) { + nodeCircle.getBackground().setTint(ContextCompat.getColor(getContext(), R.color.success_green)); + nodeDayInitial.setTextColor(ContextCompat.getColor(getContext(), R.color.white)); + } nodeProgress.setVisibility(View.GONE); } else if (i == currentDayIndex) { // Today nodeCircle.setBackgroundResource(R.drawable.node_circle_bg); - nodeCircle.getBackground().setTint(getResources().getColor(R.color.primary_purple)); - nodeDayInitial.setTextColor(getResources().getColor(R.color.white)); - nodeDayLabel.setTextColor(getResources().getColor(R.color.primary_purple)); + if (getContext() != null) { + nodeCircle.getBackground().setTint(ContextCompat.getColor(getContext(), R.color.primary_purple)); + nodeDayInitial.setTextColor(ContextCompat.getColor(getContext(), R.color.white)); + nodeDayLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.primary_purple)); + } nodeProgress.setVisibility(View.VISIBLE); } else { // Future @@ -377,30 +442,31 @@ public class InicioFragment extends Fragment { private void completeTask(Task task) { task.completed = true; FirestoreManager.getInstance().updateTask(task); - addXP(30); updateUserTaskCount(); - boolean allDone = true; - for (Task t : currentTasks) { - if (!t.completed && !t.id.equals(task.id)) { - allDone = false; - break; - } - } - if (allDone && currentTasks.size() > 0) { - addXP(20); - Toast.makeText(getContext(), "Todas as tarefas concluídas! +20 XP Bónus", Toast.LENGTH_LONG).show(); - } triggerVibration(); - showXpPopup("+30 XP"); } private void updateUserTaskCount() { + if (!isAdded()) return; FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); if (currentUser != null) { - Map updates = new HashMap<>(); - updates.put("tasks_concluidas_hoje", com.google.firebase.firestore.FieldValue.increment(1)); - updates.put("total_tasks_concluidas", com.google.firebase.firestore.FieldValue.increment(1)); - FirestoreManager.getInstance().updateUserStats(currentUser.getUid(), updates); + String uid = currentUser.getUid(); + FirestoreManager.getInstance().getUser(uid, user -> { + if (user != null && isAdded()) { + Map updates = new HashMap<>(); + updates.put("tasks_concluidas_hoje", com.google.firebase.firestore.FieldValue.increment(1)); + updates.put("total_tasks_concluidas", com.google.firebase.firestore.FieldValue.increment(1)); + + if (user.tasks_concluidas_hoje == 0) { + updates.put("streak", com.google.firebase.firestore.FieldValue.increment(1)); + if (user.streak + 1 > user.melhor_streak) { + updates.put("melhor_streak", user.streak + 1); + } + } + + FirestoreManager.getInstance().updateUserStats(uid, updates); + } + }); } } @@ -421,10 +487,13 @@ public class InicioFragment extends Fragment { } private void showXpPopup(String text) { - Toast.makeText(getContext(), text, Toast.LENGTH_SHORT).show(); + if (getContext() != null) { + Toast.makeText(getContext(), text, Toast.LENGTH_SHORT).show(); + } } private void triggerVibration() { + if (getContext() == null) return; android.os.Vibrator v = (android.os.Vibrator) getContext().getSystemService(android.content.Context.VIBRATOR_SERVICE); if (v != null) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { @@ -436,10 +505,22 @@ public class InicioFragment extends Fragment { } private void claimDailyReward() { - addXP(100); - btnClaimReward.setEnabled(false); - btnClaimReward.setText("Resgatado"); - Toast.makeText(getContext(), "Recompensa diária resgatada! +100 XP", Toast.LENGTH_LONG).show(); + FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); + if (currentUser != null) { + String today = new java.text.SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new java.util.Date()); + Map updates = new HashMap<>(); + updates.put("last_reward_claim_date", today); + FirestoreManager.getInstance().updateUserStats(currentUser.getUid(), updates); + addXP(100); + + btnClaimReward.setEnabled(false); + btnClaimReward.setText("Resgatado"); + btnClaimReward.setAlpha(0.5f); + + if (getContext() != null) { + Toast.makeText(getContext(), "Recompensa diária resgatada! +100 XP", Toast.LENGTH_LONG).show(); + } + } } private void showDeleteConfirmDialog(Task task) { @@ -459,11 +540,11 @@ public class InicioFragment extends Fragment { ProgressBar nodeProgress = todayNode.findViewById(R.id.nodeProgress); View nodeCircle = todayNode.findViewById(R.id.nodeCircle); if (nodeProgress != null) nodeProgress.setProgress(progress); - if (progress == 100) { - nodeCircle.getBackground().setTint(getResources().getColor(R.color.success_green)); + if (progress == 100 && getContext() != null) { + nodeCircle.getBackground().setTint(ContextCompat.getColor(getContext(), R.color.success_green)); triggerSuccessAnimation(todayNode); - } else { - nodeCircle.getBackground().setTint(getResources().getColor(R.color.primary_purple)); + } else if (getContext() != null) { + nodeCircle.getBackground().setTint(ContextCompat.getColor(getContext(), R.color.primary_purple)); } } @@ -477,7 +558,9 @@ public class InicioFragment extends Fragment { animatorSet.playTogether(scaleX, scaleY); animatorSet.start(); - Toast.makeText(getContext(), "Dia Completado! 🎉", Toast.LENGTH_SHORT).show(); + if (getContext() != null) { + Toast.makeText(getContext(), "Dia Completado! 🎉", Toast.LENGTH_SHORT).show(); + } } @@ -491,19 +574,123 @@ public class InicioFragment extends Fragment { } @Override public void onFinish() { + android.util.Log.d("FLUXUP_DEBUG", "FOCUS_FINISHED_START"); isTimerRunning = false; - btnStartFocus.setText("Começar Foco"); - completeTask(selectedTaskForFocus); - addXP(50); - FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); - if (currentUser != null) { - Map updates = new HashMap<>(); - updates.put("tempo_foco_total", com.google.firebase.firestore.FieldValue.increment(selectedTaskForFocus.duration)); - updates.put("tempo_foco_hoje", com.google.firebase.firestore.FieldValue.increment(selectedTaskForFocus.duration)); - updates.put("sessoes_foco_completas", com.google.firebase.firestore.FieldValue.increment(1)); - FirestoreManager.getInstance().updateUserStats(currentUser.getUid(), updates); + + if (getActivity() != null && isAdded()) { + btnStartFocus.setText("Começar Foco"); + } + + try { + if (selectedTaskForFocus == null) { + android.util.Log.e("FLUXUP_DEBUG", "FOCUS_FINISHED_ERROR: selectedTask is null"); + return; + } + + String taskId = selectedTaskForFocus.id; + android.util.Log.d("FLUXUP_DEBUG", "TASK_ID: " + taskId); + + FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser(); + if (currentUser == null) { + android.util.Log.e("FLUXUP_DEBUG", "FOCUS_FINISHED_ERROR: USER_ID is null"); + return; + } + String userId = currentUser.getUid(); + android.util.Log.d("FLUXUP_DEBUG", "USER_ID: " + userId); + + if (taskId == null || taskId.isEmpty()) { + android.util.Log.e("FLUXUP_DEBUG", "FOCUS_FINISHED_ERROR: task.id is empty"); + return; + } + + // Início da sincronização segura + android.util.Log.d("FLUXUP_DEBUG", "FOCUS_COMPLETE_START"); + + FirestoreManager.getInstance().getUser(userId, user -> { + if (user == null) { + android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: User data is null"); + return; + } + if (!isAdded()) return; + + android.util.Log.d("FLUXUP_DEBUG", "SELECTED_TASK: " + taskId); + android.util.Log.d("FLUXUP_DEBUG", "USER_ID: " + userId); + android.util.Log.d("FLUXUP_DEBUG", "XP_BEFORE: " + user.xp_hoje); + + // 1. Log de XP + Map log = new HashMap<>(); + log.put("userId", userId); + log.put("amount", 50); + log.put("type", "focus_task"); + log.put("created_at", com.google.firebase.firestore.FieldValue.serverTimestamp()); + + com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("xp_logs") + .add(log) + .addOnSuccessListener(ref -> android.util.Log.d("FLUXUP_DEBUG", "XP_LOG_INSERT_RESULT: SUCCESS")) + .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "XP_LOG_INSERT_RESULT: FAIL - " + e.getMessage())); + + // 2. Preparar updates consolidados + Map updates = new HashMap<>(); + // Usando os nomes exatos de Usuario.java: xp, xp_hoje, xp_semanal + updates.put("xp", com.google.firebase.firestore.FieldValue.increment(50)); + updates.put("xp_hoje", com.google.firebase.firestore.FieldValue.increment(50)); + updates.put("xp_semanal", com.google.firebase.firestore.FieldValue.increment(50)); + + updates.put("tempo_foco_total", com.google.firebase.firestore.FieldValue.increment(selectedTaskForFocus.duration)); + updates.put("tempo_foco_hoje", com.google.firebase.firestore.FieldValue.increment(selectedTaskForFocus.duration)); + updates.put("sessoes_foco_completas", com.google.firebase.firestore.FieldValue.increment(1)); + + updates.put("tasks_concluidas_hoje", com.google.firebase.firestore.FieldValue.increment(1)); + updates.put("total_tasks_concluidas", com.google.firebase.firestore.FieldValue.increment(1)); + + if (user.tasks_concluidas_hoje == 0) { + updates.put("streak", com.google.firebase.firestore.FieldValue.increment(1)); + if (user.streak + 1 > user.melhor_streak) { + updates.put("melhor_streak", user.streak + 1); + } + } + + // Level Up + int currentLevel = user.level; + int threshold = (currentLevel * (currentLevel + 1) / 2) * 100; + if (user.xp + 50 >= threshold) { + updates.put("level", currentLevel + 1); + } + + // 3. Gravar tudo no Backend (User Stats) + com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("users").document(userId) + .update(updates) + .addOnSuccessListener(aVoid -> { + android.util.Log.d("FLUXUP_DEBUG", "USER_XP_UPDATE_RESULT: SUCCESS"); + if (isAdded()) { + android.util.Log.d("FLUXUP_DEBUG", "HOME_REFRESH_RESULT: SUCCESS"); + } + }) + .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "USER_XP_UPDATE_RESULT: FAIL - " + e.getMessage())); + + // 4. Concluir tarefa no backend + selectedTaskForFocus.completed = true; + com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("tasks").document(taskId) + .set(selectedTaskForFocus) + .addOnSuccessListener(aVoid -> android.util.Log.d("FLUXUP_DEBUG", "TASK_UPDATE_RESULT: SUCCESS")) + .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "TASK_UPDATE_RESULT: FAIL - " + e.getMessage())); + + if (getContext() != null && isAdded()) { + Toast.makeText(getContext(), "Boa! Tarefa concluída +50 XP", Toast.LENGTH_LONG).show(); + triggerVibration(); + + selectedTaskForFocus = null; + timeLeftInMillis = 25 * 60 * 1000; + updateCountDownText(); + } + }); + + } catch (Exception e) { + android.util.Log.e("FLUXUP_DEBUG", "FOCUS_FINISHED_ERROR: " + e.getMessage()); + if (getContext() != null && isAdded()) { + Toast.makeText(getContext(), "Erro ao concluir tarefa. Tenta novamente.", Toast.LENGTH_SHORT).show(); + } } - selectedTaskForFocus = null; } }.start(); isTimerRunning = true; @@ -543,6 +730,30 @@ public class InicioFragment extends Fragment { if (countDownTimer != null) countDownTimer.cancel(); } + private void showEditTaskDialog(Task task) { + View dialogView = getLayoutInflater().inflate(R.layout.dialog_add_task, null); + android.widget.EditText etTitle = dialogView.findViewById(R.id.etTaskTitle); + android.widget.EditText etDuration = dialogView.findViewById(R.id.etTaskDuration); + + etTitle.setText(task.title); + etDuration.setText(String.valueOf(task.duration)); + + new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext()) + .setTitle("Editar Tarefa") + .setView(dialogView) + .setNegativeButton("Cancelar", null) + .setPositiveButton("Atualizar", (dialog, which) -> { + String title = etTitle.getText().toString().trim(); + String durationStr = etDuration.getText().toString().trim(); + if (!title.isEmpty()) { + task.title = title; + if (!durationStr.isEmpty()) task.duration = Integer.parseInt(durationStr); + FirestoreManager.getInstance().updateTask(task); + } + }) + .show(); + } + private void showAddTaskDialog() { View dialogView = getLayoutInflater().inflate(R.layout.dialog_add_task, null); android.widget.EditText etTitle = dialogView.findViewById(R.id.etTaskTitle); @@ -564,7 +775,14 @@ public class InicioFragment extends Fragment { return; } int duration = 25; - if (!durationStr.isEmpty()) duration = Integer.parseInt(durationStr); + if (!durationStr.isEmpty()) { + try { + duration = Integer.parseInt(durationStr); + } catch (NumberFormatException e) { + etDuration.setError("Inválido"); + return; + } + } saveNewTask(title, duration); dialog.dismiss(); }); diff --git a/app/src/main/java/com/fluxup/app/ProfileFragment.java b/app/src/main/java/com/fluxup/app/ProfileFragment.java index 3e8f325..5579369 100644 --- a/app/src/main/java/com/fluxup/app/ProfileFragment.java +++ b/app/src/main/java/com/fluxup/app/ProfileFragment.java @@ -11,6 +11,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import android.widget.TextView; +import android.widget.GridLayout; +import android.widget.LinearLayout; import com.google.firebase.auth.FirebaseUser; import com.google.firebase.firestore.ListenerRegistration; @@ -49,7 +51,7 @@ public class ProfileFragment extends Fragment { }); view.findViewById(R.id.btnManageFriends).setOnClickListener(v -> { - startActivity(new Intent(getActivity(), FindFriendsActivity.class)); + startActivity(new Intent(getActivity(), MyFriendsActivity.class)); }); view.findViewById(R.id.btnViewStatsDetails).setOnClickListener(v -> { @@ -57,7 +59,7 @@ public class ProfileFragment extends Fragment { }); view.findViewById(R.id.btnLogout).setOnClickListener(v -> { - AuthManager.getInstance().signOut(); + AuthManager.getInstance().logout(); Intent intent = new Intent(getActivity(), LoginActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); @@ -103,13 +105,17 @@ public class ProfileFragment extends Fragment { private void renderBadges(Usuario user) { badgesGrid.removeAllViews(); - if (user.streak >= 7) addBadge("🔥", "Semana Ativa"); - if (user.total_tasks_concluidas >= 50) addBadge("🏆", "Executor"); - if (user.tempo_foco_total >= 600) addBadge("🧘", "Zen"); - if (user.level >= 5) addBadge("⭐", "Nível 5"); + // 7 days streak + addBadge(user.streak >= 7 ? "🔥" : "🔒", "7 Dias", user.streak >= 7); + // 50 tasks + addBadge(user.total_tasks_concluidas >= 50 ? "🎯" : "🔒", "50 Tarefas", user.total_tasks_concluidas >= 50); + // 10 hours focus (600 min) + addBadge(user.tempo_foco_total >= 600 ? "⏱️" : "🔒", "10 Horas", user.tempo_foco_total >= 600); + // 1000 XP + addBadge(user.xp >= 1000 ? "⚡" : "🔒", "1000 XP", user.xp >= 1000); } - private void addBadge(String emoji, String name) { + private void addBadge(String emoji, String name, boolean unlocked) { LinearLayout badge = new LinearLayout(getContext()); badge.setOrientation(LinearLayout.VERTICAL); badge.setGravity(android.view.Gravity.CENTER); @@ -117,12 +123,14 @@ public class ProfileFragment extends Fragment { TextView tvEmoji = new TextView(getContext()); tvEmoji.setText(emoji); - tvEmoji.setTextSize(24); + tvEmoji.setTextSize(32); + if (!unlocked) tvEmoji.setAlpha(0.3f); TextView tvName = new TextView(getContext()); tvName.setText(name); - tvName.setTextSize(8); + tvName.setTextSize(10); tvName.setGravity(android.view.Gravity.CENTER); + tvName.setTextColor(getResources().getColor(unlocked ? R.color.text_primary : R.color.text_secondary)); badge.addView(tvEmoji); badge.addView(tvName); diff --git a/app/src/main/java/com/fluxup/app/SearchFragment.java b/app/src/main/java/com/fluxup/app/SearchFragment.java index 9d57935..7e691fa 100644 --- a/app/src/main/java/com/fluxup/app/SearchFragment.java +++ b/app/src/main/java/com/fluxup/app/SearchFragment.java @@ -12,14 +12,13 @@ import android.widget.EditText; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; +import android.widget.ListView; import java.util.ArrayList; import java.util.Arrays; public class SearchFragment extends Fragment { - private RecyclerView recyclerView; + private ListView recyclerView; private EditText searchBar; private ArrayAdapter adapter; private ArrayList allItems; @@ -35,7 +34,6 @@ public class SearchFragment extends Fragment { allItems = new ArrayList<>(Arrays.asList("Item 1", "Item 2", "Item 3", "Another Item", "More Items")); adapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_list_item_1, allItems); - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); recyclerView.setAdapter(adapter); searchBar.addTextChangedListener(new TextWatcher() { diff --git a/app/src/main/java/com/fluxup/app/SettingsActivity.java b/app/src/main/java/com/fluxup/app/SettingsActivity.java index 4c2a49d..da9049d 100644 --- a/app/src/main/java/com/fluxup/app/SettingsActivity.java +++ b/app/src/main/java/com/fluxup/app/SettingsActivity.java @@ -12,6 +12,7 @@ import android.widget.ArrayAdapter; import android.widget.ImageButton; import android.widget.Spinner; import android.widget.TextView; +import android.widget.EditText; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatDelegate; @@ -36,7 +37,8 @@ public class SettingsActivity extends AppCompatActivity { private void initSettings() { // --- CONTA --- - setupClickable(R.id.settingEmail, "Email", FirebaseAuth.getInstance().getCurrentUser().getEmail(), null); + setupClickable(R.id.settingEditProfile, "Editar Perfil", "Nome, Bio, Avatar", v -> showEditProfileDialog()); + setupClickable(R.id.settingEmail, "Email", FirebaseAuth.getInstance().getCurrentUser().getEmail(), v -> showChangeEmailDialog()); setupClickable(R.id.settingPassword, "Alterar Palavra-passe", "********", v -> resetPassword()); setupSwitch(R.id.settingPublicProfile, "Perfil Público", "public_profile", true); @@ -59,7 +61,12 @@ public class SettingsActivity extends AppCompatActivity { setupClickable(R.id.settingExportData, "Exportar Dados", "JSON/CSV", null); findViewById(R.id.btnDeleteAccount).setOnClickListener(v -> { - Toast.makeText(this, "Funcionalidade disponível em breve", Toast.LENGTH_SHORT).show(); + new AlertDialog.Builder(this) + .setTitle("Eliminar Conta") + .setMessage("Tens a certeza? Todos os teus dados serão apagados permanentemente.") + .setNegativeButton("Cancelar", null) + .setPositiveButton("Sim, Eliminar", (dialog, which) -> deleteAccount()) + .show(); }); } @@ -96,6 +103,60 @@ public class SettingsActivity extends AppCompatActivity { } } + private void showEditProfileDialog() { + View view = getLayoutInflater().inflate(R.layout.dialog_edit_profile, null); + EditText etName = view.findViewById(R.id.etEditName); + EditText etBio = view.findViewById(R.id.etEditBio); + + String uid = FirebaseAuth.getInstance().getUid(); + FirestoreManager.getInstance().getUser(uid, user -> { + etName.setText(user.usuario); + etBio.setText(user.bio); + }); + + new AlertDialog.Builder(this) + .setTitle("Editar Perfil") + .setView(view) + .setPositiveButton("Guardar", (dialog, which) -> { + String name = etName.getText().toString(); + String bio = etBio.getText().toString(); + java.util.Map updates = new java.util.HashMap<>(); + updates.put("usuario", name); + updates.put("bio", bio); + FirestoreManager.getInstance().updateUserStats(uid, updates); + Toast.makeText(this, "Perfil atualizado!", Toast.LENGTH_SHORT).show(); + }) + .setNegativeButton("Cancelar", null) + .show(); + } + + private void showChangeEmailDialog() { + EditText input = new EditText(this); + input.setHint("Novo email"); + new AlertDialog.Builder(this) + .setTitle("Alterar Email") + .setView(input) + .setPositiveButton("Confirmar", (dialog, which) -> { + String newEmail = input.getText().toString(); + FirebaseAuth.getInstance().getCurrentUser().updateEmail(newEmail) + .addOnSuccessListener(aVoid -> Toast.makeText(this, "Email alterado!", Toast.LENGTH_SHORT).show()) + .addOnFailureListener(e -> Toast.makeText(this, "Erro: " + e.getMessage(), Toast.LENGTH_LONG).show()); + }) + .setNegativeButton("Cancelar", null) + .show(); + } + + private void deleteAccount() { + String uid = FirebaseAuth.getInstance().getUid(); + com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("users").document(uid).delete(); + FirebaseAuth.getInstance().getCurrentUser().delete() + .addOnSuccessListener(aVoid -> { + Intent intent = new Intent(this, LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + startActivity(intent); + }); + } + private void showDurationDialog(String key, String title, int current) { EditText input = new EditText(this); input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER); @@ -105,9 +166,20 @@ public class SettingsActivity extends AppCompatActivity { .setTitle(title) .setView(input) .setPositiveButton("Guardar", (dialog, which) -> { - int val = Integer.parseInt(input.getText().toString()); - sharedPreferences.edit().putInt(key, val).apply(); - recreate(); + try { + int val = Integer.parseInt(input.getText().toString()); + sharedPreferences.edit().putInt(key, val).apply(); + + // Also update in Firestore if user is logged in + String uid = FirebaseAuth.getInstance().getUid(); + if (uid != null) { + java.util.Map updates = new java.util.HashMap<>(); + if (key.equals("focus_duration")) updates.put("meta_diaria_foco", val); + FirestoreManager.getInstance().updateUserStats(uid, updates); + } + + recreate(); + } catch (Exception e) {} }) .setNegativeButton("Cancelar", null) .show(); diff --git a/app/src/main/java/com/fluxup/app/TrophiesActivity.java b/app/src/main/java/com/fluxup/app/TrophiesActivity.java index 396f1c5..517260e 100644 --- a/app/src/main/java/com/fluxup/app/TrophiesActivity.java +++ b/app/src/main/java/com/fluxup/app/TrophiesActivity.java @@ -9,6 +9,9 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; +import android.widget.Button; +import android.widget.ProgressBar; +import java.util.Calendar; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.firestore.ListenerRegistration; @@ -191,12 +194,3 @@ public class TrophiesActivity extends AppCompatActivity { if (userListener != null) userListener.remove(); } } - - @Override - protected void onDestroy() { - super.onDestroy(); - if (userListener != null) { - userListener.remove(); - } - } -} diff --git a/app/src/main/java/com/fluxup/app/Usuario.java b/app/src/main/java/com/fluxup/app/Usuario.java index 46fc673..59f02bf 100644 --- a/app/src/main/java/com/fluxup/app/Usuario.java +++ b/app/src/main/java/com/fluxup/app/Usuario.java @@ -32,6 +32,7 @@ public class Usuario { public int sessoes_foco_completas = 0; public int dias_ativos = 1; public String last_active_date = ""; // YYYY-MM-DD + public String last_reward_claim_date = ""; // YYYY-MM-DD public String avatar_url = ""; public Usuario() {} diff --git a/app/src/main/java/com/fluxup/app/UsuariosService.java b/app/src/main/java/com/fluxup/app/UsuariosService.java index 47e3add..24134d0 100644 --- a/app/src/main/java/com/fluxup/app/UsuariosService.java +++ b/app/src/main/java/com/fluxup/app/UsuariosService.java @@ -98,7 +98,7 @@ public class UsuariosService { }; for (String title : defaultTasks) { String taskId = getFirestore().collection("tasks").document().getId(); - Task task = new Task(taskId, title, 50, uid); + Task task = new Task(taskId, title, 50, 25, uid); getFirestore().collection("tasks").document(taskId).set(task); } } diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 9772d36..0f450e4 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -50,6 +50,7 @@ + diff --git a/app/src/main/res/layout/fragment_inicio.xml b/app/src/main/res/layout/fragment_inicio.xml index e5996a5..b93132a 100644 --- a/app/src/main/res/layout/fragment_inicio.xml +++ b/app/src/main/res/layout/fragment_inicio.xml @@ -175,23 +175,43 @@ android:progressDrawable="@drawable/progress_bar_duo" /> - - - + +