From 3e9f82ecdf6cbe086a67d834a9caa97c09735066 Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Thu, 20 Nov 2025 23:58:31 +0800 Subject: [PATCH] feat: PartyRoomGameLogTrackerProvider --- lib/common/helper/game_log_analyzer.dart | 43 ++++---- lib/generated/intl/messages_en.dart | 4 +- lib/generated/intl/messages_ja.dart | 3 +- lib/generated/intl/messages_ru.dart | 4 +- lib/generated/intl/messages_zh_CN.dart | 5 +- lib/generated/intl/messages_zh_TW.dart | 5 +- lib/generated/l10n.dart | 13 +-- lib/l10n/intl_en.arb | 2 +- lib/l10n/intl_ja.arb | 2 +- lib/l10n/intl_ru.arb | 2 +- lib/l10n/intl_zh_CN.arb | 4 +- lib/l10n/intl_zh_TW.arb | 4 +- lib/provider/party_room.dart | 103 ++++++++++++++++++ lib/provider/party_room.g.dart | 2 +- lib/ui/party_room/party_room_ui_model.dart | 6 + lib/ui/party_room/party_room_ui_model.g.dart | 2 +- .../utils/game_log_tracker_provider.dart | 46 +++----- .../game_log_tracker_provider.freezed.dart | 86 ++++++++------- .../utils/game_log_tracker_provider.g.dart | 2 +- .../detail/party_room_member_list.dart | 15 +-- .../detail/party_room_message_list.dart | 103 ++++++++++++++++++ .../widgets/party_room_register_page.dart | 4 +- 22 files changed, 320 insertions(+), 140 deletions(-) diff --git a/lib/common/helper/game_log_analyzer.dart b/lib/common/helper/game_log_analyzer.dart index 881da31..cc01bb4 100644 --- a/lib/common/helper/game_log_analyzer.dart +++ b/lib/common/helper/game_log_analyzer.dart @@ -15,8 +15,8 @@ class LogAnalyzeLineData { // 格式化后的字段 final String? victimId; // 受害者ID (actor_death) - final String? killerId; // 击杀者ID (actor_death) final String? location; // 位置信息 (request_location_inventory) + final String? area; // 区域信息 final String? playerName; // 玩家名称 (player_login) const LogAnalyzeLineData({ @@ -26,8 +26,8 @@ class LogAnalyzeLineData { this.dateTime, this.tag, this.victimId, - this.killerId, this.location, + this.area, this.playerName, }); @@ -76,10 +76,10 @@ class GameLogAnalyzer { // 致命碰撞解析 static final _fatalCollisionPatterns = { - 'zone': RegExp(r'\[Part:[^\]]*?Zone:\s*([^,\]]+)'), + 'vehicle': RegExp(r'Fatal Collision occured for vehicle\s+(\S+)'), + 'zone': RegExp(r'Zone:\s*([^,\]]+)'), 'player_pilot': RegExp(r'PlayerPilot:\s*(\d)'), 'hit_entity': RegExp(r'hitting entity:\s*(\w+)'), - 'hit_entity_vehicle': RegExp(r'hitting entity:[^\[]*\[Zone:\s*([^\s-]+)'), 'distance': RegExp(r'Distance:\s*([\d.]+)'), }; @@ -93,10 +93,9 @@ class GameLogAnalyzer { // 角色死亡解析 static final _actorDeathPattern = RegExp( - r"CActor::Kill: '([^']+)'.*?" // 受害者ID - r"in zone '([^']+)'.*?" // 死亡位置区域 - r"killed by '([^']+)'.*?" // 击杀者ID - r"with damage type '([^']+)'", // 伤害类型 + r"Actor '([^']+)'.*?" // 受害者ID + r"ejected from zone '([^']+)'.*?" // 原载具/区域 + r"to zone '([^']+)'", // 目标区域 ); // 角色名称解析 @@ -217,7 +216,7 @@ class GameLogAnalyzer { } }); break; - case "Actor Death": + case "[ActorState] Dead": data = _parseActorDeath(line, playerName, shouldCount, (isKill, isDeath, isSelfKill) { if (isSelfKill) { selfKillCount++; @@ -372,10 +371,10 @@ class GameLogAnalyzer { static String? _safeExtract(RegExp pattern, String line) => pattern.firstMatch(line)?.group(1)?.trim(); static LogAnalyzeLineData? _parseFatalCollision(String line) { + final vehicle = _safeExtract(_fatalCollisionPatterns['vehicle']!, line) ?? unknownValue; final zone = _safeExtract(_fatalCollisionPatterns['zone']!, line) ?? unknownValue; final playerPilot = (_safeExtract(_fatalCollisionPatterns['player_pilot']!, line) ?? '0') == '1'; final hitEntity = _safeExtract(_fatalCollisionPatterns['hit_entity']!, line) ?? unknownValue; - final hitEntityVehicle = _safeExtract(_fatalCollisionPatterns['hit_entity_vehicle']!, line) ?? unknownValue; final distance = double.tryParse(_safeExtract(_fatalCollisionPatterns['distance']!, line) ?? '') ?? 0.0; return LogAnalyzeLineData( @@ -385,7 +384,7 @@ class GameLogAnalyzer { zone, playerPilot ? '✅' : '❌', hitEntity, - hitEntityVehicle, + vehicle, distance.toStringAsFixed(2), ), dateTime: _getLogLineDateTimeString(line), @@ -436,27 +435,25 @@ class GameLogAnalyzer { final match = _actorDeathPattern.firstMatch(line); if (match != null) { final victimId = match.group(1) ?? unknownValue; - final zone = match.group(2) ?? unknownValue; - final killerId = match.group(3) ?? unknownValue; - final damageType = match.group(4) ?? unknownValue; + final fromZone = match.group(2) ?? unknownValue; + final toZone = match.group(3) ?? unknownValue; if (shouldCount) { - if (victimId.trim() == killerId.trim()) { - onDeath(false, false, true); // 自杀 - } else { - final isDeath = victimId.trim() == playerName; - final isKill = killerId.trim() == playerName; - onDeath(isKill, isDeath, false); + final isDeath = victimId.trim() == playerName; + if (isDeath) { + onDeath(false, true, false); } } return LogAnalyzeLineData( type: "actor_death", title: S.current.log_analyzer_filter_character_death, - data: S.current.log_analyzer_death_details(victimId, damageType, killerId, zone), + data: S.current.log_analyzer_death_details(victimId, fromZone, toZone), dateTime: _getLogLineDateTimeString(line), - victimId: victimId, // 格式化字段 - killerId: killerId, // 格式化字段 + location: fromZone, + area: toZone, + victimId: victimId, + playerName: playerName, ); } return null; diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index d163323..567fb5a 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -130,8 +130,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(v0, v1, v2, v3, v4) => "Area: ${v0} Player driving: ${v1} Collision entity: ${v2} \nCollision vehicle: ${v3} Collision distance: ${v4} "; - static String m47(v0, v1, v2, v3) => - "Victim ID: ${v0} Cause of death: ${v1} \nKiller ID: ${v2} \nArea: ${v3}"; + static String m47(v0, v2, v3) => + "Victim ID: ${v0} \nLocation: ${v2} \nArea: ${v3}"; static String m48(v0) => "Detailed information: ${v0}"; diff --git a/lib/generated/intl/messages_ja.dart b/lib/generated/intl/messages_ja.dart index 7720faf..fc1578d 100644 --- a/lib/generated/intl/messages_ja.dart +++ b/lib/generated/intl/messages_ja.dart @@ -119,8 +119,7 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(v0, v1, v2, v3, v4) => "エリア:${v0} プレイヤー操縦:${v1} 衝突エンティティ:${v2} \n衝突ビークル:${v3} 衝突距離:${v4} "; - static String m47(v0, v1, v2, v3) => - "被害者ID:${v0} 死因:${v1} \n殺害者ID:${v2} \nエリア:${v3}"; + static String m47(v0, v2, v3) => "被害者ID:${v0} \n位置:${v2} \nエリア:${v3}"; static String m48(v0) => "詳細情報:${v0}"; diff --git a/lib/generated/intl/messages_ru.dart b/lib/generated/intl/messages_ru.dart index ff3f3d7..dc3b99c 100644 --- a/lib/generated/intl/messages_ru.dart +++ b/lib/generated/intl/messages_ru.dart @@ -124,8 +124,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(v0, v1, v2, v3, v4) => "Зона: ${v0} Управление игроком: ${v1} Объект столкновения: ${v2} \nТехника столкновения: ${v3} Дистанция столкновения: ${v4} "; - static String m47(v0, v1, v2, v3) => - "ID жертвы: ${v0} Причина смерти: ${v1} \nID убийцы: ${v2} \nЗона: ${v3}"; + static String m47(v0, v2, v3) => + "ID жертвы: ${v0} \nID убийцы: ${v2} \nЗона: ${v3}"; static String m48(v0) => "Подробная информация: ${v0}"; diff --git a/lib/generated/intl/messages_zh_CN.dart b/lib/generated/intl/messages_zh_CN.dart index d9b947e..74dfbda 100644 --- a/lib/generated/intl/messages_zh_CN.dart +++ b/lib/generated/intl/messages_zh_CN.dart @@ -119,8 +119,7 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(v0, v1, v2, v3, v4) => "区域:${v0} 玩家驾驶:${v1} 碰撞实体:${v2} \n碰撞载具: ${v3} 碰撞距离:${v4} "; - static String m47(v0, v1, v2, v3) => - "受害者ID:${v0} 死因:${v1} \n击杀者ID:${v2} \n区域:${v3}"; + static String m47(v0, v2, v3) => "受害者ID:${v0} \n位置:${v2} \n区域:${v3}"; static String m48(v0) => "详细信息:${v0}"; @@ -1406,7 +1405,7 @@ class MessageLookup extends MessageLookupByLibrary { "tools_rsi_launcher_enhance_init_msg2": MessageLookupByLibrary.simpleMessage("正在从网络获取增强数据..."), "tools_rsi_launcher_enhance_msg_error": - MessageLookupByLibrary.simpleMessage("获取增强数据失败,可能是网络问题或当前版本不支持"), + MessageLookupByLibrary.simpleMessage("当前版本暂不支持,請等待適配..."), "tools_rsi_launcher_enhance_msg_error_get_launcher_info_error": MessageLookupByLibrary.simpleMessage("读取启动器信息失败!"), "tools_rsi_launcher_enhance_msg_error_get_launcher_info_error_with_args": diff --git a/lib/generated/intl/messages_zh_TW.dart b/lib/generated/intl/messages_zh_TW.dart index 782b8e2..9afad13 100644 --- a/lib/generated/intl/messages_zh_TW.dart +++ b/lib/generated/intl/messages_zh_TW.dart @@ -115,8 +115,7 @@ class MessageLookup extends MessageLookupByLibrary { static String m46(v0, v1, v2, v3, v4) => "區域:${v0} 玩家駕駛:${v1} 碰撞實體:${v2} \n碰撞載具: ${v3} 碰撞距離:${v4} "; - static String m47(v0, v1, v2, v3) => - "受害者ID:${v0} 死因:${v1} \n擊殺者ID:${v2} \n區域:${v3}"; + static String m47(v0, v2, v3) => "受害者ID:${v0} \n位置:${v2} \n區域:${v3}"; static String m48(v0) => "詳細資訊:${v0}"; @@ -1397,7 +1396,7 @@ class MessageLookup extends MessageLookupByLibrary { "tools_rsi_launcher_enhance_init_msg2": MessageLookupByLibrary.simpleMessage("正在從網路取得增強資料..."), "tools_rsi_launcher_enhance_msg_error": - MessageLookupByLibrary.simpleMessage("增強資料取得失敗,可能是網路問題或目前版本不支援"), + MessageLookupByLibrary.simpleMessage("目前版本不支援"), "tools_rsi_launcher_enhance_msg_error_get_launcher_info_error": MessageLookupByLibrary.simpleMessage("讀取啟動器資訊失敗!"), "tools_rsi_launcher_enhance_msg_error_get_launcher_info_error_with_args": diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 0f62308..23ebfb2 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -5808,18 +5808,13 @@ class S { ); } - /// `Victim ID: {v0} Cause of death: {v1} \nKiller ID: {v2} \nArea: {v3}` - String log_analyzer_death_details( - Object v0, - Object v1, - Object v2, - Object v3, - ) { + /// `Victim ID: {v0} \nLocation: {v2} \nArea: {v3}` + String log_analyzer_death_details(Object v0, Object v2, Object v3) { return Intl.message( - 'Victim ID: $v0 Cause of death: $v1 \nKiller ID: $v2 \nArea: $v3', + 'Victim ID: $v0 \nLocation: $v2 \nArea: $v3', name: 'log_analyzer_death_details', desc: '', - args: [v0, v1, v2, v3], + args: [v0, v2, v3], ); } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 99e35ca..e07a455 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1141,7 +1141,7 @@ "@log_analyzer_disintegration": {}, "log_analyzer_vehicle_damage_details": "Vehicle model: {v0} \nArea: {v1} \nDamage level: {v2} ({v3}) Responsible party: {v4}", "@log_analyzer_vehicle_damage_details": {}, - "log_analyzer_death_details": "Victim ID: {v0} Cause of death: {v1} \nKiller ID: {v2} \nArea: {v3}", + "log_analyzer_death_details": "Victim ID: {v0} \nLocation: {v2} \nArea: {v3}", "@log_analyzer_death_details": {}, "log_analyzer_player_login": "Player {v0} logged in...", "@log_analyzer_player_login": {}, diff --git a/lib/l10n/intl_ja.arb b/lib/l10n/intl_ja.arb index 5fddd2f..7dd8058 100644 --- a/lib/l10n/intl_ja.arb +++ b/lib/l10n/intl_ja.arb @@ -1139,7 +1139,7 @@ "@log_analyzer_disintegration": {}, "log_analyzer_vehicle_damage_details": "ビークルモデル:{v0} \nエリア:{v1} \n損傷レベル:{v2} ({v3}) 責任者:{v4}", "@log_analyzer_vehicle_damage_details": {}, - "log_analyzer_death_details": "被害者ID:{v0} 死因:{v1} \n殺害者ID:{v2} \nエリア:{v3}", + "log_analyzer_death_details": "被害者ID:{v0} \n位置:{v2} \nエリア:{v3}", "@log_analyzer_death_details": {}, "log_analyzer_player_login": "プレイヤー {v0} ログイン中...", "@log_analyzer_player_login": {}, diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 3796570..7b605e7 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1139,7 +1139,7 @@ "@log_analyzer_disintegration": {}, "log_analyzer_vehicle_damage_details": "Модель техники: {v0} \nЗона: {v1} \nУровень повреждения: {v2} ({v3}) Виновник: {v4}", "@log_analyzer_vehicle_damage_details": {}, - "log_analyzer_death_details": "ID жертвы: {v0} Причина смерти: {v1} \nID убийцы: {v2} \nЗона: {v3}", + "log_analyzer_death_details": "ID жертвы: {v0} \nID убийцы: {v2} \nЗона: {v3}", "@log_analyzer_death_details": {}, "log_analyzer_player_login": "Игрок {v0} входит в игру...", "@log_analyzer_player_login": {}, diff --git a/lib/l10n/intl_zh_CN.arb b/lib/l10n/intl_zh_CN.arb index c02efa8..5983c99 100644 --- a/lib/l10n/intl_zh_CN.arb +++ b/lib/l10n/intl_zh_CN.arb @@ -772,7 +772,7 @@ "tools_rsi_launcher_enhance_title": "RSI 启动器增强", "tools_rsi_launcher_enhance_msg_version": "启动器内部版本信息:{v0}", "tools_rsi_launcher_enhance_msg_patch_status": "补丁状态:{v0}", - "tools_rsi_launcher_enhance_msg_error": "获取增强数据失败,可能是网络问题或当前版本不支持", + "tools_rsi_launcher_enhance_msg_error": "当前版本暂不支持,請等待適配...", "tools_rsi_launcher_enhance_title_localization": "RSI 启动器本地化", "tools_rsi_launcher_enhance_subtitle_localization": "为 RSI 启动器增加多语言支持。", "tools_rsi_launcher_enhance_title_download_booster": "RSI 启动器下载增强", @@ -890,7 +890,7 @@ "log_analyzer_soft_death": "软死亡", "log_analyzer_disintegration": "解体", "log_analyzer_vehicle_damage_details": "载具型号:{v0} \n区域:{v1} \n损毁等级:{v2} ({v3}) 责任方:{v4}", - "log_analyzer_death_details": "受害者ID:{v0} 死因:{v1} \n击杀者ID:{v2} \n区域:{v3}", + "log_analyzer_death_details": "受害者ID:{v0} \n位置:{v2} \n区域:{v3}", "log_analyzer_player_login": "玩家 {v0} 登录 ...", "log_analyzer_view_local_inventory": "查看本地库存", "log_analyzer_player_location": "玩家ID:{v0} 位置:{v1}", diff --git a/lib/l10n/intl_zh_TW.arb b/lib/l10n/intl_zh_TW.arb index 79098a3..a2919dc 100644 --- a/lib/l10n/intl_zh_TW.arb +++ b/lib/l10n/intl_zh_TW.arb @@ -907,7 +907,7 @@ "@tools_rsi_launcher_enhance_msg_version": {}, "tools_rsi_launcher_enhance_msg_patch_status": "補丁狀態:{v0}", "@tools_rsi_launcher_enhance_msg_patch_status": {}, - "tools_rsi_launcher_enhance_msg_error": "增強資料取得失敗,可能是網路問題或目前版本不支援", + "tools_rsi_launcher_enhance_msg_error": "目前版本不支援", "@tools_rsi_launcher_enhance_msg_error": {}, "tools_rsi_launcher_enhance_title_localization": "RSI 啟動器本地化", "@tools_rsi_launcher_enhance_title_localization": {}, @@ -1141,7 +1141,7 @@ "@log_analyzer_disintegration": {}, "log_analyzer_vehicle_damage_details": "載具型號:{v0} \n區域:{v1} \n損毀等級:{v2} ({v3}) 責任方:{v4}", "@log_analyzer_vehicle_damage_details": {}, - "log_analyzer_death_details": "受害者ID:{v0} 死因:{v1} \n擊殺者ID:{v2} \n區域:{v3}", + "log_analyzer_death_details": "受害者ID:{v0} \n位置:{v2} \n區域:{v3}", "@log_analyzer_death_details": {}, "log_analyzer_player_login": "玩家 {v0} 登入 ...", "@log_analyzer_player_login": {}, diff --git a/lib/provider/party_room.dart b/lib/provider/party_room.dart index 6ef7446..9c6a0bd 100644 --- a/lib/provider/party_room.dart +++ b/lib/provider/party_room.dart @@ -75,7 +75,11 @@ class PartyRoom extends _$PartyRoom { Box? _confBox; StreamSubscription? _eventStreamSubscription; Timer? _heartbeatTimer; + Timer? _reconnectTimer; bool _disposed = false; + int _reconnectAttempts = 0; + static const int _maxReconnectAttempts = 5; + static const Duration _reconnectDelay = Duration(seconds: 3); @override PartyRoomFullState build() { @@ -398,6 +402,7 @@ class PartyRoom extends _$PartyRoom { await _stopHeartbeat(); await _stopEventStream(); + _reconnectAttempts = 0; _dismissRoom(); dPrint('[PartyRoom] Left room: $roomUuid'); @@ -421,6 +426,7 @@ class PartyRoom extends _$PartyRoom { await _stopHeartbeat(); await _stopEventStream(); + _reconnectAttempts = 0; _dismissRoom(); dPrint('[PartyRoom] Dismissed room: $roomUuid'); @@ -754,24 +760,106 @@ class PartyRoom extends _$PartyRoom { _eventStreamSubscription = stream.listen( (event) { + // 重置重连计数器,因为连接正常 + _reconnectAttempts = 0; _handleRoomEvent(event); }, onError: (error) { dPrint('[PartyRoom] Event stream error: $error'); + // 发生错误时尝试重连 + _scheduleReconnect(roomUuid); }, onDone: () { dPrint('[PartyRoom] Event stream closed'); + // 流关闭时尝试重连 + _scheduleReconnect(roomUuid); }, ); dPrint('[PartyRoom] Event stream started'); + // 成功启动,重置重连计数 + _reconnectAttempts = 0; } catch (e) { dPrint('[PartyRoom] StartEventStream error: $e'); + // 启动失败时尝试重连 + _scheduleReconnect(roomUuid); + } + } + + /// 调度重连 + void _scheduleReconnect(String roomUuid) { + // 如果已经销毁或不在房间内,不重连 + if (_disposed || state.room.roomUuid == null || state.room.roomUuid != roomUuid) { + dPrint('[PartyRoom] Skip reconnect: disposed=$_disposed, roomUuid=${state.room.roomUuid}'); + return; + } + + // 如果已经有重连任务在进行,不重复调度 + if (_reconnectTimer?.isActive ?? false) { + return; + } + + // 检查重连次数 + if (_reconnectAttempts >= _maxReconnectAttempts) { + dPrint('[PartyRoom] Max reconnect attempts reached ($_maxReconnectAttempts)'); + return; + } + + _reconnectAttempts++; + dPrint( + '[PartyRoom] Scheduling reconnect attempt $_reconnectAttempts/$_maxReconnectAttempts in ${_reconnectDelay.inSeconds}s', + ); + + _reconnectTimer = Timer(_reconnectDelay, () async { + await _attemptReconnect(roomUuid); + }); + } + + /// 尝试重连 + Future _attemptReconnect(String roomUuid) async { + if (_disposed || state.room.roomUuid == null || state.room.roomUuid != roomUuid) { + dPrint('[PartyRoom] Abort reconnect: no longer in room'); + return; + } + + try { + dPrint('[PartyRoom] Attempting to reconnect event stream...'); + + // 先检查自己是否还在房间内 + final client = state.client.roomClient; + if (client == null) { + dPrint('[PartyRoom] Reconnect failed: client not available'); + _scheduleReconnect(roomUuid); + return; + } + + // 调用 getMyRoom 检查是否还在房间内 + final response = await client.getMyRoom(partroom.GetMyRoomRequest(), options: _getAuthCallOptions()); + + if (!response.hasRoom() || response.room.roomUuid != roomUuid) { + dPrint('[PartyRoom] Reconnect failed: no longer in room'); + // 不在房间内,清理状态 + await _stopHeartbeat(); + await _stopEventStream(); + _reconnectAttempts = 0; + _dismissRoom(); + return; + } + + // 确认还在房间内,重新启动事件流 + dPrint('[PartyRoom] Still in room, restarting event stream'); + await _startEventStream(roomUuid); + } catch (e) { + dPrint('[PartyRoom] Reconnect attempt failed: $e'); + // 重连失败,继续调度下一次重连 + _scheduleReconnect(roomUuid); } } /// 停止事件流监听 Future _stopEventStream() async { + _reconnectTimer?.cancel(); + _reconnectTimer = null; await _eventStreamSubscription?.cancel(); _eventStreamSubscription = null; dPrint('[PartyRoom] Event stream stopped'); @@ -794,6 +882,21 @@ class PartyRoom extends _$PartyRoom { case partroom.RoomEventType.MEMBER_JOINED: case partroom.RoomEventType.MEMBER_LEFT: case partroom.RoomEventType.MEMBER_KICKED: + if (event.type == partroom.RoomEventType.MEMBER_KICKED) { + // 判断被踢的是不是自己 + if (event.member.gameUserId == state.auth.userInfo?.gameUserId) { + // 离开房间 + _dismissRoom(); + return; + } + } + // + if (event.type == partroom.RoomEventType.MEMBER_LEFT) { + if (event.member.gameUserId == state.auth.userInfo?.gameUserId) { + // 判断离开的是不是自己 + return; + } + } // 刷新成员列表 if (state.room.roomUuid != null) { getRoomMembers(state.room.roomUuid!); diff --git a/lib/provider/party_room.g.dart b/lib/provider/party_room.g.dart index 5106025..d787f22 100644 --- a/lib/provider/party_room.g.dart +++ b/lib/provider/party_room.g.dart @@ -44,7 +44,7 @@ final class PartyRoomProvider } } -String _$partyRoomHash() => r'02cdd156995799411eb47107d5c197f43e78629e'; +String _$partyRoomHash() => r'd7182854c8caf5bb362c45a4e6e2ab40c2ef1b09'; /// PartyRoom Provider diff --git a/lib/ui/party_room/party_room_ui_model.dart b/lib/ui/party_room/party_room_ui_model.dart index 0bb398f..ca09df1 100644 --- a/lib/ui/party_room/party_room_ui_model.dart +++ b/lib/ui/party_room/party_room_ui_model.dart @@ -105,6 +105,12 @@ class PartyRoomUIModel extends _$PartyRoomUIModel { playTime: currentGameStartTime != gameStartTime ? gameStartTime : null, ); } + + if (next.deathEvents?.isNotEmpty ?? false) { + for (final event in next.deathEvents!) { + ref.read(partyRoomProvider.notifier).sendSignal("special_death", params: {"location": event.$1, "area": event.$2}); + } + } } /// 处理连接状态变化 diff --git a/lib/ui/party_room/party_room_ui_model.g.dart b/lib/ui/party_room/party_room_ui_model.g.dart index ff73367..ef0a433 100644 --- a/lib/ui/party_room/party_room_ui_model.g.dart +++ b/lib/ui/party_room/party_room_ui_model.g.dart @@ -41,7 +41,7 @@ final class PartyRoomUIModelProvider } } -String _$partyRoomUIModelHash() => r'a0b6c3632ff33f2d58882f9bc1ab58c69c2487f4'; +String _$partyRoomUIModelHash() => r'add4703c9129465718a7850ea09025aa1ff35358'; abstract class _$PartyRoomUIModel extends $Notifier { PartyRoomUIState build(); diff --git a/lib/ui/party_room/utils/game_log_tracker_provider.dart b/lib/ui/party_room/utils/game_log_tracker_provider.dart index 6b21537..a526b86 100644 --- a/lib/ui/party_room/utils/game_log_tracker_provider.dart +++ b/lib/ui/party_room/utils/game_log_tracker_provider.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; @@ -18,8 +19,7 @@ sealed class PartyRoomGameLogTrackerProviderState with _$PartyRoomGameLogTracker @Default(0) int kills, @Default(0) int deaths, DateTime? gameStartTime, - @Default([]) List killedIds, // 本次迭代新增的击杀ID - @Default([]) List deathIds, // 本次迭代新增的死亡ID + List<(String, String)>? deathEvents, }) = _PartyRoomGameLogTrackerProviderState; } @@ -32,6 +32,7 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider @override PartyRoomGameLogTrackerProviderState build({required DateTime startTime}) { + startTime = DateTime.now(); dPrint("[PartyRoomGameLogTrackerProvider] init $startTime"); ref.onDispose(() { _disposed = true; @@ -65,16 +66,9 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider } } catch (e) { // 游戏未启动或发生错误 - state = state.copyWith( - location: '<游戏未启动>', - gameStartTime: null, - kills: 0, - deaths: 0, - killedIds: [], - deathIds: [], - ); + state = state.copyWith(location: '<游戏未启动>', gameStartTime: null, kills: 0, deaths: 0); } - await Future.delayed(const Duration(seconds: 5)); + await Future.delayed(const Duration(seconds: 10)); } } @@ -90,9 +84,7 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider // 从统计数据中直接获取最新位置(全量查找的结果) final location = statistics.latestLocation == null ? '<主菜单>' : '[${statistics.latestLocation}]'; - // 计算基于 _lastQueryTime 之后的增量 ID - final newKilledIds = []; - final newDeathIds = []; + List<(String, String)> deathEvents = []; if (_lastQueryTime != null) { // 遍历所有 actor_death 事件 @@ -125,24 +117,15 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider } } - // 使用格式化字段,不再重新解析 - final victimId = data.victimId; - final killerId = data.killerId; - - if (victimId != null && killerId != null && victimId != killerId) { - // 如果玩家是击杀者,记录被击杀的ID - if (killerId == statistics.playerName) { - newKilledIds.add(victimId); - } - - // 如果玩家是受害者,记录击杀者ID - if (victimId == statistics.playerName) { - newDeathIds.add(killerId); - } + if (data.playerName == data.victimId) { + // 玩家死亡,增加记录 + deathEvents.add((data.location ?? "-", data.area ?? "-")); } } } - + // debugPrint( + // "[PartyRoomGameLogTrackerProvider] location $location killCount ${statistics.killCount} deathCount ${statistics.deathCount}", + // ); // 更新状态,只存储本次迭代的增量数据 state = state.copyWith( location: location, @@ -151,10 +134,7 @@ class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider deaths: statistics.deathCount, // 从 startTime 开始的总计数 gameStartTime: statistics.gameStartTime, - // 全量查找的游戏开始时间 - killedIds: newKilledIds, - // 只存储本次迭代的增量 - deathIds: newDeathIds, // 只存储本次迭代的增量 + deathEvents: deathEvents, ); // 更新查询时间为本次查询的时刻 diff --git a/lib/ui/party_room/utils/game_log_tracker_provider.freezed.dart b/lib/ui/party_room/utils/game_log_tracker_provider.freezed.dart index 0b16234..1adf1d6 100644 --- a/lib/ui/party_room/utils/game_log_tracker_provider.freezed.dart +++ b/lib/ui/party_room/utils/game_log_tracker_provider.freezed.dart @@ -12,10 +12,9 @@ part of 'game_log_tracker_provider.dart'; // dart format off T _$identity(T value) => value; /// @nodoc -mixin _$PartyRoomGameLogTrackerProviderState { +mixin _$PartyRoomGameLogTrackerProviderState implements DiagnosticableTreeMixin { - String get location; int get kills; int get deaths; DateTime? get gameStartTime; List get killedIds;// 本次迭代新增的击杀ID - List get deathIds; + String get location; int get kills; int get deaths; DateTime? get gameStartTime; List<(String, String)>? get deathEvents; /// Create a copy of PartyRoomGameLogTrackerProviderState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -23,19 +22,25 @@ mixin _$PartyRoomGameLogTrackerProviderState { $PartyRoomGameLogTrackerProviderStateCopyWith get copyWith => _$PartyRoomGameLogTrackerProviderStateCopyWithImpl(this as PartyRoomGameLogTrackerProviderState, _$identity); +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'PartyRoomGameLogTrackerProviderState')) + ..add(DiagnosticsProperty('location', location))..add(DiagnosticsProperty('kills', kills))..add(DiagnosticsProperty('deaths', deaths))..add(DiagnosticsProperty('gameStartTime', gameStartTime))..add(DiagnosticsProperty('deathEvents', deathEvents)); +} @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomGameLogTrackerProviderState&&(identical(other.location, location) || other.location == location)&&(identical(other.kills, kills) || other.kills == kills)&&(identical(other.deaths, deaths) || other.deaths == deaths)&&(identical(other.gameStartTime, gameStartTime) || other.gameStartTime == gameStartTime)&&const DeepCollectionEquality().equals(other.killedIds, killedIds)&&const DeepCollectionEquality().equals(other.deathIds, deathIds)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomGameLogTrackerProviderState&&(identical(other.location, location) || other.location == location)&&(identical(other.kills, kills) || other.kills == kills)&&(identical(other.deaths, deaths) || other.deaths == deaths)&&(identical(other.gameStartTime, gameStartTime) || other.gameStartTime == gameStartTime)&&const DeepCollectionEquality().equals(other.deathEvents, deathEvents)); } @override -int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(killedIds),const DeepCollectionEquality().hash(deathIds)); +int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(deathEvents)); @override -String toString() { - return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, killedIds: $killedIds, deathIds: $deathIds)'; +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { + return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, deathEvents: $deathEvents)'; } @@ -46,7 +51,7 @@ abstract mixin class $PartyRoomGameLogTrackerProviderStateCopyWith<$Res> { factory $PartyRoomGameLogTrackerProviderStateCopyWith(PartyRoomGameLogTrackerProviderState value, $Res Function(PartyRoomGameLogTrackerProviderState) _then) = _$PartyRoomGameLogTrackerProviderStateCopyWithImpl; @useResult $Res call({ - String location, int kills, int deaths, DateTime? gameStartTime, List killedIds, List deathIds + String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents }); @@ -63,15 +68,14 @@ class _$PartyRoomGameLogTrackerProviderStateCopyWithImpl<$Res> /// Create a copy of PartyRoomGameLogTrackerProviderState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? location = null,Object? kills = null,Object? deaths = null,Object? gameStartTime = freezed,Object? killedIds = null,Object? deathIds = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? location = null,Object? kills = null,Object? deaths = null,Object? gameStartTime = freezed,Object? deathEvents = freezed,}) { return _then(_self.copyWith( location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable as String,kills: null == kills ? _self.kills : kills // ignore: cast_nullable_to_non_nullable as int,deaths: null == deaths ? _self.deaths : deaths // ignore: cast_nullable_to_non_nullable as int,gameStartTime: freezed == gameStartTime ? _self.gameStartTime : gameStartTime // ignore: cast_nullable_to_non_nullable -as DateTime?,killedIds: null == killedIds ? _self.killedIds : killedIds // ignore: cast_nullable_to_non_nullable -as List,deathIds: null == deathIds ? _self.deathIds : deathIds // ignore: cast_nullable_to_non_nullable -as List, +as DateTime?,deathEvents: freezed == deathEvents ? _self.deathEvents : deathEvents // ignore: cast_nullable_to_non_nullable +as List<(String, String)>?, )); } @@ -153,10 +157,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List killedIds, List deathIds)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _PartyRoomGameLogTrackerProviderState() when $default != null: -return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.killedIds,_that.deathIds);case _: +return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.deathEvents);case _: return orElse(); } @@ -174,10 +178,10 @@ return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_tha /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List killedIds, List deathIds) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents) $default,) {final _that = this; switch (_that) { case _PartyRoomGameLogTrackerProviderState(): -return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.killedIds,_that.deathIds);} +return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.deathEvents);} } /// A variant of `when` that fallback to returning `null` /// @@ -191,10 +195,10 @@ return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_tha /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String location, int kills, int deaths, DateTime? gameStartTime, List killedIds, List deathIds)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents)? $default,) {final _that = this; switch (_that) { case _PartyRoomGameLogTrackerProviderState() when $default != null: -return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.killedIds,_that.deathIds);case _: +return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.deathEvents);case _: return null; } @@ -205,28 +209,21 @@ return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_tha /// @nodoc -class _PartyRoomGameLogTrackerProviderState implements PartyRoomGameLogTrackerProviderState { - const _PartyRoomGameLogTrackerProviderState({this.location = '', this.kills = 0, this.deaths = 0, this.gameStartTime, final List killedIds = const [], final List deathIds = const []}): _killedIds = killedIds,_deathIds = deathIds; +class _PartyRoomGameLogTrackerProviderState with DiagnosticableTreeMixin implements PartyRoomGameLogTrackerProviderState { + const _PartyRoomGameLogTrackerProviderState({this.location = '', this.kills = 0, this.deaths = 0, this.gameStartTime, final List<(String, String)>? deathEvents}): _deathEvents = deathEvents; @override@JsonKey() final String location; @override@JsonKey() final int kills; @override@JsonKey() final int deaths; @override final DateTime? gameStartTime; - final List _killedIds; -@override@JsonKey() List get killedIds { - if (_killedIds is EqualUnmodifiableListView) return _killedIds; + final List<(String, String)>? _deathEvents; +@override List<(String, String)>? get deathEvents { + final value = _deathEvents; + if (value == null) return null; + if (_deathEvents is EqualUnmodifiableListView) return _deathEvents; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_killedIds); -} - -// 本次迭代新增的击杀ID - final List _deathIds; -// 本次迭代新增的击杀ID -@override@JsonKey() List get deathIds { - if (_deathIds is EqualUnmodifiableListView) return _deathIds; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_deathIds); + return EqualUnmodifiableListView(value); } @@ -237,19 +234,25 @@ class _PartyRoomGameLogTrackerProviderState implements PartyRoomGameLogTrackerPr _$PartyRoomGameLogTrackerProviderStateCopyWith<_PartyRoomGameLogTrackerProviderState> get copyWith => __$PartyRoomGameLogTrackerProviderStateCopyWithImpl<_PartyRoomGameLogTrackerProviderState>(this, _$identity); +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'PartyRoomGameLogTrackerProviderState')) + ..add(DiagnosticsProperty('location', location))..add(DiagnosticsProperty('kills', kills))..add(DiagnosticsProperty('deaths', deaths))..add(DiagnosticsProperty('gameStartTime', gameStartTime))..add(DiagnosticsProperty('deathEvents', deathEvents)); +} @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomGameLogTrackerProviderState&&(identical(other.location, location) || other.location == location)&&(identical(other.kills, kills) || other.kills == kills)&&(identical(other.deaths, deaths) || other.deaths == deaths)&&(identical(other.gameStartTime, gameStartTime) || other.gameStartTime == gameStartTime)&&const DeepCollectionEquality().equals(other._killedIds, _killedIds)&&const DeepCollectionEquality().equals(other._deathIds, _deathIds)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomGameLogTrackerProviderState&&(identical(other.location, location) || other.location == location)&&(identical(other.kills, kills) || other.kills == kills)&&(identical(other.deaths, deaths) || other.deaths == deaths)&&(identical(other.gameStartTime, gameStartTime) || other.gameStartTime == gameStartTime)&&const DeepCollectionEquality().equals(other._deathEvents, _deathEvents)); } @override -int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(_killedIds),const DeepCollectionEquality().hash(_deathIds)); +int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(_deathEvents)); @override -String toString() { - return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, killedIds: $killedIds, deathIds: $deathIds)'; +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { + return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, deathEvents: $deathEvents)'; } @@ -260,7 +263,7 @@ abstract mixin class _$PartyRoomGameLogTrackerProviderStateCopyWith<$Res> implem factory _$PartyRoomGameLogTrackerProviderStateCopyWith(_PartyRoomGameLogTrackerProviderState value, $Res Function(_PartyRoomGameLogTrackerProviderState) _then) = __$PartyRoomGameLogTrackerProviderStateCopyWithImpl; @override @useResult $Res call({ - String location, int kills, int deaths, DateTime? gameStartTime, List killedIds, List deathIds + String location, int kills, int deaths, DateTime? gameStartTime, List<(String, String)>? deathEvents }); @@ -277,15 +280,14 @@ class __$PartyRoomGameLogTrackerProviderStateCopyWithImpl<$Res> /// Create a copy of PartyRoomGameLogTrackerProviderState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? location = null,Object? kills = null,Object? deaths = null,Object? gameStartTime = freezed,Object? killedIds = null,Object? deathIds = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? location = null,Object? kills = null,Object? deaths = null,Object? gameStartTime = freezed,Object? deathEvents = freezed,}) { return _then(_PartyRoomGameLogTrackerProviderState( location: null == location ? _self.location : location // ignore: cast_nullable_to_non_nullable as String,kills: null == kills ? _self.kills : kills // ignore: cast_nullable_to_non_nullable as int,deaths: null == deaths ? _self.deaths : deaths // ignore: cast_nullable_to_non_nullable as int,gameStartTime: freezed == gameStartTime ? _self.gameStartTime : gameStartTime // ignore: cast_nullable_to_non_nullable -as DateTime?,killedIds: null == killedIds ? _self._killedIds : killedIds // ignore: cast_nullable_to_non_nullable -as List,deathIds: null == deathIds ? _self._deathIds : deathIds // ignore: cast_nullable_to_non_nullable -as List, +as DateTime?,deathEvents: freezed == deathEvents ? _self._deathEvents : deathEvents // ignore: cast_nullable_to_non_nullable +as List<(String, String)>?, )); } diff --git a/lib/ui/party_room/utils/game_log_tracker_provider.g.dart b/lib/ui/party_room/utils/game_log_tracker_provider.g.dart index 7547c9f..67e9814 100644 --- a/lib/ui/party_room/utils/game_log_tracker_provider.g.dart +++ b/lib/ui/party_room/utils/game_log_tracker_provider.g.dart @@ -66,7 +66,7 @@ final class PartyRoomGameLogTrackerProviderProvider } String _$partyRoomGameLogTrackerProviderHash() => - r'ecb015eb46d25bfe11bbb153242fd5c4f20ef367'; + r'3e1560b2fffc5461a41bece57b43e27f4112ad0c'; final class PartyRoomGameLogTrackerProviderFamily extends $Family with diff --git a/lib/ui/party_room/widgets/detail/party_room_member_list.dart b/lib/ui/party_room/widgets/detail/party_room_member_list.dart index b339d1d..0be9068 100644 --- a/lib/ui/party_room/widgets/detail/party_room_member_list.dart +++ b/lib/ui/party_room/widgets/detail/party_room_member_list.dart @@ -97,15 +97,12 @@ class PartyRoomMemberItem extends ConsumerWidget { ), Row( children: [ - Text( - member.status.currentLocation.isNotEmpty ? member.status.currentLocation : '...', - style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .9)), - overflow: TextOverflow.ellipsis, - ), - SizedBox(width: 4), - Text( - "K: ${member.status.kills} D: ${member.status.deaths}", - style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)), + Expanded( + child: Text( + member.status.currentLocation.isNotEmpty ? member.status.currentLocation : '...', + style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .9)), + overflow: TextOverflow.ellipsis, + ), ), ], ), diff --git a/lib/ui/party_room/widgets/detail/party_room_message_list.dart b/lib/ui/party_room/widgets/detail/party_room_message_list.dart index 83ac2ae..3925797 100644 --- a/lib/ui/party_room/widgets/detail/party_room_message_list.dart +++ b/lib/ui/party_room/widgets/detail/party_room_message_list.dart @@ -210,6 +210,11 @@ class _MessageItem extends ConsumerWidget { final userName = _getEventUserName(roomEvent); final avatarUrl = _getEventAvatarUrl(roomEvent); + // 检查是否是死亡信号,显示特殊卡片 + if (isSignal && roomEvent.signalId == 'special_death') { + return _buildDeathMessageCard(context, roomEvent, userName, avatarUrl); + } + final text = _getEventText(roomEvent, ref); if (text == null) return const SizedBox.shrink(); @@ -259,6 +264,104 @@ class _MessageItem extends ConsumerWidget { ); } + Widget _buildDeathMessageCard( + BuildContext context, + partroom.RoomEvent roomEvent, + String userName, + String? avatarUrl, + ) { + final location = roomEvent.signalParams['location'] ?? '未知位置'; + final area = roomEvent.signalParams['area'] ?? '未知区域'; + + return Container( + margin: const EdgeInsets.only(bottom: 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildUserAvatar(userName, avatarUrl: avatarUrl, size: 28), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 用户名和时间 + Row( + children: [ + Text( + userName, + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Colors.white), + ), + const SizedBox(width: 8), + Text( + _formatTime(roomEvent.timestamp), + style: const TextStyle(fontSize: 11, color: Color(0xFF80848E)), + ), + ], + ), + const SizedBox(height: 8), + // 死亡卡片 + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFF2B2D31), + border: Border.all(color: const Color(0xFFED4245).withValues(alpha: 0.3)), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题 + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: const Color(0xFFED4245).withValues(alpha: 0.2), + shape: BoxShape.circle, + ), + child: const Icon(FluentIcons.status_error_full, size: 14, color: Color(0xFFED4245)), + ), + const SizedBox(width: 8), + const Text( + '玩家死亡', + style: TextStyle(fontSize: 14, color: Color(0xFFED4245), fontWeight: FontWeight.w600), + ), + ], + ), + const SizedBox(height: 12), + // 位置信息 + _buildInfoRow(icon: FluentIcons.location, label: '位置', value: location), + const SizedBox(height: 8), + // 区域信息 + _buildInfoRow(icon: FluentIcons.map_pin, label: '区域', value: area), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget _buildInfoRow({required IconData icon, required String label, required String value}) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(icon, size: 14, color: Colors.white.withValues(alpha: .4)), + const SizedBox(width: 8), + Text( + '$label: ', + style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: .4), fontWeight: FontWeight.w500), + ), + Expanded( + child: Text(value, style: const TextStyle(fontSize: 13, color: Color(0xFFDBDEE1))), + ), + ], + ); + } + String _getEventUserName(partroom.RoomEvent event) { switch (event.type) { case partroom.RoomEventType.SIGNAL_BROADCAST: diff --git a/lib/ui/party_room/widgets/party_room_register_page.dart b/lib/ui/party_room/widgets/party_room_register_page.dart index f51c330..705b5b6 100644 --- a/lib/ui/party_room/widgets/party_room_register_page.dart +++ b/lib/ui/party_room/widgets/party_room_register_page.dart @@ -123,7 +123,7 @@ class PartyRoomRegisterPage extends HookConsumerWidget { ? const Color(0xFF4CAF50) : isActive ? const Color(0xFF4A9EFF) - : Colors.grey.withValues(alpha: 0.3), + : Colors.white.withValues(alpha: 0.4), shape: BoxShape.circle, ), child: Center( @@ -143,7 +143,7 @@ class PartyRoomRegisterPage extends HookConsumerWidget { title, style: TextStyle( fontSize: 11, - color: isActive ? const Color(0xFF4A9EFF) : Colors.grey.withValues(alpha: 0.7), + color: isActive ? const Color(0xFF4A9EFF) : Colors.white.withValues(alpha: 0.4), fontWeight: isActive ? FontWeight.bold : FontWeight.normal, ), ),