import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:http/http.dart' as http; import '../model/account_data.dart'; import '../model/endpoint_data.dart'; class UserAvatar extends StatefulWidget { final String id; final bool isGroup; final int size; const UserAvatar({required this.id, this.isGroup = false, this.size = 20, super.key}); @override State createState() => _UserAvatarState(); } class _AvatarPayload { final Uint8List bytes; final bool isSvg; _AvatarPayload(this.bytes, this.isSvg); } final Map> _avatarCache = {}; class _UserAvatarState extends State { late Future<_AvatarPayload?> _payload; @override void initState() { super.initState(); _payload = _load(); } @override void didUpdateWidget(UserAvatar oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.id != widget.id || oldWidget.isGroup != widget.isGroup || oldWidget.size != widget.size) { _payload = _load(); } } String _url() { final host = EndpointData().nextcloud().full(); if (widget.isGroup) { return 'https://$host/ocs/v2.php/apps/spreed/api/v1/room/${widget.id}/avatar'; } return 'https://$host/avatar/${widget.id}/${widget.size}'; } Future<_AvatarPayload?> _load() { final url = _url(); return _avatarCache.putIfAbsent(url, () => _fetch(url)); } Future<_AvatarPayload?> _fetch(String url) async { try { final response = await http.get( Uri.parse(url), headers: { 'Authorization': AccountData().getBasicAuthHeader(), 'Accept': 'image/png,image/jpeg,image/webp,image/svg+xml', }, ); if (response.statusCode != 200 || response.bodyBytes.isEmpty) return null; final contentType = response.headers['content-type']?.toLowerCase() ?? ''; final bytes = response.bodyBytes; final isSvg = contentType.contains('svg') || _looksLikeSvg(bytes); return _AvatarPayload(bytes, isSvg); } catch (_) { return null; } } static bool _looksLikeSvg(Uint8List bytes) { final head = utf8.decode( bytes.sublist(0, bytes.length < 256 ? bytes.length : 256), allowMalformed: true, ).trimLeft(); return head.startsWith('( future: _payload, builder: (context, snapshot) { final payload = snapshot.data; Widget content; if (payload == null) { content = Icon( widget.isGroup ? Icons.group : Icons.person, size: radius, color: Colors.white, ); } else if (payload.isSvg) { content = SvgPicture.memory( payload.bytes, width: radius * 2, height: radius * 2, fit: BoxFit.cover, ); } else { content = Image.memory( payload.bytes, width: radius * 2, height: radius * 2, fit: BoxFit.cover, gaplessPlayback: true, ); } return CircleAvatar( radius: radius, backgroundColor: theme.primaryColor, foregroundColor: Colors.white, child: ClipOval( child: SizedBox( width: radius * 2, height: radius * 2, child: content, ), ), ); }, ); } }