Skip to content

Instantly share code, notes, and snippets.

@maharatha
Created December 12, 2025 16:56
Show Gist options
  • Select an option

  • Save maharatha/379df86774571290a2dfc3d6561e6702 to your computer and use it in GitHub Desktop.

Select an option

Save maharatha/379df86774571290a2dfc3d6561e6702 to your computer and use it in GitHub Desktop.
Claude AI & Figma POC
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