import 'package:flutter/material.dart'; import '../models.dart'; import '../services/api_service.dart'; import '../services/auth_service.dart'; import '../services/update_service.dart'; import '../widgets/server_card.dart'; import 'settings_screen.dart'; class ServersScreen extends StatefulWidget { const ServersScreen({ super.key, required this.auth, required this.api, required this.onLoggedOut, }); final AuthService auth; final ApiService api; final VoidCallback onLoggedOut; @override State createState() => _ServersScreenState(); } class _ServersScreenState extends State { List? _servers; String? _error; final _busy = {}; @override void initState() { super.initState(); _refresh(); _checkUpdate(); } Future _refresh() async { try { final servers = await widget.api.servers(); if (mounted) setState(() { _servers = servers; _error = null; }); } on UnauthorizedException { await widget.auth.logout(); widget.onLoggedOut(); } catch (e) { if (mounted) setState(() => _error = '$e'); } } Future _toggle(GameServer s) async { setState(() => _busy.add(s.id)); try { s.isUp ? await widget.api.stop(s.id) : await widget.api.start(s.id); await Future.delayed(const Duration(seconds: 2)); await _refresh(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e'))); } } finally { if (mounted) setState(() => _busy.remove(s.id)); } } Future _checkUpdate() async { final info = await UpdateService(widget.api.baseUrl).check(); if (info == null || !mounted) return; final go = await showDialog( context: context, builder: (ctx) => AlertDialog( title: Text('Mise à jour ${info.version}'), content: Text(info.notes.isEmpty ? 'Une nouvelle version est disponible.' : info.notes), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Plus tard')), FilledButton(onPressed: () => Navigator.pop(ctx, true), child: const Text('Installer')), ], ), ); if (go == true) { final ok = await UpdateService(widget.api.baseUrl).apply(info); if (mounted && !ok) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Impossible d\'ouvrir le téléchargement.')), ); } } } Future _openSettings() async { await Navigator.push(context, MaterialPageRoute(builder: (_) => SettingsScreen(api: widget.api))); _refresh(); } Future _logout() async { await widget.auth.logout(); widget.onLoggedOut(); } @override Widget build(BuildContext context) { return DefaultTabController( length: 2, child: Scaffold( appBar: AppBar( title: const Text('Serveurs'), actions: [ IconButton(onPressed: _openSettings, icon: const Icon(Icons.settings_outlined)), IconButton(onPressed: _logout, icon: const Icon(Icons.logout)), ], bottom: const TabBar( tabs: [ Tab(icon: Icon(Icons.public), text: 'Production'), Tab(icon: Icon(Icons.science_outlined), text: 'Preprod'), ], ), ), body: _buildBody(), ), ); } Widget _buildBody() { if (_error != null && _servers == null) { return ListView(children: [ const SizedBox(height: 120), Icon(Icons.cloud_off, size: 60, color: Theme.of(context).colorScheme.outline), const SizedBox(height: 12), Center(child: Text('Connexion à l\'API impossible.\n$_error', textAlign: TextAlign.center)), const SizedBox(height: 12), Center(child: FilledButton(onPressed: _refresh, child: const Text('Réessayer'))), ]); } if (_servers == null) { return const Center(child: CircularProgressIndicator()); } final prod = _servers!.where((s) => !s.isPreprod).toList(); final preprod = _servers!.where((s) => s.isPreprod).toList(); return TabBarView( children: [_envList(prod), _envList(preprod)], ); } Widget _envList(List list) { return RefreshIndicator( onRefresh: _refresh, child: list.isEmpty ? ListView(children: const [ SizedBox(height: 120), Center(child: Text('Aucun serveur dans cet environnement.')), ]) : ListView( padding: const EdgeInsets.fromLTRB(16, 12, 16, 24), children: list.map(_card).toList(), ), ); } Widget _card(GameServer s) => Padding( padding: const EdgeInsets.only(bottom: 14), child: FutureBuilder( future: widget.api.iconUrl(s.id), builder: (_, snap) => ServerCard( server: s, iconUrl: snap.data ?? '', busy: _busy.contains(s.id), onToggle: () => _toggle(s), ), ), ); }