feat(ui): la carte reprend l'image importée en fond (voile pour lisibilité)
Build APK / build (push) Successful in 2m49s
Build APK / build (push) Successful in 2m49s
- ServerCard: fond = image (MemoryImage) + scrim dégradé si présente, sinon dégradé thématique ; vignette d'icône masquée quand image de fond. - servers_screen: fetch des bytes image (cache par id, vidé au refresh) → passe à la carte ; plus de FutureBuilder iconUrl inutile. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2862fdd408
commit
fb4ab95f13
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../models.dart';
|
import '../models.dart';
|
||||||
@@ -27,6 +29,15 @@ class _ServersScreenState extends State<ServersScreen> {
|
|||||||
List<GameServer>? _servers;
|
List<GameServer>? _servers;
|
||||||
String? _error;
|
String? _error;
|
||||||
final _busy = <String>{};
|
final _busy = <String>{};
|
||||||
|
final _imgCache = <String, Uint8List?>{};
|
||||||
|
|
||||||
|
Future<Uint8List?> _image(GameServer s) async {
|
||||||
|
if (!s.hasImage) return null;
|
||||||
|
if (_imgCache.containsKey(s.id)) return _imgCache[s.id];
|
||||||
|
final b = await widget.api.imageBytes(s.id).catchError((_) => null);
|
||||||
|
_imgCache[s.id] = b;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -41,6 +52,7 @@ class _ServersScreenState extends State<ServersScreen> {
|
|||||||
if (mounted) setState(() {
|
if (mounted) setState(() {
|
||||||
_servers = servers;
|
_servers = servers;
|
||||||
_error = null;
|
_error = null;
|
||||||
|
_imgCache.clear(); // recharge les images (elles ont pu changer)
|
||||||
});
|
});
|
||||||
} on UnauthorizedException {
|
} on UnauthorizedException {
|
||||||
await widget.auth.logout();
|
await widget.auth.logout();
|
||||||
@@ -191,11 +203,12 @@ class _ServersScreenState extends State<ServersScreen> {
|
|||||||
|
|
||||||
Widget _card(GameServer s) => Padding(
|
Widget _card(GameServer s) => Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 14),
|
padding: const EdgeInsets.only(bottom: 14),
|
||||||
child: FutureBuilder<String>(
|
child: FutureBuilder<Uint8List?>(
|
||||||
future: widget.api.iconUrl(s.id),
|
future: _image(s),
|
||||||
builder: (_, snap) => ServerCard(
|
builder: (_, snap) => ServerCard(
|
||||||
server: s,
|
server: s,
|
||||||
iconUrl: snap.data ?? '',
|
iconUrl: '',
|
||||||
|
imageBytes: snap.data,
|
||||||
busy: _busy.contains(s.id),
|
busy: _busy.contains(s.id),
|
||||||
onToggle: () => _toggle(s),
|
onToggle: () => _toggle(s),
|
||||||
onTap: () => _openDetail(s),
|
onTap: () => _openDetail(s),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
@@ -11,6 +13,7 @@ class ServerCard extends StatelessWidget {
|
|||||||
required this.busy,
|
required this.busy,
|
||||||
required this.onToggle,
|
required this.onToggle,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
this.imageBytes,
|
||||||
});
|
});
|
||||||
|
|
||||||
final GameServer server;
|
final GameServer server;
|
||||||
@@ -18,31 +21,49 @@ class ServerCard extends StatelessWidget {
|
|||||||
final bool busy;
|
final bool busy;
|
||||||
final VoidCallback onToggle;
|
final VoidCallback onToggle;
|
||||||
final VoidCallback? onTap;
|
final VoidCallback? onTap;
|
||||||
|
final Uint8List? imageBytes;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final t = server.theme;
|
final t = server.theme;
|
||||||
final badge = server.statusBadge;
|
final badge = server.statusBadge;
|
||||||
|
final hasImg = imageBytes != null;
|
||||||
return Card(
|
return Card(
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
child: Ink(
|
||||||
|
decoration: hasImg
|
||||||
|
? BoxDecoration(
|
||||||
|
image: DecorationImage(image: MemoryImage(imageBytes!), fit: BoxFit.cover),
|
||||||
|
)
|
||||||
|
: BoxDecoration(
|
||||||
|
gradient: LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [t.start, t.end],
|
||||||
|
),
|
||||||
|
),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: hasImg
|
||||||
gradient: LinearGradient(
|
? const BoxDecoration(
|
||||||
begin: Alignment.topLeft,
|
gradient: LinearGradient(
|
||||||
end: Alignment.bottomRight,
|
begin: Alignment.topCenter,
|
||||||
colors: [t.start, t.end],
|
end: Alignment.bottomCenter,
|
||||||
),
|
colors: [Color(0x66000000), Color(0xCC000000)],
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
padding: const EdgeInsets.all(18),
|
padding: const EdgeInsets.all(18),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
_Icon(server: server, iconUrl: iconUrl, fallback: t.icon),
|
if (!hasImg) ...[
|
||||||
const SizedBox(width: 14),
|
_Icon(server: server, iconUrl: iconUrl, fallback: t.icon),
|
||||||
|
const SizedBox(width: 14),
|
||||||
|
],
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -107,6 +128,7 @@ class ServerCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -124,7 +146,7 @@ class _Icon extends StatelessWidget {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 52,
|
width: 52,
|
||||||
height: 52,
|
height: 52,
|
||||||
child: (server.hasIcon)
|
child: (server.hasIcon && iconUrl.isNotEmpty)
|
||||||
? Image.network(iconUrl,
|
? Image.network(iconUrl,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
errorBuilder: (_, __, ___) => _fallbackBox())
|
errorBuilder: (_, __, ___) => _fallbackBox())
|
||||||
|
|||||||
Reference in New Issue
Block a user