205 lines
7.0 KiB
Dart
205 lines
7.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:mirror_protocol/mirror_protocol.dart';
|
|
import 'package:mirror_ui/mirror_ui.dart';
|
|
import '../providers/connection_provider.dart';
|
|
import 'ha_entity_picker_screen.dart';
|
|
import 'weather_picker_screen.dart';
|
|
|
|
class DashboardEditorScreen extends ConsumerStatefulWidget {
|
|
const DashboardEditorScreen({super.key});
|
|
|
|
@override
|
|
ConsumerState<DashboardEditorScreen> createState() => _DashboardEditorScreenState();
|
|
}
|
|
|
|
class _DashboardEditorScreenState extends ConsumerState<DashboardEditorScreen> {
|
|
List<DashboardWidgetConfig> _widgets = [];
|
|
bool _loading = true;
|
|
|
|
static const _widgetMeta = <String, ({IconData icon, String label})>{
|
|
'clock': (icon: Icons.access_time, label: 'Clock'),
|
|
'weather': (icon: Icons.cloud, label: 'Weather'),
|
|
'calendar': (icon: Icons.calendar_month, label: 'Calendar'),
|
|
'ha_entities': (icon: Icons.home, label: 'HA Entities'),
|
|
};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_requestConfig();
|
|
}
|
|
|
|
void _requestConfig() {
|
|
final existing = ref.read(dashboardConfigProvider);
|
|
if (existing != null) {
|
|
setState(() {
|
|
_widgets = List.from(existing.widgets);
|
|
_loading = false;
|
|
});
|
|
} else {
|
|
// Request from display
|
|
ref.read(connectionProvider.notifier).send(DashboardConfigRequest());
|
|
// Wait briefly then fall back to default
|
|
Future.delayed(const Duration(seconds: 2), () {
|
|
if (_loading && mounted) {
|
|
setState(() {
|
|
_widgets = List.from(DashboardLayoutConfig.defaultLayout().widgets);
|
|
_loading = false;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
void didChangeDependencies() {
|
|
super.didChangeDependencies();
|
|
// Listen for config response
|
|
ref.listen(dashboardConfigProvider, (prev, next) {
|
|
if (next != null && _loading) {
|
|
setState(() {
|
|
_widgets = List.from(next.widgets);
|
|
_loading = false;
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
void _onReorder(int oldIndex, int newIndex) {
|
|
setState(() {
|
|
if (newIndex > oldIndex) newIndex--;
|
|
final item = _widgets.removeAt(oldIndex);
|
|
_widgets.insert(newIndex, item);
|
|
// Update positions
|
|
for (var i = 0; i < _widgets.length; i++) {
|
|
_widgets[i] = _widgets[i].copyWith(position: i);
|
|
}
|
|
});
|
|
}
|
|
|
|
void _toggleVisibility(int index) {
|
|
setState(() {
|
|
_widgets[index] = _widgets[index].copyWith(visible: !_widgets[index].visible);
|
|
});
|
|
}
|
|
|
|
Future<void> _openConfig(int index) async {
|
|
final widget = _widgets[index];
|
|
switch (widget.type) {
|
|
case 'ha_entities':
|
|
final currentIds =
|
|
(widget.config['entity_ids'] as List<dynamic>?)?.cast<String>() ?? [];
|
|
final result = await Navigator.of(context).push<List<String>>(
|
|
MaterialPageRoute(
|
|
builder: (_) => HaEntityPickerScreen(selectedIds: currentIds),
|
|
),
|
|
);
|
|
if (result != null && mounted) {
|
|
setState(() {
|
|
_widgets[index] = widget.copyWith(
|
|
config: {...widget.config, 'entity_ids': result},
|
|
);
|
|
});
|
|
}
|
|
case 'weather':
|
|
final currentEntity = widget.config['entity_id'] as String?;
|
|
final result = await Navigator.of(context).push<String>(
|
|
MaterialPageRoute(
|
|
builder: (_) => WeatherPickerScreen(selectedEntityId: currentEntity),
|
|
),
|
|
);
|
|
if (result != null && mounted) {
|
|
setState(() {
|
|
_widgets[index] = widget.copyWith(
|
|
config: {...widget.config, 'entity_id': result},
|
|
);
|
|
});
|
|
}
|
|
default:
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('No settings for ${widget.type}')),
|
|
);
|
|
}
|
|
}
|
|
|
|
void _saveLayout() {
|
|
final layout = DashboardLayoutConfig(widgets: _widgets);
|
|
ref.read(connectionProvider.notifier).send(DashboardConfigUpdate(layout: layout));
|
|
ref.read(dashboardConfigProvider.notifier).state = layout;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text('Dashboard layout saved')),
|
|
);
|
|
Navigator.of(context).pop();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Dashboard Layout'),
|
|
backgroundColor: MirrorTheme.background,
|
|
),
|
|
backgroundColor: MirrorTheme.background,
|
|
body: _loading
|
|
? const Center(child: CircularProgressIndicator())
|
|
: Column(
|
|
children: [
|
|
Expanded(
|
|
child: ReorderableListView.builder(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
itemCount: _widgets.length,
|
|
onReorderItem: (oldIndex, newIndex) => _onReorder(oldIndex, newIndex),
|
|
itemBuilder: (context, index) {
|
|
final widget = _widgets[index];
|
|
final meta = _widgetMeta[widget.type];
|
|
final icon = meta?.icon ?? Icons.widgets;
|
|
final label = meta?.label ?? widget.type;
|
|
final hasConfig =
|
|
widget.type == 'ha_entities' || widget.type == 'weather';
|
|
|
|
return Card(
|
|
key: ValueKey(widget.id),
|
|
color: MirrorTheme.surface,
|
|
margin: const EdgeInsets.symmetric(vertical: 4),
|
|
child: ListTile(
|
|
leading: Icon(icon, color: MirrorTheme.accent),
|
|
title: Text(label),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
if (hasConfig)
|
|
IconButton(
|
|
icon: const Icon(Icons.settings, size: 20),
|
|
onPressed: () => _openConfig(index),
|
|
),
|
|
Switch(
|
|
value: widget.visible,
|
|
onChanged: (_) => _toggleVisibility(index),
|
|
activeThumbColor: MirrorTheme.accent,
|
|
),
|
|
const Icon(Icons.drag_handle),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(16),
|
|
child: SizedBox(
|
|
width: double.infinity,
|
|
child: FilledButton.icon(
|
|
onPressed: _saveLayout,
|
|
icon: const Icon(Icons.save),
|
|
label: const Text('Save Layout'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|