Waaaay big enchancements

This commit is contained in:
Artem Kokos
2026-04-02 23:51:28 +07:00
parent 6221fbcc71
commit 5e09f41747
14 changed files with 1308 additions and 111 deletions

View File

@@ -17,7 +17,11 @@ class GroupCard extends ConsumerStatefulWidget {
class _GroupCardState extends ConsumerState<GroupCard> {
/// Текущий режим управления: temp (температура) или color (RGB)
String _mode = 'temp';
bool _showColorPicker = false;
// Локальные значения слайдеров -- обновляются мгновенно,
// а на сервер отправляются через debounce в провайдере.
double? _localBrightness;
double? _localTemp;
@override
Widget build(BuildContext context) {
@@ -31,10 +35,14 @@ class _GroupCardState extends ConsumerState<GroupCard> {
final int gVal = g['last_state']?['g'] ?? 200;
final int b = g['last_state']?['b'] ?? 100;
// Значения слайдеров: локальные (если тянем) или серверные
final briValue = (_localBrightness ?? bri.toDouble()).clamp(10.0, 100.0);
final tempValue = (_localTemp ?? temp.toDouble()).clamp(2700.0, 6500.0);
// Цвет подсветки карточки зависит от режима и состояния
final cardAccent = isOn
? (_mode == 'temp'
? Color.lerp(Colors.orange, Colors.blueAccent, (temp - 2700) / 3800)!
? Color.lerp(Colors.orange, Colors.blueAccent, (tempValue - 2700) / 3800)!
: Color.fromARGB(255, r, gVal, b))
: Colors.white12;
@@ -90,15 +98,19 @@ class _GroupCardState extends ConsumerState<GroupCard> {
// Яркость
_SliderRow(
icon: Icons.sunny,
value: bri.toDouble().clamp(10, 100),
value: briValue,
min: 10,
max: 100,
divisions: 9,
label: "$bri%",
label: "${briValue.toInt()}%",
activeColor: Colors.amber,
onChanged: (v) => ref
.read(groupsProvider.notifier)
.setBrightness(id, v.toInt()),
onChanged: (v) {
setState(() => _localBrightness = v);
ref.read(groupsProvider.notifier).setBrightness(id, v.toInt());
},
onChangeEnd: (v) {
setState(() => _localBrightness = null);
},
),
// Переключатель режима: температура / цвет / сцена
@@ -108,30 +120,21 @@ class _GroupCardState extends ConsumerState<GroupCard> {
label: 'Темп.',
icon: Icons.wb_twilight,
selected: _mode == 'temp',
onTap: () => setState(() {
_mode = 'temp';
_showColorPicker = false;
}),
onTap: () => setState(() => _mode = 'temp'),
),
const SizedBox(width: 8),
_ModeChip(
label: 'Цвет',
icon: Icons.palette,
selected: _mode == 'color',
onTap: () => setState(() {
_mode = 'color';
_showColorPicker = true;
}),
onTap: () => setState(() => _mode = 'color'),
),
const SizedBox(width: 8),
_ModeChip(
label: 'Сцена',
icon: Icons.auto_awesome,
selected: _mode == 'scene',
onTap: () => setState(() {
_mode = 'scene';
_showColorPicker = false;
}),
onTap: () => setState(() => _mode = 'scene'),
),
],
),
@@ -141,25 +144,30 @@ class _GroupCardState extends ConsumerState<GroupCard> {
if (_mode == 'temp')
_SliderRow(
icon: Icons.wb_twilight,
value: temp.toDouble().clamp(2700, 6500),
value: tempValue,
min: 2700,
max: 6500,
divisions: 38, // шаг 100K
label: "${temp}K",
label: "${tempValue.toInt()}K",
activeColor: Color.lerp(
Colors.orange, Colors.blueAccent, (temp - 2700) / 3800),
onChanged: (v) => ref
.read(groupsProvider.notifier)
.setTemperature(id, v.toInt()),
Colors.orange, Colors.blueAccent, (tempValue - 2700) / 3800),
onChanged: (v) {
setState(() => _localTemp = v);
ref.read(groupsProvider.notifier).setTemperature(id, v.toInt());
},
onChangeEnd: (v) {
setState(() => _localTemp = null);
},
),
// ─── Режим: цвет ───
if (_mode == 'color')
SimpleColorPicker(
initialColor: Color.fromARGB(255, r, gVal, b),
onColorChanged: (c) => ref
.read(groupsProvider.notifier)
.setColor(id, c.red, c.green, c.blue),
onColorChanged: (c) {
// Обновление UI-превью -- через debounce отправляется на сервер
ref.read(groupsProvider.notifier).setColor(id, c.red, c.green, c.blue);
},
),
// ─── Режим: сцена ───
@@ -183,6 +191,7 @@ class _SliderRow extends StatelessWidget {
final String label;
final Color? activeColor;
final ValueChanged<double> onChanged;
final ValueChanged<double>? onChangeEnd;
const _SliderRow({
required this.icon,
@@ -193,6 +202,7 @@ class _SliderRow extends StatelessWidget {
required this.label,
this.activeColor,
required this.onChanged,
this.onChangeEnd,
});
@override
@@ -209,6 +219,7 @@ class _SliderRow extends StatelessWidget {
label: label,
activeColor: activeColor,
onChanged: onChanged,
onChangeEnd: onChangeEnd,
),
),
SizedBox(
@@ -272,17 +283,25 @@ class _ModeChip extends StatelessWidget {
}
/// Выбор сцены из списка, загруженного с сервера
class _SceneSelector extends ConsumerWidget {
class _SceneSelector extends ConsumerStatefulWidget {
final String groupId;
const _SceneSelector({required this.groupId});
@override
Widget build(BuildContext context, WidgetRef ref) {
ConsumerState<_SceneSelector> createState() => _SceneSelectorState();
}
class _SceneSelectorState extends ConsumerState<_SceneSelector> {
bool _loadStarted = false;
@override
Widget build(BuildContext context) {
final scenes = ref.watch(scenesProvider);
if (scenes.isEmpty) {
if (scenes.isEmpty && !_loadStarted) {
// Загрузить сцены при первом показе
_loadStarted = true;
Future.microtask(() => ref.read(scenesProvider.notifier).load());
return const Padding(
padding: EdgeInsets.all(8.0),
@@ -296,24 +315,40 @@ class _SceneSelector extends ConsumerWidget {
);
}
if (scenes.isEmpty) {
return const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Сцены не найдены',
style: TextStyle(color: Colors.white38, fontSize: 12),
),
);
}
return Wrap(
spacing: 8,
runSpacing: 4,
children: scenes.map((scene) {
// Сцена может быть строкой или Map с полем 'name'/'id'
final sceneName = scene is String
? scene
: (scene['name'] ?? scene['id'] ?? scene.toString());
final sceneId = scene is String
? scene
: (scene['id'] ?? scene['name'] ?? scene.toString());
String sceneName;
String sceneId;
if (scene is String) {
sceneName = scene;
sceneId = scene;
} else if (scene is Map) {
sceneName = (scene['name'] ?? scene['id'] ?? scene.toString()).toString();
sceneId = (scene['id'] ?? scene['name'] ?? scene.toString()).toString();
} else {
sceneName = scene.toString();
sceneId = scene.toString();
}
return ActionChip(
label: Text(sceneName.toString(), style: const TextStyle(fontSize: 12)),
label: Text(sceneName, style: const TextStyle(fontSize: 12)),
backgroundColor: Colors.white10,
onPressed: () => ref
.read(groupsProvider.notifier)
.setScene(groupId, sceneId.toString()),
.setScene(widget.groupId, sceneId),
);
}).toList(),
);