Initial Commit

This commit is contained in:
ivan2282
2025-11-15 20:06:40 +03:00
commit 205d11df0d
233 changed files with 52572 additions and 0 deletions

View File

@@ -0,0 +1,189 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:gwid/models/chat.dart';
import 'package:gwid/models/contact.dart';
class GroupAvatars extends StatelessWidget {
final Chat chat;
final Map<int, Contact> contacts;
final int maxAvatars;
final double avatarSize;
final double overlap;
const GroupAvatars({
super.key,
required this.chat,
required this.contacts,
this.maxAvatars = 3,
this.avatarSize = 16.0,
this.overlap = 8.0,
});
@override
Widget build(BuildContext context) {
if (!chat.isGroup) {
return const SizedBox.shrink();
}
final participantIds = chat.groupParticipantIds;
if (participantIds.isEmpty) {
return const SizedBox.shrink();
}
final visibleParticipants = participantIds.take(maxAvatars).toList();
final remainingCount = participantIds.length - maxAvatars;
final totalParticipants = participantIds.length;
double adaptiveAvatarSize;
if (totalParticipants <= 2) {
adaptiveAvatarSize =
avatarSize * 1.5; // Большие аватары для 1-2 участников
} else if (totalParticipants <= 4) {
adaptiveAvatarSize =
avatarSize * 1.2; // Средние аватары для 3-4 участников
} else {
adaptiveAvatarSize =
avatarSize * 0.8; // Маленькие аватары для 5+ участников
}
return SizedBox(
height: adaptiveAvatarSize * 2.5,
width: adaptiveAvatarSize * 2.5,
child: Stack(
children: [
...visibleParticipants.asMap().entries.map((entry) {
final index = entry.key;
final participantId = entry.value;
final contact = contacts[participantId];
double x, y;
if (visibleParticipants.length == 1) {
x = adaptiveAvatarSize * 1.25;
y = adaptiveAvatarSize * 1.25;
} else if (visibleParticipants.length == 2) {
x = adaptiveAvatarSize * (0.5 + index * 1.5);
y = adaptiveAvatarSize * 1.25;
} else {
final angle = (index * 2 * pi) / visibleParticipants.length;
final radius = adaptiveAvatarSize * 0.6;
final center = adaptiveAvatarSize * 1.25;
x = center + radius * cos(angle);
y = center + radius * sin(angle);
}
return Positioned(
left: x - adaptiveAvatarSize / 2,
top: y - adaptiveAvatarSize / 2,
child: _buildAvatar(
context,
contact,
participantId,
adaptiveAvatarSize,
),
);
}),
if (remainingCount > 0)
Positioned(
left: adaptiveAvatarSize * 0.75,
top: adaptiveAvatarSize * 0.75,
child: _buildMoreIndicator(
context,
remainingCount,
adaptiveAvatarSize,
),
),
],
),
);
}
Widget _buildAvatar(
BuildContext context,
Contact? contact,
int participantId,
double size,
) {
final colors = Theme.of(context).colorScheme;
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: colors.surface, width: 2),
boxShadow: [
BoxShadow(
color: colors.shadow.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: CircleAvatar(
radius: size / 2,
backgroundColor: contact != null
? colors.primaryContainer
: colors.secondaryContainer,
backgroundImage: contact?.photoBaseUrl != null
? NetworkImage(contact!.photoBaseUrl!)
: null,
child: contact?.photoBaseUrl == null
? Text(
contact?.name.isNotEmpty == true
? contact!.name[0].toUpperCase()
: participantId.toString().substring(
participantId.toString().length - 1,
), // Последняя цифра ID
style: TextStyle(
color: contact != null
? colors.onPrimaryContainer
: colors.onSecondaryContainer,
fontSize: size * 0.5,
fontWeight: FontWeight.w600,
),
)
: null,
),
);
}
Widget _buildMoreIndicator(BuildContext context, int count, double size) {
final colors = Theme.of(context).colorScheme;
return Container(
width: size,
height: size,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: colors.secondaryContainer,
border: Border.all(color: colors.surface, width: 2),
boxShadow: [
BoxShadow(
color: colors.shadow.withOpacity(0.3),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
),
child: Center(
child: Text(
'+$count',
style: TextStyle(
color: colors.onSecondaryContainer,
fontSize: size * 0.4,
fontWeight: FontWeight.w600,
),
),
),
);
}
}