diff --git a/lib/main.dart b/lib/main.dart index 28cf166..bbcfad3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,7 @@ void main() { WidgetsFlutterBinding.ensureInitialized(); // Инициализация workmanager - Workmanager().initialize(callbackDispatcher, isInDebugMode: false); + Workmanager().initialize(callbackDispatcher); runApp(const ProviderScope(child: IgnisApp())); } @@ -132,4 +132,4 @@ class _MainGateState extends ConsumerState { ), ); } -} \ No newline at end of file +} diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 9501c22..ae43cbd 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -278,9 +278,10 @@ class GroupsNotifier extends Notifier> { // Если группа залочена (недавно управляли) -- берём локальное состояние if (_lockUntil.containsKey(id) && _lockUntil[id]!.isAfter(now)) { - final existing = state.cast().firstWhere( - (old) => old?['id'].toString() == id, - orElse: () => null); + final existing = state.firstWhere( + (old) => old['id'].toString() == id, + orElse: () => null, + ); return existing ?? map; } @@ -311,9 +312,10 @@ class GroupsNotifier extends Notifier> { } } catch (e) { // При ошибке опроса -- сохраняем предыдущее состояние - final existing = state.cast().firstWhere( - (s) => s?['id'].toString() == id, - orElse: () => null); + final existing = state.firstWhere( + (s) => s['id'].toString() == id, + orElse: () => null, + ); map['last_state'] = existing?['last_state'] ?? {'state': false, 'brightness': 100, 'temp': 4000}; } @@ -743,4 +745,4 @@ String formatDistance(double km) { } else { return '${km.round()} км'; } -} \ No newline at end of file +} diff --git a/lib/screens/api_keys_screen.dart b/lib/screens/api_keys_screen.dart index 0f9a2c3..f48b9bd 100644 --- a/lib/screens/api_keys_screen.dart +++ b/lib/screens/api_keys_screen.dart @@ -47,9 +47,11 @@ class _ApiKeysScreenState extends ConsumerState { margin: const EdgeInsets.all(12), padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: Colors.deepOrange.withOpacity(0.15), + color: Colors.deepOrange.withValues(alpha: 0.15), 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( crossAxisAlignment: CrossAxisAlignment.start, @@ -159,7 +161,7 @@ class _ApiKeysScreenState extends ConsumerState { SwitchListTile( title: const Text('Администратор'), value: isAdmin, - activeColor: Colors.deepOrange, + activeThumbColor: Colors.deepOrange, onChanged: (v) => setDialogState(() => isAdmin = v), contentPadding: EdgeInsets.zero, ), @@ -275,7 +277,7 @@ class _ApiKeyCard extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: Colors.amber.withOpacity(0.2), + color: Colors.amber.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(4), ), child: const Text( @@ -289,7 +291,7 @@ class _ApiKeyCard extends StatelessWidget { Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( - color: Colors.redAccent.withOpacity(0.2), + color: Colors.redAccent.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(4), ), child: const Text( @@ -325,7 +327,7 @@ class _ApiKeyCard extends StatelessWidget { String _formatDate(String iso) { try { 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}'; } catch (_) { return iso; diff --git a/lib/screens/event_log_screen.dart b/lib/screens/event_log_screen.dart index 83f1f20..aa4eb77 100644 --- a/lib/screens/event_log_screen.dart +++ b/lib/screens/event_log_screen.dart @@ -156,7 +156,7 @@ class _EventRow extends StatelessWidget { if (iso.isEmpty) return ''; try { 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)}'; } catch (_) { return iso; diff --git a/lib/screens/home_edit_screen.dart b/lib/screens/home_edit_screen.dart index 9900f0a..4043765 100644 --- a/lib/screens/home_edit_screen.dart +++ b/lib/screens/home_edit_screen.dart @@ -170,7 +170,7 @@ class _HomeEditScreenState extends ConsumerState { ), ), value: _geofenceEnabled, - activeColor: Colors.deepOrange, + activeThumbColor: Colors.deepOrange, onChanged: _hasCoordinates ? (v) => setState(() => _geofenceEnabled = v) : null, @@ -285,4 +285,4 @@ class _HomeEditScreenState extends ConsumerState { if (mounted) Navigator.of(context).pop(); } -} \ No newline at end of file +} diff --git a/lib/screens/homes_screen.dart b/lib/screens/homes_screen.dart index db0a098..759f57c 100644 --- a/lib/screens/homes_screen.dart +++ b/lib/screens/homes_screen.dart @@ -15,15 +15,18 @@ class HomesScreen extends ConsumerStatefulWidget { } class _HomesScreenState extends ConsumerState { + late final UserLocationNotifier _userLocationNotifier; + @override void initState() { super.initState(); - Future.microtask(() => ref.read(userLocationProvider.notifier).startWatching()); + _userLocationNotifier = ref.read(userLocationProvider.notifier); + Future.microtask(() => _userLocationNotifier.startWatching()); } @override void dispose() { - ref.read(userLocationProvider.notifier).stopWatching(); + _userLocationNotifier.stopWatching(); super.dispose(); } @@ -200,4 +203,4 @@ class _HomesScreenState extends ConsumerState { ), ); } -} \ No newline at end of file +} diff --git a/lib/screens/remote_screen.dart b/lib/screens/remote_screen.dart index 56e8a63..831ca6c 100644 --- a/lib/screens/remote_screen.dart +++ b/lib/screens/remote_screen.dart @@ -179,7 +179,7 @@ class _RemoteScreenState extends ConsumerState { margin: const EdgeInsets.symmetric( horizontal: 12, vertical: 6), decoration: BoxDecoration( - color: Colors.redAccent.withOpacity(0.3), + color: Colors.redAccent.withValues(alpha: 0.3), borderRadius: BorderRadius.circular(16), ), child: const Icon(Icons.delete, color: Colors.redAccent), diff --git a/lib/screens/schedules_screen.dart b/lib/screens/schedules_screen.dart index bd967b9..a5114d8 100644 --- a/lib/screens/schedules_screen.dart +++ b/lib/screens/schedules_screen.dart @@ -240,7 +240,7 @@ class _AddScheduleSheetState extends ConsumerState<_AddScheduleSheet> { // Выбор группы DropdownButtonFormField( - value: _selectedGroupId, + initialValue: _selectedGroupId, decoration: const InputDecoration(labelText: 'Группа'), items: groups.map((g) { final id = g['id'].toString(); @@ -256,7 +256,7 @@ class _AddScheduleSheetState extends ConsumerState<_AddScheduleSheet> { title: Text(_targetState ? 'Включить' : 'Выключить'), subtitle: const Text('Действие при срабатывании'), value: _targetState, - activeColor: Colors.deepOrange, + activeThumbColor: Colors.deepOrange, onChanged: (v) => setState(() => _targetState = v), contentPadding: EdgeInsets.zero, ), diff --git a/lib/services/geofence_worker.dart b/lib/services/geofence_worker.dart index 096f58d..3196093 100644 --- a/lib/services/geofence_worker.dart +++ b/lib/services/geofence_worker.dart @@ -73,9 +73,12 @@ Future executeGeofenceCheck() async { try { // Сначала lastKnown (мгновенно) pos = await Geolocator.getLastKnownPosition(); - // Если старше 5 минут -- запрашиваем свежую + final lastTimestamp = pos?.timestamp; + + // Если позиции нет или она несвежая -- запрашиваем новую if (pos == null || - DateTime.now().difference(pos.timestamp).inMinutes > 5) { + lastTimestamp == null || + DateTime.now().difference(lastTimestamp).inMinutes > 5) { pos = await Geolocator.getCurrentPosition( locationSettings: const LocationSettings( accuracy: LocationAccuracy.low, @@ -87,8 +90,6 @@ Future executeGeofenceCheck() async { return true; // не удалось получить позицию } - if (pos == null) return true; - // 4. Считаем расстояние final homeLat = (targetHome['latitude'] as num).toDouble(); final homeLon = (targetHome['longitude'] as num).toDouble(); @@ -249,4 +250,4 @@ double _haversineMeters( return earthRadiusM * c; } -double _degToRad(double deg) => deg * (math.pi / 180); \ No newline at end of file +double _degToRad(double deg) => deg * (math.pi / 180); diff --git a/lib/widgets/color_picker.dart b/lib/widgets/color_picker.dart index 327d391..c4fc9a8 100644 --- a/lib/widgets/color_picker.dart +++ b/lib/widgets/color_picker.dart @@ -1,4 +1,3 @@ -import 'dart:math'; import 'package:flutter/material.dart'; /// Простой цветовой пикер в виде HSV-слайдеров. diff --git a/lib/widgets/group_card.dart b/lib/widgets/group_card.dart index 9e56f85..cbd4123 100644 --- a/lib/widgets/group_card.dart +++ b/lib/widgets/group_card.dart @@ -23,6 +23,9 @@ class _GroupCardState extends ConsumerState { double? _localBrightness; double? _localTemp; + int _channelValue(double channel) => + (channel * 255.0).round().clamp(0, 255); + @override Widget build(BuildContext context) { final g = widget.group; @@ -53,7 +56,10 @@ class _GroupCardState extends ConsumerState { decoration: BoxDecoration( borderRadius: BorderRadius.circular(16), border: isOn - ? Border.all(color: cardAccent.withOpacity(0.3), width: 1) + ? Border.all( + color: cardAccent.withValues(alpha: 0.3), + width: 1, + ) : null, ), child: Padding( @@ -84,7 +90,7 @@ class _GroupCardState extends ConsumerState { ), Switch( value: isOn, - activeColor: Colors.deepOrange, + activeThumbColor: Colors.deepOrange, onChanged: (v) => ref.read(groupsProvider.notifier).toggleGroup(id, v), ), @@ -166,7 +172,12 @@ class _GroupCardState extends ConsumerState { initialColor: Color.fromARGB(255, r, gVal, b), onColorChanged: (c) { // Обновление 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), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), 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), border: selected ? Border.all(color: Colors.deepOrange, width: 1) diff --git a/test/widget_test.dart b/test/widget_test.dart index 7016f2d..1c4c0bd 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,30 +1,24 @@ -// This is a basic Flutter widget test. -// -// 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_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:ignis_app/main.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + TestWidgetsFlutterBinding.ensureInitialized(); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); + testWidgets('app opens homes screen when no homes are configured', + (WidgetTester tester) async { + SharedPreferences.setMockInitialValues({}); - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); + await tester.pumpWidget( + const ProviderScope( + child: IgnisApp(), + ), + ); + await tester.pumpAndSettle(); - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + expect(find.text('ДОМА'), findsOneWidget); + expect(find.text('Нет добавленных домов'), findsOneWidget); }); }