import 'package:flutter/material.dart'; import 'package:hmg_patient_app/dev_tools/environment_config.dart'; /// Public class — can be used directly with showModalBottomSheet. class EnvironmentConfigSheet extends StatefulWidget { const EnvironmentConfigSheet({Key? key}) : super(key: key); @override State createState() => _EnvironmentConfigSheetState(); } /// Convenience function — opens the sheet and returns true if app restart needed. Future showEnvironmentConfigSheet(BuildContext context) async { final result = await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (_) => const EnvironmentConfigSheet(), ); return result ?? false; } class _EnvironmentConfigSheetState extends State { final _envService = EnvironmentConfigService.instance; AppEnvironmentPreset? _selectedPreset; bool _isSaving = false; @override void initState() { super.initState(); _loadCurrent(); } Future _loadCurrent() async { final preset = await _envService.getCurrentPreset(); if (!mounted) return; setState(() { // If no preset is stored, fall back to production (first preset) _selectedPreset = preset ?? kEnvironmentPresets.first; }); } void _selectPreset(AppEnvironmentPreset p) { setState(() => _selectedPreset = p); } Future _save() async { if (_selectedPreset == null) return; setState(() => _isSaving = true); try { await _envService.applyPreset(_selectedPreset!); if (mounted) Navigator.of(context).pop(true); } catch (e) { setState(() => _isSaving = false); ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('Failed to save: $e'))); } } Future _resetToDefault() async { setState(() => _isSaving = true); try { await _envService.resetToDefault(); if (mounted) Navigator.of(context).pop(true); } catch (e) { setState(() => _isSaving = false); ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('Failed to reset: $e'))); } } @override Widget build(BuildContext context) { final mq = MediaQuery.of(context); final p = _selectedPreset; return Container( height: mq.size.height * 0.85, decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( children: [ // Handle bar Container( margin: const EdgeInsets.only(top: 12, bottom: 4), width: 40, height: 4, decoration: BoxDecoration( color: Colors.grey.shade300, borderRadius: BorderRadius.circular(2)), ), // Title row Padding( padding: const EdgeInsets.fromLTRB(20, 12, 20, 4), child: Row( children: [ const Icon(Icons.tune, color: Color(0xFF2B353E)), const SizedBox(width: 10), const Expanded( child: Text('Environment Configuration', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: Color(0xFF2B353E))), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(false)), ], ), ), const Divider(height: 1), // Scrollable body Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // ── Preset chips ────────────────────────────────────── const _SectionLabel('Select Environment'), const SizedBox(height: 10), Wrap( spacing: 8, runSpacing: 8, children: kEnvironmentPresets .map((preset) => _Chip( label: preset.label, selected: _selectedPreset?.label == preset.label, onTap: () => _selectPreset(preset), )) .toList(), ), const SizedBox(height: 24), // ── URL preview (read-only) ──────────────────────────── if (p != null) ...[ const _SectionLabel('URLs for selected environment'), const SizedBox(height: 10), _UrlDisplay(label: 'Main Base URL', value: p.baseUrl), const SizedBox(height: 8), _UrlDisplay(label: 'RC Base URL', value: p.rcBaseUrl), const SizedBox(height: 8), _UrlDisplay(label: 'Pharmacy Base URL', value: p.pharmacyBaseUrl), const SizedBox(height: 8), _UrlDisplay(label: 'ExaCart (Offers) URL', value: p.exaCartBaseUrl), const SizedBox(height: 8), _UrlDisplay(label: 'PayFort Mode', value: p.payfortMode), const SizedBox(height: 24), ], // ── Reset to Default ────────────────────────────────── InkWell( onTap: _isSaving ? null : _resetToDefault, borderRadius: BorderRadius.circular(10), child: Container( width: double.infinity, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14), decoration: BoxDecoration( border: Border.all(color: const Color(0xFFE74C3C)), borderRadius: BorderRadius.circular(10), color: const Color(0xFFFFF5F5)), child: Row( children: const [ Icon(Icons.restore, color: Color(0xFFE74C3C), size: 20), SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Reset to Default', style: TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: Color(0xFFE74C3C))), SizedBox(height: 2), Text( 'Clears saved config and uses the URLs defined in code (Production)', style: TextStyle( fontSize: 11, color: Color(0xFFE74C3C))), ], ), ), ], ), ), ), const SizedBox(height: 20), // ── Warning ─────────────────────────────────────────── Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: const Color(0xFFFFF3CD), borderRadius: BorderRadius.circular(8), border: Border.all(color: const Color(0xFFFFD700))), child: const Row(children: [ Icon(Icons.warning_amber_rounded, color: Color(0xFFE6A817), size: 18), SizedBox(width: 8), Expanded( child: Text( 'After saving, the app will restart to apply the new environment.', style: TextStyle( fontSize: 12, color: Color(0xFF856404)), ), ), ]), ), const SizedBox(height: 80), ], ), ), ), // ── Save button ─────────────────────────────────────────────── Container( padding: EdgeInsets.fromLTRB(20, 12, 20, 12 + mq.padding.bottom), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withAlpha(15), blurRadius: 8, offset: const Offset(0, -2)) ], ), child: SizedBox( width: double.infinity, height: 50, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF2B353E), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10)), ), onPressed: _isSaving ? null : _save, child: _isSaving ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2)) : const Text('Save & Restart App', style: TextStyle( fontSize: 15, fontWeight: FontWeight.w600, color: Colors.white)), ), ), ), ], ), ); } } // ─── Small private helpers ──────────────────────────────────────────────────── class _SectionLabel extends StatelessWidget { final String text; const _SectionLabel(this.text); @override Widget build(BuildContext context) => Text(text, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: Color(0xFF2B353E))); } class _Chip extends StatelessWidget { final String label; final bool selected; final VoidCallback onTap; const _Chip( {required this.label, required this.selected, required this.onTap}); @override Widget build(BuildContext context) { return GestureDetector( onTap: onTap, child: AnimatedContainer( duration: const Duration(milliseconds: 200), padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), decoration: BoxDecoration( color: selected ? const Color(0xFF2B353E) : Colors.white, borderRadius: BorderRadius.circular(20), border: Border.all( color: selected ? const Color(0xFF2B353E) : Colors.grey.shade300, width: selected ? 2 : 1), ), child: Text(label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: selected ? Colors.white : Colors.black87)), ), ); } } class _UrlDisplay extends StatelessWidget { final String label; final String value; const _UrlDisplay({required this.label, required this.value}); @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle( fontSize: 11, fontWeight: FontWeight.w600, color: Colors.grey)), const SizedBox(height: 3), Container( width: double.infinity, padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 9), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade200), ), child: Text(value, style: const TextStyle( fontSize: 12, color: Color(0xFF2B353E))), ), ], ); } }