Created
December 12, 2025 16:56
-
-
Save maharatha/379df86774571290a2dfc3d6561e6702 to your computer and use it in GitHub Desktop.
Claude AI & Figma POC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/material.dart'; | |
| import 'package:flutter/services.dart'; | |
| // ============================================================================ | |
| // GLENEAGLES QUICK ACTIONS - COMPLETE FLOW | |
| // A thermostat control system with Quick Actions, Override Options, | |
| // and Dashboard integration | |
| // ============================================================================ | |
| // App Theme Colors | |
| class GleneaglesColors { | |
| static const Color primary = Color(0xFF0F5EEA); | |
| static const Color accent = Color(0xFFEB680A); | |
| static const Color dark = Color(0xFF303030); | |
| static const Color text = Color(0xFF242424); | |
| static const Color textSecondary = Color(0xFF767676); | |
| static const Color border = Color(0xFFCCCCCC); | |
| static const Color background = Color(0xFFF5F5F5); | |
| static const Color white = Colors.white; | |
| static const Color error = Color(0xFFE53935); | |
| static const Color success = Color(0xFF43A047); | |
| static const Color disabled = Color(0xFFE2E2E2); | |
| static const Color overlay = Color(0x80000000); | |
| // Gradient for header | |
| static const LinearGradient headerGradient = LinearGradient( | |
| begin: Alignment.topCenter, | |
| end: Alignment.bottomCenter, | |
| colors: [Color(0xFFB8D4F0), Color(0xFFE8F0F8)], | |
| ); | |
| } | |
| // ============================================================================ | |
| // DATA MODELS | |
| // ============================================================================ | |
| enum QuickActionType { | |
| energySaver, | |
| comfortBoost, | |
| away, | |
| stayHome, | |
| custom, | |
| } | |
| class QuickAction { | |
| final QuickActionType type; | |
| final String name; | |
| final String description; | |
| final IconData icon; | |
| final Color iconColor; | |
| // Settings specific to each action type | |
| double? temperatureOffset; | |
| double? coolTo; | |
| double? heatTo; | |
| bool hotWaterOn; | |
| String duration; | |
| String? schedule; | |
| String? customName; | |
| List<String> selectedZones; | |
| QuickAction({ | |
| required this.type, | |
| required this.name, | |
| required this.description, | |
| required this.icon, | |
| required this.iconColor, | |
| this.temperatureOffset, | |
| this.coolTo, | |
| this.heatTo, | |
| this.hotWaterOn = true, | |
| this.duration = '3 hours', | |
| this.schedule, | |
| this.customName, | |
| List<String>? selectedZones, | |
| }) : selectedZones = selectedZones ?? []; | |
| String get summary { | |
| switch (type) { | |
| case QuickActionType.energySaver: | |
| return '-${temperatureOffset?.abs() ?? 3.0}° / $duration'; | |
| case QuickActionType.comfortBoost: | |
| return '+${temperatureOffset?.abs() ?? 3.0}° / $duration'; | |
| case QuickActionType.away: | |
| return '${heatTo?.toInt() ?? 15}° / HW ${hotWaterOn ? 'On' : 'Off'} / $duration'; | |
| case QuickActionType.stayHome: | |
| return '${schedule ?? 'Saturday'} / $duration'; | |
| case QuickActionType.custom: | |
| return '${customName ?? 'Custom Schedule'} / $duration'; | |
| } | |
| } | |
| QuickAction copyWith({ | |
| double? temperatureOffset, | |
| double? coolTo, | |
| double? heatTo, | |
| bool? hotWaterOn, | |
| String? duration, | |
| String? schedule, | |
| String? customName, | |
| List<String>? selectedZones, | |
| }) { | |
| return QuickAction( | |
| type: type, | |
| name: name, | |
| description: description, | |
| icon: icon, | |
| iconColor: iconColor, | |
| temperatureOffset: temperatureOffset ?? this.temperatureOffset, | |
| coolTo: coolTo ?? this.coolTo, | |
| heatTo: heatTo ?? this.heatTo, | |
| hotWaterOn: hotWaterOn ?? this.hotWaterOn, | |
| duration: duration ?? this.duration, | |
| schedule: schedule ?? this.schedule, | |
| customName: customName ?? this.customName, | |
| selectedZones: selectedZones ?? this.selectedZones, | |
| ); | |
| } | |
| } | |
| class ThermostatZone { | |
| final String id; | |
| final String name; | |
| final double currentTemp; | |
| double targetTemp; | |
| final String mode; // 'heat', 'cool', 'heating' | |
| bool hasActiveAction; | |
| ThermostatZone({ | |
| required this.id, | |
| required this.name, | |
| required this.currentTemp, | |
| required this.targetTemp, | |
| this.mode = 'heat', | |
| this.hasActiveAction = false, | |
| }); | |
| } | |
| // ============================================================================ | |
| // MAIN APP STATE | |
| // ============================================================================ | |
| class QuickActionsState extends ChangeNotifier { | |
| QuickAction? activeAction; | |
| List<ThermostatZone> zones = [ | |
| ThermostatZone(id: '1', name: 'Living Space', currentTemp: 19.0, targetTemp: 19.5), | |
| ThermostatZone(id: '2', name: 'Kitchen', currentTemp: 19.0, targetTemp: 19.5, mode: 'heating'), | |
| ThermostatZone(id: '3', name: 'Primary Bedroom', currentTemp: 19.5, targetTemp: 19.5), | |
| ThermostatZone(id: '4', name: 'Hot Water', currentTemp: 59.0, targetTemp: 59.0), | |
| ]; | |
| final List<QuickAction> quickActions = [ | |
| QuickAction( | |
| type: QuickActionType.energySaver, | |
| name: 'Energy Saver', | |
| description: 'Offsets set temp in all zones to save energy.', | |
| icon: Icons.eco_outlined, | |
| iconColor: const Color(0xFF43A047), | |
| temperatureOffset: -3.0, | |
| duration: '3 hours', | |
| ), | |
| QuickAction( | |
| type: QuickActionType.comfortBoost, | |
| name: 'Comfort Boost', | |
| description: 'Adjusts set temp in all zones to boost comfort.', | |
| icon: Icons.whatshot_outlined, | |
| iconColor: const Color(0xFFFF5722), | |
| temperatureOffset: 3.0, | |
| duration: '3 hours', | |
| ), | |
| QuickAction( | |
| type: QuickActionType.away, | |
| name: 'Away', | |
| description: 'Set temp in all zones to save energy while away.', | |
| icon: Icons.flight_takeoff_outlined, | |
| iconColor: const Color(0xFF1976D2), | |
| coolTo: 30.0, | |
| heatTo: 15.0, | |
| hotWaterOn: false, | |
| duration: '8 hours', | |
| ), | |
| QuickAction( | |
| type: QuickActionType.stayHome, | |
| name: 'Stay Home', | |
| description: 'Use alternate day\'s schedule while you stay home.', | |
| icon: Icons.home_outlined, | |
| iconColor: const Color(0xFF7B1FA2), | |
| schedule: 'Saturday', | |
| duration: 'Today', | |
| ), | |
| QuickAction( | |
| type: QuickActionType.custom, | |
| name: 'Custom', | |
| description: 'Use a custom schedule.', | |
| icon: Icons.settings_outlined, | |
| iconColor: const Color(0xFF455A64), | |
| customName: 'Custom Schedule', | |
| duration: '1 Day', | |
| selectedZones: ['1', '2', '3'], | |
| ), | |
| ]; | |
| void activateAction(QuickAction action) { | |
| activeAction = action; | |
| // Apply action to zones | |
| for (var zone in zones) { | |
| zone.hasActiveAction = true; | |
| if (action.type == QuickActionType.energySaver) { | |
| zone.targetTemp -= action.temperatureOffset?.abs() ?? 3.0; | |
| } else if (action.type == QuickActionType.comfortBoost) { | |
| zone.targetTemp += action.temperatureOffset?.abs() ?? 3.0; | |
| } | |
| } | |
| notifyListeners(); | |
| } | |
| void deactivateAction() { | |
| // Restore zones | |
| for (var zone in zones) { | |
| zone.hasActiveAction = false; | |
| if (activeAction?.type == QuickActionType.energySaver) { | |
| zone.targetTemp += activeAction?.temperatureOffset?.abs() ?? 3.0; | |
| } else if (activeAction?.type == QuickActionType.comfortBoost) { | |
| zone.targetTemp -= activeAction?.temperatureOffset?.abs() ?? 3.0; | |
| } | |
| } | |
| activeAction = null; | |
| notifyListeners(); | |
| } | |
| void updateAction(QuickAction action) { | |
| final index = quickActions.indexWhere((a) => a.type == action.type); | |
| if (index != -1) { | |
| quickActions[index] = action; | |
| } | |
| if (activeAction?.type == action.type) { | |
| activeAction = action; | |
| } | |
| notifyListeners(); | |
| } | |
| void updateZoneTemp(String zoneId, double delta) { | |
| final zone = zones.firstWhere((z) => z.id == zoneId); | |
| zone.targetTemp += delta; | |
| notifyListeners(); | |
| } | |
| } | |
| // ============================================================================ | |
| // MAIN APP WIDGET | |
| // ============================================================================ | |
| class QuickActionsApp extends StatefulWidget { | |
| const QuickActionsApp({super.key}); | |
| @override | |
| State<QuickActionsApp> createState() => _QuickActionsAppState(); | |
| } | |
| class _QuickActionsAppState extends State<QuickActionsApp> { | |
| final QuickActionsState _state = QuickActionsState(); | |
| @override | |
| Widget build(BuildContext context) { | |
| return MaterialApp( | |
| title: 'Gleneagles Quick Actions', | |
| debugShowCheckedModeBanner: false, | |
| theme: ThemeData( | |
| useMaterial3: true, | |
| fontFamily: 'Roboto', | |
| scaffoldBackgroundColor: GleneaglesColors.background, | |
| colorScheme: ColorScheme.fromSeed( | |
| seedColor: GleneaglesColors.primary, | |
| primary: GleneaglesColors.primary, | |
| ), | |
| ), | |
| home: ListenableBuilder( | |
| listenable: _state, | |
| builder: (context, _) => DashboardScreen(state: _state), | |
| ), | |
| ); | |
| } | |
| } | |
| // ============================================================================ | |
| // DASHBOARD SCREEN | |
| // ============================================================================ | |
| class DashboardScreen extends StatelessWidget { | |
| final QuickActionsState state; | |
| const DashboardScreen({super.key, required this.state}); | |
| void _showQuickActionsSheet(BuildContext context) { | |
| showModalBottomSheet( | |
| context: context, | |
| isScrollControlled: true, | |
| backgroundColor: Colors.transparent, | |
| barrierColor: GleneaglesColors.overlay, | |
| builder: (context) => QuickActionsBottomSheet( | |
| state: state, | |
| onActionSelected: (action) { | |
| Navigator.pop(context); | |
| _showOverrideOptions(context, action); | |
| }, | |
| onSettingsTap: () { | |
| Navigator.pop(context); | |
| // Navigate to settings | |
| }, | |
| ), | |
| ); | |
| } | |
| void _showOverrideOptions(BuildContext context, QuickAction action) { | |
| showModalBottomSheet( | |
| context: context, | |
| isScrollControlled: true, | |
| backgroundColor: Colors.transparent, | |
| barrierColor: GleneaglesColors.overlay, | |
| builder: (context) => OverrideOptionsSheet( | |
| action: action, | |
| zones: state.zones, | |
| onSave: (updatedAction) { | |
| state.updateAction(updatedAction); | |
| state.activateAction(updatedAction); | |
| Navigator.pop(context); | |
| }, | |
| onRemove: () { | |
| state.deactivateAction(); | |
| Navigator.pop(context); | |
| }, | |
| onClose: () => Navigator.pop(context), | |
| ), | |
| ); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Scaffold( | |
| body: SafeArea( | |
| top: false, | |
| child: Column( | |
| children: [ | |
| // Header | |
| _buildHeader(context), | |
| // Content | |
| Expanded( | |
| child: SingleChildScrollView( | |
| child: Padding( | |
| padding: const EdgeInsets.all(20), | |
| child: Column( | |
| children: [ | |
| _buildThermostatCard(context), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget _buildHeader(BuildContext context) { | |
| return Container( | |
| decoration: const BoxDecoration( | |
| gradient: GleneaglesColors.headerGradient, | |
| ), | |
| child: SafeArea( | |
| bottom: false, | |
| child: Padding( | |
| padding: const EdgeInsets.fromLTRB(20, 8, 20, 20), | |
| child: Column( | |
| children: [ | |
| // Top bar | |
| Row( | |
| children: [ | |
| Icon(Icons.menu, color: GleneaglesColors.dark, size: 24), | |
| const Spacer(), | |
| Row( | |
| children: [ | |
| Text( | |
| '75°', | |
| style: TextStyle( | |
| fontSize: 22, | |
| fontWeight: FontWeight.w500, | |
| color: GleneaglesColors.dark, | |
| ), | |
| ), | |
| const SizedBox(width: 4), | |
| Icon(Icons.cloud, color: GleneaglesColors.accent, size: 28), | |
| ], | |
| ), | |
| ], | |
| ), | |
| const SizedBox(height: 12), | |
| // Title row | |
| Row( | |
| children: [ | |
| Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Row( | |
| children: [ | |
| Text( | |
| 'My Home', | |
| style: TextStyle( | |
| fontSize: 28, | |
| fontWeight: FontWeight.bold, | |
| color: GleneaglesColors.dark, | |
| ), | |
| ), | |
| const SizedBox(width: 8), | |
| Icon(Icons.keyboard_arrow_down, color: GleneaglesColors.dark), | |
| ], | |
| ), | |
| const SizedBox(height: 4), | |
| Text( | |
| 'Minneapolis, MN', | |
| style: TextStyle( | |
| fontSize: 16, | |
| color: GleneaglesColors.textSecondary, | |
| ), | |
| ), | |
| ], | |
| ), | |
| const Spacer(), | |
| Column( | |
| crossAxisAlignment: CrossAxisAlignment.end, | |
| children: [ | |
| Text( | |
| 'AccuWeather', | |
| style: TextStyle( | |
| fontSize: 11, | |
| color: GleneaglesColors.textSecondary, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ], | |
| ), | |
| const SizedBox(height: 20), | |
| // Placeholder tiles | |
| Row( | |
| children: List.generate(3, (index) => Expanded( | |
| child: Container( | |
| height: 100, | |
| margin: EdgeInsets.only(right: index < 2 ? 10 : 0), | |
| decoration: BoxDecoration( | |
| color: Colors.grey[300], | |
| borderRadius: BorderRadius.circular(12), | |
| ), | |
| child: Center( | |
| child: Icon(Icons.image, color: Colors.grey[500], size: 40), | |
| ), | |
| ), | |
| )), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| } | |
| Widget _buildThermostatCard(BuildContext context) { | |
| return Container( | |
| decoration: BoxDecoration( | |
| color: GleneaglesColors.white, | |
| borderRadius: BorderRadius.circular(20), | |
| boxShadow: [ | |
| BoxShadow( | |
| color: Colors.black.withOpacity(0.05), | |
| blurRadius: 10, | |
| offset: const Offset(0, 2), | |
| ), | |
| ], | |
| ), | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| // Header | |
| Padding( | |
| padding: const EdgeInsets.all(16), | |
| child: Text( | |
| 'Thermostat Zones', | |
| style: TextStyle( | |
| fontSize: 18, | |
| fontWeight: FontWeight.w600, | |
| color: GleneaglesColors.dark, | |
| ), | |
| ), | |
| ), | |
| // Quick Actions row | |
| _buildQuickActionsRow(context), | |
| // Zones | |
| ...state.zones.map((zone) => _buildZoneRow(context, zone)), | |
| ], | |
| ), | |
| ); | |
| } | |
| Widget _buildQuickActionsRow(BuildContext context) { | |
| final hasActive = state.activeAction != null; | |
| return Container( | |
| margin: const EdgeInsets.symmetric(horizontal: 16), | |
| padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), | |
| decoration: BoxDecoration( | |
| color: hasActive ? Colors.grey[50] : null, | |
| borderRadius: BorderRadius.circular(12), | |
| border: hasActive ? Border.all(color: Colors.grey[200]!) : null, | |
| ), | |
| child: Row( | |
| children: [ | |
| Expanded( | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| hasActive ? state.activeAction!.name : 'Quick Actions', | |
| style: TextStyle( | |
| fontSize: 16, | |
| fontWeight: FontWeight.w500, | |
| color: GleneaglesColors.dark, | |
| ), | |
| ), | |
| if (hasActive) ...[ | |
| const SizedBox(height: 2), | |
| Text( | |
| state.activeAction!.summary, | |
| style: TextStyle( | |
| fontSize: 14, | |
| color: GleneaglesColors.textSecondary, | |
| ), | |
| ), | |
| ], | |
| ], | |
| ), | |
| ), | |
| if (hasActive) ...[ | |
| // Edit button | |
| GestureDetector( | |
| onTap: () => _showOverrideOptions(context, state.activeAction!), | |
| child: Container( | |
| width: 44, | |
| height: 44, | |
| decoration: BoxDecoration( | |
| color: GleneaglesColors.white, | |
| borderRadius: BorderRadius.circular(12), | |
| border: Border.all(color: Colors.grey[300]!), | |
| ), | |
| child: Icon( | |
| Icons.edit_outlined, | |
| color: GleneaglesColors.textSecondary, | |
| size: 20, | |
| ), | |
| ), | |
| ), | |
| const SizedBox(width: 8), | |
| // Close button | |
| GestureDetector( | |
| onTap: () => state.deactivateAction(), | |
| child: Container( | |
| width: 44, | |
| height: 44, | |
| decoration: BoxDecoration( | |
| color: GleneaglesColors.white, | |
| borderRadius: BorderRadius.circular(12), | |
| border: Border.all(color: Colors.grey[300]!), | |
| ), | |
| child: Icon( | |
| Icons.close, | |
| color: GleneaglesColors.textSecondary, | |
| size: 20, | |
| ), | |
| ), | |
| ), | |
| ] else ...[ | |
| // Quick Actions button | |
| GestureDetector( | |
| onTap: () => _showQuickActionsSheet(context), | |
| child: Container( | |
| width: 44, | |
| height: 44, | |
| decoration: BoxDecoration( | |
| color: GleneaglesColors.white, | |
| borderRadius: BorderRadius.circular(12), | |
| border: Border.all(color: Colors.grey[300]!), | |
| ), | |
| child: Icon( | |
| Icons.tune, | |
| color: GleneaglesColors.textSecondary, | |
| size: 20, | |
| ), | |
| ), | |
| ), | |
| ], | |
| ], | |
| ), | |
| ); | |
| } | |
| Widget _buildZoneRow(BuildContext context, ThermostatZone zone) { | |
| final isHotWater = zone.name == 'Hot Water'; | |
| return Container( | |
| padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), | |
| decoration: BoxDecoration( | |
| border: Border( | |
| top: BorderSide(color: Colors.grey[200]!, width: 1), | |
| ), | |
| ), | |
| child: Row( | |
| children: [ | |
| Expanded( | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Row( | |
| children: [ | |
| Text( | |
| zone.name, | |
| style: TextStyle( | |
| fontSize: 16, | |
| fontWeight: FontWeight.w500, | |
| color: GleneaglesColors.dark, | |
| ), | |
| ), | |
| if (zone.hasActiveAction) ...[ | |
| const SizedBox(width: 6), | |
| Icon( | |
| _getActiveActionIcon(), | |
| size: 16, | |
| color: _getActiveActionColor(), | |
| ), | |
| ], | |
| const Spacer(), | |
| Icon( | |
| Icons.chevron_right, | |
| color: Colors.grey[400], | |
| size: 20, | |
| ), | |
| ], | |
| ), | |
| const SizedBox(height: 4), | |
| if (isHotWater) | |
| Row( | |
| children: [ | |
| Icon(Icons.check_box_outlined, size: 16, color: Colors.grey), | |
| const SizedBox(width: 4), | |
| Text( | |
| 'On', | |
| style: TextStyle( | |
| fontSize: 14, | |
| color: GleneaglesColors.textSecondary, | |
| ), | |
| ), | |
| ], | |
| ) | |
| else | |
| Text( | |
| 'Currently: ${zone.currentTemp.toStringAsFixed(1)}°', | |
| style: TextStyle( | |
| fontSize: 14, | |
| color: GleneaglesColors.textSecondary, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| if (!isHotWater) ...[ | |
| // Temperature controls | |
| _buildTempControl(context, zone), | |
| ] else ...[ | |
| // Turn Off button for hot water | |
| Container( | |
| padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), | |
| decoration: BoxDecoration( | |
| border: Border.all(color: Colors.grey[300]!), | |
| borderRadius: BorderRadius.circular(20), | |
| ), | |
| child: Text( | |
| 'Turn Off', | |
| style: TextStyle( | |
| fontSize: 14, | |
| fontWeight: FontWeight.w500, | |
| color: GleneaglesColors.dark, | |
| ), | |
| ), | |
| ), | |
| ], | |
| ], | |
| ), | |
| ); | |
| } | |
| IconData _getActiveActionIcon() { | |
| switch (state.activeAction?.type) { | |
| case QuickActionType.energySaver: | |
| return Icons.eco; | |
| case QuickActionType.comfortBoost: | |
| return Icons.whatshot; | |
| case QuickActionType.away: | |
| return Icons.flight_takeoff; | |
| case QuickActionType.stayHome: | |
| return Icons.home; | |
| case QuickActionType.custom: | |
| return Icons.settings; | |
| default: | |
| return Icons.check_circle; | |
| } | |
| } | |
| Color _getActiveActionColor() { | |
| switch (state.activeAction?.type) { | |
| case QuickActionType.energySaver: | |
| return const Color(0xFF43A047); | |
| case QuickActionType.comfortBoost: | |
| return const Color(0xFFFF5722); | |
| case QuickActionType.away: | |
| return const Color(0xFF1976D2); | |
| case QuickActionType.stayHome: | |
| return const Color(0xFF7B1FA2); | |
| case QuickActionType.custom: | |
| return const Color(0xFF455A64); | |
| default: | |
| return GleneaglesColors.accent; | |
| } | |
| } | |
| Widget _buildTempControl(BuildContext context, ThermostatZone zone) { | |
| final isHeating = zone.mode == 'heating'; | |
| return Row( | |
| children: [ | |
| // Decrease button | |
| GestureDetector( | |
| onTap: () => state.updateZoneTemp(zone.id, -0.5), | |
| child: Container( | |
| width: 32, | |
| height: 32, | |
| decoration: BoxDecoration( | |
| shape: BoxShape.circle, | |
| border: Border.all(color: GleneaglesColors.accent, width: 2), | |
| ), | |
| child: Icon( | |
| Icons.remove, | |
| size: 18, | |
| color: GleneaglesColors.accent, | |
| ), | |
| ), | |
| ), | |
| const SizedBox(width: 12), | |
| // Temperature display | |
| Column( | |
| children: [ | |
| Text( | |
| isHeating ? 'Heating to' : 'Heat to', | |
| style: TextStyle( | |
| fontSize: 12, | |
| color: GleneaglesColors.accent, | |
| ), | |
| ), | |
| Text( | |
| '${zone.targetTemp.toStringAsFixed(1)}°', | |
| style: TextStyle( | |
| fontSize: 24, | |
| fontWeight: FontWeight.w300, | |
| color: GleneaglesColors.accent, | |
| ), | |
| ), | |
| ], | |
| ), | |
| const SizedBox(width: 12), | |
| // Increase button | |
| GestureDetector( | |
| onTap: () => state.updateZoneTemp(zone.id, 0.5), | |
| child: Container( | |
| width: 32, | |
| height: 32, | |
| decoration: BoxDecoration( | |
| shape: BoxShape.circle, | |
| border: Border.all(color: GleneaglesColors.accent, width: 2), | |
| ), | |
| child: Icon( | |
| Icons.add, | |
| size: 18, | |
| color: GleneaglesColors.accent, | |
| ), | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| } | |
| // ============================================================================ | |
| // QUICK ACTIONS BOTTOM SHEET | |
| // ============================================================================ | |
| class QuickActionsBottomSheet extends StatefulWidget { | |
| final QuickActionsState state; | |
| final Function(QuickAction) onActionSelected; | |
| final VoidCallback onSettingsTap; | |
| const QuickActionsBottomSheet({ | |
| super.key, | |
| required this.state, | |
| required this.onActionSelected, | |
| required this.onSettingsTap, | |
| }); | |
| @override | |
| State<QuickActionsBottomSheet> createState() => _QuickActionsBottomSheetState(); | |
| } | |
| class _QuickActionsBottomSheetState extends State<QuickActionsBottomSheet> { | |
| QuickAction? selectedAction; | |
| @override | |
| Widget build(BuildContext context) { | |
| return Container( | |
| margin: const EdgeInsets.only(top: 100), | |
| decoration: const BoxDecoration( | |
| color: GleneaglesColors.white, | |
| borderRadius: BorderRadius.vertical(top: Radius.circular(28)), | |
| ), | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| children: [ | |
| // Header | |
| Padding( | |
| padding: const EdgeInsets.fromLTRB(24, 24, 24, 0), | |
| child: Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| children: [ | |
| Text( | |
| 'Quick Actions', | |
| style: TextStyle( | |
| fontSize: 24, | |
| fontWeight: FontWeight.bold, | |
| color: GleneaglesColors.dark, | |
| ), | |
| ), | |
| GestureDetector( | |
| onTap: () => Navigator.pop(context), | |
| child: Icon( | |
| Icons.close, | |
| color: GleneaglesColors.primary, | |
| size: 24, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| const SizedBox(height: 16), | |
| // Action list | |
| Flexible( | |
| child: ListView.builder( | |
| shrinkWrap: true, | |
| padding: const EdgeInsets.symmetric(horizontal: 24), | |
| itemCount: widget.state.quickActions.length, | |
| itemBuilder: (context, index) { | |
| final action = widget.state.quickActions[index]; | |
| final isSelected = selectedAction?.type == action.type; | |
| return Padding( | |
| padding: const EdgeInsets.only(bottom: 12), | |
| child: _buildActionTile(action, isSelected), | |
| ); | |
| }, | |
| ), | |
| ), | |
| // Bottom buttons | |
| Padding( | |
| padding: const EdgeInsets.all(24), | |
| child: Column( | |
| children: [ | |
| // Settings link | |
| GestureDetector( | |
| onTap: widget.onSettingsTap, | |
| child: Text( | |
| 'Quick Action Settings', | |
| style: TextStyle( | |
| fontSize: 16, | |
| fontWeight: FontWeight.w500, | |
| color: GleneaglesColors.primary, | |
| ), | |
| ), | |
| ), | |
| const SizedBox(height: 16), | |
| // Apply button | |
| GestureDetector( | |
| onTap: selectedAction != null | |
| ? () => widget.onActionSelected(selectedAction!) | |
| : null, | |
| child: Container( | |
| width: double.infinity, | |
| height: 50, | |
| decoration: BoxDecoration( | |
| color: selectedAction != null | |
| ? GleneaglesColors.dark | |
| : GleneaglesColors.disabled, | |
| borderRadius: BorderRadius.circular(25), | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Apply', | |
| style: TextStyle( | |
| fontSize: 18, | |
| fontWeight: FontWeight.w500, | |
| color: selectedAction != null | |
| ? GleneaglesColors.white | |
| : GleneaglesColors.textSecondary, | |
| fontStyle: selectedAction == null ? FontStyle.italic : null, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ); | |
| } | |
| Widget _buildActionTile(QuickAction action, bool isSelected) { | |
| return GestureDetector( | |
| onTap: () { | |
| setState(() { | |
| selectedAction = action; | |
| }); | |
| }, | |
| child: AnimatedContainer( | |
| duration: const Duration(milliseconds: 200), | |
| padding: const EdgeInsets.all(16), | |
| decoration: BoxDecoration( | |
| color: isSelected ? Colors.blue.withOpacity(0.05) : GleneaglesColors.white, | |
| borderRadius: BorderRadius.circular(16), | |
| border: Border.all( | |
| color: isSelected ? GleneaglesColors.primary : GleneaglesColors.border, | |
| width: isSelected ? 2 : 1, | |
| ), | |
| ), | |
| child: Row( | |
| children: [ | |
| // Icon | |
| Container( | |
| width: 48, | |
| height: 48, | |
| decoration: BoxDecoration( | |
| color: action.iconColor.withOpacity(0.1), | |
| borderRadius: BorderRadius.circular(12), | |
| ), | |
| child: Icon( | |
| action.icon, | |
| color: action.iconColor, | |
| size: 24, | |
| ), | |
| ), | |
| const SizedBox(width: 16), | |
| // Text | |
| Expanded( | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| action.name, | |
| style: TextStyle( | |
| fontSize: 18, | |
| fontWeight: FontWeight.w500, | |
| color: GleneaglesColors.text, | |
| ), | |
| ), | |
| const SizedBox(height: 2), | |
| Text( | |
| action.summary, | |
| style: TextStyle( | |
| fontSize: 16, | |
| color: GleneaglesColors.textSecondary, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| // Selection indicator | |
| if (isSelected) | |
| Container( | |
| width: 24, | |
| height: 24, | |
| decoration: BoxDecoration( | |
| color: GleneaglesColors.primary, | |
| shape: BoxShape.circle, | |
| ), | |
| child: Icon( | |
| Icons.check, | |
| color: GleneaglesColors.white, | |
| size: 16, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| // ============================================================================ | |
| // OVERRIDE OPTIONS BOTTOM SHEET | |
| // ============================================================================ | |
| class OverrideOptionsSheet extends StatefulWidget { | |
| final QuickAction action; | |
| final List<ThermostatZone> zones; | |
| final Function(QuickAction) onSave; | |
| final VoidCallback onRemove; | |
| final VoidCallback onClose; | |
| const OverrideOptionsSheet({ | |
| super.key, | |
| required this.action, | |
| required this.zones, | |
| required this.onSave, | |
| required this.onRemove, | |
| required this.onClose, | |
| }); | |
| @override | |
| State<OverrideOptionsSheet> createState() => _OverrideOptionsSheetState(); | |
| } | |
| class _OverrideOptionsSheetState extends State<OverrideOptionsSheet> { | |
| late QuickAction _action; | |
| late TextEditingController _customNameController; | |
| late List<String> _selectedZones; | |
| final List<String> _durationOptions = [ | |
| '1 hour', | |
| '2 hours', | |
| '3 hours', | |
| '4 hours', | |
| '6 hours', | |
| '8 hours', | |
| '12 hours', | |
| '1 Day', | |
| 'Today', | |
| ]; | |
| final List<String> _scheduleOptions = [ | |
| 'Saturday', | |
| 'Sunday', | |
| 'Weekday', | |
| 'Weekend', | |
| ]; | |
| final List<double> _tempOffsetOptions = [1.0, 2.0, 3.0, 4.0, 5.0]; | |
| final List<double> _tempOptions = [10.0, 12.0, 15.0, 18.0, 20.0, 22.0, 25.0, 28.0, 30.0]; | |
| @override | |
| void initState() { | |
| super.initState(); | |
| _action = widget.action; | |
| _customNameController = TextEditingController(text: _action.customName ?? 'Custom'); | |
| _selectedZones = List.from(_action.selectedZones); | |
| } | |
| @override | |
| void dispose() { | |
| _customNameController.dispose(); | |
| super.dispose(); | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| return Container( | |
| margin: EdgeInsets.only(top: _getTopMargin()), | |
| decoration: const BoxDecoration( | |
| color: GleneaglesColors.white, | |
| borderRadius: BorderRadius.vertical(top: Radius.circular(28)), | |
| ), | |
| child: SingleChildScrollView( | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| // Header | |
| Padding( | |
| padding: const EdgeInsets.fromLTRB(24, 24, 24, 0), | |
| child: Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Expanded( | |
| child: Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| _action.name, | |
| style: TextStyle( | |
| fontSize: 24, | |
| fontWeight: FontWeight.bold, | |
| color: GleneaglesColors.dark, | |
| ), | |
| ), | |
| const SizedBox(height: 4), | |
| Text( | |
| _action.description, | |
| style: TextStyle( | |
| fontSize: 14, | |
| color: GleneaglesColors.textSecondary, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| GestureDetector( | |
| onTap: widget.onClose, | |
| child: Icon( | |
| Icons.close, | |
| color: GleneaglesColors.primary, | |
| size: 24, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| const SizedBox(height: 24), | |
| // Options based on action type | |
| Padding( | |
| padding: const EdgeInsets.symmetric(horizontal: 24), | |
| child: _buildOptionsForType(), | |
| ), | |
| const SizedBox(height: 24), | |
| // Buttons | |
| Padding( | |
| padding: const EdgeInsets.fromLTRB(24, 0, 24, 24), | |
| child: Column( | |
| children: [ | |
| // Remove button | |
| GestureDetector( | |
| onTap: widget.onRemove, | |
| child: Container( | |
| width: double.infinity, | |
| height: 50, | |
| decoration: BoxDecoration( | |
| color: GleneaglesColors.white, | |
| borderRadius: BorderRadius.circular(25), | |
| border: Border.all(color: GleneaglesColors.error), | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Remove', | |
| style: TextStyle( | |
| fontSize: 18, | |
| fontWeight: FontWeight.w500, | |
| color: GleneaglesColors.error, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| const SizedBox(height: 12), | |
| // Save button | |
| GestureDetector( | |
| onTap: () { | |
| if (_action.type == QuickActionType.custom) { | |
| _action = _action.copyWith( | |
| customName: _customNameController.text, | |
| selectedZones: _selectedZones, | |
| ); | |
| } | |
| widget.onSave(_action); | |
| }, | |
| child: Container( | |
| width: double.infinity, | |
| height: 50, | |
| decoration: BoxDecoration( | |
| color: GleneaglesColors.dark, | |
| borderRadius: BorderRadius.circular(25), | |
| ), | |
| child: Center( | |
| child: Text( | |
| 'Save', | |
| style: TextStyle( | |
| fontSize: 18, | |
| fontWeight: FontWeight.w500, | |
| color: GleneaglesColors.white, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| double _getTopMargin() { | |
| switch (_action.type) { | |
| case QuickActionType.custom: | |
| return 80; | |
| case QuickActionType.away: | |
| return 150; | |
| default: | |
| return 200; | |
| } | |
| } | |
| Widget _buildOptionsForType() { | |
| switch (_action.type) { | |
| case QuickActionType.energySaver: | |
| case QuickActionType.comfortBoost: | |
| return _buildTempOffsetOptions(); | |
| case QuickActionType.away: | |
| return _buildAwayOptions(); | |
| case QuickActionType.stayHome: | |
| return _buildStayHomeOptions(); | |
| case QuickActionType.custom: | |
| return _buildCustomOptions(); | |
| } | |
| } | |
| Widget _buildTempOffsetOptions() { | |
| return Column( | |
| children: [ | |
| _buildDropdownRow( | |
| label: 'Duration', | |
| value: _action.duration, | |
| options: _durationOptions, | |
| onChanged: (value) { | |
| setState(() { | |
| _action = _action.copyWith(duration: value); | |
| }); | |
| }, | |
| ), | |
| const SizedBox(height: 16), | |
| _buildDropdownRow( | |
| label: 'Set Temperature Offset', | |
| value: '${_action.temperatureOffset?.abs() ?? 3.0}°', | |
| options: _tempOffsetOptions.map((t) => '$t°').toList(), | |
| onChanged: (value) { | |
| final temp = double.tryParse(value.replaceAll('°', '')) ?? 3.0; | |
| setState(() { | |
| _action = _action.copyWith( | |
| temperatureOffset: _action.type == QuickActionType.energySaver ? -temp : temp, | |
| ); | |
| }); | |
| }, | |
| ), | |
| ], | |
| ); | |
| } | |
| Widget _buildAwayOptions() { | |
| return Column( | |
| children: [ | |
| _buildDropdownRow( | |
| label: 'Cool to', | |
| value: '${_action.coolTo?.toStringAsFixed(1) ?? '30.0'}°', | |
| options: _tempOptions.map((t) => '${t.toStringAsFixed(1)}°').toList(), | |
| onChanged: (value) { | |
| setState(() { | |
| _action = _action.copyWith( | |
| coolTo: double.tryParse(value.replaceAll('°', '')) ?? 30.0, | |
| ); | |
| }); | |
| }, | |
| ), | |
| const SizedBox(height: 16), | |
| _buildDropdownRow( | |
| label: 'Heat to', | |
| value: '${_action.heatTo?.toStringAsFixed(1) ?? '15.0'}°', | |
| options: _tempOptions.map((t) => '${t.toStringAsFixed(1)}°').toList(), | |
| onChanged: (value) { | |
| setState(() { | |
| _action = _action.copyWith( | |
| heatTo: double.tryParse(value.replaceAll('°', '')) ?? 15.0, | |
| ); | |
| }); | |
| }, | |
| ), | |
| const SizedBox(height: 16), | |
| _buildToggleRow( | |
| label: 'Hot Water', | |
| value: _action.hotWaterOn, | |
| onChanged: (value) { | |
| setState(() { | |
| _action = _action.copyWith(hotWaterOn: value); | |
| }); | |
| }, | |
| ), | |
| const SizedBox(height: 16), | |
| _buildDropdownRow( | |
| label: 'Duration', | |
| value: _action.duration, | |
| options: _durationOptions, | |
| onChanged: (value) { | |
| setState(() { | |
| _action = _action.copyWith(duration: value); | |
| }); | |
| }, | |
| ), | |
| ], | |
| ); | |
| } | |
| Widget _buildStayHomeOptions() { | |
| return Column( | |
| children: [ | |
| _buildDropdownRow( | |
| label: 'Schedule', | |
| value: _action.schedule ?? 'Saturday', | |
| options: _scheduleOptions, | |
| onChanged: (value) { | |
| setState(() { | |
| _action = _action.copyWith(schedule: value); | |
| }); | |
| }, | |
| ), | |
| const SizedBox(height: 16), | |
| _buildDropdownRow( | |
| label: 'Duration', | |
| value: _action.duration, | |
| options: _durationOptions, | |
| onChanged: (value) { | |
| setState(() { | |
| _action = _action.copyWith(duration: value); | |
| }); | |
| }, | |
| ), | |
| ], | |
| ); | |
| } | |
| Widget _buildCustomOptions() { | |
| return Column( | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| // Custom name field | |
| Text( | |
| 'Custom Name', | |
| style: TextStyle( | |
| fontSize: 14, | |
| color: GleneaglesColors.textSecondary, | |
| ), | |
| ), | |
| const SizedBox(height: 8), | |
| Container( | |
| decoration: BoxDecoration( | |
| border: Border.all(color: GleneaglesColors.border), | |
| borderRadius: BorderRadius.circular(12), | |
| ), | |
| child: TextField( | |
| controller: _customNameController, | |
| style: TextStyle( | |
| fontSize: 16, | |
| color: GleneaglesColors.text, | |
| ), | |
| decoration: InputDecoration( | |
| contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), | |
| border: InputBorder.none, | |
| hintText: 'Enter name', | |
| hintStyle: TextStyle(color: GleneaglesColors.textSecondary), | |
| ), | |
| ), | |
| ), | |
| const SizedBox(height: 16), | |
| _buildDropdownRow( | |
| label: 'Duration', | |
| value: _action.duration, | |
| options: _durationOptions, | |
| onChanged: (value) { | |
| setState(() { | |
| _action = _action.copyWith(duration: value); | |
| }); | |
| }, | |
| ), | |
| const SizedBox(height: 16), | |
| // Zone selection | |
| ...widget.zones.where((z) => z.name != 'Hot Water').map((zone) { | |
| final isSelected = _selectedZones.contains(zone.id); | |
| return Padding( | |
| padding: const EdgeInsets.only(bottom: 8), | |
| child: GestureDetector( | |
| onTap: () { | |
| setState(() { | |
| if (isSelected) { | |
| _selectedZones.remove(zone.id); | |
| } else { | |
| _selectedZones.add(zone.id); | |
| } | |
| }); | |
| }, | |
| child: Container( | |
| padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), | |
| decoration: BoxDecoration( | |
| color: isSelected ? GleneaglesColors.primary.withOpacity(0.1) : null, | |
| border: Border.all( | |
| color: isSelected ? GleneaglesColors.primary : GleneaglesColors.border, | |
| ), | |
| borderRadius: BorderRadius.circular(12), | |
| ), | |
| child: Row( | |
| children: [ | |
| Container( | |
| width: 24, | |
| height: 24, | |
| decoration: BoxDecoration( | |
| color: isSelected ? GleneaglesColors.primary : Colors.transparent, | |
| borderRadius: BorderRadius.circular(6), | |
| border: Border.all( | |
| color: isSelected ? GleneaglesColors.primary : GleneaglesColors.border, | |
| width: 2, | |
| ), | |
| ), | |
| child: isSelected | |
| ? Icon(Icons.check, color: Colors.white, size: 16) | |
| : null, | |
| ), | |
| const SizedBox(width: 12), | |
| Text( | |
| '[${zone.name}]', | |
| style: TextStyle( | |
| fontSize: 16, | |
| color: GleneaglesColors.text, | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ); | |
| }).toList(), | |
| const SizedBox(height: 8), | |
| // Edit Schedule link | |
| Center( | |
| child: TextButton( | |
| onPressed: () { | |
| // Navigate to schedule editor | |
| }, | |
| child: Text( | |
| 'Edit Schedule', | |
| style: TextStyle( | |
| fontSize: 16, | |
| fontWeight: FontWeight.w500, | |
| color: GleneaglesColors.primary, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| Widget _buildDropdownRow({ | |
| required String label, | |
| required String value, | |
| required List<String> options, | |
| required Function(String) onChanged, | |
| }) { | |
| return Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| children: [ | |
| Text( | |
| label, | |
| style: TextStyle( | |
| fontSize: 16, | |
| color: GleneaglesColors.text, | |
| ), | |
| ), | |
| GestureDetector( | |
| onTap: () => _showOptionsPicker(label, value, options, onChanged), | |
| child: Container( | |
| padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), | |
| decoration: BoxDecoration( | |
| border: Border.all(color: GleneaglesColors.border), | |
| borderRadius: BorderRadius.circular(12), | |
| ), | |
| child: Row( | |
| children: [ | |
| Text( | |
| value, | |
| style: TextStyle( | |
| fontSize: 16, | |
| color: GleneaglesColors.text, | |
| ), | |
| ), | |
| const SizedBox(width: 8), | |
| Icon( | |
| Icons.keyboard_arrow_down, | |
| color: GleneaglesColors.textSecondary, | |
| size: 20, | |
| ), | |
| ], | |
| ), | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| Widget _buildToggleRow({ | |
| required String label, | |
| required bool value, | |
| required Function(bool) onChanged, | |
| }) { | |
| return Row( | |
| mainAxisAlignment: MainAxisAlignment.spaceBetween, | |
| children: [ | |
| Text( | |
| label, | |
| style: TextStyle( | |
| fontSize: 16, | |
| color: GleneaglesColors.text, | |
| ), | |
| ), | |
| Container( | |
| decoration: BoxDecoration( | |
| border: Border.all(color: GleneaglesColors.border), | |
| borderRadius: BorderRadius.circular(8), | |
| ), | |
| child: Row( | |
| children: [ | |
| GestureDetector( | |
| onTap: () => onChanged(true), | |
| child: Container( | |
| padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), | |
| decoration: BoxDecoration( | |
| color: value ? GleneaglesColors.primary : Colors.transparent, | |
| borderRadius: const BorderRadius.horizontal(left: Radius.circular(7)), | |
| ), | |
| child: Text( | |
| 'On', | |
| style: TextStyle( | |
| fontSize: 16, | |
| color: value ? Colors.white : GleneaglesColors.text, | |
| ), | |
| ), | |
| ), | |
| ), | |
| GestureDetector( | |
| onTap: () => onChanged(false), | |
| child: Container( | |
| padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), | |
| decoration: BoxDecoration( | |
| color: !value ? GleneaglesColors.dark : Colors.transparent, | |
| borderRadius: const BorderRadius.horizontal(right: Radius.circular(7)), | |
| ), | |
| child: Text( | |
| 'Off', | |
| style: TextStyle( | |
| fontSize: 16, | |
| color: !value ? Colors.white : GleneaglesColors.text, | |
| ), | |
| ), | |
| ), | |
| ), | |
| ], | |
| ), | |
| ), | |
| ], | |
| ); | |
| } | |
| void _showOptionsPicker( | |
| String title, | |
| String currentValue, | |
| List<String> options, | |
| Function(String) onChanged, | |
| ) { | |
| showModalBottomSheet( | |
| context: context, | |
| builder: (context) => Container( | |
| padding: const EdgeInsets.all(24), | |
| child: Column( | |
| mainAxisSize: MainAxisSize.min, | |
| crossAxisAlignment: CrossAxisAlignment.start, | |
| children: [ | |
| Text( | |
| title, | |
| style: TextStyle( | |
| fontSize: 20, | |
| fontWeight: FontWeight.bold, | |
| color: GleneaglesColors.dark, | |
| ), | |
| ), | |
| const SizedBox(height: 16), | |
| ...options.map((option) => ListTile( | |
| title: Text(option), | |
| trailing: currentValue == option | |
| ? Icon(Icons.check, color: GleneaglesColors.primary) | |
| : null, | |
| onTap: () { | |
| onChanged(option); | |
| Navigator.pop(context); | |
| }, | |
| )), | |
| ], | |
| ), | |
| ), | |
| ); | |
| } | |
| } | |
| // ============================================================================ | |
| // MAIN ENTRY POINT | |
| // ============================================================================ | |
| void main() { | |
| WidgetsFlutterBinding.ensureInitialized(); | |
| SystemChrome.setSystemUIOverlayStyle( | |
| const SystemUiOverlayStyle( | |
| statusBarColor: Colors.transparent, | |
| statusBarIconBrightness: Brightness.dark, | |
| ), | |
| ); | |
| runApp(const QuickActionsApp()); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment