feat: add message feature

This commit is contained in:
2026-06-25 01:11:34 +03:30
parent e8cde8e1f7
commit fa45ef0737
6 changed files with 274 additions and 8 deletions
@@ -4,6 +4,7 @@ import 'package:random_avatar/random_avatar.dart';
import '../../../../core/network/ws_client.dart';
import '../../../../core/theme/app_theme.dart';
import '../../domain/entities/game_entities.dart';
import '../bloc/game_state.dart';
/// اوورلیِ روی صحنه‌ی Flame: آواتارِ بازیکنان با قابِ طلایی، نام، نشانِ رتبه،
/// شمارنده‌ی نوبت، تاجِ حاکم و تعدادِ کارت — به‌علاوه‌ی پنلِ امتیاز/حکم (بالا-چپ)،
@@ -12,12 +13,14 @@ import '../../domain/entities/game_entities.dart';
class TableHud extends StatelessWidget {
final GameState s;
final WsStatus connection;
final Map<int, ChatBubble> chatBubbles;
final VoidCallback onExit;
final VoidCallback onChat;
const TableHud({
super.key,
required this.s,
required this.connection,
required this.chatBubbles,
required this.onExit,
required this.onChat,
});
@@ -64,6 +67,7 @@ class TableHud extends StatelessWidget {
turnSeconds:
s.turnTimeoutMs > 0 ? s.turnTimeoutMs / 1000 : 30,
showRing: s.phase == 'playing' || s.phase == 'choose_trump',
bubble: chatBubbles[p.seat],
),
),
),
@@ -219,6 +223,7 @@ class _PlayerSeat extends StatelessWidget {
final String turnToken;
final double turnSeconds;
final bool showRing;
final ChatBubble? bubble; // پیام/شکلکِ فعالِ این بازیکن (یا null)
const _PlayerSeat({
required this.player,
required this.diameter,
@@ -230,6 +235,7 @@ class _PlayerSeat extends StatelessWidget {
required this.turnToken,
required this.turnSeconds,
required this.showRing,
required this.bubble,
});
@override
@@ -353,13 +359,74 @@ class _PlayerSeat extends StatelessWidget {
],
);
return Column(
final content = Column(
mainAxisSize: MainAxisSize.min,
children:
nameAbove
? [nameRow, SizedBox(height: diameter * 0.06), avatar]
: [avatar, SizedBox(height: diameter * 0.06), nameRow],
);
if (bubble == null) return content;
// حبابِ پیام بالای آواتار (برای بازیکنِ بالا، زیرِ آواتار تا از صفحه بیرون نزند).
return Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
content,
Positioned(
top: nameAbove ? null : -diameter * 0.62,
bottom: nameAbove ? -diameter * 0.62 : null,
child: _ChatBubbleView(bubble: bubble!, diameter: diameter),
),
],
);
}
}
/// نمایشِ حبابِ پیام/شکلک با یک «پاپ»ِ ورود. شکلک بزرگ و متن در یک پیلِ روشن.
class _ChatBubbleView extends StatelessWidget {
final ChatBubble bubble;
final double diameter;
const _ChatBubbleView({required this.bubble, required this.diameter});
@override
Widget build(BuildContext context) {
final isEmoji = (bubble.emoji ?? '').isNotEmpty;
final child = isEmoji
? Text(bubble.emoji!, style: TextStyle(fontSize: diameter * 0.7))
: Container(
constraints: BoxConstraints(maxWidth: diameter * 2.4),
padding: EdgeInsets.symmetric(
horizontal: diameter * 0.16, vertical: diameter * 0.08),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(diameter * 0.3),
border: Border.all(color: AppColors.goldDark, width: 1.4),
boxShadow: const [
BoxShadow(color: Colors.black54, blurRadius: 5)
],
),
child: Text(
bubble.text ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: TextStyle(
color: AppColors.bgDark,
fontSize: diameter * 0.2,
fontWeight: FontWeight.bold,
),
),
);
return TweenAnimationBuilder<double>(
key: ValueKey(bubble.id),
tween: Tween(begin: 0.5, end: 1.0),
duration: const Duration(milliseconds: 220),
curve: Curves.easeOutBack,
builder: (_, v, c) => Transform.scale(scale: v, child: c),
child: child,
);
}
}