recommendedNextStep function
Implementation
@riverpod
Future<RecommendedStep?> recommendedNextStep(Ref ref) async {
// Check due review cards first.
final dueCards = await ref.watch(dueCardsProvider.future);
if (dueCards.isNotEmpty) {
return RecommendedStep(
title: 'Review ${dueCards.length} card${dueCards.length == 1 ? '' : 's'}',
subtitle: 'Spaced repetition keeps knowledge fresh',
route: '/review',
icon: Icons.refresh_rounded,
color: const Color(0xFFFFB74D), // DuTaToTheme.warning
);
}
// Check progress across all domains.
final allProgress = await ref.watch(allProgressProvider.future);
final domains = await ref.watch(domainsProvider.future);
// Build mastery map: topicId -> mastery.
final masteryMap = <String, String>{};
for (final p in allProgress) {
masteryMap[p.topicId] = p.mastery;
}
// Fetch active goals to prioritize goal-linked domains.
final goals = await ref.watch(goalsProvider.future);
final goalDomainIds = goals
.where((g) => g.status == 'active' && g.domainId != null)
.map((g) => g.domainId!)
.toSet();
// Categorize content topics: goal domains first, then others.
final goalTopics = <String>[];
final otherTopics = <String>[];
for (final domain in domains) {
final ids = await ref.watch(topicIdsWithContentProvider(domain.id).future);
if (goalDomainIds.contains(domain.id)) {
goalTopics.addAll(ids);
} else {
otherTopics.addAll(ids);
}
}
// Find best topic: goal domains first, prefer advancing started topics.
final bestTopicId =
_findBestTopic(goalTopics, masteryMap) ??
_findBestTopic(otherTopics, masteryMap);
if (bestTopicId == null) {
return null; // All caught up.
}
final bestMastery = masteryMap[bestTopicId] ?? 'unstarted';
// Resolve topic name.
final ds = ref.watch(supabaseDatasourceProvider);
final topic = await ds.getTopicById(bestTopicId);
final topicTitle = topic?.title ?? 'a topic';
final encodedTitle = Uri.encodeComponent(topicTitle);
final domainId = topic?.domainId;
// Resolve domain slug for human-readable URLs.
String? domainSlug;
if (domainId != null) {
final domain = domains.where((d) => d.id == domainId).firstOrNull;
domainSlug = domain?.slug;
}
String lessonRoute(String slug) =>
'/domains/$slug/topics/$bestTopicId/lesson?title=$encodedTitle';
return switch (bestMastery) {
'unstarted' => RecommendedStep(
title: 'Read: $topicTitle',
subtitle: 'Start your journey with this topic',
route: domainSlug != null ? lessonRoute(domainSlug) : '/domains',
icon: Icons.menu_book_rounded,
color: const Color(0xFF42A5F5), // blue
),
'awareness' => RecommendedStep(
title: 'Socratic Dialogue: $topicTitle',
subtitle: 'Deepen understanding through guided questions',
route:
'/tutor/chat?mode=socratic&topicId=$bestTopicId&topicTitle=$encodedTitle'
'${domainSlug != null ? '&from=${Uri.encodeComponent(lessonRoute(domainSlug))}' : ''}',
icon: Icons.question_answer_rounded,
color: const Color(0xFF6C63FF), // purple
),
'understanding' => RecommendedStep(
title: 'Quiz: $topicTitle',
subtitle: 'Test your knowledge to advance to Application',
route:
'/tutor/chat?mode=quiz&topicId=$bestTopicId&topicTitle=$encodedTitle'
'${domainSlug != null ? '&from=${Uri.encodeComponent(lessonRoute(domainSlug))}' : ''}',
icon: Icons.quiz_rounded,
color: const Color(0xFFFFB74D), // amber
),
'application' => RecommendedStep(
title: 'Code Review: $topicTitle',
subtitle: 'Code Review or Confused Student to reach Evaluation',
route:
'/tutor/chat?mode=code_review&topicId=$bestTopicId&topicTitle=$encodedTitle'
'${domainSlug != null ? '&from=${Uri.encodeComponent(lessonRoute(domainSlug))}' : ''}',
icon: Icons.code_rounded,
color: const Color(0xFF66BB6A), // green
),
_ => null,
};
}