Extract settings and harden geofence automation

This commit is contained in:
Artem Kokos
2026-05-15 10:18:46 +07:00
parent 1963488479
commit d796537917
21 changed files with 1392 additions and 278 deletions

View File

@@ -4,9 +4,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../app/error_message.dart';
import '../models/home_config.dart';
import '../providers/providers.dart';
import '../widgets/build_info_text.dart';
import 'home_edit_screen.dart';
import 'remote_screen.dart';
import 'settings_screen.dart';
/// Экран "Дома" -- список серверов Ignis.
/// Пользователь может добавить, удалить, переключить активный дом.
@@ -61,108 +61,100 @@ class _HomesScreenState extends ConsumerState<HomesScreen>
appBar: AppBar(
title: const Text('ДОМА'),
automaticallyImplyLeading: false,
),
body: Column(
children: [
Expanded(
child: homes.isEmpty
? const _EmptyHomesView()
: RefreshIndicator(
color: Colors.deepOrange,
onRefresh: _refreshEnvironmentState,
child: ListView(
padding: const EdgeInsets.all(12),
children: [
...homes.map((home) {
final isActive = currentHome?.id == home.id;
final isSwitching = _switchingHomeId == home.id;
final isDeleting = _deletingHomeId == home.id;
final isBusy = isSwitching || isDeleting;
final distKm = location.distanceToKm(
home.latitude,
home.longitude,
);
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
enabled: !isBusy,
leading: Icon(
Icons.home,
color: isActive
? Colors.deepOrange
: Colors.white38,
size: 28,
),
title: Text(
home.name,
style: TextStyle(
fontWeight: isActive
? FontWeight.bold
: FontWeight.normal,
color: isActive
? Colors.deepOrange
: Colors.white,
),
),
subtitle: _HomeSubtitle(
home: home,
location: location,
distKm: distKm,
isActive: isActive,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isBusy)
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
else ...[
IconButton(
icon: const Icon(
Icons.edit,
size: 20,
color: Colors.white38,
),
onPressed: () => _editHome(context, home),
),
IconButton(
icon: const Icon(
Icons.delete_outline,
size: 20,
color: Colors.redAccent,
),
onPressed: () =>
_confirmDelete(context, home),
),
],
],
),
onTap: isBusy
? null
: () => _selectHome(context, home),
),
);
}),
],
),
),
),
const SafeArea(
top: false,
minimum: EdgeInsets.only(bottom: 10),
child: Padding(
padding: EdgeInsets.only(bottom: 6),
child: BuildInfoText(),
actions: [
IconButton(
icon: const Icon(Icons.settings_outlined),
tooltip: 'Настройки',
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) =>
const SettingsScreen(entryPoint: SettingsEntryPoint.homes),
),
),
),
],
),
body: homes.isEmpty
? const _EmptyHomesView()
: RefreshIndicator(
color: Colors.deepOrange,
onRefresh: _refreshEnvironmentState,
child: ListView(
padding: const EdgeInsets.all(12),
children: [
...homes.map((home) {
final isActive = currentHome?.id == home.id;
final isSwitching = _switchingHomeId == home.id;
final isDeleting = _deletingHomeId == home.id;
final isBusy = isSwitching || isDeleting;
final distKm = location.distanceToKm(
home.latitude,
home.longitude,
);
return Card(
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
enabled: !isBusy,
leading: Icon(
Icons.home,
color: isActive ? Colors.deepOrange : Colors.white38,
size: 28,
),
title: Text(
home.name,
style: TextStyle(
fontWeight: isActive
? FontWeight.bold
: FontWeight.normal,
color: isActive ? Colors.deepOrange : Colors.white,
),
),
subtitle: _HomeSubtitle(
home: home,
location: location,
distKm: distKm,
isActive: isActive,
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (isBusy)
const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
else ...[
IconButton(
icon: const Icon(
Icons.edit,
size: 20,
color: Colors.white38,
),
onPressed: () => _editHome(context, home),
),
IconButton(
icon: const Icon(
Icons.delete_outline,
size: 20,
color: Colors.redAccent,
),
onPressed: () => _confirmDelete(context, home),
),
],
],
),
onTap: isBusy ? null : () => _selectHome(context, home),
),
);
}),
SizedBox(height: MediaQuery.of(context).padding.bottom + 80),
],
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.deepOrange,
onPressed: () => _addHome(context),
@@ -282,7 +274,9 @@ class _HomesScreenState extends ConsumerState<HomesScreen>
}
Future<void> _syncLocationWatching() async {
final shouldWatch = ref.read(homesProvider).any((home) => home.hasCoordinates);
final shouldWatch = ref
.read(homesProvider)
.any((home) => home.hasCoordinates);
if (shouldWatch == _isWatchingLocation) {
return;
}