Skip to content

feat: add persistent transcription history with reusable insights viewer#34

Open
Shweta-281 wants to merge 17 commits intoAOSSIE-Org:mainfrom
Shweta-281:feat/transcription-history
Open

feat: add persistent transcription history with reusable insights viewer#34
Shweta-281 wants to merge 17 commits intoAOSSIE-Org:mainfrom
Shweta-281:feat/transcription-history

Conversation

@Shweta-281
Copy link
Copy Markdown

@Shweta-281 Shweta-281 commented Mar 29, 2026

Overview

This PR introduces persistent transcription history, enabling users to store and revisit past AI-generated medical insights.


Features

1. Persistent Storage

  • Saves transcript, summary, symptoms, medicines, timestamp
  • Uses SharedPreferences for local storage

2. History Screen

  • Displays all previous transcriptions
  • Allows navigation to detailed insights

3. Reusable Insights UI

  • Reuses MedicalInsightsScreen for consistency

Changes Made

  • Added TranscriptionHistoryModel
  • Added LocalStorageService
  • Updated TranscriptionController to store history
  • Created HistoryScreen
  • Integrated navigation from main screen

Impact

  • Improves UX significantly
  • Converts app into real usable product
  • Demonstrates scalable architecture

Related Issue

Closes #33

✅ Checklist

  • I have read the contributing guidelines.
  • I have added tests that prove my fix is effective or that my feature works.
  • I have added necessary documentation (if applicable).
  • Any dependent changes have been merged and published in downstream modules.
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there

⚠️ AI Notice - Important!

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.

Summary by CodeRabbit

  • New Features

    • Transcription History screen to view past records
    • Medical Insights screen showing extracted summary, symptoms, and medicines
    • Local secure, persisted history so records survive app restarts
  • Improvements

    • Single "insights" workflow replaces separate summary/prescription flow
    • More reliable transcriptions with automatic retries and improved punctuation/formatting
    • Clearer empty-state messaging, refreshed navigation and mic UI
    • App startup now tolerates environment/config load failures without crashing

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

Warning

Rate limit exceeded

@Shweta-281 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 7 minutes and 31 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 7 minutes and 31 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 975afcf8-7fca-4516-9bd8-02c3cb245ca4

📥 Commits

Reviewing files that changed from the base of the PR and between 12cce52 and f1ec913.

📒 Files selected for processing (1)
  • lib/features/transcription/presentation/transcription_controller.dart
📝 Walkthrough

Walkthrough

Adds persistent transcription history and a Medical Insights viewer; refactors Deepgram (retry+timeout, parsing) and Gemini (single insights JSON) services; introduces domain models, secure local storage, controller and UI updates to generate/save insights, and removes example API keys from .env.example.

Changes

Cohort / File(s) Summary
Config
\.env.example, pubspec.yaml
Removed example API keys from .env.example (file content removed); added flutter_secure_storage dependency and removed one commented asset line.
Deepgram service
lib/features/transcription/data/deepgram_service.dart
Added _retryPost (30s timeout + retries on 5xx/timeout), moved response parsing to _parseTranscript, changed request params (punctuate=true&smart_format=true) and Content-Type to application/octet-stream, simplified API key resolution and error paths.
Gemini service
lib/features/transcription/data/gemini_service.dart
Replaced generateSummary/generatePrescription with generateInsights(String) returning MedicalInsights; added _extractJson to sanitize LLM output before json.decode.
Local storage
lib/features/transcription/data/local_storage_service.dart
New LocalStorageService using flutter_secure_storage to persist chronological transcription history JSON under a fixed secure key; exposes save and getAll.
Domain models
lib/features/transcription/domain/medical_insights.dart, lib/features/transcription/domain/transcription_history_model.dart, lib/features/transcription/domain/transcription_model.dart
Added MedicalInsights; introduced TranscriptionHistoryModel with transcript, summary, symptoms, medicines, createdAt and JSON (de)serialization; added a separate TranscriptionModel/copyWith variant in another file.
Controller
lib/features/transcription/presentation/transcription_controller.dart
Controller now exposes getters from data.insights (summary, symptoms, medicines), adds checkConfigStatus, removes waveform/timer and explicit permission flow, integrates LocalStorageService to persist TranscriptionHistoryModel after Gemini insights.
UI: screens
lib/features/transcription/presentation/transcription_screen.dart, lib/features/transcription/presentation/history_screen.dart, lib/screens/medical_insights_screen.dart
Added HistoryScreen and MedicalInsightsScreen; updated TranscriptionScreen navigation/actions (replaced Prescription with Medical Insights), added empty-state UI, adjusted recording button animation and navigation tiles.
App init
lib/main.dart
Load .env with 2s timeout, propagate isConfigLoaded into MyApp, and call controller checkConfigStatus(isConfigLoaded) when creating provider.
Platform plugin registration
linux/flutter/generated_plugin_registrant.cc, linux/flutter/generated_plugins.cmake, macos/Flutter/GeneratedPluginRegistrant.swift
Registered flutter_secure_storage plugin on Linux and macOS (added includes/registration and plugin list entry).
Build tooling
android/gradle/wrapper/gradle-wrapper.properties, android/settings.gradle
Bumped Gradle wrapper to 8.7-all.zip and Android Gradle plugin declaration to 8.6.0.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as TranscriptionScreen
    participant Controller as TranscriptionController
    participant Deepgram as DeepgramService
    participant Gemini as GeminiService
    participant Storage as LocalStorageService

    User->>UI: start recording / stop recording
    UI->>Controller: provide audio file path
    Controller->>Deepgram: transcribe(path)
    Deepgram->>Deepgram: _retryPost (30s timeout, retries on 5xx/timeout)
    Deepgram-->>Controller: raw transcript
    Controller->>Gemini: generateInsights(transcript)
    Gemini->>Gemini: getGeminiResponse + _extractJson + json.decode
    Gemini-->>Controller: MedicalInsights
    Controller->>Storage: save(TranscriptionHistoryModel)
    Storage-->>Controller: persisted confirmation
    Controller-->>UI: update state with insights
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

