187 lines
6.4 KiB
Dart
187 lines
6.4 KiB
Dart
import 'package:chepuhagram/domain/services/aPI_service.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
import '/data/models/contact_model.dart';
|
||
import 'package:cached_network_image/cached_network_image.dart';
|
||
import 'package:chepuhagram/data/models/message_model.dart';
|
||
|
||
class ContactTile extends StatefulWidget {
|
||
final Contact contact;
|
||
final VoidCallback? onTap;
|
||
|
||
ContactTile({super.key, required this.contact, this.onTap});
|
||
|
||
@override
|
||
State<ContactTile> createState() => _ContactTileState();
|
||
}
|
||
|
||
class _ContactTileState extends State<ContactTile> {
|
||
SharedPreferences? _prefs;
|
||
String? token;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_initPrefs();
|
||
}
|
||
|
||
Future<void> _initPrefs() async {
|
||
final apiService = ApiService();
|
||
final accessToken = await apiService.getAccessToken();
|
||
final shared = await SharedPreferences.getInstance();
|
||
if (mounted) {
|
||
setState(() {
|
||
_prefs = shared;
|
||
token = accessToken;
|
||
});
|
||
}
|
||
}
|
||
|
||
String get displayName {
|
||
if (_prefs == null) return widget.contact.name;
|
||
|
||
final id = widget.contact.id;
|
||
final savedName = _prefs!.getString('firstname_$id');
|
||
final savedSurname = _prefs!.getString('lastname_$id');
|
||
|
||
final name = savedName ?? widget.contact.name;
|
||
final surname = savedSurname ?? widget.contact.surname;
|
||
|
||
final full =
|
||
'${name != 'Unknown' ? name : ''} ${surname != 'Unknown' ? surname : ''}'
|
||
.trim();
|
||
|
||
if (full.isNotEmpty) return full;
|
||
if (widget.contact.username != 'Unknown') return widget.contact.username;
|
||
return 'User';
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final primary = Theme.of(context).colorScheme.primary;
|
||
final username = widget.contact.username; //
|
||
|
||
final initials =
|
||
(displayName.isNotEmpty
|
||
? displayName
|
||
: (username != 'Unknown' ? username : 'U'))
|
||
.trim()
|
||
.split(RegExp(r'\s+'))
|
||
.where((p) => p.isNotEmpty)
|
||
.take(2)
|
||
.map((p) => p[0].toUpperCase())
|
||
.join(); //
|
||
debugPrint(
|
||
'=== CONTACT DEBUG: ${widget.contact.name} -> URL: ${widget.contact.effectiveAvatarUrl}',
|
||
);
|
||
return ListTile(
|
||
onTap: widget.onTap, //
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 16,
|
||
vertical: 4,
|
||
), //
|
||
// Переписываем ведущий виджет (аватарку)
|
||
leading: SizedBox(
|
||
width: 56, // Соответствует радиусу 28 * 2
|
||
height: 56,
|
||
child:
|
||
widget.contact.effectiveAvatarUrl !=
|
||
null //
|
||
? CachedNetworkImage(
|
||
imageUrl: widget.contact.effectiveAvatarUrl!, //
|
||
// Передаем токен для FastAPI, чтобы сервер разрешил скачивание файла
|
||
httpHeaders: {
|
||
if (token != null) 'Authorization': 'Bearer $token',
|
||
},
|
||
imageBuilder: (context, imageProvider) => Container(
|
||
decoration: BoxDecoration(
|
||
shape: BoxShape.circle,
|
||
image: DecorationImage(
|
||
image: imageProvider,
|
||
fit: BoxFit.cover,
|
||
),
|
||
),
|
||
),
|
||
// Пока картинка качается — показываем цветной круг с инициалами
|
||
placeholder: (context, url) => CircleAvatar(
|
||
radius: 28,
|
||
backgroundColor: primary.withAlpha((0.1 * 255).round()),
|
||
child: Text(
|
||
initials,
|
||
style: TextStyle(
|
||
color: primary,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
// Ошибка 401, 404 или упал интернет? Без паники, плавно вернем инициалы
|
||
errorWidget: (context, url, error) => CircleAvatar(
|
||
radius: 28,
|
||
backgroundColor: primary.withAlpha((0.1 * 255).round()),
|
||
child: Text(
|
||
initials,
|
||
style: TextStyle(
|
||
color: primary,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
),
|
||
)
|
||
: CircleAvatar(
|
||
radius: 28,
|
||
backgroundColor: primary.withAlpha((0.1 * 255).round()), //
|
||
child: Text(
|
||
initials,
|
||
style: TextStyle(color: primary, fontWeight: FontWeight.bold),
|
||
),
|
||
),
|
||
),
|
||
|
||
title: Text(
|
||
displayName, //
|
||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), //
|
||
),
|
||
subtitle: Text(
|
||
widget.contact.isLastMsgDecrypted
|
||
? widget.contact.lastMessage == null
|
||
? "Нет сообщений"
|
||
: "${widget.contact.lastMessageType != null ? MessageModel.getMediaPreview(widget.contact.lastMessageType!) : ''} ${widget.contact.lastMessage}"
|
||
: (widget.contact.lastMessage != null
|
||
? "Ожидание дешифровки..."
|
||
: "Нет сообщений"),
|
||
maxLines: 1,
|
||
overflow: TextOverflow.ellipsis,
|
||
style: TextStyle(color: Colors.grey), //
|
||
),
|
||
trailing: Column(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
crossAxisAlignment: CrossAxisAlignment.end,
|
||
children: [
|
||
Text(
|
||
_formatTime(widget.contact.lastMessageTime), //
|
||
style: TextStyle(color: Colors.grey, fontSize: 12), //
|
||
),
|
||
SizedBox(height: 4), //
|
||
if (widget.contact.unreadCount > 0) //
|
||
Container(
|
||
padding: EdgeInsets.all(6), //
|
||
decoration: BoxDecoration(
|
||
color: primary.withAlpha((0.5 * 255).round()), //
|
||
shape: BoxShape.circle, //
|
||
),
|
||
child: Text(
|
||
'${widget.contact.unreadCount}', //
|
||
style: TextStyle(color: Colors.white, fontSize: 10), //
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
String _formatTime(DateTime? time) {
|
||
if (time == null) return "";
|
||
return "${time.hour}:${time.minute.toString().padLeft(2, '0')}";
|
||
}
|
||
}
|