Files
Qlyra/lib/frontend/screens/contacts/contacts_tab.dart
T

217 lines
6.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import '../../../core/storage/app_database.dart';
import '../../../backend/modules/contacts.dart';
class ContactsTab extends StatefulWidget {
const ContactsTab({super.key});
@override
State<ContactsTab> createState() => _ContactsTabState();
}
class _ContactsTabState extends State<ContactsTab> {
List<CachedContact> _contacts = [];
bool _isLoading = true;
@override
void initState() {
super.initState();
_loadContacts();
}
Future<void> _loadContacts() async {
final p = await AppDatabase.loadActiveProfile();
if (p == null) {
if (mounted) setState(() => _isLoading = false);
return;
}
final contacts = await ContactsModule.getContacts(p.id);
// Sort contacts by first name
contacts.sort((a, b) => a.firstName.compareTo(b.firstName));
if (mounted) {
setState(() {
_contacts = contacts;
_isLoading = false;
});
}
}
Widget _buildPlaceholderAvatar(ColorScheme cs, String name) {
return Container(
color: cs.primaryContainer,
alignment: Alignment.center,
child: Text(
name.isNotEmpty ? name[0].toUpperCase() : '?',
style: TextStyle(
color: cs.onPrimaryContainer,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
);
}
Widget _buildContactItem(
BuildContext context,
ColorScheme cs,
CachedContact contact,
) {
final fullName =
'${contact.firstName}${contact.lastName != null ? ' ${contact.lastName}' : ''}'
.trim();
final nameToDisplay = fullName.isEmpty ? '+${contact.phone}' : fullName;
return Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
// Open contact details or chat
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: cs.primary.withValues(alpha: 0.1),
width: 1,
),
),
child: ClipOval(
child: contact.baseUrl != null && contact.baseUrl!.isNotEmpty
? CachedNetworkImage(
imageUrl: contact.baseUrl!,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 120),
errorWidget: (context, url, error) =>
_buildPlaceholderAvatar(cs, nameToDisplay),
)
: _buildPlaceholderAvatar(cs, nameToDisplay),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: Text(
nameToDisplay,
style: TextStyle(
color: cs.onSurface,
fontSize: 16,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
if (contact.isVerified) ...[
const SizedBox(width: 4),
Icon(
Symbols.verified,
color: cs.primary,
size: 16,
weight: 600,
fill: 1,
),
],
],
),
const SizedBox(height: 4),
Text(
contact.updateTime > 0
? 'Был(а) недавно'
: '+${contact.phone}',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 14,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
),
);
}
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: cs.surface,
body: SafeArea(
bottom: false,
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 20, 12),
child: Row(
children: [
Expanded(
child: Text(
'Контакты',
style: TextStyle(
color: cs.onSurface,
fontSize: 24,
fontWeight: FontWeight.w700,
fontFamily: 'Outfit',
),
),
),
IconButton(
icon: Icon(Symbols.person_add, color: cs.onSurface),
onPressed: () {},
),
IconButton(
icon: Icon(Symbols.search, color: cs.onSurface),
onPressed: () {},
),
],
),
),
Expanded(
child: _isLoading
? const Center(child: CircularProgressIndicator())
: _contacts.isEmpty
? Center(
child: Text(
'Нет контактов',
style: TextStyle(
color: cs.onSurfaceVariant,
fontSize: 16,
),
),
)
: ListView.builder(
physics: const BouncingScrollPhysics(),
padding: const EdgeInsets.only(bottom: 120),
itemCount: _contacts.length,
itemBuilder: (context, index) {
final contact = _contacts[index];
return _buildContactItem(context, cs, contact);
},
),
),
],
),
),
);
}
}