frontend

Poem

🐇 I hopped through lines of code at night,

Stashed small whispers safe from light.
Insights folded in a cozy nest,
Saved and sorted for a healing quest. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Several changes extend beyond the linked issue scope: updated GeminiService API (generateInsights replacing generateSummary/generatePrescription), refactored TranscriptionController removing microphone permissions and waveform animation, added MedicalInsights domain entity, updated Android/Gradle versions, and modified transcription_screen.dart UI significantly. Clarify whether the Gemini refactoring, controller restructuring, and UI updates were intended scope changes or separate features that should be in different PRs. Consider isolating the core persistent history feature from architectural refactoring.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add persistent transcription history with reusable insights viewer' accurately describes the main changes: adding persistent storage for transcription history and reusing the insights viewer component.
Linked Issues check ✅ Passed The PR successfully implements all coding requirements from issue #33: TranscriptionHistoryModel with required fields, LocalStorageService for persistence, HistoryScreen for display, and TranscriptionController integration with history saving and MedicalInsightsScreen reuse.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added size/XL and removed size/XL labels Mar 29, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (1)
lib/features/transcription/domain/transcription_model.dart (1)

12-18: Make copyWith able to clear insights.

insights: insights ?? this.insights means copyWith(insights: null) can never remove an existing value. lib/features/transcription/presentation/transcription_controller.dart:82 currently works around this by allocating a fresh TranscriptionModel, but the model API still can't express “clear insights”.

