feat: secure home credentials
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../models/home_config.dart';
|
||||
import '../providers/providers.dart';
|
||||
import '../services/api_client.dart';
|
||||
|
||||
/// Экран создания или редактирования "дома" (сервера Ignis).
|
||||
class HomeEditScreen extends ConsumerStatefulWidget {
|
||||
@@ -34,7 +35,6 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
if (_isEdit) {
|
||||
_nameCtrl.text = widget.home!.name;
|
||||
_urlCtrl.text = widget.home!.url;
|
||||
_keyCtrl.text = widget.home!.apiKey;
|
||||
if (widget.home!.latitude != null) {
|
||||
_latCtrl.text = widget.home!.latitude.toString();
|
||||
}
|
||||
@@ -42,6 +42,7 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
_lonCtrl.text = widget.home!.longitude.toString();
|
||||
}
|
||||
_geofenceEnabled = widget.home!.geofenceEnabled;
|
||||
_loadApiKey();
|
||||
}
|
||||
|
||||
// Следим за полями координат чтобы обновлять доступность Switch
|
||||
@@ -49,6 +50,15 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
_lonCtrl.addListener(_onCoordsChanged);
|
||||
}
|
||||
|
||||
Future<void> _loadApiKey() async {
|
||||
final apiKey = await ref
|
||||
.read(settingsServiceProvider)
|
||||
.getHomeApiKey(widget.home!.id);
|
||||
if (mounted && apiKey != null) {
|
||||
_keyCtrl.text = apiKey;
|
||||
}
|
||||
}
|
||||
|
||||
void _onCoordsChanged() {
|
||||
// Если координаты очистили -- выключаем геофенс
|
||||
if (!_hasCoordinates && _geofenceEnabled) {
|
||||
@@ -73,9 +83,7 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(_isEdit ? 'РЕДАКТИРОВАТЬ ДОМ' : 'НОВЫЙ ДОМ'),
|
||||
),
|
||||
appBar: AppBar(title: Text(_isEdit ? 'РЕДАКТИРОВАТЬ ДОМ' : 'НОВЫЙ ДОМ')),
|
||||
body: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
@@ -136,7 +144,9 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
hintText: '51.128',
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true, signed: true),
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
@@ -149,7 +159,9 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
hintText: '71.430',
|
||||
),
|
||||
keyboardType: const TextInputType.numberWithOptions(
|
||||
decimal: true, signed: true),
|
||||
decimal: true,
|
||||
signed: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -224,20 +236,41 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
|
||||
Future<void> _save() async {
|
||||
final name = _nameCtrl.text.trim();
|
||||
final url = _urlCtrl.text.trim();
|
||||
final rawUrl = _urlCtrl.text.trim();
|
||||
final key = _keyCtrl.text.trim();
|
||||
final latText = _latCtrl.text.trim();
|
||||
final lonText = _lonCtrl.text.trim();
|
||||
|
||||
if (name.isEmpty || url.isEmpty || key.isEmpty) {
|
||||
if (name.isEmpty || rawUrl.isEmpty || key.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Заполните все обязательные поля')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
late final String url;
|
||||
try {
|
||||
url = IgnisApi.normalizeBaseUrl(rawUrl);
|
||||
final parsed = Uri.parse(url);
|
||||
if ((parsed.scheme != 'http' && parsed.scheme != 'https') ||
|
||||
parsed.host.isEmpty) {
|
||||
throw const FormatException();
|
||||
}
|
||||
} catch (_) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Некорректный адрес сервера')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
double? lat;
|
||||
double? lon;
|
||||
if (latText.isEmpty != lonText.isEmpty) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(const SnackBar(content: Text('Введите обе координаты')));
|
||||
return;
|
||||
}
|
||||
if (latText.isNotEmpty && lonText.isNotEmpty) {
|
||||
lat = double.tryParse(latText);
|
||||
lon = double.tryParse(lonText);
|
||||
@@ -247,6 +280,12 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Координаты вне допустимого диапазона')),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setState(() => _saving = true);
|
||||
@@ -257,7 +296,6 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
? widget.home!.copyWith(
|
||||
name: name,
|
||||
url: url,
|
||||
apiKey: key,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
geofenceEnabled: clearCoords ? false : _geofenceEnabled,
|
||||
@@ -267,22 +305,38 @@ class _HomeEditScreenState extends ConsumerState<HomeEditScreen> {
|
||||
id: DateTime.now().millisecondsSinceEpoch.toString(),
|
||||
name: name,
|
||||
url: url,
|
||||
apiKey: key,
|
||||
latitude: lat,
|
||||
longitude: lon,
|
||||
geofenceEnabled: _geofenceEnabled,
|
||||
);
|
||||
|
||||
if (_isEdit) {
|
||||
await ref.read(homesProvider.notifier).update(home);
|
||||
} else {
|
||||
await ref.read(homesProvider.notifier).add(home);
|
||||
try {
|
||||
await ref.read(apiProvider).validateCredentials(url, key);
|
||||
|
||||
if (_isEdit) {
|
||||
await ref.read(homesProvider.notifier).update(home, apiKey: key);
|
||||
} else {
|
||||
await ref.read(homesProvider.notifier).add(home, apiKey: key);
|
||||
}
|
||||
|
||||
final currentHome = ref.read(currentHomeProvider);
|
||||
if (currentHome?.id == home.id) {
|
||||
await ref.read(currentHomeProvider.notifier).switchTo(home);
|
||||
}
|
||||
|
||||
// Синхронизировать фоновый таск с новыми настройками
|
||||
final allHomes = ref.read(homesProvider);
|
||||
await syncGeofenceTask(allHomes);
|
||||
|
||||
if (mounted) Navigator.of(context).pop();
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text('Не удалось проверить дом: $e')));
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
|
||||
// Синхронизировать фоновый таск с новыми настройками
|
||||
final allHomes = ref.read(homesProvider);
|
||||
await syncGeofenceTask(allHomes);
|
||||
|
||||
if (mounted) Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user