aves_mio1/lib/remote/remote_settings_page.dart
FabioMich66 084fa184da
Some checks failed
Quality check / Flutter analysis (push) Has been cancelled
Quality check / CodeQL analysis (java-kotlin) (push) Has been cancelled
ok con video e foto in galleria aves
2026-03-17 12:19:38 +01:00

211 lines
6.9 KiB
Dart

import 'package:flutter/material.dart';
import 'remote_settings.dart';
import 'remote_http.dart';
class RemoteSettingsPage extends StatefulWidget {
const RemoteSettingsPage({super.key});
@override
State<RemoteSettingsPage> createState() => _RemoteSettingsPageState();
}
class _RemoteSettingsPageState extends State<RemoteSettingsPage> {
final _form = GlobalKey<FormState>();
bool _loaded = false;
bool _saving = false;
bool _enabled = RemoteSettings.defaultEnabled;
final _baseUrl = TextEditingController(text: RemoteSettings.defaultBaseUrl);
final _indexPath = TextEditingController(text: RemoteSettings.defaultIndexPath);
final _email = TextEditingController();
final _password = TextEditingController();
@override
void initState() {
super.initState();
_load();
}
@override
void dispose() {
_baseUrl.dispose();
_indexPath.dispose();
_email.dispose();
_password.dispose();
super.dispose();
}
Future<void> _load() async {
try {
final s = await RemoteSettings.load();
if (!mounted) return;
setState(() {
_enabled = s.enabled;
_baseUrl.text = s.baseUrl;
_indexPath.text = s.indexPath;
_email.text = s.email;
_password.text = s.password;
_loaded = true;
});
} catch (e) {
// Fail-open: apri comunque con default/blank e notifica
_showSnack('Impossibile leggere le impostazioni sicure: $e');
if (!mounted) return;
setState(() {
_enabled = RemoteSettings.defaultEnabled;
_baseUrl.text = RemoteSettings.defaultBaseUrl;
_indexPath.text = RemoteSettings.defaultIndexPath;
_email.text = '';
_password.text = '';
_loaded = true;
});
}
}
String? _validateBaseUrl(String? v) {
final s = (v ?? '').trim();
if (s.isEmpty) return 'Obbligatorio';
final uri = Uri.tryParse(s);
if (uri == null || !(uri.hasScheme && (uri.scheme == 'http' || uri.scheme == 'https'))) {
return 'URL non valida (deve iniziare con http/https)';
}
// opzionale: blocca spazi/controlli interni
if (RegExp(r'[\u200B-\u200F\u202A-\u202E\u2060-\u2064\uFEFF]').hasMatch(s)) {
return 'URL contiene caratteri non validi (invisibili)';
}
return null;
}
String? _validateIndex(String? v) {
final s = (v ?? '').trim();
if (s.isEmpty) return 'Obbligatorio';
return null;
}
Future<void> _save() async {
if (!(_form.currentState?.validate() ?? false)) return;
setState(() => _saving = true);
try {
final s = RemoteSettings(
enabled: _enabled,
baseUrl: _baseUrl.text.trim(),
indexPath: _indexPath.text.trim(),
email: _email.text.trim(),
password: _password.text,
);
await s.save();
// ✅ forza Aves a usare SUBITO base URL & token aggiornati
await RemoteHttp.refreshFromSettings();
await RemoteHttp.warmUp(); // non bloccante: utile per loggare stato token/base
if (!mounted) return;
_showSnack('Impostazioni remote salvate');
Navigator.of(context).maybePop();
} catch (e) {
if (!mounted) return;
_showSnack('Salvataggio fallito: $e');
} finally {
if (mounted) setState(() => _saving = false);
}
}
void _showSnack(String msg) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
behavior: SnackBarBehavior.fixed, // evita "floating off screen"
content: Text(msg),
duration: const Duration(seconds: 3),
),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
appBar: AppBar(title: const Text('Remote Settings')),
body: !_loaded
? const Center(child: CircularProgressIndicator())
: AbsorbPointer(
absorbing: _saving,
child: Form(
key: _form,
child: ListView(
padding: const EdgeInsets.all(16),
children: [
SwitchListTile(
title: const Text('Abilita sync remoto'),
value: _enabled,
onChanged: (v) => setState(() => _enabled = v),
),
const SizedBox(height: 8),
TextFormField(
controller: _baseUrl,
decoration: const InputDecoration(
labelText: 'Base URL (es. https://server.tld)',
hintText: 'https://example.org',
),
keyboardType: TextInputType.url,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: _validateBaseUrl,
),
const SizedBox(height: 12),
TextFormField(
controller: _indexPath,
decoration: const InputDecoration(
labelText: 'Index path (es. photos/)',
hintText: 'photos/',
),
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: _validateIndex,
),
const SizedBox(height: 12),
TextFormField(
controller: _email,
decoration: const InputDecoration(
labelText: 'User/Email',
hintText: 'utente@example.org',
),
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 12),
TextFormField(
controller: _password,
obscureText: true,
decoration: const InputDecoration(
labelText: 'Password',
),
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _saving ? null : _save,
icon: _saving
? SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
theme.colorScheme.onPrimary,
),
),
)
: const Icon(Icons.save),
label: Text(_saving ? 'Salvataggio in corso...' : 'Salva'),
),
),
],
),
),
),
);
}
}