diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
index 5a9ece8..e2f2258 100644
--- a/.idea/caches/deviceStreaming.xml
+++ b/.idea/caches/deviceStreaming.xml
@@ -1230,6 +1230,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/fluxup/app/DailyProgress.java b/app/src/main/java/com/fluxup/app/DailyProgress.java
index 39a0d23..94b239c 100644
--- a/app/src/main/java/com/fluxup/app/DailyProgress.java
+++ b/app/src/main/java/com/fluxup/app/DailyProgress.java
@@ -1,6 +1,7 @@
package com.fluxup.app;
public class DailyProgress {
+ public String userId;
public String date;
public int completedTasks;
public int dailyGoal;
@@ -8,6 +9,9 @@ public class DailyProgress {
public int xp;
public String status; // "complete", "partial", "empty"
public boolean isGoalReached;
+ public boolean isCompleted;
+ public Object createdAt;
+ public Object updatedAt;
public DailyProgress() {}
@@ -19,11 +23,14 @@ public class DailyProgress {
this.xp = 0;
this.status = "empty";
this.isGoalReached = false;
+ this.isCompleted = false;
}
public void updateStatus() {
- if (isGoalReached || (completedTasks >= dailyGoal && dailyGoal > 0)) {
+ if (isCompleted || isGoalReached || (completedTasks >= dailyGoal && dailyGoal > 0)) {
status = "complete";
+ isCompleted = true;
+ isGoalReached = true;
} else if (completedTasks > 0) {
status = "partial";
} else {
diff --git a/app/src/main/java/com/fluxup/app/FirestoreManager.java b/app/src/main/java/com/fluxup/app/FirestoreManager.java
index ecce1d6..df28fb2 100644
--- a/app/src/main/java/com/fluxup/app/FirestoreManager.java
+++ b/app/src/main/java/com/fluxup/app/FirestoreManager.java
@@ -3,11 +3,14 @@ package com.fluxup.app;
import com.google.firebase.firestore.FirebaseFirestore;
import com.google.firebase.firestore.ListenerRegistration;
import com.google.firebase.firestore.QueryDocumentSnapshot;
+import com.google.firebase.firestore.SetOptions;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
+import com.google.firebase.firestore.FieldValue;
+import com.google.firebase.firestore.Query;
public class FirestoreManager {
@@ -369,4 +372,71 @@ public class FirestoreManager {
updateUserStats(uid, updates, () -> callback.accept(finalStreak));
});
}
+
+ /**
+ * Guarda ou atualiza o progresso diário na coleção daily_progress.
+ */
+ public void saveDailyProgress(DailyProgress dp) {
+ if (dp.userId == null || dp.date == null) return;
+
+ String docId = dp.userId + "_" + dp.date;
+
+ android.util.Log.d("FLUXUP_DEBUG", "SAVE_DAILY_PROGRESS_START");
+ android.util.Log.d("FLUXUP_DEBUG", "USER_ID: " + dp.userId);
+ android.util.Log.d("FLUXUP_DEBUG", "TODAY_DATE: " + dp.date);
+ android.util.Log.d("FLUXUP_DEBUG", "COMPLETED_TASKS_TODAY: " + dp.completedTasks);
+ android.util.Log.d("FLUXUP_DEBUG", "DAILY_GOAL: " + dp.dailyGoal);
+
+ Map data = new java.util.HashMap<>();
+ data.put("userId", dp.userId);
+ data.put("date", dp.date);
+ data.put("completedTasks", dp.completedTasks);
+ data.put("dailyGoal", dp.dailyGoal);
+ data.put("focusSessions", dp.focusSessions);
+ data.put("xp", dp.xp);
+ data.put("isCompleted", dp.isCompleted);
+ data.put("updatedAt", FieldValue.serverTimestamp());
+
+ // Se for a primeira vez, definimos o createdAt
+ db.collection("daily_progress").document(docId).get().addOnSuccessListener(snapshot -> {
+ if (!snapshot.exists()) {
+ data.put("createdAt", FieldValue.serverTimestamp());
+ }
+ db.collection("daily_progress").document(docId).set(data, SetOptions.merge())
+ .addOnSuccessListener(aVoid -> android.util.Log.d("FLUXUP_DEBUG", "DAILY_PROGRESS_SAVED"))
+ .addOnFailureListener(e -> android.util.Log.e("FLUXUP_DEBUG", "DAILY_PROGRESS_SAVE_FAIL: " + e.getMessage()));
+ });
+ }
+
+ /**
+ * Procura o progresso diário de um utilizador num intervalo de datas.
+ */
+ public void getMonthlyDailyProgress(String userId, String startDate, String endDate, Consumer> callback) {
+ android.util.Log.d("FLUXUP_DEBUG", "LOAD_MONTHLY_PROGRESS_START");
+ android.util.Log.d("FLUXUP_DEBUG", "MONTH_START: " + startDate);
+ android.util.Log.d("FLUXUP_DEBUG", "MONTH_END: " + endDate);
+
+ db.collection("daily_progress")
+ .whereEqualTo("userId", userId)
+ .whereEqualTo("isCompleted", true)
+ .whereGreaterThanOrEqualTo("date", startDate)
+ .whereLessThanOrEqualTo("date", endDate)
+ .get()
+ .addOnSuccessListener(snapshots -> {
+ List results = new ArrayList<>();
+ if (snapshots != null) {
+ for (QueryDocumentSnapshot doc : snapshots) {
+ DailyProgress dp = doc.toObject(DailyProgress.class);
+ results.add(dp);
+ }
+ }
+ android.util.Log.d("FLUXUP_DEBUG", "COMPLETED_DAYS_FROM_FIREBASE: " + results.size());
+ android.util.Log.d("FLUXUP_DEBUG", "DIAS_CUMPRIDOS_COUNT: " + results.size());
+ callback.accept(results);
+ })
+ .addOnFailureListener(e -> {
+ android.util.Log.e("FLUXUP_DEBUG", "LOAD_MONTHLY_PROGRESS_FAIL: " + e.getMessage());
+ callback.accept(new ArrayList<>());
+ });
+ }
}
diff --git a/app/src/main/java/com/fluxup/app/InicioFragment.java b/app/src/main/java/com/fluxup/app/InicioFragment.java
index e753aef..c5a9eed 100644
--- a/app/src/main/java/com/fluxup/app/InicioFragment.java
+++ b/app/src/main/java/com/fluxup/app/InicioFragment.java
@@ -396,7 +396,24 @@ public class InicioFragment extends Fragment {
int currentStreak = user.streak;
String lastStreakDate = user.last_streak_completed_date;
- boolean willIncrement = (completedTasksToday >= dailyTaskGoal) && !today.equals(lastStreakDate);
+ boolean isGoalReached = (completedTasksToday >= dailyTaskGoal);
+
+ // PERSISTÊNCIA NO FIRESTORE (daily_progress)
+ DailyProgress dp = new DailyProgress(today, dailyTaskGoal);
+ dp.userId = user.id_usuario;
+ dp.completedTasks = completedTasksToday;
+ dp.isCompleted = isGoalReached;
+ dp.updateStatus();
+ FirestoreManager.getInstance().saveDailyProgress(dp);
+
+ // PERSISTÊNCIA DO HISTÓRICO REAL NO OBJETO USUÁRIO (Retrocompatibilidade)
+ if (isGoalReached && (user.dias_concluidos == null || !user.dias_concluidos.contains(today))) {
+ if (user.dias_concluidos == null) user.dias_concluidos = new java.util.ArrayList<>();
+ user.dias_concluidos.add(today);
+ FirestoreManager.getInstance().updateUserField(user.id_usuario, "dias_concluidos", com.google.firebase.firestore.FieldValue.arrayUnion(today));
+ }
+
+ boolean willIncrement = isGoalReached && !today.equals(lastStreakDate);
int displayedStreak = 0;
if (completedTasksToday < dailyTaskGoal) {
@@ -415,7 +432,7 @@ public class InicioFragment extends Fragment {
}
FirestoreManager.getInstance().updateUserStats(user.id_usuario, updates);
- // NOVO: Adicionar log de conclusão permanente da meta
+ // Adicionar log de conclusão permanente da meta
FirestoreManager.getInstance().addXpLog(user.id_usuario, 0, "daily_goal_reached");
// Atualizar objeto local
@@ -621,35 +638,23 @@ public class InicioFragment extends Fragment {
ProgressBar nodeProgress = node.findViewById(R.id.nodeProgress);
View rightConnector = node.findViewById(R.id.rightConnector);
- // LÓGICA DE STATUS SOLICITADA
+ // LÓGICA DE HISTÓRICO PERSISTENTE SOLICITADA
boolean isToday = (i == currentDayIndex);
- int completedTasksFromLogs = (dp != null) ? dp.completedTasks : 0;
+ boolean isDateCompleted = (user != null && user.dias_concluidos != null && user.dias_concluidos.contains(dateStr));
- // Se for hoje, usamos o valor mais atualizado do objeto user
- int completedTasksForDay = isToday ? Math.max(completedTasksFromLogs, (user != null ? user.tasks_concluidas_hoje : 0)) : completedTasksFromLogs;
-
- int dailyTaskGoal = (dp != null && dp.dailyGoal > 0) ? dp.dailyGoal : 4;
- if (dailyTaskGoal <= 0) dailyTaskGoal = 4;
-
- // LÓGICA DE STATUS SOLICITADA
String status = "empty";
- if (dp != null) dp.updateStatus();
-
- if (dp != null && "complete".equals(dp.status)) {
- status = "completed";
- } else if (completedTasksForDay >= dailyTaskGoal) {
+ if (isDateCompleted) {
status = "completed";
} else if (isToday) {
status = "today";
}
+ int completedTasksForDay = isToday ? (user != null ? user.tasks_concluidas_hoje : 0) : ((progressMap != null && progressMap.get(dateStr) != null) ? progressMap.get(dateStr).completedTasks : 0);
+ int dailyTaskGoal = (progressMap != null && progressMap.get(dateStr) != null && progressMap.get(dateStr).dailyGoal > 0) ? progressMap.get(dateStr).dailyGoal : (user != null ? user.meta_diaria_tarefas : 4);
+ if (dailyTaskGoal <= 0) dailyTaskGoal = 4;
+
// DEBUG LOGS
- android.util.Log.d("FLUXUP_DEBUG", "WEEK_DAY: " + i);
- android.util.Log.d("FLUXUP_DEBUG", "DATE: " + dateStr);
- android.util.Log.d("FLUXUP_DEBUG", "COMPLETED_TASKS: " + completedTasksForDay);
- android.util.Log.d("FLUXUP_DEBUG", "DAILY_GOAL: " + dailyTaskGoal);
- android.util.Log.d("FLUXUP_DEBUG", "IS_TODAY: " + isToday);
- android.util.Log.d("FLUXUP_DEBUG", "FINAL_STATUS: " + status);
+ android.util.Log.d("FLUXUP_DEBUG", "WEEK_DAY: " + i + " | DATE: " + dateStr + " | COMPLETED: " + isDateCompleted + " | STATUS: " + status);
// RENDERIZAÇÃO
if ("completed".equals(status)) {
@@ -660,10 +665,9 @@ public class InicioFragment extends Fragment {
Calendar nextDayCal = (Calendar) dayCal.clone();
nextDayCal.add(Calendar.DAY_OF_YEAR, 1);
String nextDateStr = sdf.format(nextDayCal.getTime());
- DailyProgress nextDp = progressMap.get(nextDateStr);
+ boolean isNextCompleted = (user != null && user.dias_concluidos != null && user.dias_concluidos.contains(nextDateStr));
- // Se o próximo dia também estiver completo, conector verde
- if (nextDp != null && nextDp.completedTasks >= nextDp.dailyGoal && nextDp.dailyGoal > 0) {
+ if (isNextCompleted) {
rightConnector.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.success_green));
} else {
rightConnector.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.border_color));
diff --git a/app/src/main/java/com/fluxup/app/StreakActivity.java b/app/src/main/java/com/fluxup/app/StreakActivity.java
index ae98b75..84f76e2 100644
--- a/app/src/main/java/com/fluxup/app/StreakActivity.java
+++ b/app/src/main/java/com/fluxup/app/StreakActivity.java
@@ -21,6 +21,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
public class StreakActivity extends AppCompatActivity {
@@ -116,74 +117,72 @@ public class StreakActivity extends AppCompatActivity {
endCal.add(Calendar.MONTH, 1);
java.util.Date endDate = endCal.getTime();
- android.util.Log.d("FLUXUP_DEBUG", "OFFENSIVA_LOAD_MONTH_START");
+ android.util.Log.d("FLUXUP_DEBUG", "STREAK_VALUE_FROM_HOME_SOURCE: " + (currentUserObj != null ? currentUserObj.streak : "null"));
android.util.Log.d("FLUXUP_DEBUG", "OFFENSIVA_USER_ID: " + currentUserId);
- android.util.Log.d("FLUXUP_DEBUG", "OFFENSIVA_MONTH_START: " + startDate);
- android.util.Log.d("FLUXUP_DEBUG", "OFFENSIVA_MONTH_END: " + endDate);
- android.util.Log.d("FLUXUP_DEBUG", "OFFENSIVA_CURRENT_DAILY_GOAL: " + currentDailyGoal);
+ android.util.Log.d("FLUXUP_DEBUG", "LOAD_MONTHLY_PROGRESS_START");
+
+ String startDateStr = String.format(Locale.US, "%04d-%02d-%02d", year, month + 1, 1);
+ Calendar endMonthCal = (Calendar) startCal.clone();
+ endMonthCal.set(Calendar.DAY_OF_MONTH, endMonthCal.getActualMaximum(Calendar.DAY_OF_MONTH));
+ String endDateStr = String.format(Locale.US, "%04d-%02d-%02d", year, month + 1, endMonthCal.getActualMaximum(Calendar.DAY_OF_MONTH));
+
+ android.util.Log.d("FLUXUP_DEBUG", "MONTH_START: " + startDateStr);
+ android.util.Log.d("FLUXUP_DEBUG", "MONTH_END: " + endDateStr);
- FirestoreManager.getInstance().getDailyProgress(currentUserId, startDate, endDate, currentDailyGoal, progressMap -> {
+ FirestoreManager.getInstance().getMonthlyDailyProgress(currentUserId, startDateStr, endDateStr, progressList -> {
if (isDestroyed()) return;
- android.util.Log.d("FLUXUP_DEBUG", "OFFENSIVA_PROGRESS_DATA_SIZE: " + progressMap.size());
+ android.util.Log.d("FLUXUP_DEBUG", "COMPLETED_DAYS_FROM_FIREBASE: " + progressList.size());
int focusSessionsCount = 0;
Map dailyProgressMap = new HashMap<>();
+ Set completedDatesThisMonth = new java.util.HashSet<>();
- for (Map.Entry entry : progressMap.entrySet()) {
- DailyProgress dp = entry.getValue();
+ for (DailyProgress dp : progressList) {
focusSessionsCount += dp.focusSessions;
- android.util.Log.d("FLUXUP_DEBUG", "TASK_DATE: " + entry.getKey() + " | COMPLETED_TASKS: " + dp.completedTasks + " | GOAL: " + dp.dailyGoal + " | PERMANENT: " + dp.isGoalReached);
-
- String[] parts = entry.getKey().split("-");
+ completedDatesThisMonth.add(dp.date);
+
+ String[] parts = dp.date.split("-");
if (parts.length == 3) {
- int y = Integer.parseInt(parts[0]);
- int m = Integer.parseInt(parts[1]) - 1; // Calendar.MONTH is 0-indexed
int d = Integer.parseInt(parts[2]);
+ dailyProgressMap.put(d, dp);
+ }
+ }
- if (y == year && m == month) {
- dailyProgressMap.put(d, dp);
+ // MERGE com dias_concluidos do objeto user para retrocompatibilidade/consistência
+ if (currentUserObj != null && currentUserObj.dias_concluidos != null) {
+ String monthPrefix = String.format(Locale.US, "%04d-%02d-", year, month + 1);
+ for (String date : currentUserObj.dias_concluidos) {
+ if (date.startsWith(monthPrefix)) {
+ completedDatesThisMonth.add(date);
}
}
}
+ android.util.Log.d("FLUXUP_DEBUG", "COMPLETED_DAYS_COUNT: " + completedDatesThisMonth.size());
+
// Aplicar o fallback seguro para HOJE usando o tasks_concluidas_hoje do utilizador
- // devido a potenciais atrasos na indexação dos xp_logs com serverTimestamp
Calendar todayCal = Calendar.getInstance();
if (todayCal.get(Calendar.YEAR) == year && todayCal.get(Calendar.MONTH) == month) {
int todayDay = todayCal.get(Calendar.DAY_OF_MONTH);
DailyProgress dpToday = dailyProgressMap.get(todayDay);
if (dpToday == null) {
- dpToday = new DailyProgress(year + "-" + (month + 1) + "-" + todayDay, currentDailyGoal);
+ dpToday = new DailyProgress(String.format(Locale.US, "%04d-%02d-%02d", year, month + 1, todayDay), currentDailyGoal);
dailyProgressMap.put(todayDay, dpToday);
}
int userObjectToday = (currentUserObj != null) ? currentUserObj.tasks_concluidas_hoje : 0;
dpToday.completedTasks = Math.max(dpToday.completedTasks, userObjectToday);
dpToday.updateStatus();
- }
-
- // Contar quantos dias no mês atingiram a meta
- int completedDaysCount = 0;
- Calendar calcCal = (Calendar) currentCalendar.clone();
- int maxDaysInMonth = calcCal.getActualMaximum(Calendar.DAY_OF_MONTH);
-
- for (int d = 1; d <= maxDaysInMonth; d++) {
- DailyProgress dp = dailyProgressMap.get(d);
- if (dp != null) {
- dp.updateStatus();
- boolean isComplete = "complete".equals(dp.status);
- if (isComplete) {
- completedDaysCount++;
- }
- if (d == 6 || d == 11) {
- android.util.Log.d("FLUXUP_DEBUG", "DAY_" + d + "_COMPLETED_TASKS: " + dp.completedTasks);
- android.util.Log.d("FLUXUP_DEBUG", "DAY_" + d + "_IS_COMPLETED: " + isComplete);
- }
+
+ if (dpToday.isCompleted) {
+ completedDatesThisMonth.add(dpToday.date);
}
}
- android.util.Log.d("FLUXUP_DEBUG", "DIAS_CUMPRIDOS_COUNT: " + completedDaysCount);
- tvDaysOfPractice.setText(String.valueOf(completedDaysCount));
+ // Mostrar total de dias concluídos (Histórico Real do Mês)
+ int diasCumpridos = completedDatesThisMonth.size();
+ android.util.Log.d("FLUXUP_DEBUG", "DIAS_CUMPRIDOS_VALUE: " + diasCumpridos);
+ tvDaysOfPractice.setText(String.valueOf(diasCumpridos));
tvFocusSessions.setText(String.valueOf(focusSessionsCount));
updateCalendar(dailyProgressMap);
@@ -231,16 +230,23 @@ public class StreakActivity extends AppCompatActivity {
}
}
- // Regra: status do DailyProgress (respeita a meta permanente se existir)
+ // Regra: Histórico Persistente
DailyProgress dp = dailyProgressMap.get(i);
+ String dateKey = String.format(Locale.US, "%04d-%02d-%02d", calYear, calMonth + 1, i);
String status = "empty";
- if (dp != null) {
+
+ if (currentUserObj != null && currentUserObj.dias_concluidos != null && currentUserObj.dias_concluidos.contains(dateKey)) {
+ status = "complete";
+ } else if (dp != null) {
dp.updateStatus();
status = dp.status;
}
- if (i == 6 && isCurrentMonth) {
- android.util.Log.d("FLUXUP_DEBUG", "DAY_6_STATUS: " + status);
+ if (i == 11 && isCurrentMonth) {
+ android.util.Log.d("FLUXUP_DEBUG", "DAY_11_STATUS: " + status);
+ }
+ if (i == 12 && isCurrentMonth) {
+ android.util.Log.d("FLUXUP_DEBUG", "DAY_12_STATUS: " + status);
}
days.add(new CalendarDay(i, dp != null ? dp.completedTasks : 0, isCurrent, isFuture, status));
diff --git a/app/src/main/java/com/fluxup/app/Usuario.java b/app/src/main/java/com/fluxup/app/Usuario.java
index 0dbe5f3..fa265b2 100644
--- a/app/src/main/java/com/fluxup/app/Usuario.java
+++ b/app/src/main/java/com/fluxup/app/Usuario.java
@@ -37,6 +37,7 @@ public class Usuario {
public String last_streak_completed_date = ""; // YYYY-MM-DD
public String last_reward_claim_date = ""; // YYYY-MM-DD
public String avatar_url = "";
+ public java.util.List dias_concluidos = new java.util.ArrayList<>();
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 24134d0..66360e7 100644
--- a/app/src/main/java/com/fluxup/app/UsuariosService.java
+++ b/app/src/main/java/com/fluxup/app/UsuariosService.java
@@ -71,8 +71,7 @@ public class UsuariosService {
android.util.Log.d("FLUXUP_SERVICE", "A guardar utilizador no Firestore (Coleção: users, ID: " + uid + ")...");
getFirestore().collection("users").document(uid).set(usuario)
.addOnSuccessListener(aVoid -> {
- android.util.Log.d("FLUXUP_SERVICE", "Utilizador guardado no Firestore com sucesso. A criar tarefas iniciais...");
- createInitialTasks(uid);
+ android.util.Log.d("FLUXUP_SERVICE", "Utilizador guardado no Firestore com sucesso.");
usuario.palavra_passe = tempPass;
callback.onSuccess(usuario);
})
@@ -90,18 +89,6 @@ public class UsuariosService {
});
}
- private static void createInitialTasks(String uid) {
- String[] defaultTasks = {
- "Completar o perfil",
- "Iniciar primeira sessão de foco",
- "Definir objetivo diário"
- };
- for (String title : defaultTasks) {
- String taskId = getFirestore().collection("tasks").document().getId();
- Task task = new Task(taskId, title, 50, 25, uid);
- getFirestore().collection("tasks").document(taskId).set(task);
- }
- }
public static void recuperarPalavraPasse(Context context, String email, ServiceCallback callback) {
FirebaseAuth.getInstance().sendPasswordResetEmail(email)