feat: add message feature
This commit is contained in:
@@ -17,6 +17,7 @@ class GameBloc extends Bloc<GameEvent, GameUiState> {
|
||||
|
||||
Map<String, dynamic> _joinAction = const {};
|
||||
bool _joined = false;
|
||||
int _bubbleSeq = 0; // شناسهی یکتا برای هر حبابِ پیام (انقضای دقیق)
|
||||
|
||||
GameBloc(this.repository) : super(const GameUiState()) {
|
||||
_msgSub =
|
||||
@@ -45,6 +46,18 @@ class GameBloc extends Bloc<GameEvent, GameUiState> {
|
||||
on<PlayCardEvent>(
|
||||
(event, emit) => repository.send({'type': 'play_card', 'card': event.card}));
|
||||
on<ReshuffleEvent>((event, emit) => repository.send({'type': 'reshuffle'}));
|
||||
on<SendChatEvent>((event, emit) => repository.send({
|
||||
'type': 'chat',
|
||||
if (event.text != null) 'text': event.text,
|
||||
if (event.emoji != null) 'emoji': event.emoji,
|
||||
}));
|
||||
on<ExpireChatEvent>((event, emit) {
|
||||
final cur = state.chatBubbles[event.seat];
|
||||
if (cur == null || cur.id != event.id) return; // حبابِ جدیدتر است ⇒ نگهدار
|
||||
final next = Map<int, ChatBubble>.from(state.chatBubbles)
|
||||
..remove(event.seat);
|
||||
emit(state.copyWith(chatBubbles: next));
|
||||
});
|
||||
on<LeaveGameEvent>((event, emit) => repository.send({'type': 'leave'}));
|
||||
on<StartTableEvent>((event, emit) =>
|
||||
repository.send({'type': 'start_table', 'hands': event.hands}));
|
||||
@@ -75,6 +88,19 @@ class GameBloc extends Bloc<GameEvent, GameUiState> {
|
||||
emit(state.copyWith(notice: 'یک بازیکن قطع شد'));
|
||||
case 'player_reconnected':
|
||||
emit(state.copyWith(notice: 'بازیکن بازگشت'));
|
||||
case 'chat':
|
||||
final seat = (msg['seat'] ?? 0) as int;
|
||||
final id = ++_bubbleSeq;
|
||||
final bubble = ChatBubble(
|
||||
text: msg['text'] as String?,
|
||||
emoji: msg['emoji'] as String?,
|
||||
id: id,
|
||||
);
|
||||
final next = Map<int, ChatBubble>.from(state.chatBubbles)
|
||||
..[seat] = bubble;
|
||||
emit(state.copyWith(chatBubbles: next));
|
||||
// انقضای خودکار پس از ۴ ثانیه (مگر حبابِ جدیدتری روی همان جایگاه بنشیند).
|
||||
Timer(const Duration(seconds: 4), () => add(ExpireChatEvent(seat, id)));
|
||||
case 'player_left':
|
||||
emit(state.copyWith(notice: 'یک بازیکن میز را ترک کرد'));
|
||||
case 'error':
|
||||
@@ -85,6 +111,8 @@ class GameBloc extends Bloc<GameEvent, GameUiState> {
|
||||
// --- متدهای کمکی برای موتور Flame و صفحهها ---
|
||||
void chooseTrump(String suit) => add(ChooseTrumpEvent(suit));
|
||||
void reshuffle() => add(ReshuffleEvent());
|
||||
void sendChatText(String text) => add(SendChatEvent(text: text));
|
||||
void sendChatEmoji(String emoji) => add(SendChatEvent(emoji: emoji));
|
||||
void playCard(String card) => add(PlayCardEvent(card));
|
||||
void leave() => add(LeaveGameEvent());
|
||||
void startTable(int hands) => add(StartTableEvent(hands));
|
||||
|
||||
@@ -32,6 +32,20 @@ class PlayCardEvent extends GameEvent {
|
||||
|
||||
class ReshuffleEvent extends GameEvent {} // بُرِ مجدد با مصرفِ یک بلیت
|
||||
|
||||
/// ارسالِ پیام/شکلک (یکی از text یا emoji پر است).
|
||||
class SendChatEvent extends GameEvent {
|
||||
final String? text;
|
||||
final String? emoji;
|
||||
SendChatEvent({this.text, this.emoji});
|
||||
}
|
||||
|
||||
/// پاککردنِ حبابِ پیامِ یک جایگاه پس از انقضا (id برای جلوگیری از پاکِ حبابِ جدیدتر).
|
||||
class ExpireChatEvent extends GameEvent {
|
||||
final int seat;
|
||||
final int id;
|
||||
ExpireChatEvent(this.seat, this.id);
|
||||
}
|
||||
|
||||
class LeaveGameEvent extends GameEvent {}
|
||||
|
||||
class StartTableEvent extends GameEvent {
|
||||
|
||||
@@ -47,6 +47,15 @@ class TableLobby {
|
||||
'${players.map((p) => '${p.name}${p.host ? '*' : ''}@${p.seat}').join(',')}';
|
||||
}
|
||||
|
||||
/// حبابِ پیام/شکلکِ یک بازیکن که کنارِ آواتارش نشان داده میشود.
|
||||
/// id برای انقضای دقیق (پاککردنِ همین حباب و نه حبابِ جدیدتر) بهکار میرود.
|
||||
class ChatBubble {
|
||||
final String? text;
|
||||
final String? emoji;
|
||||
final int id;
|
||||
const ChatBubble({this.text, this.emoji, required this.id});
|
||||
}
|
||||
|
||||
class GameUiState extends Equatable {
|
||||
final WsStatus connection;
|
||||
final GameState? state;
|
||||
@@ -56,6 +65,7 @@ class GameUiState extends Equatable {
|
||||
final TableLobby? lobby;
|
||||
final int? countdown;
|
||||
final bool tableClosed;
|
||||
final Map<int, ChatBubble> chatBubbles; // جایگاه ⇒ حبابِ پیامِ فعال
|
||||
|
||||
const GameUiState({
|
||||
this.connection = WsStatus.connecting,
|
||||
@@ -66,6 +76,7 @@ class GameUiState extends Equatable {
|
||||
this.lobby,
|
||||
this.countdown,
|
||||
this.tableClosed = false,
|
||||
this.chatBubbles = const {},
|
||||
});
|
||||
|
||||
GameUiState copyWith({
|
||||
@@ -77,6 +88,7 @@ class GameUiState extends Equatable {
|
||||
TableLobby? lobby,
|
||||
int? countdown,
|
||||
bool? tableClosed,
|
||||
Map<int, ChatBubble>? chatBubbles,
|
||||
bool clearHandResult = false,
|
||||
bool clearNotice = false,
|
||||
}) =>
|
||||
@@ -89,6 +101,7 @@ class GameUiState extends Equatable {
|
||||
lobby: lobby ?? this.lobby,
|
||||
countdown: countdown ?? this.countdown,
|
||||
tableClosed: tableClosed ?? this.tableClosed,
|
||||
chatBubbles: chatBubbles ?? this.chatBubbles,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -101,5 +114,8 @@ class GameUiState extends Equatable {
|
||||
lobby?.sig,
|
||||
countdown,
|
||||
tableClosed,
|
||||
chatBubbles.entries
|
||||
.map((e) => '${e.key}:${e.value.id}')
|
||||
.join(','),
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user