@@ -31,12 +31,37 @@ class AnalyticsService(private val context: Context) {
3131 instance ? : AnalyticsService (context.applicationContext).also { instance = it }
3232 }
3333 }
34+
35+ /* *
36+ * The streak as Home's Study Streak card shows it: from the progress
37+ * document (users/{username}/data/progress) that LearningPathFragment's
38+ * saveStreak keeps, with the same defaults when there is none yet. Null
39+ * when the read failed. Analytics used to read users/{username}/progress/
40+ * current, which nothing writes, so the streak and pace were always 0.
41+ */
42+ internal fun streakFrom (load : UserProgressSyncService .ProgressLoad ): Streak ? = when (load) {
43+ is UserProgressSyncService .ProgressLoad .Loaded -> Streak (load.progress.streak, load.progress.bestStreak)
44+ UserProgressSyncService .ProgressLoad .Absent ->
45+ UserProgressSyncService .UserProgress ().let { Streak (it.streak, it.bestStreak) }
46+ is UserProgressSyncService .ProgressLoad .Failed -> null
47+ }
48+
49+ /* * Learning pace from the current streak. */
50+ internal fun paceFor (streak : Int ): String = when {
51+ streak >= 5 -> " fast"
52+ streak >= 3 -> " steady"
53+ else -> " slow"
54+ }
3455 }
3556
3657 private val geminiService = OllamaService .getInstance(context)
3758 private val firestoreManager = FirestoreManager .getInstance(context)
59+ private val progressSyncService = UserProgressSyncService (context)
3860 private val gson = Gson ()
3961
62+ /* * The streak figures analytics uses. */
63+ internal data class Streak (val current : Int , val best : Int )
64+
4065 /* *
4166 * Learning analytics summary
4267 */
@@ -83,11 +108,21 @@ class AnalyticsService(private val context: Context) {
83108 callback.onProgress(" Analyzing your progress..." )
84109
85110 // Collect data
86- val stages = firestoreManager.getStagesWithProgress().getOrNull() ? : emptyList()
111+ val stages = when (val read = firestoreManager.readPath()) {
112+ is PathRead .Path -> read.stages
113+ PathRead .NoPath -> {
114+ callback.onError(" No learning path found. Please complete the assessment first." )
115+ return @withContext
116+ }
117+ is PathRead .Failed -> {
118+ Log .w(TAG , " Could not read the learning path" , read.cause)
119+ callback.onError(FirestoreManager .PROGRESS_LOAD_FAILED_MESSAGE )
120+ return @withContext
121+ }
122+ }
87123 val progress = firestoreManager.getProgress().getOrNull()
88-
89- if (stages.isEmpty()) {
90- callback.onError(" No learning path found. Please complete the assessment first." )
124+ val streak = streakFrom(progressSyncService.loadProgressResult()) ? : run {
125+ callback.onError(FirestoreManager .PROGRESS_LOAD_FAILED_MESSAGE )
91126 return @withContext
92127 }
93128
@@ -108,19 +143,19 @@ class AnalyticsService(private val context: Context) {
108143 val (strongTopics, weakTopics) = categorizeTopics(stages)
109144
110145 // Estimate completion time
111- val estimatedDays = estimateCompletionDays(stages, completedStages, progress )
146+ val estimatedDays = estimateCompletionDays(stages, completedStages, streak.current )
112147
113148 // Determine learning pace
114- val learningPace = determinePace(progress )
149+ val learningPace = paceFor(streak.current )
115150
116151 val analytics = LearningAnalytics (
117152 totalXP = totalXP,
118153 totalStages = totalStages,
119154 completedStages = completedStages,
120155 averageStars = averageStars,
121156 totalTimeMinutes = 0 , // Would need to track this separately
122- currentStreak = progress?.currentStreak ? : 0 ,
123- longestStreak = progress?.longestStreak ? : 0 ,
157+ currentStreak = streak.current ,
158+ longestStreak = streak.best ,
124159 strongTopics = strongTopics,
125160 weakTopics = weakTopics,
126161 progressPercentage = progressPercentage,
@@ -196,31 +231,20 @@ class AnalyticsService(private val context: Context) {
196231 private fun estimateCompletionDays (
197232 stages : List <PersonalizedStage >,
198233 completedStages : Int ,
199- progress : UserProgress ?
234+ currentStreak : Int
200235 ): Int {
201236 val remainingStages = stages.size - completedStages
202237
203238 // Assume average of 1-2 stages per day based on pace
204239 val stagesPerDay = when {
205240 completedStages == 0 -> 1
206- progress?. currentStreak ? : 0 >= 7 -> 2
241+ currentStreak >= 7 -> 2
207242 else -> 1
208243 }
209244
210245 return (remainingStages / stagesPerDay.coerceAtLeast(1 )).coerceAtLeast(1 )
211246 }
212247
213- /* *
214- * Determine learning pace based on activity
215- */
216- private fun determinePace (progress : UserProgress ? ): String {
217- return when {
218- progress?.currentStreak ? : 0 >= 5 -> " fast"
219- progress?.currentStreak ? : 0 >= 3 -> " steady"
220- else -> " slow"
221- }
222- }
223-
224248 /* *
225249 * Generate AI-powered recommendations
226250 */
@@ -379,12 +403,18 @@ class AnalyticsService(private val context: Context) {
379403 }
380404
381405 /* *
382- * Get quick stats for dashboard display
406+ * Get quick stats for dashboard display. A failed read is a failure, not zeros.
383407 */
384- suspend fun getQuickStats (): LearningAnalytics = withContext(Dispatchers .IO ) {
408+ suspend fun getQuickStats (): Result < LearningAnalytics > = withContext(Dispatchers .IO ) {
385409 try {
386- val stages = firestoreManager.getStagesWithProgress().getOrNull() ? : emptyList()
410+ val stages = when (val read = firestoreManager.readPath()) {
411+ is PathRead .Path -> read.stages
412+ PathRead .NoPath -> emptyList()
413+ is PathRead .Failed -> return @withContext Result .failure(read.cause)
414+ }
387415 val progress = firestoreManager.getProgress().getOrNull()
416+ val streak = streakFrom(progressSyncService.loadProgressResult())
417+ ? : return @withContext Result .failure(IllegalStateException (FirestoreManager .PROGRESS_LOAD_FAILED_MESSAGE ))
388418
389419 val completedStages = stages.count { it.isCompleted }
390420 val totalStages = stages.size
@@ -393,18 +423,18 @@ class AnalyticsService(private val context: Context) {
393423 stages.sumOf { it.starsEarned }.toFloat() / completedStages
394424 } else 0f
395425
396- LearningAnalytics (
426+ Result .success( LearningAnalytics (
397427 totalXP = totalXP,
398428 totalStages = totalStages,
399429 completedStages = completedStages,
400430 averageStars = averageStars,
401- currentStreak = progress?.currentStreak ? : 0 ,
402- longestStreak = progress?.longestStreak ? : 0 ,
431+ currentStreak = streak.current ,
432+ longestStreak = streak.best ,
403433 progressPercentage = (completedStages * 100 ) / totalStages.coerceAtLeast(1 )
404- )
434+ ))
405435 } catch (e: Exception ) {
406436 Log .e(TAG , " Error getting quick stats" , e)
407- LearningAnalytics ( )
437+ Result .failure(e )
408438 }
409439 }
410440}
0 commit comments