chore: fix Flutter baseline checks
This commit is contained in:
@@ -21,7 +21,7 @@ void main() {
|
|||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
// Инициализация workmanager
|
// Инициализация workmanager
|
||||||
Workmanager().initialize(callbackDispatcher, isInDebugMode: false);
|
Workmanager().initialize(callbackDispatcher);
|
||||||
|
|
||||||
runApp(const ProviderScope(child: IgnisApp()));
|
runApp(const ProviderScope(child: IgnisApp()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,9 +278,10 @@ class GroupsNotifier extends Notifier<List<dynamic>> {
|
|||||||
|
|
||||||
// Если группа залочена (недавно управляли) -- берём локальное состояние
|
// Если группа залочена (недавно управляли) -- берём локальное состояние
|
||||||
if (_lockUntil.containsKey(id) && _lockUntil[id]!.isAfter(now)) {
|
if (_lockUntil.containsKey(id) && _lockUntil[id]!.isAfter(now)) {
|
||||||
final existing = state.cast<dynamic?>().firstWhere(
|
final existing = state.firstWhere(
|
||||||
(old) => old?['id'].toString() == id,
|
(old) => old['id'].toString() == id,
|
||||||
orElse: () => null);
|
orElse: () => null,
|
||||||
|
);
|
||||||
return existing ?? map;
|
return existing ?? map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -311,9 +312,10 @@ class GroupsNotifier extends Notifier<List<dynamic>> {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// При ошибке опроса -- сохраняем предыдущее состояние
|
// При ошибке опроса -- сохраняем предыдущее состояние
|
||||||
final existing = state.cast<dynamic?>().firstWhere(
|
final existing = state.firstWhere(
|
||||||
(s) => s?['id'].toString() == id,
|
(s) => s['id'].toString() == id,
|
||||||
orElse: () => null);
|
orElse: () => null,
|
||||||
|
);
|
||||||
map['last_state'] = existing?['last_state'] ??
|
map['last_state'] = existing?['last_state'] ??
|
||||||
{'state': false, 'brightness': 100, 'temp': 4000};
|
{'state': false, 'brightness': 100, 'temp': 4000};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,9 +47,11 @@ class _ApiKeysScreenState extends ConsumerState<ApiKeysScreen> {
|
|||||||
margin: const EdgeInsets.all(12),
|
margin: const EdgeInsets.all(12),
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.all(12),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.deepOrange.withOpacity(0.15),
|
color: Colors.deepOrange.withValues(alpha: 0.15),
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(color: Colors.deepOrange.withOpacity(0.3)),
|
border: Border.all(
|
||||||
|
color: Colors.deepOrange.withValues(alpha: 0.3),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -159,7 +161,7 @@ class _ApiKeysScreenState extends ConsumerState<ApiKeysScreen> {
|
|||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: const Text('Администратор'),
|
title: const Text('Администратор'),
|
||||||
value: isAdmin,
|
value: isAdmin,
|
||||||
activeColor: Colors.deepOrange,
|
activeThumbColor: Colors.deepOrange,
|
||||||
onChanged: (v) => setDialogState(() => isAdmin = v),
|
onChanged: (v) => setDialogState(() => isAdmin = v),
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
@@ -275,7 +277,7 @@ class _ApiKeyCard extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.amber.withOpacity(0.2),
|
color: Colors.amber.withValues(alpha: 0.2),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -289,7 +291,7 @@ class _ApiKeyCard extends StatelessWidget {
|
|||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.redAccent.withOpacity(0.2),
|
color: Colors.redAccent.withValues(alpha: 0.2),
|
||||||
borderRadius: BorderRadius.circular(4),
|
borderRadius: BorderRadius.circular(4),
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -325,7 +327,7 @@ class _ApiKeyCard extends StatelessWidget {
|
|||||||
String _formatDate(String iso) {
|
String _formatDate(String iso) {
|
||||||
try {
|
try {
|
||||||
final d = DateTime.parse(iso);
|
final d = DateTime.parse(iso);
|
||||||
final pad = (int n) => n.toString().padLeft(2, '0');
|
String pad(int n) => n.toString().padLeft(2, '0');
|
||||||
return '${pad(d.day)}.${pad(d.month)}.${d.year}';
|
return '${pad(d.day)}.${pad(d.month)}.${d.year}';
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return iso;
|
return iso;
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ class _EventRow extends StatelessWidget {
|
|||||||
if (iso.isEmpty) return '';
|
if (iso.isEmpty) return '';
|
||||||
try {
|
try {
|
||||||
final d = DateTime.parse(iso);
|
final d = DateTime.parse(iso);
|
||||||
final pad = (int n) => n.toString().padLeft(2, '0');
|
String pad(int n) => n.toString().padLeft(2, '0');
|
||||||
return '${pad(d.day)}.${pad(d.month)} ${pad(d.hour)}:${pad(d.minute)}:${pad(d.second)}';
|
return '${pad(d.day)}.${pad(d.month)} ${pad(d.hour)}:${pad(d.minute)}:${pad(d.second)}';
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return iso;
|
return iso;
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
value: _geofenceEnabled,
|
value: _geofenceEnabled,
|
||||||
activeColor: Colors.deepOrange,
|
activeThumbColor: Colors.deepOrange,
|
||||||
onChanged: _hasCoordinates
|
onChanged: _hasCoordinates
|
||||||
? (v) => setState(() => _geofenceEnabled = v)
|
? (v) => setState(() => _geofenceEnabled = v)
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -15,15 +15,18 @@ class HomesScreen extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _HomesScreenState extends ConsumerState<HomesScreen> {
|
class _HomesScreenState extends ConsumerState<HomesScreen> {
|
||||||
|
late final UserLocationNotifier _userLocationNotifier;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
Future.microtask(() => ref.read(userLocationProvider.notifier).startWatching());
|
_userLocationNotifier = ref.read(userLocationProvider.notifier);
|
||||||
|
Future.microtask(() => _userLocationNotifier.startWatching());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
ref.read(userLocationProvider.notifier).stopWatching();
|
_userLocationNotifier.stopWatching();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class _RemoteScreenState extends ConsumerState<RemoteScreen> {
|
|||||||
margin: const EdgeInsets.symmetric(
|
margin: const EdgeInsets.symmetric(
|
||||||
horizontal: 12, vertical: 6),
|
horizontal: 12, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.redAccent.withOpacity(0.3),
|
color: Colors.redAccent.withValues(alpha: 0.3),
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.delete, color: Colors.redAccent),
|
child: const Icon(Icons.delete, color: Colors.redAccent),
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ class _AddScheduleSheetState extends ConsumerState<_AddScheduleSheet> {
|
|||||||
|
|
||||||
// Выбор группы
|
// Выбор группы
|
||||||
DropdownButtonFormField<String>(
|
DropdownButtonFormField<String>(
|
||||||
value: _selectedGroupId,
|
initialValue: _selectedGroupId,
|
||||||
decoration: const InputDecoration(labelText: 'Группа'),
|
decoration: const InputDecoration(labelText: 'Группа'),
|
||||||
items: groups.map((g) {
|
items: groups.map((g) {
|
||||||
final id = g['id'].toString();
|
final id = g['id'].toString();
|
||||||
@@ -256,7 +256,7 @@ class _AddScheduleSheetState extends ConsumerState<_AddScheduleSheet> {
|
|||||||
title: Text(_targetState ? 'Включить' : 'Выключить'),
|
title: Text(_targetState ? 'Включить' : 'Выключить'),
|
||||||
subtitle: const Text('Действие при срабатывании'),
|
subtitle: const Text('Действие при срабатывании'),
|
||||||
value: _targetState,
|
value: _targetState,
|
||||||
activeColor: Colors.deepOrange,
|
activeThumbColor: Colors.deepOrange,
|
||||||
onChanged: (v) => setState(() => _targetState = v),
|
onChanged: (v) => setState(() => _targetState = v),
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -73,9 +73,12 @@ Future<bool> executeGeofenceCheck() async {
|
|||||||
try {
|
try {
|
||||||
// Сначала lastKnown (мгновенно)
|
// Сначала lastKnown (мгновенно)
|
||||||
pos = await Geolocator.getLastKnownPosition();
|
pos = await Geolocator.getLastKnownPosition();
|
||||||
// Если старше 5 минут -- запрашиваем свежую
|
final lastTimestamp = pos?.timestamp;
|
||||||
|
|
||||||
|
// Если позиции нет или она несвежая -- запрашиваем новую
|
||||||
if (pos == null ||
|
if (pos == null ||
|
||||||
DateTime.now().difference(pos.timestamp).inMinutes > 5) {
|
lastTimestamp == null ||
|
||||||
|
DateTime.now().difference(lastTimestamp).inMinutes > 5) {
|
||||||
pos = await Geolocator.getCurrentPosition(
|
pos = await Geolocator.getCurrentPosition(
|
||||||
locationSettings: const LocationSettings(
|
locationSettings: const LocationSettings(
|
||||||
accuracy: LocationAccuracy.low,
|
accuracy: LocationAccuracy.low,
|
||||||
@@ -87,8 +90,6 @@ Future<bool> executeGeofenceCheck() async {
|
|||||||
return true; // не удалось получить позицию
|
return true; // не удалось получить позицию
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pos == null) return true;
|
|
||||||
|
|
||||||
// 4. Считаем расстояние
|
// 4. Считаем расстояние
|
||||||
final homeLat = (targetHome['latitude'] as num).toDouble();
|
final homeLat = (targetHome['latitude'] as num).toDouble();
|
||||||
final homeLon = (targetHome['longitude'] as num).toDouble();
|
final homeLon = (targetHome['longitude'] as num).toDouble();
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'dart:math';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// Простой цветовой пикер в виде HSV-слайдеров.
|
/// Простой цветовой пикер в виде HSV-слайдеров.
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ class _GroupCardState extends ConsumerState<GroupCard> {
|
|||||||
double? _localBrightness;
|
double? _localBrightness;
|
||||||
double? _localTemp;
|
double? _localTemp;
|
||||||
|
|
||||||
|
int _channelValue(double channel) =>
|
||||||
|
(channel * 255.0).round().clamp(0, 255);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final g = widget.group;
|
final g = widget.group;
|
||||||
@@ -53,7 +56,10 @@ class _GroupCardState extends ConsumerState<GroupCard> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
border: isOn
|
border: isOn
|
||||||
? Border.all(color: cardAccent.withOpacity(0.3), width: 1)
|
? Border.all(
|
||||||
|
color: cardAccent.withValues(alpha: 0.3),
|
||||||
|
width: 1,
|
||||||
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
@@ -84,7 +90,7 @@ class _GroupCardState extends ConsumerState<GroupCard> {
|
|||||||
),
|
),
|
||||||
Switch(
|
Switch(
|
||||||
value: isOn,
|
value: isOn,
|
||||||
activeColor: Colors.deepOrange,
|
activeThumbColor: Colors.deepOrange,
|
||||||
onChanged: (v) =>
|
onChanged: (v) =>
|
||||||
ref.read(groupsProvider.notifier).toggleGroup(id, v),
|
ref.read(groupsProvider.notifier).toggleGroup(id, v),
|
||||||
),
|
),
|
||||||
@@ -166,7 +172,12 @@ class _GroupCardState extends ConsumerState<GroupCard> {
|
|||||||
initialColor: Color.fromARGB(255, r, gVal, b),
|
initialColor: Color.fromARGB(255, r, gVal, b),
|
||||||
onColorChanged: (c) {
|
onColorChanged: (c) {
|
||||||
// Обновление UI-превью -- через debounce отправляется на сервер
|
// Обновление UI-превью -- через debounce отправляется на сервер
|
||||||
ref.read(groupsProvider.notifier).setColor(id, c.red, c.green, c.blue);
|
ref.read(groupsProvider.notifier).setColor(
|
||||||
|
id,
|
||||||
|
_channelValue(c.r),
|
||||||
|
_channelValue(c.g),
|
||||||
|
_channelValue(c.b),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -257,7 +268,9 @@ class _ModeChip extends StatelessWidget {
|
|||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 200),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: selected ? Colors.deepOrange.withOpacity(0.2) : Colors.white10,
|
color: selected
|
||||||
|
? Colors.deepOrange.withValues(alpha: 0.2)
|
||||||
|
: Colors.white10,
|
||||||
borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
border: selected
|
border: selected
|
||||||
? Border.all(color: Colors.deepOrange, width: 1)
|
? Border.all(color: Colors.deepOrange, width: 1)
|
||||||
|
|||||||
@@ -1,30 +1,24 @@
|
|||||||
// This is a basic Flutter widget test.
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import 'package:ignis_app/main.dart';
|
import 'package:ignis_app/main.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
// Build our app and trigger a frame.
|
|
||||||
await tester.pumpWidget(const MyApp());
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
testWidgets('app opens homes screen when no homes are configured',
|
||||||
expect(find.text('0'), findsOneWidget);
|
(WidgetTester tester) async {
|
||||||
expect(find.text('1'), findsNothing);
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
await tester.pumpWidget(
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
const ProviderScope(
|
||||||
await tester.pump();
|
child: IgnisApp(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
expect(find.text('ДОМА'), findsOneWidget);
|
||||||
expect(find.text('0'), findsNothing);
|
expect(find.text('Нет добавленных домов'), findsOneWidget);
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user