Preserve Flutter mirror-os codebase
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
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'),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user