feat: Implement Gemini AI service for AI coach functionality - #49
Conversation
- Added GeminiAiService to handle AI coach chat, program generation, and insights using Google Generative AI. - Created IAiService interface to define the contract for AI services. - Developed GeminiContextBuilder to construct context for AI interactions. - Introduced ConversationManager to manage AI conversations, including persistence and active conversation logic. - Implemented storage service methods for saving and retrieving AI conversations. - Built AiCoachViewModel to orchestrate AI interactions and manage conversation state. - Added unit tests for AiCoachViewModel, CoachToolService, and ConversationManager to ensure functionality and persistence. - Updated analytics and exercise progress views to use the new GeminiAiService.
WalkthroughThis PR introduces a comprehensive AI coach chat feature with persistent conversations and tool-calling support, alongside a refactored AI service layer. The feature enables conversational coaching with database-backed exercise/workout/recovery data queries, while updating existing screens to use the new ChangesAI Coach Chat with Tool Calling
Possibly related PRs
π₯ Pre-merge checks | β 5β Passed checks (5 passed)
βοΈ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
β Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (1)
workout-logger/lib/services/storage_service.dart (1)
345-376: π§Ή Nitpick | π΅ Trivial | ποΈ Heavy liftConsider including conversations in export/import for data portability.
The
exportAllDatamethod does not include AI conversations (or training programs and personal records). While this is consistent with existing exclusions, users may expect their conversation history to transfer when exporting and importing data across devices.π‘ Suggested enhancement
Add conversations to the export data map and corresponding import logic:
final data = { 'sessions': _sessionsBox.values .map(_normalizeExportValue) .toList(growable: false), 'routines': _routinesBoxInstance.values .map(_normalizeExportValue) .toList(growable: false), 'targets': _targetsBoxInstance.values .map(_normalizeExportValue) .toList(growable: false), 'muscleGroups': _muscleGroupsBoxInstance.values .map(_normalizeExportValue) .toList(growable: false), 'customExercises': _customExercisesBoxInstance.values .map(_normalizeExportValue) .toList(growable: false), + 'aiConversations': _aiConversationsBoxInstance.values + .map(_normalizeExportValue) + .toList(growable: false), 'settings': settingsMap, 'exportDate': DateTime.now().toIso8601String(), 'appVersion': _appVersion, };And add corresponding import logic after line 451 following the existing merge pattern.
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@workout-logger/lib/services/storage_service.dart` around lines 345 - 376, The exportAllData method currently omits AI conversations (and related user artifacts); update exportAllData to include conversations by adding a 'conversations' entry to the exported data map that mirrors the other collections (e.g., 'conversations': _conversationsBoxInstance.values.map(_normalizeExportValue).toList(growable: false)), and then implement corresponding import logic in the import path (the method that consumes this JSON, e.g., importAllData or similar) to merge/restore conversation entries using the same merge pattern used for sessions/routines (follow existing behavior for deduping/merging and use the same helper(s) that process normalized values).
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@workout-logger/lib/services/ai/coach_tool_service.dart`:
- Around line 375-398: The current _resolveExercise and _resolveRoutine
functions pick the first partial match which can return the wrong entity; change
them to collect all partial matches (search _wp.allExercises and _wp.routines
using contains on lowercased names), return the single match if exactly one,
return null if none, and when more than one partial match exists return an
ambiguity signal (e.g., throw an AmbiguousMatchException or return a result type
carrying an ambiguity with the list of candidate names/IDs) so callers can
surface available_examples-style choices to the user instead of confidently
using the wrong entity.
- Around line 263-285: The routine and exercise performance outputs are
truncating the oldest 40 entries instead of the most recent 40; in
_routinePerformance, replace the final volume_over_time generation that uses
sessions.take(40) with logic that selects the most recent 40 sessions (e.g.,
iterate over sessions.reversed.take(40).toList().reversed) so the presented list
remains chronological but contains the latest data, and in _exercisePerformance
(and places using _wp.getVolumeProgression) similarly select the last 40 items
from the progression (e.g., progression.reversed.take(40).toList().reversed)
before mapping to the output so the newest logs are included.
In `@workout-logger/lib/services/managers/conversation_manager.dart`:
- Around line 83-94: The current renameConversation uses
_conversations.where(...).toList() to find a match which is inefficient; replace
that with _conversations.firstWhere((c) => c.id == id, orElse: () => null) (or
an equivalent null-safe check) to find the conversation directly, return if
null, then create updated via copyWith, update _active if ids match, call
_upsert(updated), await _storage.saveConversation(updated), and
notifyListeners(); keep the same symbols (renameConversation, _conversations,
_active, _upsert, _storage.saveConversation) but use firstWhere+orElse for
cleaner code.
- Around line 46-51: The selectConversation method creates an intermediate list
via _conversations.where(...).toList(); replace it with
_conversations.firstWhere(...) to avoid allocation: call
_conversations.firstWhere((c) => c.id == id, orElse: () => null) (adjust the
orElse return type to your Conversation nullable type if needed), check for null
and return early, then assign to _active and call notifyListeners(); this
changes only the body of selectConversation and references the existing symbols
_conversations, selectConversation, _active, and notifyListeners.
In `@workout-logger/lib/viewmodels/ai_coach_view_model.dart`:
- Around line 71-114: The sendMessage method can permanently leave _loading true
if any persistence (_conversations.appendMessage) or streaming code throws; wrap
the entire send flow in a try/finally so _loading and _streamingText are always
reset (set _streamingText = '' and _loading = false in finally) and keep
notifyListeners calls as needed; also fix the interpolation in the catch by
using $e instead of ${e} to satisfy flutter_lints. Use the existing sendMessage
function, the calls to _conversations.appendMessage, the buffer/catch block and
ensure the finally block clears state even on errors.
- Around line 28-37: Change the five-argument positional AiCoachViewModel
constructor to use named, required parameters to avoid ordering mistakes: update
the constructor signature to AiCoachViewModel({required GeminiAiService ai,
required CoachToolService coachTools, required ConversationManager
conversations, required WorkoutProvider wp, required SettingsProvider settings})
and assign these to the existing private fields (e.g. this._ai = ai;
this._coachTools = coachTools; etc.) while preserving the constructor body that
adds the listener (_conversations.addListener(notifyListeners)). Then update all
call sites (notably in ai_coach_screen.dart and the VM test) to call
AiCoachViewModel with named arguments (ai:, coachTools:, conversations:, wp:,
settings:) and keep the chained ..loadConversations() where used.
- Around line 56-67: newConversation and selectConversation can change the
active conversation while a sendMessage stream is in progress (when _loading is
true), causing ConversationManager.appendMessage to attach the model reply to
the wrong _active; update AiCoachViewModel.newConversation and
AiCoachViewModel.selectConversation to check the _loading flag and no-op (or
return a failed Future) when a send is active, and ensure any UI triggers (e.g.,
header β+β / conversations sheet) are disabled when vm._loading or
vm.isConfigured && vm._loading is true so users cannot navigate mid-stream; this
prevents sendMessage from persisting the final model reply to the wrong
conversation by keeping _active stable during streaming.
---
Outside diff comments:
In `@workout-logger/lib/services/storage_service.dart`:
- Around line 345-376: The exportAllData method currently omits AI conversations
(and related user artifacts); update exportAllData to include conversations by
adding a 'conversations' entry to the exported data map that mirrors the other
collections (e.g., 'conversations':
_conversationsBoxInstance.values.map(_normalizeExportValue).toList(growable:
false)), and then implement corresponding import logic in the import path (the
method that consumes this JSON, e.g., importAllData or similar) to merge/restore
conversation entries using the same merge pattern used for sessions/routines
(follow existing behavior for deduping/merging and use the same helper(s) that
process normalized values).
πͺ Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
βΉοΈ Review info
βοΈ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3dc0216f-b48c-472e-b156-7ec9aa3bf212
π Files selected for processing (23)
workout-logger/lib/main.dartworkout-logger/lib/models/models.dartworkout-logger/lib/screens/ai_coach_screen.dartworkout-logger/lib/screens/ai_program_generator_screen.dartworkout-logger/lib/screens/home_screen.dartworkout-logger/lib/screens/widgets/exercise_progress_view.dartworkout-logger/lib/screens/widgets/muscle_detail_sheet.dartworkout-logger/lib/screens/widgets/profile_sections.dartworkout-logger/lib/screens/widgets/targets_tab.dartworkout-logger/lib/services/ai/coach_tool_service.dartworkout-logger/lib/services/ai/gemini_ai_service.dartworkout-logger/lib/services/gemini_context_builder.dartworkout-logger/lib/services/interfaces/ai_service_interface.dartworkout-logger/lib/services/interfaces/storage_service_interface.dartworkout-logger/lib/services/managers/conversation_manager.dartworkout-logger/lib/services/storage_service.dartworkout-logger/lib/viewmodels/ai_coach_view_model.dartworkout-logger/test/ai_coach_view_model_test.dartworkout-logger/test/analytics_screen_test.dartworkout-logger/test/coach_tool_service_test.dartworkout-logger/test/conversation_manager_test.dartworkout-logger/test/exercise_progress_view_test.dartworkout-logger/test/test_utils/mock_storage_service.dart
β¦tes for AI coach
β¦dability and error handling
|
@coderabbitai review |
β Actions performedReview triggered.
|
|
@coderabbitai review |
β Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
β»οΈ Duplicate comments (1)
workout-logger/lib/viewmodels/ai_coach_view_model.dart (1)
113-113:β οΈ Potential issue | π‘ Minor | β‘ Quick winUse
$einstead of${e}to satisfy flutter_lints.The braces are unnecessary in
'${e}'and trigger theunnecessary_brace_in_string_interpslint rule.β»οΈ Simplify string interpolation
- buffer.write('\n\n_Error: ${e}_'); + buffer.write('\n\n_Error: $e_');As per coding guidelines: Follow flutter_lints rules as enforced by analysis_options.yaml.
π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@workout-logger/lib/viewmodels/ai_coach_view_model.dart` at line 113, Replace the unnecessary braced interpolation in the error write call: locate the buffer.write call that currently uses '\n\n_Error: ${e}_' (in ai_coach_view_model.dart, inside the AI coach view model error handling) and change it to use '\n\n_Error: $e_' so the string interpolation uses $e without braces to satisfy the unnecessary_brace_in_string_interps flutter_lints rule.
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@workout-logger/lib/main.dart`:
- Line 164: Wrap the call to gemini.loadUsage() in its own try-catch so failures
don't abort app startup: replace the direct await gemini.loadUsage() with a try
{ await gemini.loadUsage(); } catch (e, st) { /* log error via your logger
(e.g., logger.error or print) with e and st */ } so token-usage load errors are
logged and swallowed and initialization continues; reference gemini.loadUsage()
and the surrounding init/main function where it is called.
In `@workout-logger/test/gemini_ai_service_usage_test.dart`:
- Around line 34-37: The test is flaky because it relies on event-loop timing
after calling service.recordUsage; update the implementation and/or test so
persistence can be awaited deterministically: modify recordUsage in
GeminiAiService (or the class under test) to return a Future that completes when
persistence finishes OR add a new method like flush() or
waitForPendingPersistence() on GeminiAiService that completes when all pending
writes finish, and update the test to await that instead of awaiting
Future.delayed(Duration.zero); alternatively, allow injecting a synchronous/mock
storage into the service for tests and use that in this test to make persistence
immediate.
- Around line 48-50: The test assumes persistence from asynchronous operations
in service.recordUsage and service.resetUsage completes immediately; instead,
change the test to await the async persistence step β either by awaiting the
Futures returned by recordUsage/resetUsage (ensure recordUsage and resetUsage
return Futures if they don't) or by awaiting a provided flush/persist method on
the service or using an event-pump helper before asserting; update calls to
service.recordUsage(...) and await service.resetUsage() (or await
service.persist/flush) so the test waits for async persistence to finish.
---
Duplicate comments:
In `@workout-logger/lib/viewmodels/ai_coach_view_model.dart`:
- Line 113: Replace the unnecessary braced interpolation in the error write
call: locate the buffer.write call that currently uses '\n\n_Error: ${e}_' (in
ai_coach_view_model.dart, inside the AI coach view model error handling) and
change it to use '\n\n_Error: $e_' so the string interpolation uses $e without
braces to satisfy the unnecessary_brace_in_string_interps flutter_lints rule.
πͺ Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
βΉοΈ Review info
βοΈ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 99809e62-4e0a-4b16-a2d2-9208ce2ff608
π Files selected for processing (12)
workout-logger/lib/main.dartworkout-logger/lib/screens/ai_coach_screen.dartworkout-logger/lib/screens/widgets/profile_sections.dartworkout-logger/lib/services/ai/coach_tool_service.dartworkout-logger/lib/services/ai/gemini_ai_service.dartworkout-logger/lib/services/gemini_context_builder.dartworkout-logger/lib/services/managers/conversation_manager.dartworkout-logger/lib/services/storage_service.dartworkout-logger/lib/viewmodels/ai_coach_view_model.dartworkout-logger/pubspec.yamlworkout-logger/test/ai_coach_view_model_test.dartworkout-logger/test/gemini_ai_service_usage_test.dart
|
@coderabbitai review |
β Actions performedReview triggered.
|
There was a problem hiding this comment.
Caution
Some comments are outside the diff and canβt be posted inline due to platform limitations.
β οΈ Outside diff range comments (1)
workout-logger/lib/services/ai/gemini_ai_service.dart (1)
101-118:β οΈ Potential issue | π Major | β‘ Quick winAwait
recordUsage()from_recordUsage()to keep persistence/exception handling consistent
recordUsage()isasyncand awaits_persistUsage()beforenotifyListeners(), but_recordUsage()is stillvoidand calls it withoutawait. This makes persistence fire-and-forget, so errors fromsaveSetting()wonβt be caught by the surroundingtry/catchinstreamCoachReply()/generate*().Proposed fix
- void _recordUsage(UsageMetadata? m) { + Future<void> _recordUsage(UsageMetadata? m) async { if (m == null) return; final p = m.promptTokenCount ?? 0; final r = m.candidatesTokenCount ?? 0; - recordUsage(prompt: p, response: r, total: m.totalTokenCount ?? (p + r)); + await recordUsage( + prompt: p, + response: r, + total: m.totalTokenCount ?? (p + r), + ); }- _recordUsage(roundUsage); + await _recordUsage(roundUsage);- _recordUsage(response.usageMetadata); + await _recordUsage(response.usageMetadata);π€ Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@workout-logger/lib/services/ai/gemini_ai_service.dart` around lines 101 - 118, The helper _recordUsage currently returns void and calls the async recordUsage without awaiting, causing persistence errors to escape the callers' try/catch; change _recordUsage to async Future<void> _recordUsage(UsageMetadata? m) async { ... await recordUsage(...); } and update all call sites (e.g., where streamCoachReply()/generate* invoke _recordUsage) to await the returned Future so exceptions from _persistUsage()/saveSetting propagate and are caught by the existing try/catch.
π€ Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@workout-logger/lib/services/ai/gemini_ai_service.dart`:
- Around line 101-118: The helper _recordUsage currently returns void and calls
the async recordUsage without awaiting, causing persistence errors to escape the
callers' try/catch; change _recordUsage to async Future<void>
_recordUsage(UsageMetadata? m) async { ... await recordUsage(...); } and update
all call sites (e.g., where streamCoachReply()/generate* invoke _recordUsage) to
await the returned Future so exceptions from _persistUsage()/saveSetting
propagate and are caught by the existing try/catch.
βΉοΈ Review info
βοΈ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: bc13c13f-f04d-47b1-bf7f-3ab534f21ef9
π Files selected for processing (3)
workout-logger/lib/main.dartworkout-logger/lib/services/ai/gemini_ai_service.dartworkout-logger/test/gemini_ai_service_usage_test.dart
* feat: add workout summary screen and enhance app theme colors
- Implemented WorkoutSummaryScreen to display post-workout details including duration, volume, sets, and exercises.
- Added visual elements such as trophy header and muscle groups trained section.
- Refactored AppTheme to centralize color management through AppColors, improving maintainability and consistency across the app.
- Updated theme properties for better visual coherence and modern styling.
* feat: add predictive back page transitions for Android in AppTheme
* Refactor Programs Screen and add Week Structure Editor
- Updated ProgramsScreen to improve UI elements and replace AppTheme with AppColors.
- Enhanced FloatingActionButton styles and added new RFWidgets for better consistency.
- Implemented a new ProgramWeekEditorStep widget for editing week structures in training programs.
- Introduced ProgramWeekTile for displaying collapsible week cards in ProgramDetailScreen.
- Added functionality for managing deload weeks and intensity factors within the week editor.
- Improved overall code organization and readability across the modified files.
* feat: Enhance UI with new glassmorphic components and charts
- Introduced `GlassCard` with gradient background and accent border option.
- Added `AmbientGlow` for decorative ambient effects.
- Implemented `RFNavBar` for a custom bottom navigation bar with glassmorphism.
- Created `Sparkline` and `VolumeChart` widgets for data visualization.
- Developed `WheelPickerField` for weight and reps input with haptic feedback.
- Updated `WorkoutHeader` with gradient background and improved text styles.
- Refined `AppTheme` with new color definitions and text styles using Google Fonts.
- Added Google Fonts dependency for enhanced typography.
* feat: add personal records feature
- Introduced PersonalRecord model to track best weight, reps, and volume for exercises.
- Implemented PRManager to manage personal records, including checking and updating records after workouts.
- Added methods in storage service for saving and retrieving personal records.
- Updated UI to display personal records in the Analytics screen and Workout Summary screen.
- Enhanced onboarding process to prompt for user name and handle version updates.
- Refactored various screens to improve layout and user experience.
* feat: update exercise library screen tests with new icon and text changes
* feat: update .gitignore and CLAUDE.md for new graphify output and project guidelines; remove MainActivity.kt
* analytics_screen.dart
_FrequencyGrid: added w >= 0 && guard so future-dated sessions can't insert negative map keys
_MuscleVolumeChart: early-return isEmpty when maxVol == 0 (prevents NaN); converted volume display via settings.toDisplay + settings.unitLabel
dashboard_widgets.dart
startOfWeek now truncated to midnight (DateTime(y,m,d)) before subtracting weekdays β fixes the bug where sessions earlier in the day than "now" were excluded
Volume StatGridCard now uses settings.toDisplay(weeklyVolume) and 'Volume (${settings.unitLabel})'
edit_workout_session_screen.dart
drops: s.drops?.toList() β defensive copy prevents shared-mutation with the original WorkoutSession
exercise_details_sheet.dart
Added else branch on delete failure β shows error SnackBar instead of silently doing nothing
Set chips now use settings.toDisplay(s.weight) + settings.unitLabel
Growth trend label uses settings.toDisplay(growthModel.slope) + settings.unitLabel
exercise_input_section.dart
Icon(Icons.auto_awesome_rounded, β¦) β const Icon(β¦)
exercise_progress_view.dart
DropdownButton.value guarded with ids.contains(selected) ? selected : null β prevents assert/crash when the selected exercise id is no longer in the performed set
profile_sections.dart
const _SectionDivider() and const _ComingSoonBadge() constructors + all call sites updated
rf_inputs.dart
Added onLongPressEnd to _StepButton (wired to GestureDetector.onLongPressEnd)
_NumberPickerSheetState now holds Timer? _holdTimer; onLongPress assigns _holdTimer = Timer.periodic(β¦), onLongPressEnd cancels it, dispose() also cancels it β no more leaked timers
session_details_sheet.dart
Volume banner: settings.toDisplay(session.totalVolume) + 'Volume ${settings.unitLabel}'
Per-exercise total: settings.toDisplay(log.totalVolume) + settings.unitLabel
Per-set row: settings.toDisplay(set.weight) formatted + settings.unitLabel
targets_tab.dart
Added _isSubmitting bool; _submit guards against re-entry and wraps provider call in try/finally; GlowButton.onPressed is null while submitting
workout_flow_screen.dart
_toggleDropset: weight controller text now uses settings.toDisplay(_currentWeight) with proper decimal formatting (matching _loadLastSessionData)
_addDrop: new drop controller text also uses settings.toDisplay(newWeight)
WorkoutHeader now receives restSeconds: _restSeconds
workout_header.dart
Added optional restSeconds field (defaults to 90 for backwards compatibility); _OptionsMenu receives widget.restSeconds instead of the hardcoded literal
workout_summary_screen.dart
volStr built from settings.toDisplay(session.totalVolume); label updated to 'Volume (${settings.unitLabel})'
* feat: enhance UI components and improve data handling across screens
* feat: update ExerciseLibraryScreen tests to include SettingsProvider in widget setup
* feat: enhance workout session data collection by including weight in HealthConnectService
* feat: update Android build configuration, enhance analytics screen, and improve UI components
* feat: add muscle recovery and growth tracking features in WorkoutProvider and MLService
* feat: refactor NumberInputCard to StatefulWidget and enhance RFNavBar design
* feat: enhance ProfileScreen and ProfileSections with Google Fonts for improved typography
* feat: update Create button in ProgramsScreen to allow for non-full width display
* feat: Integrate Gemini AI features for personalized coaching and insights
- Added GeminiService for AI integration, including chat and program generation capabilities.
- Implemented GeminiContextBuilder to create context strings for AI prompts.
- Introduced _WeeklyInsightsCard to display weekly insights based on user workouts.
- Added AiSettingsSection in profile settings for managing Gemini API key and model selection.
- Updated home screen to include weekly insights and AI coach button.
- Enhanced ProgramsScreen with AI program generator button.
- Updated SettingsProvider to manage Gemini API key and insights storage.
- Added necessary dependencies for Google Generative AI.
* feat: enhance various screens and models with improved error handling, UI adjustments, and new features
* feat: improve error handling in program saving and adjust opacity calculation in body heatmap
* Add comprehensive tests for MLService, model serialization, PRManager, and WorkoutProvider
- Implement tests for MLService covering growth model training, set recommendations, default recommendations, target completion prediction, and muscle recovery score computation.
- Create model serialization tests for WorkoutSet, ExerciseLog, WorkoutSession, Exercise, MuscleGroup, Target, GrowthModel, Routine, PersonalRecord, TrainingProgram, and ProgramExerciseSlot to ensure data integrity during JSON serialization/deserialization.
- Introduce tests for PRManager to validate loading records, backfilling from sessions, checking and updating personal records, and retrieving records.
- Enhance WorkoutProvider tests to verify initialization, session and routine loading, active workout management, and exercise name retrieval.
* fix: ensure opacity calculation in heatmap drawing is explicitly a double
* chore: update version to 2.0.0+21 in pubspec.yaml
* feat: enhance UI responsiveness with layout adjustments and breakpoints across multiple screens
* feat: enhance UI layout and responsiveness with padding adjustments for floating action buttons and improved data representation
* feat: add advanced metrics toggle and display estimated 1RM badge in workout input section
* Add widget tests for AnalyticsScreen and ExerciseProgressView
- Implement comprehensive widget tests for the AnalyticsScreen, covering the Overview, Targets, and Records tabs.
- Validate functionality such as tab switching, data display, and interaction with UI elements.
- Add widget tests for ExerciseProgressView, including exercise picker, chart mode toggle, and set progression chart.
- Ensure tests cover empty states, search functionality, and interaction with various UI components.
* feat: update app label handling and improve navigation in home screen
* feat: Implement Gemini AI service for AI coach functionality (#49)
* feat: Implement Gemini AI service for AI coach functionality
- Added GeminiAiService to handle AI coach chat, program generation, and insights using Google Generative AI.
- Created IAiService interface to define the contract for AI services.
- Developed GeminiContextBuilder to construct context for AI interactions.
- Introduced ConversationManager to manage AI conversations, including persistence and active conversation logic.
- Implemented storage service methods for saving and retrieving AI conversations.
- Built AiCoachViewModel to orchestrate AI interactions and manage conversation state.
- Added unit tests for AiCoachViewModel, CoachToolService, and ConversationManager to ensure functionality and persistence.
- Updated analytics and exercise progress views to use the new GeminiAiService.
* feat: Enhance Gemini AI service with token usage tracking and UI updates for AI coach
* feat: Refactor AiCoachViewModel and related services for improved readability and error handling
* feat: Improve error handling and async behavior in Gemini AI service usage tracking
---------
Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
* feat: Add muscle volume trend chart and recent sessions section in MuscleDetailSheet
* docs: add conversational routine optimizer design spec
Replaces the one-shot optimizer sheet with a dedicated conversational
optimizer screen that reuses the coach streaming tool-loop, adds an
interactive ask_user_questions tool, gates on insufficient data, and
persists sessions to a separate optimizer inbox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* refactor: remove one-shot routine optimizer (replaced by conversational flow)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add Conversation.kind and AI question models (QuestionSpec, AnswerSpec, PendingQuestions)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: scope ConversationManager by kind ('coach' | 'optimizer')
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add ask_user_questions tool declaration and optimizer system prompt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add RFQuestionCard reusable widget (option chips + custom input)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: rewrite RoutineOptimizerViewModel as conversational streaming VM with ask_user_questions pausing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: handle aborted completer and await appendMessage in submitAnswers
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add RoutineOptimizerScreen (conversational UI with question card + history inbox)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style: fix magic padding values and header button consistency in RoutineOptimizerScreen
* feat: add data gate (<3 sessions) and navigate to RoutineOptimizerScreen from routine card
* feat: add testBody method for RoutineOptimizerScreen to facilitate widget testing
* feat: add workout summary screen and enhance app theme colors (#50)
feat: add workout summary screen, health analytics, and production APK signing
- Workout summary screen with session details
- Sleep HR chart and debug log buffer
- HealthHistoryManager for sleep/heart rate data
- Workout heart rate analysis and recovery metrics
- EC P-256 production keystore signing via GitHub Secrets
- Split-per-ABI APK builds for arm64, armeabi-v7a, x86_64
- FUTURE_IMPROVEMENTS.md documenting Options B & C (F-Droid, fastlane)
* test: update lastNightSleep test to sum all periods in the night window
* feat: bundle Geist fonts locally and add F-Droid metadata (Option B) (#51)
- Remove google_fonts dependency; replace with bundled variable font files
(Geist-Variable.ttf + GeistMono-Variable.ttf from Vercel v1.7.2, MIT licensed)
- Replace all 377 GoogleFonts.geist/geistMono() calls with TextStyle(fontFamily:)
across 31 dart files β no runtime Google CDN fetch, F-Droid build-compatible
- Declare fonts in pubspec.yaml flutter.fonts section
- Add fdroid/metadata/com.devasy.repforge.yml with anti-features (NonFreeNet)
and auto-update config for F-Droid submission
Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Devasy Patel <110348311+Devasy23@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Summary by CodeRabbit
New Features
Improvements
Tests