mirror of
https://github.com/StarCitizenToolBox/app.git
synced 2026-01-14 20:20:28 +00:00
feat: update messages
This commit is contained in:
parent
078eae8e52
commit
4718b4627b
@ -39,6 +39,7 @@ sealed class PartyRoomState with _$PartyRoomState {
|
|||||||
@Default(false) bool isOwner,
|
@Default(false) bool isOwner,
|
||||||
String? roomUuid,
|
String? roomUuid,
|
||||||
@Default([]) List<partroom.RoomEvent> recentEvents,
|
@Default([]) List<partroom.RoomEvent> recentEvents,
|
||||||
|
@Default(false) bool eventStreamDisconnected,
|
||||||
}) = _PartyRoomState;
|
}) = _PartyRoomState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,11 +76,7 @@ class PartyRoom extends _$PartyRoom {
|
|||||||
Box? _confBox;
|
Box? _confBox;
|
||||||
StreamSubscription<partroom.RoomEvent>? _eventStreamSubscription;
|
StreamSubscription<partroom.RoomEvent>? _eventStreamSubscription;
|
||||||
Timer? _heartbeatTimer;
|
Timer? _heartbeatTimer;
|
||||||
Timer? _reconnectTimer;
|
|
||||||
bool _disposed = false;
|
bool _disposed = false;
|
||||||
int _reconnectAttempts = 0;
|
|
||||||
static const int _maxReconnectAttempts = 5;
|
|
||||||
static const Duration _reconnectDelay = Duration(seconds: 3);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PartyRoomFullState build() {
|
PartyRoomFullState build() {
|
||||||
@ -402,7 +399,6 @@ class PartyRoom extends _$PartyRoom {
|
|||||||
await _stopHeartbeat();
|
await _stopHeartbeat();
|
||||||
await _stopEventStream();
|
await _stopEventStream();
|
||||||
|
|
||||||
_reconnectAttempts = 0;
|
|
||||||
_dismissRoom();
|
_dismissRoom();
|
||||||
|
|
||||||
dPrint('[PartyRoom] Left room: $roomUuid');
|
dPrint('[PartyRoom] Left room: $roomUuid');
|
||||||
@ -426,7 +422,6 @@ class PartyRoom extends _$PartyRoom {
|
|||||||
await _stopHeartbeat();
|
await _stopHeartbeat();
|
||||||
await _stopEventStream();
|
await _stopEventStream();
|
||||||
|
|
||||||
_reconnectAttempts = 0;
|
|
||||||
_dismissRoom();
|
_dismissRoom();
|
||||||
|
|
||||||
dPrint('[PartyRoom] Dismissed room: $roomUuid');
|
dPrint('[PartyRoom] Dismissed room: $roomUuid');
|
||||||
@ -760,106 +755,35 @@ class PartyRoom extends _$PartyRoom {
|
|||||||
|
|
||||||
_eventStreamSubscription = stream.listen(
|
_eventStreamSubscription = stream.listen(
|
||||||
(event) {
|
(event) {
|
||||||
// 重置重连计数器,因为连接正常
|
|
||||||
_reconnectAttempts = 0;
|
|
||||||
_handleRoomEvent(event);
|
_handleRoomEvent(event);
|
||||||
},
|
},
|
||||||
onError: (error) {
|
onError: (error) {
|
||||||
dPrint('[PartyRoom] Event stream error: $error');
|
dPrint('[PartyRoom] Event stream error: $error');
|
||||||
// 发生错误时尝试重连
|
// 标记事件流断开
|
||||||
_scheduleReconnect(roomUuid);
|
state = state.copyWith(room: state.room.copyWith(eventStreamDisconnected: true));
|
||||||
},
|
},
|
||||||
onDone: () {
|
onDone: () {
|
||||||
dPrint('[PartyRoom] Event stream closed');
|
dPrint('[PartyRoom] Event stream closed');
|
||||||
// 流关闭时尝试重连
|
// 标记事件流断开
|
||||||
_scheduleReconnect(roomUuid);
|
state = state.copyWith(room: state.room.copyWith(eventStreamDisconnected: true));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 成功启动,重置断开标记
|
||||||
|
state = state.copyWith(room: state.room.copyWith(eventStreamDisconnected: false));
|
||||||
|
|
||||||
dPrint('[PartyRoom] Event stream started');
|
dPrint('[PartyRoom] Event stream started');
|
||||||
// 成功启动,重置重连计数
|
|
||||||
_reconnectAttempts = 0;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
dPrint('[PartyRoom] StartEventStream error: $e');
|
dPrint('[PartyRoom] StartEventStream error: $e');
|
||||||
// 启动失败时尝试重连
|
// 启动失败,标记断开
|
||||||
_scheduleReconnect(roomUuid);
|
state = state.copyWith(room: state.room.copyWith(eventStreamDisconnected: true));
|
||||||
}
|
rethrow;
|
||||||
}
|
|
||||||
|
|
||||||
/// 调度重连
|
|
||||||
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<void> _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<void> _stopEventStream() async {
|
Future<void> _stopEventStream() async {
|
||||||
_reconnectTimer?.cancel();
|
if (_eventStreamSubscription == null) return;
|
||||||
_reconnectTimer = null;
|
|
||||||
await _eventStreamSubscription?.cancel();
|
await _eventStreamSubscription?.cancel();
|
||||||
_eventStreamSubscription = null;
|
_eventStreamSubscription = null;
|
||||||
dPrint('[PartyRoom] Event stream stopped');
|
dPrint('[PartyRoom] Event stream stopped');
|
||||||
@ -948,6 +872,43 @@ class PartyRoom extends _$PartyRoom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 手动重连方法 ==========
|
||||||
|
|
||||||
|
/// 刷新房间信息并重新启动事件流
|
||||||
|
/// 供 UI 层在检测到连接断开时调用
|
||||||
|
Future<void> refreshRoomAndReconnect() async {
|
||||||
|
try {
|
||||||
|
final roomUuid = state.room.roomUuid;
|
||||||
|
if (roomUuid == null) {
|
||||||
|
throw Exception('Not in a room');
|
||||||
|
}
|
||||||
|
|
||||||
|
dPrint('[PartyRoom] Refreshing room and reconnecting...');
|
||||||
|
|
||||||
|
// 刷新房间信息
|
||||||
|
await getRoomInfo(roomUuid);
|
||||||
|
|
||||||
|
// 刷新成员列表
|
||||||
|
await getRoomMembers(roomUuid);
|
||||||
|
|
||||||
|
// 重新启动事件流
|
||||||
|
await _startEventStream(roomUuid);
|
||||||
|
|
||||||
|
dPrint('[PartyRoom] Room refreshed and reconnected successfully');
|
||||||
|
} catch (e) {
|
||||||
|
dPrint('[PartyRoom] Refresh and reconnect error: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 确认已看到断开连接通知(用于清除断开标记而不重连)
|
||||||
|
void acknowledgeDisconnection() {
|
||||||
|
state = state.copyWith(room: state.room.copyWith(eventStreamDisconnected: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 检查事件流是否处于活跃状态
|
||||||
|
bool get isEventStreamActive => _eventStreamSubscription != null && !(_eventStreamSubscription?.isPaused ?? true);
|
||||||
|
|
||||||
// ========== 通用服务方法 ==========
|
// ========== 通用服务方法 ==========
|
||||||
|
|
||||||
/// 获取服务器时间
|
/// 获取服务器时间
|
||||||
|
|||||||
@ -277,7 +277,7 @@ as DateTime?,
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$PartyRoomState {
|
mixin _$PartyRoomState {
|
||||||
|
|
||||||
partroom.RoomInfo? get currentRoom; List<partroom.RoomMember> get members; Map<String, common.Tag> get tags; Map<String, common.SignalType> get signalTypes; bool get isInRoom; bool get isOwner; String? get roomUuid; List<partroom.RoomEvent> get recentEvents;
|
partroom.RoomInfo? get currentRoom; List<partroom.RoomMember> get members; Map<String, common.Tag> get tags; Map<String, common.SignalType> get signalTypes; bool get isInRoom; bool get isOwner; String? get roomUuid; List<partroom.RoomEvent> get recentEvents; bool get eventStreamDisconnected;
|
||||||
/// Create a copy of PartyRoomState
|
/// Create a copy of PartyRoomState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@ -288,16 +288,16 @@ $PartyRoomStateCopyWith<PartyRoomState> get copyWith => _$PartyRoomStateCopyWith
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomState&&(identical(other.currentRoom, currentRoom) || other.currentRoom == currentRoom)&&const DeepCollectionEquality().equals(other.members, members)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.signalTypes, signalTypes)&&(identical(other.isInRoom, isInRoom) || other.isInRoom == isInRoom)&&(identical(other.isOwner, isOwner) || other.isOwner == isOwner)&&(identical(other.roomUuid, roomUuid) || other.roomUuid == roomUuid)&&const DeepCollectionEquality().equals(other.recentEvents, recentEvents));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomState&&(identical(other.currentRoom, currentRoom) || other.currentRoom == currentRoom)&&const DeepCollectionEquality().equals(other.members, members)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.signalTypes, signalTypes)&&(identical(other.isInRoom, isInRoom) || other.isInRoom == isInRoom)&&(identical(other.isOwner, isOwner) || other.isOwner == isOwner)&&(identical(other.roomUuid, roomUuid) || other.roomUuid == roomUuid)&&const DeepCollectionEquality().equals(other.recentEvents, recentEvents)&&(identical(other.eventStreamDisconnected, eventStreamDisconnected) || other.eventStreamDisconnected == eventStreamDisconnected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,currentRoom,const DeepCollectionEquality().hash(members),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(signalTypes),isInRoom,isOwner,roomUuid,const DeepCollectionEquality().hash(recentEvents));
|
int get hashCode => Object.hash(runtimeType,currentRoom,const DeepCollectionEquality().hash(members),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(signalTypes),isInRoom,isOwner,roomUuid,const DeepCollectionEquality().hash(recentEvents),eventStreamDisconnected);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PartyRoomState(currentRoom: $currentRoom, members: $members, tags: $tags, signalTypes: $signalTypes, isInRoom: $isInRoom, isOwner: $isOwner, roomUuid: $roomUuid, recentEvents: $recentEvents)';
|
return 'PartyRoomState(currentRoom: $currentRoom, members: $members, tags: $tags, signalTypes: $signalTypes, isInRoom: $isInRoom, isOwner: $isOwner, roomUuid: $roomUuid, recentEvents: $recentEvents, eventStreamDisconnected: $eventStreamDisconnected)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -308,7 +308,7 @@ abstract mixin class $PartyRoomStateCopyWith<$Res> {
|
|||||||
factory $PartyRoomStateCopyWith(PartyRoomState value, $Res Function(PartyRoomState) _then) = _$PartyRoomStateCopyWithImpl;
|
factory $PartyRoomStateCopyWith(PartyRoomState value, $Res Function(PartyRoomState) _then) = _$PartyRoomStateCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents
|
partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents, bool eventStreamDisconnected
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -325,7 +325,7 @@ class _$PartyRoomStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of PartyRoomState
|
/// Create a copy of PartyRoomState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? currentRoom = freezed,Object? members = null,Object? tags = null,Object? signalTypes = null,Object? isInRoom = null,Object? isOwner = null,Object? roomUuid = freezed,Object? recentEvents = null,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? currentRoom = freezed,Object? members = null,Object? tags = null,Object? signalTypes = null,Object? isInRoom = null,Object? isOwner = null,Object? roomUuid = freezed,Object? recentEvents = null,Object? eventStreamDisconnected = null,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
currentRoom: freezed == currentRoom ? _self.currentRoom : currentRoom // ignore: cast_nullable_to_non_nullable
|
currentRoom: freezed == currentRoom ? _self.currentRoom : currentRoom // ignore: cast_nullable_to_non_nullable
|
||||||
as partroom.RoomInfo?,members: null == members ? _self.members : members // ignore: cast_nullable_to_non_nullable
|
as partroom.RoomInfo?,members: null == members ? _self.members : members // ignore: cast_nullable_to_non_nullable
|
||||||
@ -335,7 +335,8 @@ as Map<String, common.SignalType>,isInRoom: null == isInRoom ? _self.isInRoom :
|
|||||||
as bool,isOwner: null == isOwner ? _self.isOwner : isOwner // ignore: cast_nullable_to_non_nullable
|
as bool,isOwner: null == isOwner ? _self.isOwner : isOwner // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,roomUuid: freezed == roomUuid ? _self.roomUuid : roomUuid // ignore: cast_nullable_to_non_nullable
|
as bool,roomUuid: freezed == roomUuid ? _self.roomUuid : roomUuid // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,recentEvents: null == recentEvents ? _self.recentEvents : recentEvents // ignore: cast_nullable_to_non_nullable
|
as String?,recentEvents: null == recentEvents ? _self.recentEvents : recentEvents // ignore: cast_nullable_to_non_nullable
|
||||||
as List<partroom.RoomEvent>,
|
as List<partroom.RoomEvent>,eventStreamDisconnected: null == eventStreamDisconnected ? _self.eventStreamDisconnected : eventStreamDisconnected // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,10 +418,10 @@ return $default(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents)? $default,{required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents, bool eventStreamDisconnected)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PartyRoomState() when $default != null:
|
case _PartyRoomState() when $default != null:
|
||||||
return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents);case _:
|
return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents,_that.eventStreamDisconnected);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -438,10 +439,10 @@ return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_th
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents) $default,) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents, bool eventStreamDisconnected) $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PartyRoomState():
|
case _PartyRoomState():
|
||||||
return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents);}
|
return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents,_that.eventStreamDisconnected);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@ -455,10 +456,10 @@ return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_th
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents)? $default,) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents, bool eventStreamDisconnected)? $default,) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case _PartyRoomState() when $default != null:
|
case _PartyRoomState() when $default != null:
|
||||||
return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents);case _:
|
return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents,_that.eventStreamDisconnected);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -470,7 +471,7 @@ return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_th
|
|||||||
|
|
||||||
|
|
||||||
class _PartyRoomState implements PartyRoomState {
|
class _PartyRoomState implements PartyRoomState {
|
||||||
const _PartyRoomState({this.currentRoom, final List<partroom.RoomMember> members = const [], final Map<String, common.Tag> tags = const {}, final Map<String, common.SignalType> signalTypes = const {}, this.isInRoom = false, this.isOwner = false, this.roomUuid, final List<partroom.RoomEvent> recentEvents = const []}): _members = members,_tags = tags,_signalTypes = signalTypes,_recentEvents = recentEvents;
|
const _PartyRoomState({this.currentRoom, final List<partroom.RoomMember> members = const [], final Map<String, common.Tag> tags = const {}, final Map<String, common.SignalType> signalTypes = const {}, this.isInRoom = false, this.isOwner = false, this.roomUuid, final List<partroom.RoomEvent> recentEvents = const [], this.eventStreamDisconnected = false}): _members = members,_tags = tags,_signalTypes = signalTypes,_recentEvents = recentEvents;
|
||||||
|
|
||||||
|
|
||||||
@override final partroom.RoomInfo? currentRoom;
|
@override final partroom.RoomInfo? currentRoom;
|
||||||
@ -505,6 +506,7 @@ class _PartyRoomState implements PartyRoomState {
|
|||||||
return EqualUnmodifiableListView(_recentEvents);
|
return EqualUnmodifiableListView(_recentEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override@JsonKey() final bool eventStreamDisconnected;
|
||||||
|
|
||||||
/// Create a copy of PartyRoomState
|
/// Create a copy of PartyRoomState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@ -516,16 +518,16 @@ _$PartyRoomStateCopyWith<_PartyRoomState> get copyWith => __$PartyRoomStateCopyW
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomState&&(identical(other.currentRoom, currentRoom) || other.currentRoom == currentRoom)&&const DeepCollectionEquality().equals(other._members, _members)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._signalTypes, _signalTypes)&&(identical(other.isInRoom, isInRoom) || other.isInRoom == isInRoom)&&(identical(other.isOwner, isOwner) || other.isOwner == isOwner)&&(identical(other.roomUuid, roomUuid) || other.roomUuid == roomUuid)&&const DeepCollectionEquality().equals(other._recentEvents, _recentEvents));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomState&&(identical(other.currentRoom, currentRoom) || other.currentRoom == currentRoom)&&const DeepCollectionEquality().equals(other._members, _members)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._signalTypes, _signalTypes)&&(identical(other.isInRoom, isInRoom) || other.isInRoom == isInRoom)&&(identical(other.isOwner, isOwner) || other.isOwner == isOwner)&&(identical(other.roomUuid, roomUuid) || other.roomUuid == roomUuid)&&const DeepCollectionEquality().equals(other._recentEvents, _recentEvents)&&(identical(other.eventStreamDisconnected, eventStreamDisconnected) || other.eventStreamDisconnected == eventStreamDisconnected));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,currentRoom,const DeepCollectionEquality().hash(_members),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_signalTypes),isInRoom,isOwner,roomUuid,const DeepCollectionEquality().hash(_recentEvents));
|
int get hashCode => Object.hash(runtimeType,currentRoom,const DeepCollectionEquality().hash(_members),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_signalTypes),isInRoom,isOwner,roomUuid,const DeepCollectionEquality().hash(_recentEvents),eventStreamDisconnected);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'PartyRoomState(currentRoom: $currentRoom, members: $members, tags: $tags, signalTypes: $signalTypes, isInRoom: $isInRoom, isOwner: $isOwner, roomUuid: $roomUuid, recentEvents: $recentEvents)';
|
return 'PartyRoomState(currentRoom: $currentRoom, members: $members, tags: $tags, signalTypes: $signalTypes, isInRoom: $isInRoom, isOwner: $isOwner, roomUuid: $roomUuid, recentEvents: $recentEvents, eventStreamDisconnected: $eventStreamDisconnected)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -536,7 +538,7 @@ abstract mixin class _$PartyRoomStateCopyWith<$Res> implements $PartyRoomStateCo
|
|||||||
factory _$PartyRoomStateCopyWith(_PartyRoomState value, $Res Function(_PartyRoomState) _then) = __$PartyRoomStateCopyWithImpl;
|
factory _$PartyRoomStateCopyWith(_PartyRoomState value, $Res Function(_PartyRoomState) _then) = __$PartyRoomStateCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents
|
partroom.RoomInfo? currentRoom, List<partroom.RoomMember> members, Map<String, common.Tag> tags, Map<String, common.SignalType> signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List<partroom.RoomEvent> recentEvents, bool eventStreamDisconnected
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -553,7 +555,7 @@ class __$PartyRoomStateCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of PartyRoomState
|
/// Create a copy of PartyRoomState
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? currentRoom = freezed,Object? members = null,Object? tags = null,Object? signalTypes = null,Object? isInRoom = null,Object? isOwner = null,Object? roomUuid = freezed,Object? recentEvents = null,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? currentRoom = freezed,Object? members = null,Object? tags = null,Object? signalTypes = null,Object? isInRoom = null,Object? isOwner = null,Object? roomUuid = freezed,Object? recentEvents = null,Object? eventStreamDisconnected = null,}) {
|
||||||
return _then(_PartyRoomState(
|
return _then(_PartyRoomState(
|
||||||
currentRoom: freezed == currentRoom ? _self.currentRoom : currentRoom // ignore: cast_nullable_to_non_nullable
|
currentRoom: freezed == currentRoom ? _self.currentRoom : currentRoom // ignore: cast_nullable_to_non_nullable
|
||||||
as partroom.RoomInfo?,members: null == members ? _self._members : members // ignore: cast_nullable_to_non_nullable
|
as partroom.RoomInfo?,members: null == members ? _self._members : members // ignore: cast_nullable_to_non_nullable
|
||||||
@ -563,7 +565,8 @@ as Map<String, common.SignalType>,isInRoom: null == isInRoom ? _self.isInRoom :
|
|||||||
as bool,isOwner: null == isOwner ? _self.isOwner : isOwner // ignore: cast_nullable_to_non_nullable
|
as bool,isOwner: null == isOwner ? _self.isOwner : isOwner // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,roomUuid: freezed == roomUuid ? _self.roomUuid : roomUuid // ignore: cast_nullable_to_non_nullable
|
as bool,roomUuid: freezed == roomUuid ? _self.roomUuid : roomUuid // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,recentEvents: null == recentEvents ? _self._recentEvents : recentEvents // ignore: cast_nullable_to_non_nullable
|
as String?,recentEvents: null == recentEvents ? _self._recentEvents : recentEvents // ignore: cast_nullable_to_non_nullable
|
||||||
as List<partroom.RoomEvent>,
|
as List<partroom.RoomEvent>,eventStreamDisconnected: null == eventStreamDisconnected ? _self.eventStreamDisconnected : eventStreamDisconnected // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,7 @@ final class PartyRoomProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$partyRoomHash() => r'd7182854c8caf5bb362c45a4e6e2ab40c2ef1b09';
|
String _$partyRoomHash() => r'5640c173d0820c681f3bc68872a2ab4f2fa29285';
|
||||||
|
|
||||||
/// PartyRoom Provider
|
/// PartyRoom Provider
|
||||||
|
|
||||||
|
|||||||
@ -1,23 +1,28 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||||
|
import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.dart' as partroom;
|
||||||
import 'package:starcitizen_doctor/provider/party_room.dart';
|
import 'package:starcitizen_doctor/provider/party_room.dart';
|
||||||
|
|
||||||
/// 创建房间对话框
|
/// 创建/编辑房间对话框
|
||||||
class CreateRoomDialog extends HookConsumerWidget {
|
class CreateRoomDialog extends HookConsumerWidget {
|
||||||
const CreateRoomDialog({super.key});
|
final partroom.RoomInfo? roomInfo;
|
||||||
|
|
||||||
|
const CreateRoomDialog({super.key, this.roomInfo});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final partyRoomState = ref.watch(partyRoomProvider);
|
final partyRoomState = ref.watch(partyRoomProvider);
|
||||||
final partyRoom = ref.read(partyRoomProvider.notifier);
|
final partyRoom = ref.read(partyRoomProvider.notifier);
|
||||||
|
final isEdit = roomInfo != null;
|
||||||
|
|
||||||
final selectedMainTag = useState<String?>(null);
|
final selectedMainTag = useState<String?>(roomInfo?.mainTagId);
|
||||||
final selectedSubTag = useState<String?>(null);
|
final selectedSubTag = useState<String?>(roomInfo?.subTagId);
|
||||||
final targetMembersController = useTextEditingController(text: '6');
|
final targetMembersController = useTextEditingController(text: roomInfo?.targetMembers.toString() ?? '6');
|
||||||
final hasPassword = useState(false);
|
final hasPassword = useState(roomInfo?.hasPassword ?? false);
|
||||||
final passwordController = useTextEditingController();
|
final passwordController = useTextEditingController();
|
||||||
final socialLinksController = useTextEditingController();
|
final socialLinksController = useTextEditingController(text: roomInfo?.socialLinks.join('\n'));
|
||||||
final isCreating = useState(false);
|
final isCreating = useState(false);
|
||||||
|
|
||||||
// 获取选中的主标签
|
// 获取选中的主标签
|
||||||
@ -25,7 +30,7 @@ class CreateRoomDialog extends HookConsumerWidget {
|
|||||||
|
|
||||||
return ContentDialog(
|
return ContentDialog(
|
||||||
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.5),
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.5),
|
||||||
title: const Text('创建房间'),
|
title: Text(isEdit ? '编辑房间' : '创建房间'),
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
@ -135,34 +140,37 @@ class CreateRoomDialog extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
if (!isEdit) ...[
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Checkbox(
|
Checkbox(
|
||||||
checked: hasPassword.value,
|
checked: hasPassword.value,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
hasPassword.value = value ?? false;
|
hasPassword.value = value ?? false;
|
||||||
},
|
},
|
||||||
content: const Text('设置密码'),
|
content: const Text('设置密码'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
|
|
||||||
// 密码输入框 - 始终显示,避免布局跳动
|
|
||||||
InfoLabel(
|
|
||||||
label: '房间密码',
|
|
||||||
child: TextBox(
|
|
||||||
controller: passwordController,
|
|
||||||
placeholder: hasPassword.value ? '输入密码' : '未启用密码',
|
|
||||||
obscureText: hasPassword.value,
|
|
||||||
maxLines: 1,
|
|
||||||
maxLength: 12,
|
|
||||||
enabled: hasPassword.value,
|
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 8),
|
||||||
const SizedBox(height: 16),
|
// 密码输入框 - 始终显示,避免布局跳动
|
||||||
|
InfoLabel(
|
||||||
|
label: '房间密码',
|
||||||
|
child: TextBox(
|
||||||
|
controller: passwordController,
|
||||||
|
placeholder: isEdit
|
||||||
|
? "为空则不更新密码,取消勾选则取消密码"
|
||||||
|
: hasPassword.value
|
||||||
|
? '输入密码'
|
||||||
|
: '未启用密码',
|
||||||
|
obscureText: hasPassword.value,
|
||||||
|
maxLines: 1,
|
||||||
|
maxLength: 12,
|
||||||
|
enabled: hasPassword.value,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
InfoLabel(
|
InfoLabel(
|
||||||
label: '社交链接 (可选)',
|
label: '社交链接 (可选)',
|
||||||
child: TextBox(
|
child: TextBox(
|
||||||
@ -207,32 +215,50 @@ class CreateRoomDialog extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (hasPassword.value && passwordController.text.trim().isEmpty) {
|
if (hasPassword.value && passwordController.text.trim().isEmpty) {
|
||||||
await showDialog(
|
if (!isEdit) {
|
||||||
context: context,
|
await showDialog(
|
||||||
builder: (context) => ContentDialog(
|
context: context,
|
||||||
title: const Text('提示'),
|
builder: (context) => ContentDialog(
|
||||||
content: const Text('请输入密码'),
|
title: const Text('提示'),
|
||||||
actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))],
|
content: const Text('请输入密码'),
|
||||||
),
|
actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))],
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final socialLinks = socialLinksController.text.split('\n');
|
||||||
|
|
||||||
|
// 检查是否为 https 开头的链接
|
||||||
|
final invalidLinks = socialLinks.where((link) => !link.startsWith('https://')).toList();
|
||||||
|
if (invalidLinks.isNotEmpty) {
|
||||||
|
showToast(context, "链接格式错误!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final socialLinks = socialLinksController.text
|
|
||||||
.split('\n')
|
|
||||||
.where((link) => link.trim().isNotEmpty && link.trim().startsWith('http'))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
isCreating.value = true;
|
isCreating.value = true;
|
||||||
try {
|
try {
|
||||||
await partyRoom.createRoom(
|
if (isEdit) {
|
||||||
mainTagId: mainTagId,
|
await partyRoom.updateRoom(
|
||||||
subTagId: selectedSubTag.value,
|
mainTagId: mainTagId,
|
||||||
targetMembers: targetMembers,
|
subTagId: selectedSubTag.value,
|
||||||
hasPassword: hasPassword.value,
|
targetMembers: targetMembers,
|
||||||
password: hasPassword.value ? passwordController.text : null,
|
password: !hasPassword.value
|
||||||
socialLinks: socialLinks.isEmpty ? null : socialLinks,
|
? ''
|
||||||
);
|
: (passwordController.text.isNotEmpty ? passwordController.text : null),
|
||||||
|
socialLinks: socialLinks,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await partyRoom.createRoom(
|
||||||
|
mainTagId: mainTagId,
|
||||||
|
subTagId: selectedSubTag.value,
|
||||||
|
targetMembers: targetMembers,
|
||||||
|
hasPassword: hasPassword.value,
|
||||||
|
password: hasPassword.value ? passwordController.text : null,
|
||||||
|
socialLinks: socialLinks.isEmpty ? null : socialLinks,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@ -243,7 +269,7 @@ class CreateRoomDialog extends HookConsumerWidget {
|
|||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => ContentDialog(
|
builder: (context) => ContentDialog(
|
||||||
title: const Text('创建失败'),
|
title: Text(isEdit ? '更新失败' : '创建失败'),
|
||||||
content: Text(e.toString()),
|
content: Text(e.toString()),
|
||||||
actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))],
|
actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))],
|
||||||
),
|
),
|
||||||
@ -253,7 +279,7 @@ class CreateRoomDialog extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
child: isCreating.value
|
child: isCreating.value
|
||||||
? const SizedBox(width: 16, height: 16, child: ProgressRing(strokeWidth: 2))
|
? const SizedBox(width: 16, height: 16, child: ProgressRing(strokeWidth: 2))
|
||||||
: const Text('创建'),
|
: Text(isEdit ? '保存' : '创建'),
|
||||||
),
|
),
|
||||||
Button(onPressed: isCreating.value ? null : () => Navigator.pop(context), child: const Text('取消')),
|
Button(onPressed: isCreating.value ? null : () => Navigator.pop(context), child: const Text('取消')),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'package:fluent_ui/fluent_ui.dart';
|
import 'package:fluent_ui/fluent_ui.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:starcitizen_doctor/common/utils/base_utils.dart';
|
||||||
|
import 'package:starcitizen_doctor/common/utils/log.dart';
|
||||||
import 'package:starcitizen_doctor/provider/party_room.dart';
|
import 'package:starcitizen_doctor/provider/party_room.dart';
|
||||||
import 'package:starcitizen_doctor/ui/party_room/widgets/detail/party_room_message_list.dart';
|
import 'package:starcitizen_doctor/ui/party_room/widgets/detail/party_room_message_list.dart';
|
||||||
|
|
||||||
@ -18,6 +20,7 @@ class PartyRoomDetailPage extends ConsumerStatefulWidget {
|
|||||||
class _PartyRoomDetailPageState extends ConsumerState<PartyRoomDetailPage> {
|
class _PartyRoomDetailPageState extends ConsumerState<PartyRoomDetailPage> {
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
int _lastEventCount = 0;
|
int _lastEventCount = 0;
|
||||||
|
bool _isShowingDialog = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -25,6 +28,59 @@ class _PartyRoomDetailPageState extends ConsumerState<PartyRoomDetailPage> {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _showReconnectDialog() async {
|
||||||
|
if (_isShowingDialog || !mounted) return;
|
||||||
|
_isShowingDialog = true;
|
||||||
|
|
||||||
|
final partyRoom = ref.read(partyRoomProvider.notifier);
|
||||||
|
|
||||||
|
final result = await showBaseDialog(
|
||||||
|
context,
|
||||||
|
title: '连接已断开',
|
||||||
|
content: const Text('与房间服务器的连接已断开,是否重新连接?'),
|
||||||
|
actions: [
|
||||||
|
Button(
|
||||||
|
onPressed: () => Navigator.of(context).pop('leave'),
|
||||||
|
child: const Padding(padding: EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text('退出房间')),
|
||||||
|
),
|
||||||
|
FilledButton(
|
||||||
|
onPressed: () => Navigator.of(context).pop('reconnect'),
|
||||||
|
child: const Padding(padding: EdgeInsets.only(top: 2, bottom: 2, left: 8, right: 8), child: Text('重新连接')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
_isShowingDialog = false;
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
if (result == 'reconnect') {
|
||||||
|
try {
|
||||||
|
await partyRoom.refreshRoomAndReconnect();
|
||||||
|
dPrint('[PartyRoomDetailPage] Reconnect success');
|
||||||
|
} catch (e) {
|
||||||
|
dPrint('[PartyRoomDetailPage] Reconnect failed: $e');
|
||||||
|
// 重连失败,重新显示对话框
|
||||||
|
if (mounted) {
|
||||||
|
_showReconnectDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (result == 'leave') {
|
||||||
|
try {
|
||||||
|
partyRoom.acknowledgeDisconnection();
|
||||||
|
await partyRoom.leaveRoom();
|
||||||
|
} catch (e) {
|
||||||
|
dPrint('[PartyRoomDetailPage] Leave room failed: $e');
|
||||||
|
if (mounted) {
|
||||||
|
await showToast(context, '退出房间失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 用户关闭对话框
|
||||||
|
partyRoom.acknowledgeDisconnection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _scrollToBottom() {
|
void _scrollToBottom() {
|
||||||
if (_scrollController.hasClients) {
|
if (_scrollController.hasClients) {
|
||||||
Future.delayed(const Duration(milliseconds: 100), () {
|
Future.delayed(const Duration(milliseconds: 100), () {
|
||||||
@ -41,6 +97,17 @@ class _PartyRoomDetailPageState extends ConsumerState<PartyRoomDetailPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
ref.listen<PartyRoomFullState>(partyRoomProvider, (previous, next) {
|
||||||
|
// 监听事件流断开状态
|
||||||
|
if (next.room.isInRoom && next.room.eventStreamDisconnected && !_isShowingDialog) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (mounted) {
|
||||||
|
_showReconnectDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
final partyRoomState = ref.watch(partyRoomProvider);
|
final partyRoomState = ref.watch(partyRoomProvider);
|
||||||
final partyRoom = ref.read(partyRoomProvider.notifier);
|
final partyRoom = ref.read(partyRoomProvider.notifier);
|
||||||
final room = partyRoomState.room.currentRoom;
|
final room = partyRoomState.room.currentRoom;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import 'package:fluent_ui/fluent_ui.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:starcitizen_doctor/provider/party_room.dart';
|
import 'package:starcitizen_doctor/provider/party_room.dart';
|
||||||
import 'package:starcitizen_doctor/ui/party_room/party_room_ui_model.dart';
|
import 'package:starcitizen_doctor/ui/party_room/party_room_ui_model.dart';
|
||||||
|
import 'package:starcitizen_doctor/ui/party_room/widgets/create_room_dialog.dart';
|
||||||
|
|
||||||
/// 房间信息头部组件
|
/// 房间信息头部组件
|
||||||
class PartyRoomHeader extends ConsumerWidget {
|
class PartyRoomHeader extends ConsumerWidget {
|
||||||
@ -58,39 +59,54 @@ class PartyRoomHeader extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (isOwner) ...[
|
if (isOwner) ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
SizedBox(
|
Row(
|
||||||
width: double.infinity,
|
children: [
|
||||||
child: Button(
|
Expanded(
|
||||||
onPressed: () async {
|
child: Button(
|
||||||
final confirmed = await showDialog<bool>(
|
onPressed: () {
|
||||||
context: context,
|
showDialog(
|
||||||
builder: (context) => ContentDialog(
|
context: context,
|
||||||
title: const Text('确认解散'),
|
builder: (context) => CreateRoomDialog(roomInfo: room),
|
||||||
content: const Text('确定要解散房间吗?所有成员将被移出。'),
|
);
|
||||||
actions: [
|
},
|
||||||
Button(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)),
|
child: const Text('编辑房间'),
|
||||||
FilledButton(
|
),
|
||||||
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFFDA373C))),
|
|
||||||
child: const Text('解散', style: TextStyle(color: Colors.white)),
|
|
||||||
onPressed: () => Navigator.pop(context, true),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (confirmed == true) {
|
|
||||||
ref.read(partyRoomUIModelProvider.notifier).dismissRoom();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style: ButtonStyle(
|
|
||||||
backgroundColor: WidgetStateProperty.resolveWith((state) {
|
|
||||||
if (state.isHovered || state.isPressed) {
|
|
||||||
return const Color(0xFFB3261E);
|
|
||||||
}
|
|
||||||
return const Color(0xFFDA373C);
|
|
||||||
}),
|
|
||||||
),
|
),
|
||||||
child: const Text('解散房间', style: TextStyle(color: Colors.white)),
|
const SizedBox(width: 8),
|
||||||
),
|
Expanded(
|
||||||
|
child: Button(
|
||||||
|
onPressed: () async {
|
||||||
|
final confirmed = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ContentDialog(
|
||||||
|
title: const Text('确认解散'),
|
||||||
|
content: const Text('确定要解散房间吗?所有成员将被移出。'),
|
||||||
|
actions: [
|
||||||
|
Button(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)),
|
||||||
|
FilledButton(
|
||||||
|
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFFDA373C))),
|
||||||
|
child: const Text('解散', style: TextStyle(color: Colors.white)),
|
||||||
|
onPressed: () => Navigator.pop(context, true),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (confirmed == true) {
|
||||||
|
ref.read(partyRoomUIModelProvider.notifier).dismissRoom();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: WidgetStateProperty.resolveWith((state) {
|
||||||
|
if (state.isHovered || state.isPressed) {
|
||||||
|
return const Color(0xFFB3261E);
|
||||||
|
}
|
||||||
|
return const Color(0xFFDA373C);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
child: const Text('解散房间', style: TextStyle(color: Colors.white)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
] else ...[
|
] else ...[
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
@ -100,8 +116,21 @@ class PartyRoomHeader extends ConsumerWidget {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await partyRoom.leaveRoom();
|
await partyRoom.leaveRoom();
|
||||||
},
|
},
|
||||||
style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFF404249))),
|
style: ButtonStyle(
|
||||||
child: const Text('离开房间', style: TextStyle(color: Color(0xFFB5BAC1))),
|
backgroundColor: WidgetStateProperty.resolveWith((state) {
|
||||||
|
if (state.isHovered || state.isPressed) {
|
||||||
|
return const Color(0xFFDA373C);
|
||||||
|
}
|
||||||
|
return const Color(0xFF404249);
|
||||||
|
}),
|
||||||
|
foregroundColor: WidgetStateProperty.resolveWith((state) {
|
||||||
|
if (state.isHovered || state.isPressed) {
|
||||||
|
return Colors.white;
|
||||||
|
}
|
||||||
|
return const Color(0xFFDBDEE1);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
child: const Text('离开房间'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -430,6 +430,7 @@ class PartyRoomListPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
Future<void> _showCreateRoomDialog(BuildContext context, WidgetRef ref) async {
|
Future<void> _showCreateRoomDialog(BuildContext context, WidgetRef ref) async {
|
||||||
final uiState = ref.read(partyRoomUIModelProvider);
|
final uiState = ref.read(partyRoomUIModelProvider);
|
||||||
|
final partyRoomState = ref.read(partyRoomProvider);
|
||||||
|
|
||||||
// 检查是否为游客模式
|
// 检查是否为游客模式
|
||||||
if (uiState.isGuestMode) {
|
if (uiState.isGuestMode) {
|
||||||
@ -451,6 +452,26 @@ class PartyRoomListPage extends HookConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否已经在房间内
|
||||||
|
if (partyRoomState.room.isInRoom) {
|
||||||
|
final shouldLeave = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ContentDialog(
|
||||||
|
title: const Text('创建新房间'),
|
||||||
|
content: const Text('你已经在其他房间中,创建新房间将自动退出当前房间。是否继续?'),
|
||||||
|
actions: [
|
||||||
|
Button(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)),
|
||||||
|
FilledButton(child: const Text('继续'), onPressed: () => Navigator.pop(context, true)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldLeave != true) return;
|
||||||
|
|
||||||
|
// 退出当前房间
|
||||||
|
await ref.read(partyRoomProvider.notifier).leaveRoom();
|
||||||
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
await showDialog(context: context, builder: (context) => const CreateRoomDialog());
|
await showDialog(context: context, builder: (context) => const CreateRoomDialog());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,7 @@ final class SettingsUIModelProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$settingsUIModelHash() => r'5c08c56bf5464ef44bee8edb8c18c08d4217f135';
|
String _$settingsUIModelHash() => r'd19104d924f018a9230548d0372692fc344adacd';
|
||||||
|
|
||||||
abstract class _$SettingsUIModel extends $Notifier<SettingsUIState> {
|
abstract class _$SettingsUIModel extends $Notifier<SettingsUIState> {
|
||||||
SettingsUIState build();
|
SettingsUIState build();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user