Possible fix
 class TranscriptionModel {
+  static const _unset = Object();
+
   final String rawTranscript;
   final MedicalInsights? insights;
 
   const TranscriptionModel({
     this.rawTranscript = '',
     this.insights,
   });
 
   TranscriptionModel copyWith({
     String? rawTranscript,
-    MedicalInsights? insights,
+    Object? insights = _unset,
   }) {
     return TranscriptionModel(
       rawTranscript: rawTranscript ?? this.rawTranscript,
-      insights: insights ?? this.insights,
+      insights: identical(insights, _unset)
+          ? this.insights
+          : insights as MedicalInsights?,
     );
   }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/transcription/domain/transcription_model.dart` around lines 12 -
18, The copyWith cannot clear insights because `insights: insights ??
this.insights` treats a passed null as "no change"; update
TranscriptionModel.copyWith to differentiate "parameter omitted" vs "explicit
null" by using a sentinel default (e.g., change the signature to accept `Object?
insights = _sentinel` and add a private sentinel like `static const _sentinel =
Object()`), then set the returned model's insights to `this.insights` when
`insights == _sentinel` otherwise to `insights as MedicalInsights?`, so callers
can pass null to clear insights while preserving the same behavior when the
parameter is omitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/transcription/data/deepgram_service.dart`:
- Around line 38-51: The code currently throws Exception('Deepgram failed:
${response.statusCode} - ${response.body}') but that exception is caught by the
generic catch (e) and retried; change the logic so non-retryable HTTP errors
(4xx, auth/validation) do not get swallowed and retried: after receiving
response, if statusCode >= 500 keep the existing exponential backoff and
continue, but for any non-5xx (statusCode < 500) immediately rethrow or return
the specific failure so it bypasses the retry loop; update the catch (e) block
to only handle transient errors (or rethrow for other Exceptions), and preserve
the existing TimeoutException handling that only retries up to retries using
attempt and retries variables.

In `@lib/features/transcription/data/local_storage_service.dart`:
- Around line 5-25: LocalStorageService currently stores sensitive medical data
in plaintext SharedPreferences under key "transcription_history" and rewrites
the entire collection in save/getAll; replace this with an encrypted persistent
store (e.g., Hive with encryptionCipher, Drift with SQLCipher, or
platform-backed secure storage) and stop using SharedPreferences for
TranscriptionHistoryModel; update the LocalStorageService implementation
(methods save and getAll and the static key) to use the chosen encrypted client,
derive/store the encryption key securely (e.g., from flutter_secure_storage or
platform keychain/keystore), ensure entries are encrypted at rest and migrated
from old plaintext safely (provide a one-time migration routine that reads
existing SharedPreferences, encrypts records into the new store, and then
securely deletes the plaintext), and avoid rewriting the whole dataset
unnecessarily by appending/updating single records via the encrypted DB APIs.

In `@lib/features/transcription/domain/transcription_history_model.dart`:
- Around line 24-31: TranscriptionHistoryModel.fromJson currently uses
List<String>.from(json['symptoms']) and List<String>.from(json['medicines'])
which will throw if those keys are missing or not lists; change the deserializer
in TranscriptionHistoryModel.fromJson to defensively read symptoms and medicines
(e.g., check for null and that the value is Iterable/List) and default to an
empty List<String> when missing or invalid so a single partial/old row cannot
break HistoryScreen.loadHistory; keep using DateTime.parse(json['createdAt'])
and the same field names (transcript, summary, symptoms, medicines) while
implementing this tolerant behavior.

In `@lib/features/transcription/presentation/history_screen.dart`:
- Around line 39-52: The history item tap currently only forwards symptoms and
medicines and the list title shows a truncated summary (item.summary); fix by
passing the full persisted data into the detail screen: include item.transcript
and the full item.summary (not just truncated display) in the Navigator.push
call to MedicalInsightsScreen inside the onTap handler, and update
MedicalInsightsScreen's constructor/signature to accept and use those new
parameters (e.g., transcript and fullSummary) so the detail view can show the
complete transcript and summary while keeping the list title truncated for
brevity.
- Around line 22-24: The async loadHistory method currently awaits
LocalStorageService().getAll() and calls setState() unguarded; change it to
store the result in a local variable, check if (!mounted) return; and only call
setState(() { history = localVar; }) so you never call setState after the widget
has been disposed (update the loadHistory function and references to history
accordingly).

In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 129-137: The TranscriptionHistoryModel instances (transcript,
insights.symptoms, insights.medicines) are being saved via
_localStorageService.save which uses SharedPreferences; move sensitive health
data to encrypted storage or require explicit user consent and deletion
controls: replace the call to _localStorageService.save(historyItem) with a
secure alternative (e.g., _secureStorageService.saveEncryptedHistory or a
repository that encrypts the model before persisting) and implement
corresponding methods to encrypt/decrypt TranscriptionHistoryModel, add an
explicit user consent check before saving (e.g., isHealthDataConsentGranted),
and expose clear/delete methods (e.g., deleteEncryptedHistory,
clearAllEncryptedHistory) so users can remove stored medical data. Ensure the
TranscriptionHistoryModel serialization supports encryption (to/from JSON) and
update any getters to read from the secure store instead of SharedPreferences.
- Around line 123-143: The current try/catch wraps both insight generation and
persistence so a save failure can overwrite a successful result; in
_processWithGemini move the state update (data = data.copyWith(...), state =
TranscriptionState.done, notifyListeners(), developer.log(...)) to occur
immediately after _geminiService.generateInsights completes, then perform await
_localStorageService.save(historyItem) inside a separate try/catch so
persistence errors are caught/logged or surfaced via a persistence-specific
handler without calling _setError which would flip the UI back to error; refer
to _processWithGemini, _geminiService.generateInsights, data.copyWith,
_localStorageService.save, TranscriptionState.done and _setError when making
this change.

In `@lib/features/transcription/presentation/transcription_screen.dart`:
- Around line 155-161: The status helper methods (_statusText and
_statusDetailText) currently ignore explicit error/permission messages and treat
the sentinel "No speech detected" as a normal success; update these helpers in
transcription_controller.dart to check for controller.state ==
TranscriptionState.error and return the controller.error or permission/config
messages directly, and also treat controller.transcription == "No speech
detected" as a distinct no-speech state (return a no-speech-specific
status/detail instead of the generic "done" messages) so the UI's empty-state
logic (which checks controller.state and controller.transcription) and the
error/permission flows are shown consistently.
- Around line 204-218: The navigation button for "Medical Insights" uses
controller.summary.isNotEmpty to set enabled but the screen actually consumes
controller.symptoms and controller.medicines; change the gating to a dedicated
check that reflects the actual data used (e.g. add/use controller.hasInsights or
compute (controller.symptoms.isNotEmpty || controller.medicines.isNotEmpty)) and
pass that to _buildNavigationButton instead of controller.summary.isNotEmpty so
MedicalInsightsScreen is only enabled when it has relevant data; update the
controller to expose hasInsights if needed and replace the enabled argument in
the call that creates MedicalInsightsScreen.

---

Nitpick comments:
In `@lib/features/transcription/domain/transcription_model.dart`:
- Around line 12-18: The copyWith cannot clear insights because `insights:
insights ?? this.insights` treats a passed null as "no change"; update
TranscriptionModel.copyWith to differentiate "parameter omitted" vs "explicit
null" by using a sentinel default (e.g., change the signature to accept `Object?
insights = _sentinel` and add a private sentinel like `static const _sentinel =
Object()`), then set the returned model's insights to `this.insights` when
`insights == _sentinel` otherwise to `insights as MedicalInsights?`, so callers
can pass null to clear insights while preserving the same behavior when the
parameter is omitted.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 68602584-03e9-4ff6-93f2-68d3306fd4ef

📥 Commits

Reviewing files that changed from the base of the PR and between d04e2f6 and 56050f6.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • .env.example
  • lib/features/transcription/data/deepgram_service.dart
  • lib/features/transcription/data/gemini_service.dart
  • lib/features/transcription/data/local_storage_service.dart
  • lib/features/transcription/domain/medical_insights.dart
  • lib/features/transcription/domain/transcription_history_model.dart
  • lib/features/transcription/domain/transcription_model.dart
  • lib/features/transcription/presentation/history_screen.dart
  • lib/features/transcription/presentation/transcription_controller.dart
  • lib/features/transcription/presentation/transcription_screen.dart
  • lib/main.dart
  • lib/screens/medical_insights_screen.dart
  • pubspec.yaml
💤 Files with no reviewable changes (2)
  • pubspec.yaml
  • .env.example

@github-actions github-actions bot added size/XL and removed size/XL labels Mar 29, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
lib/features/transcription/data/deepgram_service.dart (1)

18-57: Previous retry bug is fixed; minor optimization possible.

The restructured catch blocks correctly handle error cases:

  • on Exception { rethrow; } at lines 44-45 ensures non-5xx errors (thrown at line 40) exit immediately without retrying.
  • 5xx errors retry with backoff via continue at line 38.
  • TimeoutException correctly throws on the last attempt.

Minor optimization: On the last 5xx attempt, line 37 delays before the loop naturally exits and line 53 throws. This adds unnecessary latency.

♻️ Optional: Skip delay on final 5xx attempt
         if (response.statusCode >= 500) {
+          if (attempt == retries - 1) {
+            throw Exception(
+              'Deepgram failed: ${response.statusCode} - ${response.body}',
+            );
+          }
           await Future.delayed(Duration(seconds: 2 * (attempt + 1)));
           continue;
         } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/transcription/data/deepgram_service.dart` around lines 18 - 57,
In _retryPost, avoid the extra backoff delay after the final 5xx attempt: when
handling the 5xx branch inside the loop (the block that currently does await
Future.delayed(Duration(seconds: 2 * (attempt + 1))); and then continue), only
perform that await when attempt < retries - 1 so the last attempt does not sleep
before the method falls through to the final throw; keep the rest of the
retry/backoff logic (including the unconditional end-of-loop delay for non-5xx
paths) unchanged.
lib/features/transcription/presentation/transcription_controller.dart (1)

120-124: Minor: Inconsistent indentation in checkConfigStatus.

The method body uses 2-space indentation while the rest of the file uses standard Dart indentation.

🧹 Fix indentation
   void checkConfigStatus(bool isLoaded) {
-  if (!isLoaded) {
-    _setError('Configuration Error: API keys could not be loaded. Please check your .env file.');
-  }
+    if (!isLoaded) {
+      _setError('Configuration Error: API keys could not be loaded. Please check your .env file.');
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/transcription/presentation/transcription_controller.dart` around
lines 120 - 124, The method checkConfigStatus has inconsistent 2-space
indentation; reformat its body to use standard Dart indentation (typically 2
spaces per indent level? actually file uses different—match the rest of the
file) so it aligns with the surrounding code: update the indentation inside
checkConfigStatus and its if-block and the _setError call to match the file's
existing style, leaving the logic unchanged (function name checkConfigStatus and
call to _setError remain as-is).
lib/features/transcription/data/local_storage_service.dart (1)

46-48: Consider logging decryption/parsing failures for diagnostics.

Returning an empty list on error provides resilience, but silently swallowing exceptions makes debugging data corruption difficult. Adding a log statement would help diagnose issues without breaking the user experience.

🔧 Add error logging
     } catch (e) {
+      developer.log('Failed to load history: $e', name: 'LocalStorageService');
       return [];
     }

Add import at top:

import 'dart:developer' as developer;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/transcription/data/local_storage_service.dart` around lines 46 -
48, The catch block that currently swallows exceptions in
local_storage_service.dart should log decryption/parsing failures for
diagnostics: add the import "import 'dart:developer' as developer;", change the
naked catch to capture the stack (catch (e, st)) in the method that returns the
empty list, and call developer.log with a clear message (e.g., "Failed to
load/decrypt/parsing transcriptions"), passing error: e and stackTrace: st
before returning the empty list so failures are recorded without changing
behavior.
lib/features/transcription/domain/transcription_model.dart (1)

1-1: Unused import of medical_insights.dart.

The import is not referenced anywhere in this file. TranscriptionHistoryModel stores the flattened fields (summary, symptoms, medicines) rather than a MedicalInsights object.

🧹 Remove unused import
-import 'medical_insights.dart';
-
 class TranscriptionHistoryModel {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/transcription/domain/transcription_model.dart` at line 1, The
file contains an unused import "import 'medical_insights.dart';" which is not
referenced by TranscriptionHistoryModel (it stores flattened fields like
summary, symptoms, medicines); remove that import line to eliminate the unused
dependency and keep the file focused on TranscriptionHistoryModel.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/transcription/data/local_storage_service.dart`:
- Around line 15-28: The save() implementation currently calls getAll() (which
returns a reversed newest-first list) causing order corruption; change save() to
read the raw stored value from _storage.read(key: _key), jsonDecode it into a
List preserving the stored oldest→newest order (or use an existing raw-get
helper if available), append the new TranscriptionHistoryModel.toJson() to that
list (or start a new list if null/empty), jsonEncode the result and write it
back with _storage.write(key: _key, value: ...); keep getAll()'s reversed
display behavior untouched.

In `@lib/features/transcription/domain/transcription_model.dart`:
- Around line 25-27: The createdAt parsing in the Transcription model's fromJson
uses DateTime.parse directly and can throw FormatException on malformed input;
add a defensive static helper (e.g., static DateTime _parseDate(dynamic value))
in the Transcription model/class that attempts DateTime.parse inside a try-catch
and returns DateTime.now() on any error or when value is null, then replace the
inline DateTime.parse(json['createdAt']) usage with a call to
_parseDate(json['createdAt']) to ensure fromJson never throws on bad date
strings.

In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Line 11: The code imports a non-existent TranscriptionModel and then
instantiates and calls copyWith on it; either define TranscriptionModel or
switch to TranscriptionHistoryModel — to fix, add a TranscriptionModel class (or
adapt TranscriptionHistoryModel) that includes fields rawTranscript, insights
(MedicalInsights), prescription, a const default constructor, and a
copyWith({String? rawTranscript, MedicalInsights? insights, String?
prescription}) returning a new instance so lines using TranscriptionModel data =
const TranscriptionModel(); and data.copyWith(...) compile; ensure the class
name and copyWith signature match usages in transcription_controller.dart
(transcriptionModel/TranscriptionModel and copyWith).
- Around line 17-31: The UI calls controller.toggleRecording but that method and
its helpers were removed; restore toggleRecording plus implementations of
_startRecording and _stopRecording that use the existing _audioRecorder to
begin/stop capture, set/clear _recordingPath, update the isRecording
getter/state (TranscriptionState or a boolean), manage the _waveformTimer to
periodically update waveformValues and call notifyListeners(), persist recording
metadata via _localStorageService as needed, and handle errors by setting
errorMessage and notifying listeners so microphone taps no longer cause
NoSuchMethodError; locate these changes around the TranscriptionController class
members (_audioRecorder, _recordingPath, waveformValues, _waveformTimer,
isRecording) and wire them to the existing state transitions and
notifyListeners() calls.
- Around line 69-77: The controller currently calls non-existent methods and
uses a missing model/copyWith; fix by (1) adding a TranscriptionModel class (or
extend TranscriptionHistoryModel) with fields rawTranscript, insights
(MedicalInsights), prescription (String) and a copyWith({MedicalInsights?
insights, String? prescription, String? rawTranscript}) method so
data.copyWith(...) works; (2) change the controller code that calls
_geminiService.generateSummary/generatePrescription to call only
_geminiService.generateInsights(transcript) via Future or await, then extract
summary and prescription from the returned MedicalInsights (e.g.,
insights.summary and insights.medicines or insights.prescription field) and
assign them to the TranscriptionModel via copyWith; keep references to
_geminiService.generateInsights, TranscriptionModel, MedicalInsights, and
data.copyWith to locate the changes.

---

Nitpick comments:
In `@lib/features/transcription/data/deepgram_service.dart`:
- Around line 18-57: In _retryPost, avoid the extra backoff delay after the
final 5xx attempt: when handling the 5xx branch inside the loop (the block that
currently does await Future.delayed(Duration(seconds: 2 * (attempt + 1))); and
then continue), only perform that await when attempt < retries - 1 so the last
attempt does not sleep before the method falls through to the final throw; keep
the rest of the retry/backoff logic (including the unconditional end-of-loop
delay for non-5xx paths) unchanged.

In `@lib/features/transcription/data/local_storage_service.dart`:
- Around line 46-48: The catch block that currently swallows exceptions in
local_storage_service.dart should log decryption/parsing failures for
diagnostics: add the import "import 'dart:developer' as developer;", change the
naked catch to capture the stack (catch (e, st)) in the method that returns the
empty list, and call developer.log with a clear message (e.g., "Failed to
load/decrypt/parsing transcriptions"), passing error: e and stackTrace: st
before returning the empty list so failures are recorded without changing
behavior.

In `@lib/features/transcription/domain/transcription_model.dart`:
- Line 1: The file contains an unused import "import 'medical_insights.dart';"
which is not referenced by TranscriptionHistoryModel (it stores flattened fields
like summary, symptoms, medicines); remove that import line to eliminate the
unused dependency and keep the file focused on TranscriptionHistoryModel.

In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 120-124: The method checkConfigStatus has inconsistent 2-space
indentation; reformat its body to use standard Dart indentation (typically 2
spaces per indent level? actually file uses different—match the rest of the
file) so it aligns with the surrounding code: update the indentation inside
checkConfigStatus and its if-block and the _setError call to match the file's
existing style, leaving the logic unchanged (function name checkConfigStatus and
call to _setError remain as-is).
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: e69c8c56-08ed-4f90-9f50-492644bf0c6e

📥 Commits

Reviewing files that changed from the base of the PR and between 56050f6 and b5eab34.

📒 Files selected for processing (5)
  • lib/features/transcription/data/deepgram_service.dart
  • lib/features/transcription/data/local_storage_service.dart
  • lib/features/transcription/domain/transcription_model.dart
  • lib/features/transcription/presentation/history_screen.dart
  • lib/features/transcription/presentation/transcription_controller.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/features/transcription/presentation/history_screen.dart

@github-actions github-actions bot added size/XL and removed size/XL labels Mar 29, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
lib/features/transcription/presentation/transcription_controller.dart (2)

102-106: Minor: Inconsistent indentation in method body.

The body of checkConfigStatus uses 2-space indentation while the rest of the file uses 4-space indentation.

Suggested fix
  void checkConfigStatus(bool isLoaded) {
-  if (!isLoaded) {
-    _setError('Configuration Error: API keys could not be loaded. Please check your .env file.');
-  }
+    if (!isLoaded) {
+      _setError('Configuration Error: API keys could not be loaded. Please check your .env file.');
+    }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/transcription/presentation/transcription_controller.dart` around
lines 102 - 106, Fix the inconsistent 2-space indentation inside the
checkConfigStatus method by aligning its body to the file's 4-space indentation
style; update the lines within the checkConfigStatus function (the if block and
call to _setError) so they use 4 spaces and match surrounding methods'
formatting.

1-12: Remove unused import.

dart:developer is imported (line 2) but is not used anywhere in the file. Remove the import to clean up unused dependencies.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/transcription/presentation/transcription_controller.dart` around
lines 1 - 12, Remove the unused import of dart:developer from
transcription_controller.dart by deleting the line importing 'dart:developer'
(the unused developer alias), leaving only the necessary imports (e.g.,
dart:async, package:flutter/foundation.dart, package:record/record.dart, etc.);
then run the analyzer/formatter to ensure no remaining unused-import warnings
for the TranscriptionController file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 108-113: The dispose() method references an undeclared
_waveformTimer which causes a compile error; either declare a nullable Timer
field named _waveformTimer and add import 'dart:async' to the file (if waveform
animation/timer is required) or remove the _waveformTimer?.cancel() call from
dispose() (if waveform functionality was removed) — locate the dispose method in
the TranscriptionController class and update accordingly alongside
_audioRecorder.dispose().

---

Nitpick comments:
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 102-106: Fix the inconsistent 2-space indentation inside the
checkConfigStatus method by aligning its body to the file's 4-space indentation
style; update the lines within the checkConfigStatus function (the if block and
call to _setError) so they use 4 spaces and match surrounding methods'
formatting.
- Around line 1-12: Remove the unused import of dart:developer from
transcription_controller.dart by deleting the line importing 'dart:developer'
(the unused developer alias), leaving only the necessary imports (e.g.,
dart:async, package:flutter/foundation.dart, package:record/record.dart, etc.);
then run the analyzer/formatter to ensure no remaining unused-import warnings
for the TranscriptionController file.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 31a9bd48-0215-4201-a5e0-12a1bf01addb

📥 Commits

Reviewing files that changed from the base of the PR and between b5eab34 and 916c566.

⛔ Files ignored due to path filters (1)
  • pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (9)
  • android/gradle/wrapper/gradle-wrapper.properties
  • android/settings.gradle
  • lib/features/transcription/data/local_storage_service.dart
  • lib/features/transcription/domain/transcription_history_model.dart
  • lib/features/transcription/presentation/transcription_controller.dart
  • linux/flutter/generated_plugin_registrant.cc
  • linux/flutter/generated_plugins.cmake
  • macos/Flutter/GeneratedPluginRegistrant.swift
  • pubspec.yaml
✅ Files skipped from review due to trivial changes (3)
  • android/gradle/wrapper/gradle-wrapper.properties
  • pubspec.yaml
  • android/settings.gradle
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/features/transcription/domain/transcription_history_model.dart
  • lib/features/transcription/data/local_storage_service.dart

@github-actions github-actions bot added size/XL and removed size/XL labels Mar 29, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
lib/features/transcription/presentation/transcription_controller.dart (2)

79-99: ⚠️ Potential issue | 🟠 Major

Keep successful insights as done even if history save fails.

The same try/catch handles both AI generation and persistence, so a local save failure can incorrectly flip a successful result into error.

Suggested restructuring
     try {
       final insights = await _geminiService.generateInsights(transcript);
       
       data = data.copyWith(
         rawTranscript: transcript,
         insights: insights,
         summary: insights.summary,
       );
+      state = TranscriptionState.done;
+      notifyListeners();

-      final history = TranscriptionHistoryModel(
-        transcript: transcript,
-        summary: insights.summary,
-        symptoms: insights.symptoms,
-        medicines: insights.medicines,
-        createdAt: DateTime.now(),
-      );
-
-      await _localStorageService.save(history);
-      state = TranscriptionState.done;
+      try {
+        final history = TranscriptionHistoryModel(
+          transcript: transcript,
+          summary: insights.summary,
+          symptoms: insights.symptoms,
+          medicines: insights.medicines,
+          createdAt: DateTime.now(),
+        );
+        await _localStorageService.save(history);
+      } catch (e) {
+        developer.log('History save failed: $e');
+      }
     } catch (e) {
       _setError("AI Processing failed: $e");
-    } finally {
-      notifyListeners();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/transcription/presentation/transcription_controller.dart` around
lines 79 - 99, The AI generation and persistence are wrapped in one try/catch so
a storage failure can mark a successful insights run as an error; split the flow
so you call _geminiService.generateInsights(transcript), update data via
data.copyWith(...) and set state = TranscriptionState.done immediately after
successful generation, then separately attempt to create the
TranscriptionHistoryModel and await _localStorageService.save(history) inside
its own try/catch—on save failure call _setError or log the failure but do NOT
change the previously set state; keep references to
_geminiService.generateInsights, data.copyWith, TranscriptionHistoryModel,
_localStorageService.save, state, and _setError to locate the code.

88-97: ⚠️ Potential issue | 🟠 Major

Verify medical-history storage is encrypted at rest.

This path persists transcript/symptoms/medicines; if LocalStorageService still uses plain SharedPreferences, it is a privacy/compliance risk.

#!/bin/bash
# Verify storage backend + encryption posture for transcription history

set -euo pipefail

echo "== Locate LocalStorageService =="
fd -i "local_storage_service.dart" lib

echo ""
echo "== Inspect implementation =="
cat lib/features/transcription/data/local_storage_service.dart

echo ""
echo "== Check for plain SharedPreferences usage =="
rg -n "SharedPreferences|getStringList|setStringList|setString" lib/features/transcription/data/local_storage_service.dart

echo ""
echo "== Check for encryption/secure storage usage =="
rg -n "flutter_secure_storage|encrypt|AES|Cipher|Key|secure" lib/features/transcription/data/local_storage_service.dart

Expected verification outcome:

  • If only SharedPreferences appears and no encryption/secure-storage signal exists, this finding is confirmed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/features/transcription/presentation/transcription_controller.dart` around
lines 88 - 97, The TranscriptionHistoryModel
(transcript/summary/symptoms/medicines) is being persisted via
_localStorageService.save in transcription_controller.dart and must be stored
encrypted at rest; update LocalStorageService (the class handling save/load) to
use a secure backend (e.g., flutter_secure_storage or platform keystore) or
perform strong client-side encryption (AES with a key stored in secure
enclave/keystore) before writing, update its save/load APIs to accept encrypted
payloads, and ensure _localStorageService.save used by TranscriptionState
transitions encrypts data; add a short migration/compatibility path for existing
plaintext entries and unit/integration tests that assert saved bytes are not
plaintext and that decryption works via the LocalStorageService methods.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 58-60: When _audioRecorder.stop() returns null, the method
currently returns early without updating controller state; update the state to a
deterministic value before returning (e.g., set recording state to idle). Locate
the block where you call final path = await _audioRecorder.stop(); and replace
the early return with a state update then return — for example call your
existing state setter (emit(TranscriptionState.idle) or setRecording(false) or
_setIdleState()) or set an error state if you prefer
(emit(TranscriptionState.error) / setError(...)) before the return so the UI
flow remains consistent.
- Around line 45-46: The permission check currently returns silently when
!_audioRecorder.hasPermission(), leaving the UI unchanged; update the method
containing this code to set a user-visible error/state before returning — for
example, call a controller/state method (e.g., setError("Microphone permission
denied") or set transcriptionState = TranscriptionState.permissionDenied and
notify listeners) immediately before the return so the UI can show an error or
open a permission prompt; ensure you reference and update the existing UI/state
handling mechanism in TranscriptionController (or the surrounding class) rather
than just returning from the function that calls _audioRecorder.hasPermission().

---

Duplicate comments:
In `@lib/features/transcription/presentation/transcription_controller.dart`:
- Around line 79-99: The AI generation and persistence are wrapped in one
try/catch so a storage failure can mark a successful insights run as an error;
split the flow so you call _geminiService.generateInsights(transcript), update
data via data.copyWith(...) and set state = TranscriptionState.done immediately
after successful generation, then separately attempt to create the
TranscriptionHistoryModel and await _localStorageService.save(history) inside
its own try/catch—on save failure call _setError or log the failure but do NOT
change the previously set state; keep references to
_geminiService.generateInsights, data.copyWith, TranscriptionHistoryModel,
_localStorageService.save, state, and _setError to locate the code.
- Around line 88-97: The TranscriptionHistoryModel
(transcript/summary/symptoms/medicines) is being persisted via
_localStorageService.save in transcription_controller.dart and must be stored
encrypted at rest; update LocalStorageService (the class handling save/load) to
use a secure backend (e.g., flutter_secure_storage or platform keystore) or
perform strong client-side encryption (AES with a key stored in secure
enclave/keystore) before writing, update its save/load APIs to accept encrypted
payloads, and ensure _localStorageService.save used by TranscriptionState
transitions encrypts data; add a short migration/compatibility path for existing
plaintext entries and unit/integration tests that assert saved bytes are not
plaintext and that decryption works via the LocalStorageService methods.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: ad19f1a1-fafc-484f-8351-d58dcd1e7f60

📥 Commits

Reviewing files that changed from the base of the PR and between 916c566 and 12cce52.

📒 Files selected for processing (1)
  • lib/features/transcription/presentation/transcription_controller.dart

@github-actions github-actions bot added size/XL and removed size/XL labels Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Add persistent transcription history with reusable insights viewer

1 participant