Files
gamemanager/lib/widgets/server_card.dart
T

169 lines
5.4 KiB
Dart

import 'package:flutter/material.dart';
import '../models.dart';
class ServerCard extends StatelessWidget {
const ServerCard({
super.key,
required this.server,
required this.iconUrl,
required this.busy,
required this.onToggle,
});
final GameServer server;
final String iconUrl;
final bool busy;
final VoidCallback onToggle;
@override
Widget build(BuildContext context) {
final t = server.theme;
final badge = server.statusBadge;
return Card(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [t.start, t.end],
),
),
padding: const EdgeInsets.all(18),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
_Icon(server: server, iconUrl: iconUrl, fallback: t.icon),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(server.name,
style: const TextStyle(
color: Colors.white,
fontSize: 19,
fontWeight: FontWeight.w800),
maxLines: 1,
overflow: TextOverflow.ellipsis),
if (server.subtitle != null)
Text(server.subtitle!,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.75), fontSize: 13)),
],
),
),
_StatusPill(label: badge.label, color: badge.color, pulse: server.isBusy),
],
),
const SizedBox(height: 16),
Row(
children: [
if (server.status == 'online') ...[
const Icon(Icons.people, color: Colors.white70, size: 18),
const SizedBox(width: 5),
Text('${server.players ?? 0}/${server.maxPlayers ?? 0}',
style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w600)),
const SizedBox(width: 14),
],
if (server.connect != null)
Expanded(
child: Text(server.connect!,
style: TextStyle(
color: Colors.white.withValues(alpha: 0.7),
fontFamily: 'monospace',
fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis),
)
else
const Spacer(),
_ToggleButton(up: server.isUp, busy: busy, onToggle: onToggle),
],
),
],
),
),
);
}
}
class _Icon extends StatelessWidget {
const _Icon({required this.server, required this.iconUrl, required this.fallback});
final GameServer server;
final String iconUrl;
final IconData fallback;
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(12),
child: SizedBox(
width: 52,
height: 52,
child: (server.hasIcon)
? Image.network(iconUrl,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => _fallbackBox())
: _fallbackBox(),
),
);
}
Widget _fallbackBox() => Container(
color: Colors.white.withValues(alpha: 0.15),
child: Icon(fallback, color: Colors.white, size: 28),
);
}
class _StatusPill extends StatelessWidget {
const _StatusPill({required this.label, required this.color, required this.pulse});
final String label;
final Color color;
final bool pulse;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.22),
borderRadius: BorderRadius.circular(999),
border: Border.all(color: color, width: 1),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(width: 7, height: 7, decoration: BoxDecoration(color: color, shape: BoxShape.circle)),
const SizedBox(width: 6),
Text(label, style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700)),
],
),
);
}
}
class _ToggleButton extends StatelessWidget {
const _ToggleButton({required this.up, required this.busy, required this.onToggle});
final bool up;
final bool busy;
final VoidCallback onToggle;
@override
Widget build(BuildContext context) {
return FilledButton.icon(
onPressed: busy ? null : onToggle,
style: FilledButton.styleFrom(
backgroundColor: up ? Colors.white.withValues(alpha: 0.9) : Colors.white,
foregroundColor: up ? const Color(0xFFB3261E) : const Color(0xFF1B5E20),
),
icon: busy
? const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2))
: Icon(up ? Icons.stop : Icons.play_arrow),
label: Text(up ? 'Éteindre' : 'Démarrer'),
);
}
}