feat: type remote device models

This commit is contained in:
Artem Kokos
2026-04-23 20:44:51 +07:00
parent 736a61d54b
commit fa403bfcce
9 changed files with 619 additions and 189 deletions

203
lib/models/ignis_group.dart Normal file
View File

@@ -0,0 +1,203 @@
class IgnisGroup {
final String id;
final String name;
final List<String> macs;
final IgnisGroupState state;
const IgnisGroup({
required this.id,
required this.name,
this.macs = const [],
this.state = const IgnisGroupState(),
});
IgnisGroup copyWith({
String? id,
String? name,
List<String>? macs,
IgnisGroupState? state,
}) {
return IgnisGroup(
id: id ?? this.id,
name: name ?? this.name,
macs: macs ?? this.macs,
state: state ?? this.state,
);
}
static IgnisGroup fromApi(Object? value, {String? fallbackId}) {
if (value is! Map) {
final id = value?.toString() ?? fallbackId;
if (id == null || id.isEmpty) {
throw const FormatException('group должен быть объектом или id');
}
return IgnisGroup(id: id, name: id);
}
final map = Map<String, dynamic>.from(value);
final id = _stringValue(map, const ['id', 'group_id']) ?? fallbackId;
if (id == null || id.isEmpty) {
throw const FormatException('group не содержит id');
}
final name = _stringValue(map, const ['name', 'label']) ?? id;
final macs = _stringList(
map['macs'] ?? map['devices'] ?? map['device_ids'],
);
final state = IgnisGroupState.fromApi(
map['last_state'] ?? map['state'] ?? map['status'],
);
return IgnisGroup(id: id, name: name, macs: macs, state: state);
}
static List<IgnisGroup> listFromApi(Object? data) {
final values = _collectionValues(data, const ['data', 'groups']);
return values.map((value) {
if (value.entryKey == null) return IgnisGroup.fromApi(value.value);
return IgnisGroup.fromApi(value.value, fallbackId: value.entryKey);
}).toList();
}
}
class IgnisGroupState {
final bool isOn;
final int brightness;
final int temp;
final int r;
final int g;
final int b;
final String? sceneId;
const IgnisGroupState({
this.isOn = false,
this.brightness = 100,
this.temp = 4000,
this.r = 255,
this.g = 200,
this.b = 100,
this.sceneId,
});
IgnisGroupState copyWith({
bool? isOn,
int? brightness,
int? temp,
int? r,
int? g,
int? b,
String? sceneId,
}) {
return IgnisGroupState(
isOn: isOn ?? this.isOn,
brightness: brightness ?? this.brightness,
temp: temp ?? this.temp,
r: r ?? this.r,
g: g ?? this.g,
b: b ?? this.b,
sceneId: sceneId ?? this.sceneId,
);
}
IgnisGroupState applyPatch(Map<String, dynamic> patch) {
return copyWith(
isOn: patch.containsKey('state') ? patch['state'] == true : null,
brightness: _intValue(patch['brightness'] ?? patch['dimming']),
temp: _intValue(patch['temp']),
r: _intValue(patch['r']),
g: _intValue(patch['g']),
b: _intValue(patch['b']),
sceneId: patch['scene']?.toString(),
);
}
static IgnisGroupState fromApi(Object? value, {IgnisGroupState? fallback}) {
final base = fallback ?? const IgnisGroupState();
if (value is! Map) return base;
final map = Map<String, dynamic>.from(value);
return base.applyPatch({
'state': map['state'],
'brightness': map['brightness'] ?? map['dimming'],
'temp': map['temp'],
'r': map['r'],
'g': map['g'],
'b': map['b'],
'scene': map['scene'],
});
}
static IgnisGroupState? firstFromStatusResponse(
Object? data, {
IgnisGroupState? fallback,
}) {
if (data is! Map) return fallback;
final map = Map<String, dynamic>.from(data);
final results = map['results'];
if (results is! List || results.isEmpty) return fallback;
Object? validResult;
for (final result in results) {
if (result is Map &&
result['status'] != null &&
result['error'] == null) {
validResult = result;
break;
}
}
validResult ??= results.first;
if (validResult is! Map) return fallback;
return IgnisGroupState.fromApi(validResult['status'], fallback: fallback);
}
}
String? _stringValue(Map<String, dynamic> map, List<String> keys) {
for (final key in keys) {
final value = map[key];
if (value != null && value.toString().isNotEmpty) {
return value.toString();
}
}
return null;
}
int? _intValue(Object? value) {
if (value is int) return value;
if (value is num) return value.toInt();
return int.tryParse(value?.toString() ?? '');
}
List<String> _stringList(Object? value) {
if (value is! List) return const [];
return value.map((item) => item.toString()).toList();
}
List<_CollectionValue> _collectionValues(Object? data, List<String> wrappers) {
if (data is List) {
return data.map((value) => _CollectionValue(value)).toList();
}
if (data is Map) {
final map = Map<String, dynamic>.from(data);
for (final wrapper in wrappers) {
final value = map[wrapper];
if (value is List) {
return value.map((item) => _CollectionValue(item)).toList();
}
}
return map.entries
.map((entry) => _CollectionValue(entry.value, entryKey: entry.key))
.toList();
}
throw const FormatException('ожидался список или объект');
}
class _CollectionValue {
final Object? value;
final String? entryKey;
const _CollectionValue(this.value, {this.entryKey});
}