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

@@ -20,6 +20,7 @@ class GeofenceAutomationService {
final apiKey = await _settingsService.requireHomeApiKey(home.id);
await _invoke('armGeofence', {
'homeId': home.id,
'homeName': home.name,
'baseUrl': home.url,
'apiKey': apiKey,
'latitude': home.latitude,

View File

@@ -0,0 +1,110 @@
import 'package:flutter/material.dart';
enum AppThemePreset {
ember,
graphite,
moss;
static const AppThemePreset fallback = AppThemePreset.ember;
String get storageValue => switch (this) {
AppThemePreset.ember => 'ember',
AppThemePreset.graphite => 'graphite',
AppThemePreset.moss => 'moss',
};
String get title => switch (this) {
AppThemePreset.ember => 'Ember',
AppThemePreset.graphite => 'Graphite',
AppThemePreset.moss => 'Moss',
};
String get subtitle => switch (this) {
AppThemePreset.ember => 'Тёплая тёмная тема по умолчанию',
AppThemePreset.graphite => 'Холодная тёмная тема для пульта',
AppThemePreset.moss => 'Тёмная зелёная тема для спокойного режима',
};
Color get accentColor => switch (this) {
AppThemePreset.ember => const Color(0xFFFF6B2C),
AppThemePreset.graphite => const Color(0xFF5BC0BE),
AppThemePreset.moss => const Color(0xFF8AA05A),
};
ThemeData get themeData => switch (this) {
AppThemePreset.ember => _buildDarkTheme(
seedColor: const Color(0xFFFF6B2C),
scaffoldBackgroundColor: const Color(0xFF0E0E0E),
surfaceColor: const Color(0xFF1C1A18),
appBarColor: const Color(0xFF191715),
),
AppThemePreset.graphite => _buildDarkTheme(
seedColor: const Color(0xFF5BC0BE),
scaffoldBackgroundColor: const Color(0xFF0C1217),
surfaceColor: const Color(0xFF151D24),
appBarColor: const Color(0xFF121920),
),
AppThemePreset.moss => _buildDarkTheme(
seedColor: const Color(0xFF8AA05A),
scaffoldBackgroundColor: const Color(0xFF10140F),
surfaceColor: const Color(0xFF1A2118),
appBarColor: const Color(0xFF161C15),
),
};
static AppThemePreset fromStorageValue(String? value) {
for (final preset in AppThemePreset.values) {
if (preset.storageValue == value) {
return preset;
}
}
return fallback;
}
}
ThemeData _buildDarkTheme({
required Color seedColor,
required Color scaffoldBackgroundColor,
required Color surfaceColor,
required Color appBarColor,
}) {
final scheme = ColorScheme.fromSeed(
seedColor: seedColor,
brightness: Brightness.dark,
);
return ThemeData(
useMaterial3: true,
colorScheme: scheme,
scaffoldBackgroundColor: scaffoldBackgroundColor,
appBarTheme: AppBarTheme(
backgroundColor: appBarColor,
foregroundColor: Colors.white,
elevation: 0,
centerTitle: true,
),
cardTheme: CardThemeData(
color: surfaceColor,
elevation: 1.5,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
),
listTileTheme: const ListTileThemeData(iconColor: Colors.white70),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: surfaceColor,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(14)),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: Colors.white.withValues(alpha: 0.08)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide(color: scheme.primary, width: 1.4),
),
),
sliderTheme: const SliderThemeData(
trackHeight: 4,
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 8),
),
);
}

View File

@@ -0,0 +1,17 @@
enum GeofenceSystemIssue {
noActiveHome,
missingCoordinates,
locationServicesDisabled,
permissionDenied,
permissionDeniedForever,
backgroundPermissionRequired,
ready,
}
class GeofenceSystemState {
final GeofenceSystemIssue issue;
const GeofenceSystemState(this.issue);
bool get isReady => issue == GeofenceSystemIssue.ready;
}

View File

@@ -0,0 +1,89 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';
import '../../homes/providers/homes_providers.dart';
import '../../shared/providers/core_providers.dart';
import '../models/app_theme_preset.dart';
import '../models/geofence_system_state.dart';
final initialAppThemePresetProvider = Provider<AppThemePreset>(
(ref) => AppThemePreset.fallback,
);
final appThemeProvider = NotifierProvider<AppThemeNotifier, AppThemePreset>(
AppThemeNotifier.new,
);
class AppThemeNotifier extends Notifier<AppThemePreset> {
@override
AppThemePreset build() => ref.read(initialAppThemePresetProvider);
Future<void> setTheme(AppThemePreset preset) async {
if (state == preset) {
return;
}
state = preset;
await ref.read(settingsServiceProvider).setAppThemePreset(preset);
}
}
abstract class GeofenceSystemStatusService {
Future<GeofenceSystemState> inspect({
required bool hasActiveHome,
required bool hasCoordinates,
});
}
class DeviceGeofenceSystemStatusService implements GeofenceSystemStatusService {
@override
Future<GeofenceSystemState> inspect({
required bool hasActiveHome,
required bool hasCoordinates,
}) async {
if (!hasActiveHome) {
return const GeofenceSystemState(GeofenceSystemIssue.noActiveHome);
}
if (!hasCoordinates) {
return const GeofenceSystemState(GeofenceSystemIssue.missingCoordinates);
}
if (!await Geolocator.isLocationServiceEnabled()) {
return const GeofenceSystemState(
GeofenceSystemIssue.locationServicesDisabled,
);
}
final permission = await Geolocator.checkPermission();
return switch (permission) {
LocationPermission.denied => const GeofenceSystemState(
GeofenceSystemIssue.permissionDenied,
),
LocationPermission.deniedForever => const GeofenceSystemState(
GeofenceSystemIssue.permissionDeniedForever,
),
LocationPermission.whileInUse => const GeofenceSystemState(
GeofenceSystemIssue.backgroundPermissionRequired,
),
LocationPermission.always => const GeofenceSystemState(
GeofenceSystemIssue.ready,
),
_ => const GeofenceSystemState(GeofenceSystemIssue.permissionDenied),
};
}
}
final geofenceSystemStatusServiceProvider =
Provider<GeofenceSystemStatusService>(
(ref) => DeviceGeofenceSystemStatusService(),
);
final geofenceSystemStatusProvider = FutureProvider<GeofenceSystemState>((
ref,
) async {
final currentHome = ref.watch(currentHomeProvider);
return ref
.watch(geofenceSystemStatusServiceProvider)
.inspect(
hasActiveHome: currentHome != null,
hasCoordinates: currentHome?.hasCoordinates == true,
);
});