aves_mio22/lib/remote.ok/remote_settings_page.dart.ok
2026-04-18 20:05:02 +02:00

241 lines
7.9 KiB
Text

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:aves/model/source/collection_source.dart';
import 'package:aves/remote/collection_source_remote_ext.dart'; // per appendRemoteEntriesFromDb()
import 'package:aves/remote/remote_controller.dart';
import 'package:aves/remote/remote_sync_bus.dart';
import 'remote_settings.dart';
import 'remote_http.dart';
import 'remote_origin.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) {
_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)';
}
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> _applyRuntimeEffects() async {
// Applica subito l'effetto ON/OFF in UI
try {
final source = context.read<CollectionSource>();
if (!_enabled) {
// OFF: nascondi remoti dalla UI e imposta icona grigia
final remotesInMemory = source.allEntries.where((e) => e.origin == RemoteOrigin.value).toSet();
if (remotesInMemory.isNotEmpty) {
source.removeEntriesFromMemory(remotesInMemory);
}
RemoteSyncBus.instance.setDisabled();
} else {
// ON: mostra subito remoti da DB (senza full sync qui)
await source.appendRemoteEntriesFromDb();
await RemoteController.instance.initBusFromSettings();
}
} catch (_) {
// se la pagina non è nel contesto con Provider(CollectionSource), non facciamo crash
}
}
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();
// aggiorna headers/token
await RemoteHttp.refreshFromSettings();
await RemoteHttp.warmUp();
// ✅ applica subito ON/OFF live
await _applyRuntimeEffects();
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,
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'),
),
),
],
),
),
),
);
}
}