Extract settings and harden geofence automation
This commit is contained in:
@@ -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,
|
||||
|
||||
110
lib/features/settings/models/app_theme_preset.dart
Normal file
110
lib/features/settings/models/app_theme_preset.dart
Normal 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),
|
||||
),
|
||||
);
|
||||
}
|
||||
17
lib/features/settings/models/geofence_system_state.dart
Normal file
17
lib/features/settings/models/geofence_system_state.dart
Normal 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;
|
||||
}
|
||||
89
lib/features/settings/providers/settings_providers.dart
Normal file
89
lib/features/settings/providers/settings_providers.dart
Normal 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,
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user