Files
PapF/app/src/main/java/com/fluxup/app/InicioFragment.java
2026-05-05 17:14:37 +01:00

1077 lines
47 KiB
Java

package com.fluxup.app;
import android.annotation.SuppressLint;
import android.os.Bundle;
import android.os.CountDownTimer;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.FrameLayout;
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;
import java.util.Map;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.view.animation.BounceInterpolator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
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 {
// Focus Mode
private enum FocusState { NOT_STARTED, RUNNING, PAUSED }
private FocusState currentFocusState = FocusState.NOT_STARTED;
private TextView tvTimer, tvGreeting, tvMotivationalSubtitle, tvFocusStatus, tvFocusTitle;
private TextView tvTodayStreak, tvTodayXP, tvTodayTasksCount, tvNoTasksIncentive;
private TextView tvDailyRewardGoal;
private ProgressBar pbDailyTasksProgress;
private androidx.recyclerview.widget.RecyclerView rvTasks;
private TasksAdapter tasksAdapter;
private LinearLayout miniRankingContainer, layoutTasksSection;
private Button btnStartFocus, btnSecondaryFocus, btnAddTasks, btnClaimReward;
private androidx.cardview.widget.CardView btnStreakPage;
private CountDownTimer countDownTimer;
private LinearLayout progressPathContainer;
private List<View> dayNodes = new ArrayList<>();
private int currentDayIndex = 0;
private ListenerRegistration tasksListener, userListener, rankingListener;
private List<Task> currentTasks = new ArrayList<>();
private Task selectedTaskForFocus = null;
private boolean isCompletingFocus = false;
private boolean isTimerRunning = false;
private long timeLeftInMillis = 25 * 60 * 1000; // 25 minutos
private String[] motivationalQuotes = {
"Pronto para vencer hoje?",
"Só precisas de começar.",
"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!",
"Vence a procrastinação agora.",
"A tua versão do futuro vai agradecer.",
"Pequenos passos, grandes resultados."
};
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_inicio, container, false);
// Header
tvGreeting = view.findViewById(R.id.tvGreeting);
tvMotivationalSubtitle = view.findViewById(R.id.tvMotivationalSubtitle);
view.findViewById(R.id.cardProfileAvatar).setOnClickListener(v -> {
if (getActivity() instanceof MainActivity) {
BottomNavigationView nav = ((MainActivity) getActivity()).findViewById(R.id.bottom_navigation);
if (nav != null) {
nav.setSelectedItemId(R.id.nav_profile);
}
}
});
// Today Card
tvTodayStreak = view.findViewById(R.id.tvTodayStreak);
tvTodayXP = view.findViewById(R.id.tvTodayXP);
tvTodayTasksCount = view.findViewById(R.id.tvTodayTasksCount);
pbDailyTasksProgress = view.findViewById(R.id.pbDailyTasksProgress);
// Tasks
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());
// Focus Mode
tvTimer = view.findViewById(R.id.tvTimer);
tvFocusStatus = view.findViewById(R.id.tvFocusStatus);
tvFocusTitle = view.findViewById(R.id.tvFocusTitle);
btnStartFocus = view.findViewById(R.id.btnStartFocus);
btnSecondaryFocus = view.findViewById(R.id.btnSecondaryFocus);
btnStartFocus.setOnClickListener(v -> handleStartFocusClick());
btnSecondaryFocus.setOnClickListener(v -> handleSecondaryFocusClick());
// Progress
progressPathContainer = view.findViewById(R.id.progressPathContainer);
// Mini Ranking
miniRankingContainer = view.findViewById(R.id.miniRankingContainer);
view.findViewById(R.id.btnViewFullRanking).setOnClickListener(v -> {
startActivity(new android.content.Intent(getActivity(), TrophiesActivity.class));
});
// Reward
tvDailyRewardGoal = view.findViewById(R.id.tvDailyRewardGoal);
btnClaimReward = view.findViewById(R.id.btnClaimReward);
btnClaimReward.setOnClickListener(v -> claimDailyReward());
// Streak Page
btnStreakPage = view.findViewById(R.id.btnStreakPage);
btnStreakPage.setOnClickListener(v -> {
startActivity(new android.content.Intent(getActivity(), StreakActivity.class));
});
setRandomMotivationalQuote();
initProgressPath();
startObservingTasks();
startObservingUser();
startObservingRanking();
updateCountDownText();
return view;
}
private void setRandomMotivationalQuote() {
int index = (int) (Math.random() * motivationalQuotes.length);
if (tvMotivationalSubtitle != null) {
tvMotivationalSubtitle.setText(motivationalQuotes[index]);
}
}
private void startObservingTasks() {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) {
tasksListener = FirestoreManager.getInstance().observeTasks(currentUser.getUid(), tasks -> {
List<Task> activeTasks = new ArrayList<>();
for (Task t : tasks) {
if (!t.completed) activeTasks.add(t);
}
currentTasks = activeTasks;
updateTasksUI();
});
}
}
private void startObservingUser() {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) {
userListener = FirestoreManager.getInstance().observeUser(currentUser.getUid(), user -> {
if (tvGreeting != null) {
tvGreeting.setText("Olá, " + user.usuario + "!");
}
if (tvTodayStreak != null) {
syncDailyStreak(user);
}
refreshTodayStats(user);
// Update Reward Button State
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");
}
updateTodayCard(user);
checkDailyResetAndStreak(user);
loadWeeklyProgress(user);
});
}
}
private void loadWeeklyProgress(Usuario user) {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK); // 1 = Sunday, 2 = Monday
int daysToSubtract = (dayOfWeek + 5) % 7; // Monday = 0, Sunday = 6
Calendar startCal = (Calendar) cal.clone();
startCal.add(Calendar.DAY_OF_YEAR, -daysToSubtract);
java.util.Date startDate = startCal.getTime();
Calendar endCal = (Calendar) startCal.clone();
endCal.add(Calendar.DAY_OF_YEAR, 7);
java.util.Date endDate = endCal.getTime();
android.util.Log.d("FLUXUP_DEBUG", "CURRENT_WEEK_RANGE: " + startDate + " to " + endDate);
FirestoreManager.getInstance().getDailyProgress(user.id_usuario, startDate, endDate, user.meta_diaria_tarefas > 0 ? user.meta_diaria_tarefas : 3, progressMap -> {
if (!isAdded()) return;
String todayStr = new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(new java.util.Date());
DailyProgress dpToday = progressMap.get(todayStr);
android.util.Log.d("FLUXUP_DEBUG", "HOME_TODAY_COMPLETED_TASKS: " + (dpToday != null ? dpToday.completedTasks : 0));
android.util.Log.d("FLUXUP_DEBUG", "HOME_PROGRESS_DATA: " + progressMap.size());
updateProgressPath(progressMap);
});
}
private void updateTasksUI() {
if (tasksAdapter != null) {
tasksAdapter.setTasks(currentTasks);
}
refreshTodayStats();
}
private void refreshTodayStats() {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser == null) return;
FirestoreManager.getInstance().getUser(currentUser.getUid(), this::refreshTodayStats);
}
private void refreshTodayStats(Usuario user) {
if (user == null || !isAdded()) return;
// Update basic card info (Tasks, Progress)
updateTodayCard(user);
// Update XP specifically from logs
FirestoreManager.getInstance().getTodayXp(user.id_usuario, todayXp -> {
if (isAdded() && tvTodayXP != null) {
tvTodayXP.setText(String.valueOf(todayXp));
android.util.Log.d("FLUXUP_DEBUG", "XP_STATE_UPDATED: " + todayXp);
android.util.Log.d("FLUXUP_DEBUG", "XP_VALUE_RENDERED_IN_CARD: " + todayXp);
}
});
}
private void selectTaskForFocus(Task task) {
selectedTaskForFocus = task;
timeLeftInMillis = task.duration * 60 * 1000;
updateCountDownText();
updateFocusUI(FocusState.NOT_STARTED);
if (tvFocusTitle != null) {
tvFocusTitle.setText(task.title);
}
if (getContext() != null) {
Toast.makeText(getContext(), "Tarefa selecionada: " + 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<String, Object> 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));
FirestoreManager.getInstance().addXpLog(uid, amount, "bonus");
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);
refreshTodayStats();
}
});
}
}
private void updateTodayCard() {
refreshTodayStats();
}
private void syncDailyStreak(Usuario user) {
if (user == null) return;
String today = new java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault()).format(new java.util.Date());
int completedTasksToday = user.tasks_concluidas_hoje;
int dailyTaskGoal = user.meta_diaria_tarefas > 0 ? user.meta_diaria_tarefas : 3;
int currentStreak = user.streak;
String lastStreakDate = user.last_streak_completed_date;
boolean willIncrement = (completedTasksToday >= dailyTaskGoal) && !today.equals(lastStreakDate);
int displayedStreak = 0;
if (completedTasksToday < dailyTaskGoal) {
displayedStreak = 0;
} else {
if (willIncrement) {
int newStreak = Math.max(currentStreak, 0) + 1;
displayedStreak = newStreak;
// Salvar no backend
Map<String, Object> updates = new HashMap<>();
updates.put("streak", newStreak);
updates.put("last_streak_completed_date", today);
if (newStreak > user.melhor_streak) {
updates.put("melhor_streak", newStreak);
}
FirestoreManager.getInstance().updateUserStats(user.id_usuario, updates);
// Atualizar objeto local para evitar loops ou UI atrasada
user.streak = newStreak;
user.last_streak_completed_date = today;
} else {
displayedStreak = currentStreak;
}
}
// Debug Logs solicitados
android.util.Log.d("FLUXUP_STREAK_DEBUG", "--- syncDailyStreak ---");
android.util.Log.d("FLUXUP_STREAK_DEBUG", "completedTasksToday = " + completedTasksToday);
android.util.Log.d("FLUXUP_STREAK_DEBUG", "dailyTaskGoal = " + dailyTaskGoal);
android.util.Log.d("FLUXUP_STREAK_DEBUG", "currentStreak = " + currentStreak);
android.util.Log.d("FLUXUP_STREAK_DEBUG", "lastStreakDate = " + lastStreakDate);
android.util.Log.d("FLUXUP_STREAK_DEBUG", "today = " + today);
android.util.Log.d("FLUXUP_STREAK_DEBUG", "willIncrement = " + willIncrement);
android.util.Log.d("FLUXUP_STREAK_DEBUG", "displayedStreak = " + displayedStreak);
if (tvTodayStreak != null) {
tvTodayStreak.setText(displayedStreak + " dias");
}
}
private void updateTodayCard(Usuario user) {
if (user == null) return;
int completed = user.tasks_concluidas_hoje;
int goal = user.meta_diaria_tarefas;
if (goal <= 0) goal = 3; // Default fallback
// Sincronizar e mostrar streak
syncDailyStreak(user);
if (tvTodayTasksCount != null) {
tvTodayTasksCount.setText(completed + "/" + goal);
}
if (pbDailyTasksProgress != null && goal > 0) {
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);
}
}
if (tasksAdapter != null) {
tasksAdapter.setTasks(currentTasks);
}
}
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<String, Object> updates = new HashMap<>();
// 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();
}
}
}
private void startObservingRanking() {
rankingListener = com.google.firebase.firestore.FirebaseFirestore.getInstance()
.collection("users")
.orderBy("xp_hoje", com.google.firebase.firestore.Query.Direction.DESCENDING)
.limit(3)
.addSnapshotListener((snapshots, e) -> {
if (e != null || snapshots == null) return;
miniRankingContainer.removeAllViews();
int rank = 1;
for (com.google.firebase.firestore.DocumentSnapshot doc : snapshots.getDocuments()) {
Usuario u = doc.toObject(Usuario.class);
if (u != null) {
addMiniRankingItem(rank++, u.usuario, u.xp_hoje);
}
}
});
}
private void addMiniRankingItem(int rank, String name, int xp) {
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);
}
private void initProgressPath() {
if (getContext() == null || progressPathContainer == null) return;
progressPathContainer.removeAllViews();
dayNodes.clear();
String[] days = {"SEG", "TER", "QUA", "QUI", "SEX", "SÁB", "DOM"};
String[] initials = {"S", "T", "Q", "Q", "S", "S", "D"};
// Get current day of week (Calendar.MONDAY is 2, SUNDAY is 1)
Calendar calendar = Calendar.getInstance();
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
// Map to 0-6 (Mon-Sun)
currentDayIndex = (dayOfWeek + 5) % 7;
for (int i = 0; i < 7; i++) {
View node = LayoutInflater.from(getContext()).inflate(R.layout.item_day_node, progressPathContainer, false);
View leftConnector = node.findViewById(R.id.leftConnector);
View rightConnector = node.findViewById(R.id.rightConnector);
View nodeCircle = node.findViewById(R.id.nodeCircle);
TextView nodeDayInitial = node.findViewById(R.id.nodeDayInitial);
TextView nodeDayLabel = node.findViewById(R.id.nodeDayLabel);
ProgressBar nodeProgress = node.findViewById(R.id.nodeProgress);
nodeDayLabel.setText(days[i]);
nodeDayInitial.setText(initials[i]);
// Hide connectors at ends
if (i == 0) leftConnector.setVisibility(View.INVISIBLE);
if (i == 6) rightConnector.setVisibility(View.INVISIBLE);
// Default empty state
nodeCircle.setBackgroundResource(R.drawable.node_circle_bg);
if (getContext() != null) {
nodeCircle.getBackground().setTint(android.graphics.Color.parseColor("#E0E0E0"));
nodeDayInitial.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
nodeDayLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
}
nodeProgress.setVisibility(View.GONE);
dayNodes.add(node);
progressPathContainer.addView(node);
}
}
private void updateProgressPath(Map<String, DailyProgress> progressMap) {
if (getContext() == null || dayNodes == null || dayNodes.isEmpty()) return;
Calendar cal = Calendar.getInstance();
cal.set(Calendar.HOUR_OF_DAY, 0);
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
cal.set(Calendar.MILLISECOND, 0);
int dayOfWeek = cal.get(Calendar.DAY_OF_WEEK);
int daysToSubtract = (dayOfWeek + 5) % 7;
Calendar startCal = (Calendar) cal.clone();
startCal.add(Calendar.DAY_OF_YEAR, -daysToSubtract);
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
for (int i = 0; i < 7; i++) {
Calendar dayCal = (Calendar) startCal.clone();
dayCal.add(Calendar.DAY_OF_YEAR, i);
String dateStr = sdf.format(dayCal.getTime());
DailyProgress dp = progressMap.get(dateStr);
View node = dayNodes.get(i);
View nodeCircle = node.findViewById(R.id.nodeCircle);
TextView nodeDayInitial = node.findViewById(R.id.nodeDayInitial);
TextView nodeDayLabel = node.findViewById(R.id.nodeDayLabel);
ProgressBar nodeProgress = node.findViewById(R.id.nodeProgress);
View rightConnector = node.findViewById(R.id.rightConnector);
if (dp != null && "complete".equals(dp.status)) {
nodeCircle.getBackground().setTint(ContextCompat.getColor(getContext(), R.color.success_green));
nodeDayInitial.setTextColor(ContextCompat.getColor(getContext(), R.color.white));
} else if (dp != null && "partial".equals(dp.status)) {
nodeCircle.getBackground().setTint(android.graphics.Color.parseColor("#4FC3F7"));
nodeDayInitial.setTextColor(ContextCompat.getColor(getContext(), R.color.white));
} else {
nodeCircle.getBackground().setTint(android.graphics.Color.parseColor("#E0E0E0"));
nodeDayInitial.setTextColor(ContextCompat.getColor(getContext(), R.color.text_primary));
}
// Streak connector color logic
if (i < 6) {
Calendar nextDayCal = (Calendar) dayCal.clone();
nextDayCal.add(Calendar.DAY_OF_YEAR, 1);
DailyProgress nextDp = progressMap.get(sdf.format(nextDayCal.getTime()));
if (dp != null && "complete".equals(dp.status) && nextDp != null && "complete".equals(nextDp.status)) {
rightConnector.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.success_green));
} else {
rightConnector.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.border_color));
}
}
if (i == currentDayIndex) {
nodeDayLabel.setVisibility(View.VISIBLE);
nodeDayLabel.setTextColor(ContextCompat.getColor(getContext(), R.color.primary_purple));
if (dp == null || !"complete".equals(dp.status)) {
nodeCircle.getBackground().setTint(ContextCompat.getColor(getContext(), R.color.primary_purple));
nodeDayInitial.setTextColor(ContextCompat.getColor(getContext(), R.color.white));
}
nodeProgress.setVisibility(View.VISIBLE);
if (dp != null && dp.dailyGoal > 0) {
nodeProgress.setProgress(dp.completedTasks * 100 / dp.dailyGoal);
} else {
nodeProgress.setProgress(0);
}
} else {
nodeDayLabel.setVisibility(View.GONE);
nodeProgress.setVisibility(View.GONE);
}
if (i > currentDayIndex) {
node.setAlpha(0.4f);
} else {
node.setAlpha(1.0f);
}
}
}
private void completeTask(Task task) {
task.completed = true;
FirestoreManager.getInstance().updateTask(task);
updateUserTaskCount();
triggerVibration();
}
private void updateUserTaskCount() {
if (!isAdded()) return;
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser != null) {
String uid = currentUser.getUid();
FirestoreManager.getInstance().getUser(uid, user -> {
if (user != null && isAdded()) {
Map<String, Object> 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));
// Adicionar log de XP mesmo em manual para permitir recálculo se necessário
FirestoreManager.getInstance().addXpLog(uid, 0, "task_manual_completion");
FirestoreManager.getInstance().updateUserStats(uid, updates, () -> {
if (isAdded()) refreshTodayStats();
});
}
});
}
}
private void showLevelUpAnimation(int newLevel) {
if (getContext() == null) return;
View dialogView = getLayoutInflater().inflate(R.layout.dialog_level_up, null);
TextView tvNewLevel = dialogView.findViewById(R.id.tvNewLevel);
tvNewLevel.setText(String.valueOf(newLevel));
androidx.appcompat.app.AlertDialog dialog = new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setView(dialogView)
.setPositiveButton("Incrível!", null)
.create();
dialog.show();
triggerVibration();
NotificationHelper.showNotification(getContext(), "🎉 SUBISTE DE NÍVEL!", "Chegaste ao nível " + newLevel + "! Continua assim.");
}
private void showXpPopup(String text) {
if (getContext() != null) {
Toast.makeText(getContext(), text, Toast.LENGTH_SHORT).show();
}
}
private void triggerVibration() {
if (getContext() == null) return;
try {
// Check settings
android.content.SharedPreferences prefs = getContext().getSharedPreferences("FluxupSettings", android.content.Context.MODE_PRIVATE);
boolean vibrationEnabled = prefs.getBoolean("vibration_on_finish", true);
if (!vibrationEnabled) {
android.util.Log.d("FLUXUP_DEBUG", "VIBRATION_DISABLED_BY_USER");
return;
}
android.os.Vibrator v = (android.os.Vibrator) getContext().getSystemService(android.content.Context.VIBRATOR_SERVICE);
if (v != null && v.hasVibrator()) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
v.vibrate(android.os.VibrationEffect.createOneShot(100, android.os.VibrationEffect.DEFAULT_AMPLITUDE));
} else {
v.vibrate(100);
}
android.util.Log.d("FLUXUP_DEBUG", "VIBRATION_SUCCESS");
}
} catch (Exception e) {
android.util.Log.e("FLUXUP_DEBUG", "VIBRATION_ERROR: " + e.getMessage());
}
}
private void claimDailyReward() {
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<String, Object> 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) {
new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setTitle("Eliminar Tarefa")
.setMessage("Tens a certeza que queres eliminar esta tarefa?")
.setNegativeButton("Cancelar", null)
.setPositiveButton("Eliminar", (dialog, which) -> {
FirestoreManager.getInstance().deleteTask(task.id);
})
.show();
}
private void updatePathProgress(int progress) {
if (currentDayIndex >= dayNodes.size()) return;
View todayNode = dayNodes.get(currentDayIndex);
ProgressBar nodeProgress = todayNode.findViewById(R.id.nodeProgress);
View nodeCircle = todayNode.findViewById(R.id.nodeCircle);
if (nodeProgress != null) nodeProgress.setProgress(progress);
if (progress == 100 && getContext() != null) {
nodeCircle.getBackground().setTint(ContextCompat.getColor(getContext(), R.color.success_green));
triggerSuccessAnimation(todayNode);
} else if (getContext() != null) {
nodeCircle.getBackground().setTint(ContextCompat.getColor(getContext(), R.color.primary_purple));
}
}
private void triggerSuccessAnimation(View view) {
ObjectAnimator scaleX = ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.2f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.2f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(500);
animatorSet.setInterpolator(new BounceInterpolator());
animatorSet.playTogether(scaleX, scaleY);
animatorSet.start();
if (getContext() != null) {
Toast.makeText(getContext(), "Dia Completado! 🎉", Toast.LENGTH_SHORT).show();
}
}
private void handleStartFocusClick() {
if (currentFocusState == FocusState.NOT_STARTED) {
if (selectedTaskForFocus == null) {
if (getContext() != null) Toast.makeText(getContext(), "Seleciona uma tarefa primeiro!", Toast.LENGTH_SHORT).show();
return;
}
startTimer();
} else if (currentFocusState == FocusState.RUNNING) {
pauseTimer();
} else if (currentFocusState == FocusState.PAUSED) {
startTimer();
}
}
private void handleSecondaryFocusClick() {
if (currentFocusState == FocusState.RUNNING) {
showFinishEarlyWarning();
} else if (currentFocusState == FocusState.PAUSED) {
cancelTimer();
}
}
private void updateFocusUI(FocusState state) {
if (!isAdded()) return;
currentFocusState = state;
if (selectedTaskForFocus == null) {
tvFocusTitle.setText("Seleciona uma tarefa");
tvFocusStatus.setText("Foca no teu objetivo");
} else {
tvFocusTitle.setText(selectedTaskForFocus.title);
tvFocusStatus.setText("Sessão de foco");
}
switch (state) {
case NOT_STARTED:
tvFocusStatus.setTextColor(ContextCompat.getColor(getContext(), R.color.text_secondary));
btnStartFocus.setText("Começar Foco");
btnSecondaryFocus.setVisibility(View.GONE);
break;
case RUNNING:
tvFocusStatus.setText("• EM FOCO");
tvFocusStatus.setTextColor(ContextCompat.getColor(getContext(), R.color.primary_purple));
btnStartFocus.setText("Pausar");
btnSecondaryFocus.setText("Concluir");
btnSecondaryFocus.setVisibility(View.VISIBLE);
break;
case PAUSED:
tvFocusStatus.setText("• PAUSADO");
tvFocusStatus.setTextColor(android.graphics.Color.parseColor("#FFA000"));
btnStartFocus.setText("Continuar");
btnSecondaryFocus.setText("Cancelar");
btnSecondaryFocus.setVisibility(View.VISIBLE);
break;
}
}
private void startTimer() {
if (selectedTaskForFocus == null) return;
if (countDownTimer != null) countDownTimer.cancel();
countDownTimer = new CountDownTimer(timeLeftInMillis, 1000) {
@Override
public void onTick(long millisUntilFinished) {
timeLeftInMillis = millisUntilFinished;
updateCountDownText();
}
@Override
public void onFinish() {
handleFocusComplete();
}
}.start();
isTimerRunning = true;
updateFocusUI(FocusState.RUNNING);
}
private void pauseTimer() {
if (countDownTimer != null) {
countDownTimer.cancel();
}
isTimerRunning = false;
updateFocusUI(FocusState.PAUSED);
}
private void cancelTimer() {
new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setTitle("Cancelar Foco")
.setMessage("Tens a certeza que queres cancelar? Não ganharás XP.")
.setNegativeButton("Não", null)
.setPositiveButton("Sim, cancelar", (dialog, which) -> {
if (countDownTimer != null) countDownTimer.cancel();
isTimerRunning = false;
selectedTaskForFocus = null;
timeLeftInMillis = 25 * 60 * 1000;
updateCountDownText();
updateFocusUI(FocusState.NOT_STARTED);
})
.show();
}
private void showFinishEarlyWarning() {
new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setTitle("Concluir cedo?")
.setMessage("Ainda não terminaste o tempo. Se concluíres agora, não ganharás o bónus de XP.")
.setNegativeButton("Continuar Foco", null)
.setPositiveButton("Concluir sem bónus", (dialog, which) -> {
// Similar to cancel but maybe we still mark task as done?
// User said "concluir → só dá XP se terminar o tempo".
// I'll just reset for now as per "XP: sessão cancelada → 0 XP"
if (countDownTimer != null) countDownTimer.cancel();
isTimerRunning = false;
selectedTaskForFocus = null;
timeLeftInMillis = 25 * 60 * 1000;
updateCountDownText();
updateFocusUI(FocusState.NOT_STARTED);
})
.show();
}
private void handleFocusComplete() {
if (isCompletingFocus) return;
isCompletingFocus = true;
android.util.Log.d("FLUXUP_DEBUG", "FOCUS_COMPLETE_START");
try {
if (countDownTimer != null) {
countDownTimer.cancel();
android.util.Log.d("FLUXUP_DEBUG", "TIMER_CANCELLED");
}
if (selectedTaskForFocus == null) {
android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: selectedTask null");
isCompletingFocus = false;
return;
}
isTimerRunning = false;
updateFocusUI(FocusState.NOT_STARTED);
android.util.Log.d("FLUXUP_DEBUG", "SELECTED_TASK: " + selectedTaskForFocus.id);
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser == null) {
android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: userId null");
isCompletingFocus = false;
return;
}
String userId = currentUser.getUid();
android.util.Log.d("FLUXUP_DEBUG", "USER_ID: " + userId);
final Task task = selectedTaskForFocus;
final String taskId = task.id;
android.util.Log.d("FLUXUP_DEBUG", "TASK_UPDATE_START");
task.completed = true;
FirestoreManager.getInstance().updateTask(task, () -> {
// Task update success is logged in FirestoreManager
android.util.Log.d("FLUXUP_DEBUG", "XP_LOG_INSERT_START");
FirestoreManager.getInstance().addXpLog(userId, 50, "focus_task", taskId);
FirestoreManager.getInstance().getUser(userId, user -> {
if (!isAdded()) {
isCompletingFocus = false;
return;
}
if (user == null) {
android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: User object null");
isCompletingFocus = false;
return;
}
Map<String, Object> updates = new HashMap<>();
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(task.duration));
updates.put("tempo_foco_hoje", com.google.firebase.firestore.FieldValue.increment(task.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));
int currentLevel = user.level;
int threshold = (currentLevel * (currentLevel + 1) / 2) * 100;
if (user.xp + 50 >= threshold) {
updates.put("level", currentLevel + 1);
showLevelUpAnimation(currentLevel + 1);
}
FirestoreManager.getInstance().updateUserStats(userId, updates, () -> {
android.util.Log.d("FLUXUP_DEBUG", "REFRESH_HOME_START");
refreshTodayStats();
if (isAdded()) {
android.util.Log.d("FLUXUP_DEBUG", "HOME_STATE_UPDATED");
Toast.makeText(getContext(), "Boa! Tarefa concluída +50 XP", Toast.LENGTH_LONG).show();
triggerVibration();
selectedTaskForFocus = null;
timeLeftInMillis = 25 * 60 * 1000;
updateCountDownText();
updateFocusUI(FocusState.NOT_STARTED);
android.util.Log.d("FLUXUP_DEBUG", "FOCUS_COMPLETE_SUCCESS");
}
isCompletingFocus = false;
});
});
});
} catch (Exception e) {
android.util.Log.e("FLUXUP_DEBUG", "FOCUS_COMPLETE_ERROR: " + e.getMessage());
e.printStackTrace();
if (getContext() != null) {
Toast.makeText(getContext(), "Não foi possível concluir a tarefa. Tenta novamente.", Toast.LENGTH_SHORT).show();
}
isCompletingFocus = false;
}
}
private void updateCountDownText() {
int minutes = (int) (timeLeftInMillis / 1000) / 60;
int seconds = (int) (timeLeftInMillis / 1000) % 60;
String timeLeftFormatted = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);
if(tvTimer != null) tvTimer.setText(timeLeftFormatted);
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (tasksListener != null) tasksListener.remove();
if (userListener != null) userListener.remove();
if (rankingListener != null) rankingListener.remove();
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);
android.widget.EditText etDuration = dialogView.findViewById(R.id.etTaskDuration);
androidx.appcompat.app.AlertDialog dialog = new com.google.android.material.dialog.MaterialAlertDialogBuilder(getContext())
.setTitle("Nova Tarefa")
.setView(dialogView)
.setNegativeButton("Cancelar", null)
.setPositiveButton("Guardar", null)
.create();
dialog.setOnShowListener(d -> {
dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
String title = etTitle.getText().toString().trim();
String durationStr = etDuration.getText().toString().trim();
if (title.isEmpty()) {
etTitle.setError("Obrigatório");
return;
}
int duration = 25;
if (!durationStr.isEmpty()) {
try {
duration = Integer.parseInt(durationStr);
} catch (NumberFormatException e) {
etDuration.setError("Inválido");
return;
}
}
saveNewTask(title, duration);
dialog.dismiss();
});
});
dialog.show();
}
private void saveNewTask(String title, int duration) {
FirebaseUser currentUser = AuthManager.getInstance().getCurrentUser();
if (currentUser == null) return;
String uid = currentUser.getUid();
String taskId = com.google.firebase.firestore.FirebaseFirestore.getInstance().collection("tasks").document().getId();
Task task = new Task(taskId, title, 30, duration, uid);
FirestoreManager.getInstance().addTask(task);
}
}