From f4a0fd7e88934ac283b2842b57147abc7793c604 Mon Sep 17 00:00:00 2001 From: Ishaan Kumar Date: Fri, 26 Dec 2025 01:07:08 +0530 Subject: [PATCH 1/2] Add friendly empty-state UI and Retry actions for summary and prescription --- lib/main.dart | 16 +++- lib/screens/prescription_screen.dart | 109 +++++++++++++++++---------- lib/screens/summary_screen.dart | 107 +++++++++++++++++--------- 3 files changed, 155 insertions(+), 77 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1013f29..21a990d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -474,7 +474,13 @@ class _TranscriptionScreenState extends State with SingleTi () => Navigator.push( context, MaterialPageRoute( - builder: (context) => SummaryScreen(summary: _summaryContent), + builder: (context) => SummaryScreen( + summary: _summaryContent, + onRetry: () { + Navigator.pop(context); + _processWithGemini(_transcription); + }, + ), ), ), ), @@ -487,7 +493,13 @@ class _TranscriptionScreenState extends State with SingleTi () => Navigator.push( context, MaterialPageRoute( - builder: (context) => PrescriptionScreen(prescription: _prescriptionContent), + builder: (context) => PrescriptionScreen( + prescription: _prescriptionContent, + onRetry: () { + Navigator.pop(context); + _processWithGemini(_transcription); + }, + ), ), ), ), diff --git a/lib/screens/prescription_screen.dart b/lib/screens/prescription_screen.dart index 2ed59c0..4514c8a 100644 --- a/lib/screens/prescription_screen.dart +++ b/lib/screens/prescription_screen.dart @@ -7,8 +7,9 @@ import 'package:share_plus/share_plus.dart'; class PrescriptionScreen extends StatefulWidget { final String prescription; + final VoidCallback? onRetry; - const PrescriptionScreen({super.key, required this.prescription}); + const PrescriptionScreen({super.key, required this.prescription, this.onRetry}); @override State createState() => _PrescriptionScreenState(); @@ -125,43 +126,75 @@ class _PrescriptionScreenState extends State { Expanded( child: SingleChildScrollView( child: widget.prescription.isEmpty - ? const Text( - 'No prescription available', - style: TextStyle( - fontSize: 16, - fontStyle: FontStyle.italic, - color: Colors.grey, - ), - ) + ? Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 40.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.medication, size: 64, color: Colors.deepPurple.withOpacity(0.9)), + const SizedBox(height: 16), + const Text( + 'No prescription available', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + const Text( + 'Generate a prescription by recording a conversation and processing it.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + if (widget.onRetry != null) ...[ + const SizedBox(height: 16), + ElevatedButton.icon( + onPressed: widget.onRetry, + icon: const Icon(Icons.refresh), + label: const Text('Retry'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.deepPurple, + ), + ), + ] + ], + ), + ), + ) : MarkdownBody( - data: widget.prescription, - styleSheet: MarkdownStyleSheet( - p: const TextStyle( - fontSize: 16, - height: 1.5, - color: Colors.black87, - ), - h1: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.deepPurple, - ), - h2: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.deepPurple, - ), - h3: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.deepPurple, - ), - listBullet: const TextStyle( - fontSize: 16, - color: Colors.deepPurple, - ), - ), - ), + data: widget.prescription, + styleSheet: MarkdownStyleSheet( + p: const TextStyle( + fontSize: 16, + height: 1.5, + color: Colors.black87, + ), + h1: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.deepPurple, + ), + h2: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.deepPurple, + ), + h3: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.deepPurple, + ), + listBullet: const TextStyle( + fontSize: 16, + color: Colors.deepPurple, + ), + ), + ), ), ), ], @@ -171,7 +204,7 @@ class _PrescriptionScreenState extends State { ), ), floatingActionButton: FloatingActionButton( - onPressed: _isSaving ? null : _savePrescription, + onPressed: (widget.prescription.isEmpty || _isSaving) ? null : _savePrescription, backgroundColor: Colors.deepPurple, foregroundColor: Colors.white, elevation: 8, diff --git a/lib/screens/summary_screen.dart b/lib/screens/summary_screen.dart index 421bf9a..700c258 100644 --- a/lib/screens/summary_screen.dart +++ b/lib/screens/summary_screen.dart @@ -3,8 +3,9 @@ import 'package:flutter_markdown/flutter_markdown.dart'; class SummaryScreen extends StatelessWidget { final String summary; + final VoidCallback? onRetry; - const SummaryScreen({super.key, required this.summary}); + const SummaryScreen({super.key, required this.summary, this.onRetry}); @override Widget build(BuildContext context) { @@ -56,43 +57,75 @@ class SummaryScreen extends StatelessWidget { Expanded( child: SingleChildScrollView( child: summary.isEmpty - ? const Text( - 'No summary available', - style: TextStyle( - fontSize: 16, - fontStyle: FontStyle.italic, - color: Colors.grey, - ), - ) + ? Center( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 40.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.summarize, size: 64, color: Colors.deepPurple.withOpacity(0.9)), + const SizedBox(height: 16), + const Text( + 'No summary available', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + const Text( + 'Try recording again or tap retry to process the latest transcription.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + if (onRetry != null) ...[ + const SizedBox(height: 16), + ElevatedButton.icon( + onPressed: onRetry, + icon: const Icon(Icons.refresh), + label: const Text('Retry'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.deepPurple, + ), + ), + ] + ], + ), + ), + ) : MarkdownBody( - data: summary, - styleSheet: MarkdownStyleSheet( - p: const TextStyle( - fontSize: 16, - height: 1.5, - color: Colors.black87, - ), - h1: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.deepPurple, - ), - h2: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.deepPurple, - ), - h3: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.deepPurple, - ), - listBullet: const TextStyle( - fontSize: 16, - color: Colors.deepPurple, - ), - ), - ), + data: summary, + styleSheet: MarkdownStyleSheet( + p: const TextStyle( + fontSize: 16, + height: 1.5, + color: Colors.black87, + ), + h1: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.deepPurple, + ), + h2: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.deepPurple, + ), + h3: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.deepPurple, + ), + listBullet: const TextStyle( + fontSize: 16, + color: Colors.deepPurple, + ), + ), + ), ), ), ], From cfba4e06bf638b6b652558132920709a0bec3c82 Mon Sep 17 00:00:00 2001 From: Ishaan Kumar Date: Fri, 26 Dec 2025 13:58:18 +0530 Subject: [PATCH 2/2] Add config fallback for API keys (dart-define > .env) and load .env safely --- lib/main.dart | 11 +++++++++-- lib/services/chatbot_service.dart | 3 ++- lib/services/config.dart | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) create mode 100644 lib/services/config.dart diff --git a/lib/main.dart b/lib/main.dart index 21a990d..6be36fc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,7 +17,14 @@ import 'screens/summary_screen.dart'; import 'screens/prescription_screen.dart'; Future main() async { - await dotenv.load(); + // Try loading .env if present; if not, continue and rely on --dart-define. + try { + await dotenv.load(fileName: '.env'); + } catch (e) { + // No .env found in production builds; use --dart-define fallback + developer.log('No .env loaded: $e'); + } + runApp(const MyApp()); } @@ -186,7 +193,7 @@ class _TranscriptionScreenState extends State with SingleTi Future _transcribeAudio() async { try { - final apiKey = dotenv.env['DEEPGRAM_API_KEY'] ?? ''; + final apiKey = getDeepgramApiKey(); final uri = Uri.parse('https://api.deepgram.com/v1/listen?model=nova-2'); final file = File(_recordingPath); diff --git a/lib/services/chatbot_service.dart b/lib/services/chatbot_service.dart index 22d00b5..980af59 100644 --- a/lib/services/chatbot_service.dart +++ b/lib/services/chatbot_service.dart @@ -5,7 +5,8 @@ import 'package:flutter_dotenv/flutter_dotenv.dart'; class ChatbotService { // Get API key from .env file - final String apiKey = dotenv.env['GEMINI_API_KEY'] ?? ''; + // Get API key using config fallback (dart-define > .env) + String get apiKey => getGeminiApiKey(); // Get a response from Gemini based on a prompt Future getGeminiResponse(String prompt) async { diff --git a/lib/services/config.dart b/lib/services/config.dart new file mode 100644 index 0000000..85b6287 --- /dev/null +++ b/lib/services/config.dart @@ -0,0 +1,15 @@ +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +/// Return GEMINI API key from --dart-define or .env as fallback. +String getGeminiApiKey() { + const envValue = String.fromEnvironment('GEMINI_API_KEY'); + if (envValue.isNotEmpty) return envValue; + return dotenv.env['GEMINI_API_KEY'] ?? ''; +} + +/// Return DEEPGRAM API key from --dart-define or .env as fallback. +String getDeepgramApiKey() { + const envValue = String.fromEnvironment('DEEPGRAM_API_KEY'); + if (envValue.isNotEmpty) return envValue; + return dotenv.env['DEEPGRAM_API_KEY'] ?? ''; +}