From aaaee303689069a0959fd714adf01496dba71467 Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Tue, 18 Nov 2025 23:10:04 +0800 Subject: [PATCH 01/11] feat: init Party Room --- lib/common/conf/url_conf.dart | 7 + lib/generated/proto/auth/auth.pb.dart | 731 ++++ lib/generated/proto/auth/auth.pbenum.dart | 11 + lib/generated/proto/auth/auth.pbgrpc.dart | 187 ++ lib/generated/proto/auth/auth.pbjson.dart | 188 ++ lib/generated/proto/common/common.pb.dart | 601 ++++ lib/generated/proto/common/common.pbenum.dart | 11 + lib/generated/proto/common/common.pbgrpc.dart | 164 + lib/generated/proto/common/common.pbjson.dart | 160 + lib/generated/proto/partroom/partroom.pb.dart | 2980 +++++++++++++++++ .../proto/partroom/partroom.pbenum.dart | 56 + .../proto/partroom/partroom.pbgrpc.dart | 543 +++ .../proto/partroom/partroom.pbjson.dart | 759 +++++ lib/provider/party_room.dart | 889 +++++ lib/provider/party_room.freezed.dart | 1153 +++++++ lib/provider/party_room.g.dart | 68 + .../input_method_dialog_ui_model.g.dart | 2 +- .../localization/localization_ui_model.dart | 8 +- .../localization/localization_ui_model.g.dart | 2 +- lib/ui/index_ui.dart | 135 +- lib/ui/nav/nav_state.dart | 2 + lib/ui/nav/nav_state.g.dart | 2 +- lib/ui/party_room/party_room_ui.dart | 55 +- lib/ui/party_room/party_room_ui_model.dart | 262 ++ .../party_room_ui_model.freezed.dart | 313 ++ lib/ui/party_room/party_room_ui_model.g.dart | 63 + .../widgets/create_room_dialog.dart | 190 ++ .../widgets/party_room_connect_page.dart | 98 + .../widgets/party_room_detail_page.dart | 689 ++++ .../widgets/party_room_list_page.dart | 371 ++ .../widgets/party_room_register_page.dart | 364 ++ pubspec.lock | 72 +- pubspec.yaml | 8 +- update_grpc.bat | 1 - 34 files changed, 10999 insertions(+), 146 deletions(-) create mode 100644 lib/generated/proto/auth/auth.pb.dart create mode 100644 lib/generated/proto/auth/auth.pbenum.dart create mode 100644 lib/generated/proto/auth/auth.pbgrpc.dart create mode 100644 lib/generated/proto/auth/auth.pbjson.dart create mode 100644 lib/generated/proto/common/common.pb.dart create mode 100644 lib/generated/proto/common/common.pbenum.dart create mode 100644 lib/generated/proto/common/common.pbgrpc.dart create mode 100644 lib/generated/proto/common/common.pbjson.dart create mode 100644 lib/generated/proto/partroom/partroom.pb.dart create mode 100644 lib/generated/proto/partroom/partroom.pbenum.dart create mode 100644 lib/generated/proto/partroom/partroom.pbgrpc.dart create mode 100644 lib/generated/proto/partroom/partroom.pbjson.dart create mode 100644 lib/provider/party_room.dart create mode 100644 lib/provider/party_room.freezed.dart create mode 100644 lib/provider/party_room.g.dart create mode 100644 lib/ui/party_room/party_room_ui_model.dart create mode 100644 lib/ui/party_room/party_room_ui_model.freezed.dart create mode 100644 lib/ui/party_room/party_room_ui_model.g.dart create mode 100644 lib/ui/party_room/widgets/create_room_dialog.dart create mode 100644 lib/ui/party_room/widgets/party_room_connect_page.dart create mode 100644 lib/ui/party_room/widgets/party_room_detail_page.dart create mode 100644 lib/ui/party_room/widgets/party_room_list_page.dart create mode 100644 lib/ui/party_room/widgets/party_room_register_page.dart delete mode 100644 update_grpc.bat diff --git a/lib/common/conf/url_conf.dart b/lib/common/conf/url_conf.dart index cee49ce..c46a977 100644 --- a/lib/common/conf/url_conf.dart +++ b/lib/common/conf/url_conf.dart @@ -32,6 +32,13 @@ class URLConf { static String get devReleaseUrl => "$gitApiHome/SCToolBox/Release/releases"; + /// PartyRoom Server + static const String partyRoomServerAddress = "localhost"; + static const int partyRoomServerPort = 50051; + + /// RSI Avatar Base URL + static const String rsiAvatarBaseUrl = "https://robertsspaceindustries.com"; + static Future checkHost() async { // 使用 DNS 获取可用列表 final gitApiList = _genFinalList(await dnsLookupTxt("git.dns.scbox.org")); diff --git a/lib/generated/proto/auth/auth.pb.dart b/lib/generated/proto/auth/auth.pb.dart new file mode 100644 index 0000000..2dbcda6 --- /dev/null +++ b/lib/generated/proto/auth/auth.pb.dart @@ -0,0 +1,731 @@ +// This is a generated file - do not edit. +// +// Generated from proto/auth/auth.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +/// 服务状态请求 +class StatusRequest extends $pb.GeneratedMessage { + factory StatusRequest() => create(); + + StatusRequest._(); + + factory StatusRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory StatusRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'StatusRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StatusRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StatusRequest copyWith(void Function(StatusRequest) updates) => + super.copyWith((message) => updates(message as StatusRequest)) + as StatusRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static StatusRequest create() => StatusRequest._(); + @$core.override + StatusRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static StatusRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static StatusRequest? _defaultInstance; +} + +/// 服务状态响应 +class StatusResponse extends $pb.GeneratedMessage { + factory StatusResponse({ + $core.bool? online, + $core.String? message, + $fixnum.Int64? serverTime, + }) { + final result = create(); + if (online != null) result.online = online; + if (message != null) result.message = message; + if (serverTime != null) result.serverTime = serverTime; + return result; + } + + StatusResponse._(); + + factory StatusResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory StatusResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'StatusResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'online') + ..aOS(2, _omitFieldNames ? '' : 'message') + ..aInt64(3, _omitFieldNames ? '' : 'serverTime') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StatusResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + StatusResponse copyWith(void Function(StatusResponse) updates) => + super.copyWith((message) => updates(message as StatusResponse)) + as StatusResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static StatusResponse create() => StatusResponse._(); + @$core.override + StatusResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static StatusResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static StatusResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get online => $_getBF(0); + @$pb.TagNumber(1) + set online($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasOnline() => $_has(0); + @$pb.TagNumber(1) + void clearOnline() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get message => $_getSZ(1); + @$pb.TagNumber(2) + set message($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasMessage() => $_has(1); + @$pb.TagNumber(2) + void clearMessage() => $_clearField(2); + + @$pb.TagNumber(3) + $fixnum.Int64 get serverTime => $_getI64(2); + @$pb.TagNumber(3) + set serverTime($fixnum.Int64 value) => $_setInt64(2, value); + @$pb.TagNumber(3) + $core.bool hasServerTime() => $_has(2); + @$pb.TagNumber(3) + void clearServerTime() => $_clearField(3); +} + +/// 登录请求 +class LoginRequest extends $pb.GeneratedMessage { + factory LoginRequest() => create(); + + LoginRequest._(); + + factory LoginRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory LoginRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'LoginRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LoginRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LoginRequest copyWith(void Function(LoginRequest) updates) => + super.copyWith((message) => updates(message as LoginRequest)) + as LoginRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static LoginRequest create() => LoginRequest._(); + @$core.override + LoginRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static LoginRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static LoginRequest? _defaultInstance; +} + +/// 游戏用户信息 +class GameUserInfo extends $pb.GeneratedMessage { + factory GameUserInfo({ + $core.String? gameUserId, + $core.String? handleName, + $core.String? avatarUrl, + $core.String? citizenRecord, + $fixnum.Int64? enlistedDate, + }) { + final result = create(); + if (gameUserId != null) result.gameUserId = gameUserId; + if (handleName != null) result.handleName = handleName; + if (avatarUrl != null) result.avatarUrl = avatarUrl; + if (citizenRecord != null) result.citizenRecord = citizenRecord; + if (enlistedDate != null) result.enlistedDate = enlistedDate; + return result; + } + + GameUserInfo._(); + + factory GameUserInfo.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GameUserInfo.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GameUserInfo', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'gameUserId') + ..aOS(2, _omitFieldNames ? '' : 'handleName') + ..aOS(3, _omitFieldNames ? '' : 'avatarUrl') + ..aOS(4, _omitFieldNames ? '' : 'citizenRecord') + ..aInt64(5, _omitFieldNames ? '' : 'enlistedDate') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GameUserInfo clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GameUserInfo copyWith(void Function(GameUserInfo) updates) => + super.copyWith((message) => updates(message as GameUserInfo)) + as GameUserInfo; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GameUserInfo create() => GameUserInfo._(); + @$core.override + GameUserInfo createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GameUserInfo getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GameUserInfo? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get gameUserId => $_getSZ(0); + @$pb.TagNumber(1) + set gameUserId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasGameUserId() => $_has(0); + @$pb.TagNumber(1) + void clearGameUserId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get handleName => $_getSZ(1); + @$pb.TagNumber(2) + set handleName($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasHandleName() => $_has(1); + @$pb.TagNumber(2) + void clearHandleName() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get avatarUrl => $_getSZ(2); + @$pb.TagNumber(3) + set avatarUrl($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasAvatarUrl() => $_has(2); + @$pb.TagNumber(3) + void clearAvatarUrl() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get citizenRecord => $_getSZ(3); + @$pb.TagNumber(4) + set citizenRecord($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasCitizenRecord() => $_has(3); + @$pb.TagNumber(4) + void clearCitizenRecord() => $_clearField(4); + + @$pb.TagNumber(5) + $fixnum.Int64 get enlistedDate => $_getI64(4); + @$pb.TagNumber(5) + set enlistedDate($fixnum.Int64 value) => $_setInt64(4, value); + @$pb.TagNumber(5) + $core.bool hasEnlistedDate() => $_has(4); + @$pb.TagNumber(5) + void clearEnlistedDate() => $_clearField(5); +} + +/// 登录响应 +class LoginResponse extends $pb.GeneratedMessage { + factory LoginResponse({ + $core.String? uuid, + GameUserInfo? userInfo, + $fixnum.Int64? lastLoginTime, + }) { + final result = create(); + if (uuid != null) result.uuid = uuid; + if (userInfo != null) result.userInfo = userInfo; + if (lastLoginTime != null) result.lastLoginTime = lastLoginTime; + return result; + } + + LoginResponse._(); + + factory LoginResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory LoginResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'LoginResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'uuid') + ..aOM(2, _omitFieldNames ? '' : 'userInfo', + subBuilder: GameUserInfo.create) + ..aInt64(3, _omitFieldNames ? '' : 'lastLoginTime') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LoginResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LoginResponse copyWith(void Function(LoginResponse) updates) => + super.copyWith((message) => updates(message as LoginResponse)) + as LoginResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static LoginResponse create() => LoginResponse._(); + @$core.override + LoginResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static LoginResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static LoginResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get uuid => $_getSZ(0); + @$pb.TagNumber(1) + set uuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasUuid() => $_has(0); + @$pb.TagNumber(1) + void clearUuid() => $_clearField(1); + + @$pb.TagNumber(2) + GameUserInfo get userInfo => $_getN(1); + @$pb.TagNumber(2) + set userInfo(GameUserInfo value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasUserInfo() => $_has(1); + @$pb.TagNumber(2) + void clearUserInfo() => $_clearField(2); + @$pb.TagNumber(2) + GameUserInfo ensureUserInfo() => $_ensure(1); + + @$pb.TagNumber(3) + $fixnum.Int64 get lastLoginTime => $_getI64(2); + @$pb.TagNumber(3) + set lastLoginTime($fixnum.Int64 value) => $_setInt64(2, value); + @$pb.TagNumber(3) + $core.bool hasLastLoginTime() => $_has(2); + @$pb.TagNumber(3) + void clearLastLoginTime() => $_clearField(3); +} + +/// 预注册请求 +class PreRegisterRequest extends $pb.GeneratedMessage { + factory PreRegisterRequest({ + $core.String? uuid, + $core.String? gameUserId, + }) { + final result = create(); + if (uuid != null) result.uuid = uuid; + if (gameUserId != null) result.gameUserId = gameUserId; + return result; + } + + PreRegisterRequest._(); + + factory PreRegisterRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory PreRegisterRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'PreRegisterRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'uuid') + ..aOS(2, _omitFieldNames ? '' : 'gameUserId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PreRegisterRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PreRegisterRequest copyWith(void Function(PreRegisterRequest) updates) => + super.copyWith((message) => updates(message as PreRegisterRequest)) + as PreRegisterRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PreRegisterRequest create() => PreRegisterRequest._(); + @$core.override + PreRegisterRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static PreRegisterRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static PreRegisterRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get uuid => $_getSZ(0); + @$pb.TagNumber(1) + set uuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasUuid() => $_has(0); + @$pb.TagNumber(1) + void clearUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get gameUserId => $_getSZ(1); + @$pb.TagNumber(2) + set gameUserId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasGameUserId() => $_has(1); + @$pb.TagNumber(2) + void clearGameUserId() => $_clearField(2); +} + +/// 预注册响应 +class PreRegisterResponse extends $pb.GeneratedMessage { + factory PreRegisterResponse({ + $core.String? verificationCode, + $fixnum.Int64? expireTime, + }) { + final result = create(); + if (verificationCode != null) result.verificationCode = verificationCode; + if (expireTime != null) result.expireTime = expireTime; + return result; + } + + PreRegisterResponse._(); + + factory PreRegisterResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory PreRegisterResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'PreRegisterResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'verificationCode') + ..aInt64(2, _omitFieldNames ? '' : 'expireTime') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PreRegisterResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + PreRegisterResponse copyWith(void Function(PreRegisterResponse) updates) => + super.copyWith((message) => updates(message as PreRegisterResponse)) + as PreRegisterResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static PreRegisterResponse create() => PreRegisterResponse._(); + @$core.override + PreRegisterResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static PreRegisterResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static PreRegisterResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get verificationCode => $_getSZ(0); + @$pb.TagNumber(1) + set verificationCode($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasVerificationCode() => $_has(0); + @$pb.TagNumber(1) + void clearVerificationCode() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get expireTime => $_getI64(1); + @$pb.TagNumber(2) + set expireTime($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasExpireTime() => $_has(1); + @$pb.TagNumber(2) + void clearExpireTime() => $_clearField(2); +} + +/// 注册请求 +class RegisterRequest extends $pb.GeneratedMessage { + factory RegisterRequest({ + $core.String? uuid, + $core.String? gameUserId, + }) { + final result = create(); + if (uuid != null) result.uuid = uuid; + if (gameUserId != null) result.gameUserId = gameUserId; + return result; + } + + RegisterRequest._(); + + factory RegisterRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RegisterRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RegisterRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'uuid') + ..aOS(2, _omitFieldNames ? '' : 'gameUserId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterRequest copyWith(void Function(RegisterRequest) updates) => + super.copyWith((message) => updates(message as RegisterRequest)) + as RegisterRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RegisterRequest create() => RegisterRequest._(); + @$core.override + RegisterRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RegisterRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RegisterRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get uuid => $_getSZ(0); + @$pb.TagNumber(1) + set uuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasUuid() => $_has(0); + @$pb.TagNumber(1) + void clearUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get gameUserId => $_getSZ(1); + @$pb.TagNumber(2) + set gameUserId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasGameUserId() => $_has(1); + @$pb.TagNumber(2) + void clearGameUserId() => $_clearField(2); +} + +/// 注册响应 +class RegisterResponse extends $pb.GeneratedMessage { + factory RegisterResponse({ + $core.String? partyRoomSecretKey, + GameUserInfo? userInfo, + }) { + final result = create(); + if (partyRoomSecretKey != null) + result.partyRoomSecretKey = partyRoomSecretKey; + if (userInfo != null) result.userInfo = userInfo; + return result; + } + + RegisterResponse._(); + + factory RegisterResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RegisterResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RegisterResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'partyRoomSecretKey') + ..aOM(2, _omitFieldNames ? '' : 'userInfo', + subBuilder: GameUserInfo.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RegisterResponse copyWith(void Function(RegisterResponse) updates) => + super.copyWith((message) => updates(message as RegisterResponse)) + as RegisterResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RegisterResponse create() => RegisterResponse._(); + @$core.override + RegisterResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RegisterResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RegisterResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get partyRoomSecretKey => $_getSZ(0); + @$pb.TagNumber(1) + set partyRoomSecretKey($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasPartyRoomSecretKey() => $_has(0); + @$pb.TagNumber(1) + void clearPartyRoomSecretKey() => $_clearField(1); + + @$pb.TagNumber(2) + GameUserInfo get userInfo => $_getN(1); + @$pb.TagNumber(2) + set userInfo(GameUserInfo value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasUserInfo() => $_has(1); + @$pb.TagNumber(2) + void clearUserInfo() => $_clearField(2); + @$pb.TagNumber(2) + GameUserInfo ensureUserInfo() => $_ensure(1); +} + +/// 注销请求 +class UnregisterRequest extends $pb.GeneratedMessage { + factory UnregisterRequest() => create(); + + UnregisterRequest._(); + + factory UnregisterRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UnregisterRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UnregisterRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnregisterRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnregisterRequest copyWith(void Function(UnregisterRequest) updates) => + super.copyWith((message) => updates(message as UnregisterRequest)) + as UnregisterRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UnregisterRequest create() => UnregisterRequest._(); + @$core.override + UnregisterRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UnregisterRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UnregisterRequest? _defaultInstance; +} + +/// 注销响应 +class UnregisterResponse extends $pb.GeneratedMessage { + factory UnregisterResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + UnregisterResponse._(); + + factory UnregisterResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UnregisterResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UnregisterResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'auth'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnregisterResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UnregisterResponse copyWith(void Function(UnregisterResponse) updates) => + super.copyWith((message) => updates(message as UnregisterResponse)) + as UnregisterResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UnregisterResponse create() => UnregisterResponse._(); + @$core.override + UnregisterResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UnregisterResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UnregisterResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/generated/proto/auth/auth.pbenum.dart b/lib/generated/proto/auth/auth.pbenum.dart new file mode 100644 index 0000000..d79107f --- /dev/null +++ b/lib/generated/proto/auth/auth.pbenum.dart @@ -0,0 +1,11 @@ +// This is a generated file - do not edit. +// +// Generated from proto/auth/auth.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names diff --git a/lib/generated/proto/auth/auth.pbgrpc.dart b/lib/generated/proto/auth/auth.pbgrpc.dart new file mode 100644 index 0000000..e366d42 --- /dev/null +++ b/lib/generated/proto/auth/auth.pbgrpc.dart @@ -0,0 +1,187 @@ +// This is a generated file - do not edit. +// +// Generated from proto/auth/auth.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:grpc/service_api.dart' as $grpc; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'auth.pb.dart' as $0; + +export 'auth.pb.dart'; + +/// 认证服务 +@$pb.GrpcServiceName('auth.AuthService') +class AuthServiceClient extends $grpc.Client { + /// The hostname for this service. + static const $core.String defaultHost = ''; + + /// OAuth scopes needed for the client. + static const $core.List<$core.String> oauthScopes = [ + '', + ]; + + AuthServiceClient(super.channel, {super.options, super.interceptors}); + + /// 获取服务状态(匿名接口) + $grpc.ResponseFuture<$0.StatusResponse> status( + $0.StatusRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$status, request, options: options); + } + + /// 获取当前账号状态(需认证) + $grpc.ResponseFuture<$0.LoginResponse> login( + $0.LoginRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$login, request, options: options); + } + + /// 请求注册(匿名接口) + $grpc.ResponseFuture<$0.PreRegisterResponse> preRegister( + $0.PreRegisterRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$preRegister, request, options: options); + } + + /// 注册账号(匿名接口) + $grpc.ResponseFuture<$0.RegisterResponse> register( + $0.RegisterRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$register, request, options: options); + } + + /// 注销账号(需认证) + $grpc.ResponseFuture<$0.UnregisterResponse> unregister( + $0.UnregisterRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$unregister, request, options: options); + } + + // method descriptors + + static final _$status = + $grpc.ClientMethod<$0.StatusRequest, $0.StatusResponse>( + '/auth.AuthService/Status', + ($0.StatusRequest value) => value.writeToBuffer(), + $0.StatusResponse.fromBuffer); + static final _$login = $grpc.ClientMethod<$0.LoginRequest, $0.LoginResponse>( + '/auth.AuthService/Login', + ($0.LoginRequest value) => value.writeToBuffer(), + $0.LoginResponse.fromBuffer); + static final _$preRegister = + $grpc.ClientMethod<$0.PreRegisterRequest, $0.PreRegisterResponse>( + '/auth.AuthService/PreRegister', + ($0.PreRegisterRequest value) => value.writeToBuffer(), + $0.PreRegisterResponse.fromBuffer); + static final _$register = + $grpc.ClientMethod<$0.RegisterRequest, $0.RegisterResponse>( + '/auth.AuthService/Register', + ($0.RegisterRequest value) => value.writeToBuffer(), + $0.RegisterResponse.fromBuffer); + static final _$unregister = + $grpc.ClientMethod<$0.UnregisterRequest, $0.UnregisterResponse>( + '/auth.AuthService/Unregister', + ($0.UnregisterRequest value) => value.writeToBuffer(), + $0.UnregisterResponse.fromBuffer); +} + +@$pb.GrpcServiceName('auth.AuthService') +abstract class AuthServiceBase extends $grpc.Service { + $core.String get $name => 'auth.AuthService'; + + AuthServiceBase() { + $addMethod($grpc.ServiceMethod<$0.StatusRequest, $0.StatusResponse>( + 'Status', + status_Pre, + false, + false, + ($core.List<$core.int> value) => $0.StatusRequest.fromBuffer(value), + ($0.StatusResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.LoginRequest, $0.LoginResponse>( + 'Login', + login_Pre, + false, + false, + ($core.List<$core.int> value) => $0.LoginRequest.fromBuffer(value), + ($0.LoginResponse value) => value.writeToBuffer())); + $addMethod( + $grpc.ServiceMethod<$0.PreRegisterRequest, $0.PreRegisterResponse>( + 'PreRegister', + preRegister_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.PreRegisterRequest.fromBuffer(value), + ($0.PreRegisterResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.RegisterRequest, $0.RegisterResponse>( + 'Register', + register_Pre, + false, + false, + ($core.List<$core.int> value) => $0.RegisterRequest.fromBuffer(value), + ($0.RegisterResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.UnregisterRequest, $0.UnregisterResponse>( + 'Unregister', + unregister_Pre, + false, + false, + ($core.List<$core.int> value) => $0.UnregisterRequest.fromBuffer(value), + ($0.UnregisterResponse value) => value.writeToBuffer())); + } + + $async.Future<$0.StatusResponse> status_Pre( + $grpc.ServiceCall $call, $async.Future<$0.StatusRequest> $request) async { + return status($call, await $request); + } + + $async.Future<$0.StatusResponse> status( + $grpc.ServiceCall call, $0.StatusRequest request); + + $async.Future<$0.LoginResponse> login_Pre( + $grpc.ServiceCall $call, $async.Future<$0.LoginRequest> $request) async { + return login($call, await $request); + } + + $async.Future<$0.LoginResponse> login( + $grpc.ServiceCall call, $0.LoginRequest request); + + $async.Future<$0.PreRegisterResponse> preRegister_Pre($grpc.ServiceCall $call, + $async.Future<$0.PreRegisterRequest> $request) async { + return preRegister($call, await $request); + } + + $async.Future<$0.PreRegisterResponse> preRegister( + $grpc.ServiceCall call, $0.PreRegisterRequest request); + + $async.Future<$0.RegisterResponse> register_Pre($grpc.ServiceCall $call, + $async.Future<$0.RegisterRequest> $request) async { + return register($call, await $request); + } + + $async.Future<$0.RegisterResponse> register( + $grpc.ServiceCall call, $0.RegisterRequest request); + + $async.Future<$0.UnregisterResponse> unregister_Pre($grpc.ServiceCall $call, + $async.Future<$0.UnregisterRequest> $request) async { + return unregister($call, await $request); + } + + $async.Future<$0.UnregisterResponse> unregister( + $grpc.ServiceCall call, $0.UnregisterRequest request); +} diff --git a/lib/generated/proto/auth/auth.pbjson.dart b/lib/generated/proto/auth/auth.pbjson.dart new file mode 100644 index 0000000..076cbdd --- /dev/null +++ b/lib/generated/proto/auth/auth.pbjson.dart @@ -0,0 +1,188 @@ +// This is a generated file - do not edit. +// +// Generated from proto/auth/auth.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use statusRequestDescriptor instead') +const StatusRequest$json = { + '1': 'StatusRequest', +}; + +/// Descriptor for `StatusRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List statusRequestDescriptor = + $convert.base64Decode('Cg1TdGF0dXNSZXF1ZXN0'); + +@$core.Deprecated('Use statusResponseDescriptor instead') +const StatusResponse$json = { + '1': 'StatusResponse', + '2': [ + {'1': 'online', '3': 1, '4': 1, '5': 8, '10': 'online'}, + {'1': 'message', '3': 2, '4': 1, '5': 9, '10': 'message'}, + {'1': 'server_time', '3': 3, '4': 1, '5': 3, '10': 'serverTime'}, + ], +}; + +/// Descriptor for `StatusResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List statusResponseDescriptor = $convert.base64Decode( + 'Cg5TdGF0dXNSZXNwb25zZRIWCgZvbmxpbmUYASABKAhSBm9ubGluZRIYCgdtZXNzYWdlGAIgAS' + 'gJUgdtZXNzYWdlEh8KC3NlcnZlcl90aW1lGAMgASgDUgpzZXJ2ZXJUaW1l'); + +@$core.Deprecated('Use loginRequestDescriptor instead') +const LoginRequest$json = { + '1': 'LoginRequest', +}; + +/// Descriptor for `LoginRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List loginRequestDescriptor = + $convert.base64Decode('CgxMb2dpblJlcXVlc3Q='); + +@$core.Deprecated('Use gameUserInfoDescriptor instead') +const GameUserInfo$json = { + '1': 'GameUserInfo', + '2': [ + {'1': 'game_user_id', '3': 1, '4': 1, '5': 9, '10': 'gameUserId'}, + {'1': 'handle_name', '3': 2, '4': 1, '5': 9, '10': 'handleName'}, + {'1': 'avatar_url', '3': 3, '4': 1, '5': 9, '10': 'avatarUrl'}, + {'1': 'citizen_record', '3': 4, '4': 1, '5': 9, '10': 'citizenRecord'}, + {'1': 'enlisted_date', '3': 5, '4': 1, '5': 3, '10': 'enlistedDate'}, + ], +}; + +/// Descriptor for `GameUserInfo`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List gameUserInfoDescriptor = $convert.base64Decode( + 'CgxHYW1lVXNlckluZm8SIAoMZ2FtZV91c2VyX2lkGAEgASgJUgpnYW1lVXNlcklkEh8KC2hhbm' + 'RsZV9uYW1lGAIgASgJUgpoYW5kbGVOYW1lEh0KCmF2YXRhcl91cmwYAyABKAlSCWF2YXRhclVy' + 'bBIlCg5jaXRpemVuX3JlY29yZBgEIAEoCVINY2l0aXplblJlY29yZBIjCg1lbmxpc3RlZF9kYX' + 'RlGAUgASgDUgxlbmxpc3RlZERhdGU='); + +@$core.Deprecated('Use loginResponseDescriptor instead') +const LoginResponse$json = { + '1': 'LoginResponse', + '2': [ + {'1': 'uuid', '3': 1, '4': 1, '5': 9, '10': 'uuid'}, + { + '1': 'user_info', + '3': 2, + '4': 1, + '5': 11, + '6': '.auth.GameUserInfo', + '10': 'userInfo' + }, + {'1': 'last_login_time', '3': 3, '4': 1, '5': 3, '10': 'lastLoginTime'}, + ], +}; + +/// Descriptor for `LoginResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List loginResponseDescriptor = $convert.base64Decode( + 'Cg1Mb2dpblJlc3BvbnNlEhIKBHV1aWQYASABKAlSBHV1aWQSLwoJdXNlcl9pbmZvGAIgASgLMh' + 'IuYXV0aC5HYW1lVXNlckluZm9SCHVzZXJJbmZvEiYKD2xhc3RfbG9naW5fdGltZRgDIAEoA1IN' + 'bGFzdExvZ2luVGltZQ=='); + +@$core.Deprecated('Use preRegisterRequestDescriptor instead') +const PreRegisterRequest$json = { + '1': 'PreRegisterRequest', + '2': [ + {'1': 'uuid', '3': 1, '4': 1, '5': 9, '10': 'uuid'}, + {'1': 'game_user_id', '3': 2, '4': 1, '5': 9, '10': 'gameUserId'}, + ], +}; + +/// Descriptor for `PreRegisterRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List preRegisterRequestDescriptor = $convert.base64Decode( + 'ChJQcmVSZWdpc3RlclJlcXVlc3QSEgoEdXVpZBgBIAEoCVIEdXVpZBIgCgxnYW1lX3VzZXJfaW' + 'QYAiABKAlSCmdhbWVVc2VySWQ='); + +@$core.Deprecated('Use preRegisterResponseDescriptor instead') +const PreRegisterResponse$json = { + '1': 'PreRegisterResponse', + '2': [ + { + '1': 'verification_code', + '3': 1, + '4': 1, + '5': 9, + '10': 'verificationCode' + }, + {'1': 'expire_time', '3': 2, '4': 1, '5': 3, '10': 'expireTime'}, + ], +}; + +/// Descriptor for `PreRegisterResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List preRegisterResponseDescriptor = $convert.base64Decode( + 'ChNQcmVSZWdpc3RlclJlc3BvbnNlEisKEXZlcmlmaWNhdGlvbl9jb2RlGAEgASgJUhB2ZXJpZm' + 'ljYXRpb25Db2RlEh8KC2V4cGlyZV90aW1lGAIgASgDUgpleHBpcmVUaW1l'); + +@$core.Deprecated('Use registerRequestDescriptor instead') +const RegisterRequest$json = { + '1': 'RegisterRequest', + '2': [ + {'1': 'uuid', '3': 1, '4': 1, '5': 9, '10': 'uuid'}, + {'1': 'game_user_id', '3': 2, '4': 1, '5': 9, '10': 'gameUserId'}, + ], +}; + +/// Descriptor for `RegisterRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List registerRequestDescriptor = $convert.base64Decode( + 'Cg9SZWdpc3RlclJlcXVlc3QSEgoEdXVpZBgBIAEoCVIEdXVpZBIgCgxnYW1lX3VzZXJfaWQYAi' + 'ABKAlSCmdhbWVVc2VySWQ='); + +@$core.Deprecated('Use registerResponseDescriptor instead') +const RegisterResponse$json = { + '1': 'RegisterResponse', + '2': [ + { + '1': 'party_room_secret_key', + '3': 1, + '4': 1, + '5': 9, + '10': 'partyRoomSecretKey' + }, + { + '1': 'user_info', + '3': 2, + '4': 1, + '5': 11, + '6': '.auth.GameUserInfo', + '10': 'userInfo' + }, + ], +}; + +/// Descriptor for `RegisterResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List registerResponseDescriptor = $convert.base64Decode( + 'ChBSZWdpc3RlclJlc3BvbnNlEjEKFXBhcnR5X3Jvb21fc2VjcmV0X2tleRgBIAEoCVIScGFydH' + 'lSb29tU2VjcmV0S2V5Ei8KCXVzZXJfaW5mbxgCIAEoCzISLmF1dGguR2FtZVVzZXJJbmZvUgh1' + 'c2VySW5mbw=='); + +@$core.Deprecated('Use unregisterRequestDescriptor instead') +const UnregisterRequest$json = { + '1': 'UnregisterRequest', +}; + +/// Descriptor for `UnregisterRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List unregisterRequestDescriptor = + $convert.base64Decode('ChFVbnJlZ2lzdGVyUmVxdWVzdA=='); + +@$core.Deprecated('Use unregisterResponseDescriptor instead') +const UnregisterResponse$json = { + '1': 'UnregisterResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `UnregisterResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List unregisterResponseDescriptor = + $convert.base64Decode( + 'ChJVbnJlZ2lzdGVyUmVzcG9uc2USGAoHc3VjY2VzcxgBIAEoCFIHc3VjY2Vzcw=='); diff --git a/lib/generated/proto/common/common.pb.dart b/lib/generated/proto/common/common.pb.dart new file mode 100644 index 0000000..ac359d1 --- /dev/null +++ b/lib/generated/proto/common/common.pb.dart @@ -0,0 +1,601 @@ +// This is a generated file - do not edit. +// +// Generated from proto/common/common.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +/// 获取服务器时间请求 +class GetServerTimeRequest extends $pb.GeneratedMessage { + factory GetServerTimeRequest() => create(); + + GetServerTimeRequest._(); + + factory GetServerTimeRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetServerTimeRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetServerTimeRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetServerTimeRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetServerTimeRequest copyWith(void Function(GetServerTimeRequest) updates) => + super.copyWith((message) => updates(message as GetServerTimeRequest)) + as GetServerTimeRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetServerTimeRequest create() => GetServerTimeRequest._(); + @$core.override + GetServerTimeRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetServerTimeRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetServerTimeRequest? _defaultInstance; +} + +/// 获取服务器时间响应 +class GetServerTimeResponse extends $pb.GeneratedMessage { + factory GetServerTimeResponse({ + $fixnum.Int64? timestamp, + $core.String? timezone, + }) { + final result = create(); + if (timestamp != null) result.timestamp = timestamp; + if (timezone != null) result.timezone = timezone; + return result; + } + + GetServerTimeResponse._(); + + factory GetServerTimeResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetServerTimeResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetServerTimeResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..aInt64(1, _omitFieldNames ? '' : 'timestamp') + ..aOS(2, _omitFieldNames ? '' : 'timezone') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetServerTimeResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetServerTimeResponse copyWith( + void Function(GetServerTimeResponse) updates) => + super.copyWith((message) => updates(message as GetServerTimeResponse)) + as GetServerTimeResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetServerTimeResponse create() => GetServerTimeResponse._(); + @$core.override + GetServerTimeResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetServerTimeResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetServerTimeResponse? _defaultInstance; + + @$pb.TagNumber(1) + $fixnum.Int64 get timestamp => $_getI64(0); + @$pb.TagNumber(1) + set timestamp($fixnum.Int64 value) => $_setInt64(0, value); + @$pb.TagNumber(1) + $core.bool hasTimestamp() => $_has(0); + @$pb.TagNumber(1) + void clearTimestamp() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get timezone => $_getSZ(1); + @$pb.TagNumber(2) + set timezone($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasTimezone() => $_has(1); + @$pb.TagNumber(2) + void clearTimezone() => $_clearField(2); +} + +/// 获取版本请求 +class GetVersionRequest extends $pb.GeneratedMessage { + factory GetVersionRequest() => create(); + + GetVersionRequest._(); + + factory GetVersionRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetVersionRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetVersionRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetVersionRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetVersionRequest copyWith(void Function(GetVersionRequest) updates) => + super.copyWith((message) => updates(message as GetVersionRequest)) + as GetVersionRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetVersionRequest create() => GetVersionRequest._(); + @$core.override + GetVersionRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetVersionRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetVersionRequest? _defaultInstance; +} + +/// 获取版本响应 +class GetVersionResponse extends $pb.GeneratedMessage { + factory GetVersionResponse({ + $core.int? version, + $core.int? latestVersion, + $core.int? minClientVersion, + }) { + final result = create(); + if (version != null) result.version = version; + if (latestVersion != null) result.latestVersion = latestVersion; + if (minClientVersion != null) result.minClientVersion = minClientVersion; + return result; + } + + GetVersionResponse._(); + + factory GetVersionResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetVersionResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetVersionResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..aI(1, _omitFieldNames ? '' : 'version') + ..aI(2, _omitFieldNames ? '' : 'latestVersion') + ..aI(3, _omitFieldNames ? '' : 'minClientVersion') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetVersionResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetVersionResponse copyWith(void Function(GetVersionResponse) updates) => + super.copyWith((message) => updates(message as GetVersionResponse)) + as GetVersionResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetVersionResponse create() => GetVersionResponse._(); + @$core.override + GetVersionResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetVersionResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetVersionResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.int get version => $_getIZ(0); + @$pb.TagNumber(1) + set version($core.int value) => $_setSignedInt32(0, value); + @$pb.TagNumber(1) + $core.bool hasVersion() => $_has(0); + @$pb.TagNumber(1) + void clearVersion() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get latestVersion => $_getIZ(1); + @$pb.TagNumber(2) + set latestVersion($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasLatestVersion() => $_has(1); + @$pb.TagNumber(2) + void clearLatestVersion() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get minClientVersion => $_getIZ(2); + @$pb.TagNumber(3) + set minClientVersion($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasMinClientVersion() => $_has(2); + @$pb.TagNumber(3) + void clearMinClientVersion() => $_clearField(3); +} + +/// 信号类型 +class SignalType extends $pb.GeneratedMessage { + factory SignalType({ + $core.String? id, + $core.String? name, + $core.bool? isSpecial, + }) { + final result = create(); + if (id != null) result.id = id; + if (name != null) result.name = name; + if (isSpecial != null) result.isSpecial = isSpecial; + return result; + } + + SignalType._(); + + factory SignalType.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SignalType.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SignalType', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(2, _omitFieldNames ? '' : 'name') + ..aOB(3, _omitFieldNames ? '' : 'isSpecial') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SignalType clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SignalType copyWith(void Function(SignalType) updates) => + super.copyWith((message) => updates(message as SignalType)) as SignalType; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SignalType create() => SignalType._(); + @$core.override + SignalType createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SignalType getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SignalType? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get name => $_getSZ(1); + @$pb.TagNumber(2) + set name($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasName() => $_has(1); + @$pb.TagNumber(2) + void clearName() => $_clearField(2); + + @$pb.TagNumber(3) + $core.bool get isSpecial => $_getBF(2); + @$pb.TagNumber(3) + set isSpecial($core.bool value) => $_setBool(2, value); + @$pb.TagNumber(3) + $core.bool hasIsSpecial() => $_has(2); + @$pb.TagNumber(3) + void clearIsSpecial() => $_clearField(3); +} + +/// 获取信号类型请求 +class GetSignalTypesRequest extends $pb.GeneratedMessage { + factory GetSignalTypesRequest() => create(); + + GetSignalTypesRequest._(); + + factory GetSignalTypesRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetSignalTypesRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetSignalTypesRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetSignalTypesRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetSignalTypesRequest copyWith( + void Function(GetSignalTypesRequest) updates) => + super.copyWith((message) => updates(message as GetSignalTypesRequest)) + as GetSignalTypesRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetSignalTypesRequest create() => GetSignalTypesRequest._(); + @$core.override + GetSignalTypesRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetSignalTypesRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetSignalTypesRequest? _defaultInstance; +} + +/// 获取信号类型响应 +class GetSignalTypesResponse extends $pb.GeneratedMessage { + factory GetSignalTypesResponse({ + $core.Iterable? signals, + }) { + final result = create(); + if (signals != null) result.signals.addAll(signals); + return result; + } + + GetSignalTypesResponse._(); + + factory GetSignalTypesResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetSignalTypesResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetSignalTypesResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..pPM(1, _omitFieldNames ? '' : 'signals', + subBuilder: SignalType.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetSignalTypesResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetSignalTypesResponse copyWith( + void Function(GetSignalTypesResponse) updates) => + super.copyWith((message) => updates(message as GetSignalTypesResponse)) + as GetSignalTypesResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetSignalTypesResponse create() => GetSignalTypesResponse._(); + @$core.override + GetSignalTypesResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetSignalTypesResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetSignalTypesResponse? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList get signals => $_getList(0); +} + +/// 标签 +class Tag extends $pb.GeneratedMessage { + factory Tag({ + $core.String? id, + $core.String? name, + $core.String? info, + $core.String? color, + $core.Iterable? subTags, + }) { + final result = create(); + if (id != null) result.id = id; + if (name != null) result.name = name; + if (info != null) result.info = info; + if (color != null) result.color = color; + if (subTags != null) result.subTags.addAll(subTags); + return result; + } + + Tag._(); + + factory Tag.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory Tag.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'Tag', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'id') + ..aOS(2, _omitFieldNames ? '' : 'name') + ..aOS(3, _omitFieldNames ? '' : 'info') + ..aOS(4, _omitFieldNames ? '' : 'color') + ..pPM(5, _omitFieldNames ? '' : 'subTags', subBuilder: Tag.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Tag clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + Tag copyWith(void Function(Tag) updates) => + super.copyWith((message) => updates(message as Tag)) as Tag; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static Tag create() => Tag._(); + @$core.override + Tag createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static Tag getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static Tag? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get id => $_getSZ(0); + @$pb.TagNumber(1) + set id($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasId() => $_has(0); + @$pb.TagNumber(1) + void clearId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get name => $_getSZ(1); + @$pb.TagNumber(2) + set name($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasName() => $_has(1); + @$pb.TagNumber(2) + void clearName() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get info => $_getSZ(2); + @$pb.TagNumber(3) + set info($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasInfo() => $_has(2); + @$pb.TagNumber(3) + void clearInfo() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get color => $_getSZ(3); + @$pb.TagNumber(4) + set color($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasColor() => $_has(3); + @$pb.TagNumber(4) + void clearColor() => $_clearField(4); + + @$pb.TagNumber(5) + $pb.PbList get subTags => $_getList(4); +} + +/// 获取标签请求 +class GetTagsRequest extends $pb.GeneratedMessage { + factory GetTagsRequest() => create(); + + GetTagsRequest._(); + + factory GetTagsRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetTagsRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetTagsRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetTagsRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetTagsRequest copyWith(void Function(GetTagsRequest) updates) => + super.copyWith((message) => updates(message as GetTagsRequest)) + as GetTagsRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetTagsRequest create() => GetTagsRequest._(); + @$core.override + GetTagsRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetTagsRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetTagsRequest? _defaultInstance; +} + +/// 获取标签响应 +class GetTagsResponse extends $pb.GeneratedMessage { + factory GetTagsResponse({ + $core.Iterable? tags, + }) { + final result = create(); + if (tags != null) result.tags.addAll(tags); + return result; + } + + GetTagsResponse._(); + + factory GetTagsResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetTagsResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetTagsResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'common'), + createEmptyInstance: create) + ..pPM(1, _omitFieldNames ? '' : 'tags', subBuilder: Tag.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetTagsResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetTagsResponse copyWith(void Function(GetTagsResponse) updates) => + super.copyWith((message) => updates(message as GetTagsResponse)) + as GetTagsResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetTagsResponse create() => GetTagsResponse._(); + @$core.override + GetTagsResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetTagsResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetTagsResponse? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList get tags => $_getList(0); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/generated/proto/common/common.pbenum.dart b/lib/generated/proto/common/common.pbenum.dart new file mode 100644 index 0000000..429a18e --- /dev/null +++ b/lib/generated/proto/common/common.pbenum.dart @@ -0,0 +1,11 @@ +// This is a generated file - do not edit. +// +// Generated from proto/common/common.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names diff --git a/lib/generated/proto/common/common.pbgrpc.dart b/lib/generated/proto/common/common.pbgrpc.dart new file mode 100644 index 0000000..88aba82 --- /dev/null +++ b/lib/generated/proto/common/common.pbgrpc.dart @@ -0,0 +1,164 @@ +// This is a generated file - do not edit. +// +// Generated from proto/common/common.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:grpc/service_api.dart' as $grpc; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'common.pb.dart' as $0; + +export 'common.pb.dart'; + +/// 通用服务 +@$pb.GrpcServiceName('common.CommonService') +class CommonServiceClient extends $grpc.Client { + /// The hostname for this service. + static const $core.String defaultHost = ''; + + /// OAuth scopes needed for the client. + static const $core.List<$core.String> oauthScopes = [ + '', + ]; + + CommonServiceClient(super.channel, {super.options, super.interceptors}); + + /// 获取服务器时间(匿名接口) + $grpc.ResponseFuture<$0.GetServerTimeResponse> getServerTime( + $0.GetServerTimeRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getServerTime, request, options: options); + } + + /// 获取版本信息(匿名接口) + $grpc.ResponseFuture<$0.GetVersionResponse> getVersion( + $0.GetVersionRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getVersion, request, options: options); + } + + /// 获取信号类型列表(匿名接口) + $grpc.ResponseFuture<$0.GetSignalTypesResponse> getSignalTypes( + $0.GetSignalTypesRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getSignalTypes, request, options: options); + } + + /// 获取房间标签(匿名接口) + $grpc.ResponseFuture<$0.GetTagsResponse> getTags( + $0.GetTagsRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getTags, request, options: options); + } + + // method descriptors + + static final _$getServerTime = + $grpc.ClientMethod<$0.GetServerTimeRequest, $0.GetServerTimeResponse>( + '/common.CommonService/GetServerTime', + ($0.GetServerTimeRequest value) => value.writeToBuffer(), + $0.GetServerTimeResponse.fromBuffer); + static final _$getVersion = + $grpc.ClientMethod<$0.GetVersionRequest, $0.GetVersionResponse>( + '/common.CommonService/GetVersion', + ($0.GetVersionRequest value) => value.writeToBuffer(), + $0.GetVersionResponse.fromBuffer); + static final _$getSignalTypes = + $grpc.ClientMethod<$0.GetSignalTypesRequest, $0.GetSignalTypesResponse>( + '/common.CommonService/GetSignalTypes', + ($0.GetSignalTypesRequest value) => value.writeToBuffer(), + $0.GetSignalTypesResponse.fromBuffer); + static final _$getTags = + $grpc.ClientMethod<$0.GetTagsRequest, $0.GetTagsResponse>( + '/common.CommonService/GetTags', + ($0.GetTagsRequest value) => value.writeToBuffer(), + $0.GetTagsResponse.fromBuffer); +} + +@$pb.GrpcServiceName('common.CommonService') +abstract class CommonServiceBase extends $grpc.Service { + $core.String get $name => 'common.CommonService'; + + CommonServiceBase() { + $addMethod( + $grpc.ServiceMethod<$0.GetServerTimeRequest, $0.GetServerTimeResponse>( + 'GetServerTime', + getServerTime_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.GetServerTimeRequest.fromBuffer(value), + ($0.GetServerTimeResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.GetVersionRequest, $0.GetVersionResponse>( + 'GetVersion', + getVersion_Pre, + false, + false, + ($core.List<$core.int> value) => $0.GetVersionRequest.fromBuffer(value), + ($0.GetVersionResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.GetSignalTypesRequest, + $0.GetSignalTypesResponse>( + 'GetSignalTypes', + getSignalTypes_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.GetSignalTypesRequest.fromBuffer(value), + ($0.GetSignalTypesResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.GetTagsRequest, $0.GetTagsResponse>( + 'GetTags', + getTags_Pre, + false, + false, + ($core.List<$core.int> value) => $0.GetTagsRequest.fromBuffer(value), + ($0.GetTagsResponse value) => value.writeToBuffer())); + } + + $async.Future<$0.GetServerTimeResponse> getServerTime_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.GetServerTimeRequest> $request) async { + return getServerTime($call, await $request); + } + + $async.Future<$0.GetServerTimeResponse> getServerTime( + $grpc.ServiceCall call, $0.GetServerTimeRequest request); + + $async.Future<$0.GetVersionResponse> getVersion_Pre($grpc.ServiceCall $call, + $async.Future<$0.GetVersionRequest> $request) async { + return getVersion($call, await $request); + } + + $async.Future<$0.GetVersionResponse> getVersion( + $grpc.ServiceCall call, $0.GetVersionRequest request); + + $async.Future<$0.GetSignalTypesResponse> getSignalTypes_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.GetSignalTypesRequest> $request) async { + return getSignalTypes($call, await $request); + } + + $async.Future<$0.GetSignalTypesResponse> getSignalTypes( + $grpc.ServiceCall call, $0.GetSignalTypesRequest request); + + $async.Future<$0.GetTagsResponse> getTags_Pre($grpc.ServiceCall $call, + $async.Future<$0.GetTagsRequest> $request) async { + return getTags($call, await $request); + } + + $async.Future<$0.GetTagsResponse> getTags( + $grpc.ServiceCall call, $0.GetTagsRequest request); +} diff --git a/lib/generated/proto/common/common.pbjson.dart b/lib/generated/proto/common/common.pbjson.dart new file mode 100644 index 0000000..75a7c16 --- /dev/null +++ b/lib/generated/proto/common/common.pbjson.dart @@ -0,0 +1,160 @@ +// This is a generated file - do not edit. +// +// Generated from proto/common/common.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use getServerTimeRequestDescriptor instead') +const GetServerTimeRequest$json = { + '1': 'GetServerTimeRequest', +}; + +/// Descriptor for `GetServerTimeRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getServerTimeRequestDescriptor = + $convert.base64Decode('ChRHZXRTZXJ2ZXJUaW1lUmVxdWVzdA=='); + +@$core.Deprecated('Use getServerTimeResponseDescriptor instead') +const GetServerTimeResponse$json = { + '1': 'GetServerTimeResponse', + '2': [ + {'1': 'timestamp', '3': 1, '4': 1, '5': 3, '10': 'timestamp'}, + {'1': 'timezone', '3': 2, '4': 1, '5': 9, '10': 'timezone'}, + ], +}; + +/// Descriptor for `GetServerTimeResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getServerTimeResponseDescriptor = $convert.base64Decode( + 'ChVHZXRTZXJ2ZXJUaW1lUmVzcG9uc2USHAoJdGltZXN0YW1wGAEgASgDUgl0aW1lc3RhbXASGg' + 'oIdGltZXpvbmUYAiABKAlSCHRpbWV6b25l'); + +@$core.Deprecated('Use getVersionRequestDescriptor instead') +const GetVersionRequest$json = { + '1': 'GetVersionRequest', +}; + +/// Descriptor for `GetVersionRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getVersionRequestDescriptor = + $convert.base64Decode('ChFHZXRWZXJzaW9uUmVxdWVzdA=='); + +@$core.Deprecated('Use getVersionResponseDescriptor instead') +const GetVersionResponse$json = { + '1': 'GetVersionResponse', + '2': [ + {'1': 'version', '3': 1, '4': 1, '5': 5, '10': 'version'}, + {'1': 'latest_version', '3': 2, '4': 1, '5': 5, '10': 'latestVersion'}, + { + '1': 'min_client_version', + '3': 3, + '4': 1, + '5': 5, + '10': 'minClientVersion' + }, + ], +}; + +/// Descriptor for `GetVersionResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getVersionResponseDescriptor = $convert.base64Decode( + 'ChJHZXRWZXJzaW9uUmVzcG9uc2USGAoHdmVyc2lvbhgBIAEoBVIHdmVyc2lvbhIlCg5sYXRlc3' + 'RfdmVyc2lvbhgCIAEoBVINbGF0ZXN0VmVyc2lvbhIsChJtaW5fY2xpZW50X3ZlcnNpb24YAyAB' + 'KAVSEG1pbkNsaWVudFZlcnNpb24='); + +@$core.Deprecated('Use signalTypeDescriptor instead') +const SignalType$json = { + '1': 'SignalType', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, + {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'}, + {'1': 'is_special', '3': 3, '4': 1, '5': 8, '10': 'isSpecial'}, + ], +}; + +/// Descriptor for `SignalType`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List signalTypeDescriptor = $convert.base64Decode( + 'CgpTaWduYWxUeXBlEg4KAmlkGAEgASgJUgJpZBISCgRuYW1lGAIgASgJUgRuYW1lEh0KCmlzX3' + 'NwZWNpYWwYAyABKAhSCWlzU3BlY2lhbA=='); + +@$core.Deprecated('Use getSignalTypesRequestDescriptor instead') +const GetSignalTypesRequest$json = { + '1': 'GetSignalTypesRequest', +}; + +/// Descriptor for `GetSignalTypesRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getSignalTypesRequestDescriptor = + $convert.base64Decode('ChVHZXRTaWduYWxUeXBlc1JlcXVlc3Q='); + +@$core.Deprecated('Use getSignalTypesResponseDescriptor instead') +const GetSignalTypesResponse$json = { + '1': 'GetSignalTypesResponse', + '2': [ + { + '1': 'signals', + '3': 1, + '4': 3, + '5': 11, + '6': '.common.SignalType', + '10': 'signals' + }, + ], +}; + +/// Descriptor for `GetSignalTypesResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getSignalTypesResponseDescriptor = + $convert.base64Decode( + 'ChZHZXRTaWduYWxUeXBlc1Jlc3BvbnNlEiwKB3NpZ25hbHMYASADKAsyEi5jb21tb24uU2lnbm' + 'FsVHlwZVIHc2lnbmFscw=='); + +@$core.Deprecated('Use tagDescriptor instead') +const Tag$json = { + '1': 'Tag', + '2': [ + {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'}, + {'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'}, + {'1': 'info', '3': 3, '4': 1, '5': 9, '10': 'info'}, + {'1': 'color', '3': 4, '4': 1, '5': 9, '10': 'color'}, + { + '1': 'sub_tags', + '3': 5, + '4': 3, + '5': 11, + '6': '.common.Tag', + '10': 'subTags' + }, + ], +}; + +/// Descriptor for `Tag`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List tagDescriptor = $convert.base64Decode( + 'CgNUYWcSDgoCaWQYASABKAlSAmlkEhIKBG5hbWUYAiABKAlSBG5hbWUSEgoEaW5mbxgDIAEoCV' + 'IEaW5mbxIUCgVjb2xvchgEIAEoCVIFY29sb3ISJgoIc3ViX3RhZ3MYBSADKAsyCy5jb21tb24u' + 'VGFnUgdzdWJUYWdz'); + +@$core.Deprecated('Use getTagsRequestDescriptor instead') +const GetTagsRequest$json = { + '1': 'GetTagsRequest', +}; + +/// Descriptor for `GetTagsRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getTagsRequestDescriptor = + $convert.base64Decode('Cg5HZXRUYWdzUmVxdWVzdA=='); + +@$core.Deprecated('Use getTagsResponseDescriptor instead') +const GetTagsResponse$json = { + '1': 'GetTagsResponse', + '2': [ + {'1': 'tags', '3': 1, '4': 3, '5': 11, '6': '.common.Tag', '10': 'tags'}, + ], +}; + +/// Descriptor for `GetTagsResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getTagsResponseDescriptor = $convert.base64Decode( + 'Cg9HZXRUYWdzUmVzcG9uc2USHwoEdGFncxgBIAMoCzILLmNvbW1vbi5UYWdSBHRhZ3M='); diff --git a/lib/generated/proto/partroom/partroom.pb.dart b/lib/generated/proto/partroom/partroom.pb.dart new file mode 100644 index 0000000..aa5820d --- /dev/null +++ b/lib/generated/proto/partroom/partroom.pb.dart @@ -0,0 +1,2980 @@ +// This is a generated file - do not edit. +// +// Generated from proto/partroom/partroom.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names + +import 'dart:core' as $core; + +import 'package:fixnum/fixnum.dart' as $fixnum; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'partroom.pbenum.dart'; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +export 'partroom.pbenum.dart'; + +/// 房间信息(列表用) +class RoomListItem extends $pb.GeneratedMessage { + factory RoomListItem({ + $core.String? roomUuid, + $core.String? ownerGameId, + $core.String? ownerHandleName, + $core.String? ownerAvatar, + $core.String? mainTagId, + $core.String? subTagId, + $fixnum.Int64? createdAt, + $fixnum.Int64? ownerLastActive, + $core.int? currentMembers, + $core.int? targetMembers, + $core.bool? hasPassword, + $core.Iterable<$core.String>? socialLinks, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (ownerGameId != null) result.ownerGameId = ownerGameId; + if (ownerHandleName != null) result.ownerHandleName = ownerHandleName; + if (ownerAvatar != null) result.ownerAvatar = ownerAvatar; + if (mainTagId != null) result.mainTagId = mainTagId; + if (subTagId != null) result.subTagId = subTagId; + if (createdAt != null) result.createdAt = createdAt; + if (ownerLastActive != null) result.ownerLastActive = ownerLastActive; + if (currentMembers != null) result.currentMembers = currentMembers; + if (targetMembers != null) result.targetMembers = targetMembers; + if (hasPassword != null) result.hasPassword = hasPassword; + if (socialLinks != null) result.socialLinks.addAll(socialLinks); + return result; + } + + RoomListItem._(); + + factory RoomListItem.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RoomListItem.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RoomListItem', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aOS(2, _omitFieldNames ? '' : 'ownerGameId') + ..aOS(3, _omitFieldNames ? '' : 'ownerHandleName') + ..aOS(4, _omitFieldNames ? '' : 'ownerAvatar') + ..aOS(5, _omitFieldNames ? '' : 'mainTagId') + ..aOS(6, _omitFieldNames ? '' : 'subTagId') + ..aInt64(7, _omitFieldNames ? '' : 'createdAt') + ..aInt64(8, _omitFieldNames ? '' : 'ownerLastActive') + ..aI(9, _omitFieldNames ? '' : 'currentMembers') + ..aI(10, _omitFieldNames ? '' : 'targetMembers') + ..aOB(11, _omitFieldNames ? '' : 'hasPassword') + ..pPS(12, _omitFieldNames ? '' : 'socialLinks') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RoomListItem clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RoomListItem copyWith(void Function(RoomListItem) updates) => + super.copyWith((message) => updates(message as RoomListItem)) + as RoomListItem; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RoomListItem create() => RoomListItem._(); + @$core.override + RoomListItem createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RoomListItem getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RoomListItem? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get ownerGameId => $_getSZ(1); + @$pb.TagNumber(2) + set ownerGameId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasOwnerGameId() => $_has(1); + @$pb.TagNumber(2) + void clearOwnerGameId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get ownerHandleName => $_getSZ(2); + @$pb.TagNumber(3) + set ownerHandleName($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasOwnerHandleName() => $_has(2); + @$pb.TagNumber(3) + void clearOwnerHandleName() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get ownerAvatar => $_getSZ(3); + @$pb.TagNumber(4) + set ownerAvatar($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasOwnerAvatar() => $_has(3); + @$pb.TagNumber(4) + void clearOwnerAvatar() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get mainTagId => $_getSZ(4); + @$pb.TagNumber(5) + set mainTagId($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasMainTagId() => $_has(4); + @$pb.TagNumber(5) + void clearMainTagId() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get subTagId => $_getSZ(5); + @$pb.TagNumber(6) + set subTagId($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasSubTagId() => $_has(5); + @$pb.TagNumber(6) + void clearSubTagId() => $_clearField(6); + + @$pb.TagNumber(7) + $fixnum.Int64 get createdAt => $_getI64(6); + @$pb.TagNumber(7) + set createdAt($fixnum.Int64 value) => $_setInt64(6, value); + @$pb.TagNumber(7) + $core.bool hasCreatedAt() => $_has(6); + @$pb.TagNumber(7) + void clearCreatedAt() => $_clearField(7); + + @$pb.TagNumber(8) + $fixnum.Int64 get ownerLastActive => $_getI64(7); + @$pb.TagNumber(8) + set ownerLastActive($fixnum.Int64 value) => $_setInt64(7, value); + @$pb.TagNumber(8) + $core.bool hasOwnerLastActive() => $_has(7); + @$pb.TagNumber(8) + void clearOwnerLastActive() => $_clearField(8); + + @$pb.TagNumber(9) + $core.int get currentMembers => $_getIZ(8); + @$pb.TagNumber(9) + set currentMembers($core.int value) => $_setSignedInt32(8, value); + @$pb.TagNumber(9) + $core.bool hasCurrentMembers() => $_has(8); + @$pb.TagNumber(9) + void clearCurrentMembers() => $_clearField(9); + + @$pb.TagNumber(10) + $core.int get targetMembers => $_getIZ(9); + @$pb.TagNumber(10) + set targetMembers($core.int value) => $_setSignedInt32(9, value); + @$pb.TagNumber(10) + $core.bool hasTargetMembers() => $_has(9); + @$pb.TagNumber(10) + void clearTargetMembers() => $_clearField(10); + + @$pb.TagNumber(11) + $core.bool get hasPassword => $_getBF(10); + @$pb.TagNumber(11) + set hasPassword($core.bool value) => $_setBool(10, value); + @$pb.TagNumber(11) + $core.bool hasHasPassword() => $_has(10); + @$pb.TagNumber(11) + void clearHasPassword() => $_clearField(11); + + @$pb.TagNumber(12) + $pb.PbList<$core.String> get socialLinks => $_getList(11); +} + +/// 获取房间列表请求 +class GetRoomListRequest extends $pb.GeneratedMessage { + factory GetRoomListRequest({ + $core.String? mainTagId, + $core.String? subTagId, + $core.String? searchOwnerName, + $core.int? page, + $core.int? pageSize, + }) { + final result = create(); + if (mainTagId != null) result.mainTagId = mainTagId; + if (subTagId != null) result.subTagId = subTagId; + if (searchOwnerName != null) result.searchOwnerName = searchOwnerName; + if (page != null) result.page = page; + if (pageSize != null) result.pageSize = pageSize; + return result; + } + + GetRoomListRequest._(); + + factory GetRoomListRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetRoomListRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetRoomListRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'mainTagId') + ..aOS(2, _omitFieldNames ? '' : 'subTagId') + ..aOS(3, _omitFieldNames ? '' : 'searchOwnerName') + ..aI(4, _omitFieldNames ? '' : 'page') + ..aI(5, _omitFieldNames ? '' : 'pageSize') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomListRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomListRequest copyWith(void Function(GetRoomListRequest) updates) => + super.copyWith((message) => updates(message as GetRoomListRequest)) + as GetRoomListRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetRoomListRequest create() => GetRoomListRequest._(); + @$core.override + GetRoomListRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetRoomListRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetRoomListRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get mainTagId => $_getSZ(0); + @$pb.TagNumber(1) + set mainTagId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasMainTagId() => $_has(0); + @$pb.TagNumber(1) + void clearMainTagId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get subTagId => $_getSZ(1); + @$pb.TagNumber(2) + set subTagId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasSubTagId() => $_has(1); + @$pb.TagNumber(2) + void clearSubTagId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get searchOwnerName => $_getSZ(2); + @$pb.TagNumber(3) + set searchOwnerName($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasSearchOwnerName() => $_has(2); + @$pb.TagNumber(3) + void clearSearchOwnerName() => $_clearField(3); + + @$pb.TagNumber(4) + $core.int get page => $_getIZ(3); + @$pb.TagNumber(4) + set page($core.int value) => $_setSignedInt32(3, value); + @$pb.TagNumber(4) + $core.bool hasPage() => $_has(3); + @$pb.TagNumber(4) + void clearPage() => $_clearField(4); + + @$pb.TagNumber(5) + $core.int get pageSize => $_getIZ(4); + @$pb.TagNumber(5) + set pageSize($core.int value) => $_setSignedInt32(4, value); + @$pb.TagNumber(5) + $core.bool hasPageSize() => $_has(4); + @$pb.TagNumber(5) + void clearPageSize() => $_clearField(5); +} + +/// 获取房间列表响应 +class GetRoomListResponse extends $pb.GeneratedMessage { + factory GetRoomListResponse({ + $core.Iterable? rooms, + $core.int? total, + $core.int? page, + $core.int? pageSize, + }) { + final result = create(); + if (rooms != null) result.rooms.addAll(rooms); + if (total != null) result.total = total; + if (page != null) result.page = page; + if (pageSize != null) result.pageSize = pageSize; + return result; + } + + GetRoomListResponse._(); + + factory GetRoomListResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetRoomListResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetRoomListResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..pPM(1, _omitFieldNames ? '' : 'rooms', + subBuilder: RoomListItem.create) + ..aI(2, _omitFieldNames ? '' : 'total') + ..aI(3, _omitFieldNames ? '' : 'page') + ..aI(4, _omitFieldNames ? '' : 'pageSize') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomListResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomListResponse copyWith(void Function(GetRoomListResponse) updates) => + super.copyWith((message) => updates(message as GetRoomListResponse)) + as GetRoomListResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetRoomListResponse create() => GetRoomListResponse._(); + @$core.override + GetRoomListResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetRoomListResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetRoomListResponse? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList get rooms => $_getList(0); + + @$pb.TagNumber(2) + $core.int get total => $_getIZ(1); + @$pb.TagNumber(2) + set total($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasTotal() => $_has(1); + @$pb.TagNumber(2) + void clearTotal() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get page => $_getIZ(2); + @$pb.TagNumber(3) + set page($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasPage() => $_has(2); + @$pb.TagNumber(3) + void clearPage() => $_clearField(3); + + @$pb.TagNumber(4) + $core.int get pageSize => $_getIZ(3); + @$pb.TagNumber(4) + set pageSize($core.int value) => $_setSignedInt32(3, value); + @$pb.TagNumber(4) + $core.bool hasPageSize() => $_has(3); + @$pb.TagNumber(4) + void clearPageSize() => $_clearField(4); +} + +/// 创建房间请求 +class CreateRoomRequest extends $pb.GeneratedMessage { + factory CreateRoomRequest({ + $core.String? mainTagId, + $core.String? subTagId, + $core.int? targetMembers, + $core.bool? hasPassword, + $core.String? password_5, + $core.Iterable<$core.String>? socialLinks, + }) { + final result = create(); + if (mainTagId != null) result.mainTagId = mainTagId; + if (subTagId != null) result.subTagId = subTagId; + if (targetMembers != null) result.targetMembers = targetMembers; + if (hasPassword != null) result.hasPassword = hasPassword; + if (password_5 != null) result.password_5 = password_5; + if (socialLinks != null) result.socialLinks.addAll(socialLinks); + return result; + } + + CreateRoomRequest._(); + + factory CreateRoomRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory CreateRoomRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'CreateRoomRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'mainTagId') + ..aOS(2, _omitFieldNames ? '' : 'subTagId') + ..aI(3, _omitFieldNames ? '' : 'targetMembers') + ..aOB(4, _omitFieldNames ? '' : 'hasPassword') + ..aOS(5, _omitFieldNames ? '' : 'password') + ..pPS(6, _omitFieldNames ? '' : 'socialLinks') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CreateRoomRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CreateRoomRequest copyWith(void Function(CreateRoomRequest) updates) => + super.copyWith((message) => updates(message as CreateRoomRequest)) + as CreateRoomRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static CreateRoomRequest create() => CreateRoomRequest._(); + @$core.override + CreateRoomRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static CreateRoomRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static CreateRoomRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get mainTagId => $_getSZ(0); + @$pb.TagNumber(1) + set mainTagId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasMainTagId() => $_has(0); + @$pb.TagNumber(1) + void clearMainTagId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get subTagId => $_getSZ(1); + @$pb.TagNumber(2) + set subTagId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasSubTagId() => $_has(1); + @$pb.TagNumber(2) + void clearSubTagId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get targetMembers => $_getIZ(2); + @$pb.TagNumber(3) + set targetMembers($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasTargetMembers() => $_has(2); + @$pb.TagNumber(3) + void clearTargetMembers() => $_clearField(3); + + @$pb.TagNumber(4) + $core.bool get hasPassword => $_getBF(3); + @$pb.TagNumber(4) + set hasPassword($core.bool value) => $_setBool(3, value); + @$pb.TagNumber(4) + $core.bool hasHasPassword() => $_has(3); + @$pb.TagNumber(4) + void clearHasPassword() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get password_5 => $_getSZ(4); + @$pb.TagNumber(5) + set password_5($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasPassword_5() => $_has(4); + @$pb.TagNumber(5) + void clearPassword_5() => $_clearField(5); + + @$pb.TagNumber(6) + $pb.PbList<$core.String> get socialLinks => $_getList(5); +} + +/// 创建房间响应 +class CreateRoomResponse extends $pb.GeneratedMessage { + factory CreateRoomResponse({ + $core.String? roomUuid, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + return result; + } + + CreateRoomResponse._(); + + factory CreateRoomResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory CreateRoomResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'CreateRoomResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CreateRoomResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + CreateRoomResponse copyWith(void Function(CreateRoomResponse) updates) => + super.copyWith((message) => updates(message as CreateRoomResponse)) + as CreateRoomResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static CreateRoomResponse create() => CreateRoomResponse._(); + @$core.override + CreateRoomResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static CreateRoomResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static CreateRoomResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); +} + +/// 加入房间请求 +class JoinRoomRequest extends $pb.GeneratedMessage { + factory JoinRoomRequest({ + $core.String? roomUuid, + $core.String? password, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (password != null) result.password = password; + return result; + } + + JoinRoomRequest._(); + + factory JoinRoomRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory JoinRoomRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'JoinRoomRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aOS(2, _omitFieldNames ? '' : 'password') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + JoinRoomRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + JoinRoomRequest copyWith(void Function(JoinRoomRequest) updates) => + super.copyWith((message) => updates(message as JoinRoomRequest)) + as JoinRoomRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static JoinRoomRequest create() => JoinRoomRequest._(); + @$core.override + JoinRoomRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static JoinRoomRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static JoinRoomRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get password => $_getSZ(1); + @$pb.TagNumber(2) + set password($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasPassword() => $_has(1); + @$pb.TagNumber(2) + void clearPassword() => $_clearField(2); +} + +/// 加入房间响应 +class JoinRoomResponse extends $pb.GeneratedMessage { + factory JoinRoomResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + JoinRoomResponse._(); + + factory JoinRoomResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory JoinRoomResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'JoinRoomResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + JoinRoomResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + JoinRoomResponse copyWith(void Function(JoinRoomResponse) updates) => + super.copyWith((message) => updates(message as JoinRoomResponse)) + as JoinRoomResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static JoinRoomResponse create() => JoinRoomResponse._(); + @$core.override + JoinRoomResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static JoinRoomResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static JoinRoomResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +/// 离开房间请求 +class LeaveRoomRequest extends $pb.GeneratedMessage { + factory LeaveRoomRequest({ + $core.String? roomUuid, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + return result; + } + + LeaveRoomRequest._(); + + factory LeaveRoomRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory LeaveRoomRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'LeaveRoomRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LeaveRoomRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LeaveRoomRequest copyWith(void Function(LeaveRoomRequest) updates) => + super.copyWith((message) => updates(message as LeaveRoomRequest)) + as LeaveRoomRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static LeaveRoomRequest create() => LeaveRoomRequest._(); + @$core.override + LeaveRoomRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static LeaveRoomRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static LeaveRoomRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); +} + +/// 离开房间响应 +class LeaveRoomResponse extends $pb.GeneratedMessage { + factory LeaveRoomResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + LeaveRoomResponse._(); + + factory LeaveRoomResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory LeaveRoomResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'LeaveRoomResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LeaveRoomResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + LeaveRoomResponse copyWith(void Function(LeaveRoomResponse) updates) => + super.copyWith((message) => updates(message as LeaveRoomResponse)) + as LeaveRoomResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static LeaveRoomResponse create() => LeaveRoomResponse._(); + @$core.override + LeaveRoomResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static LeaveRoomResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static LeaveRoomResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +/// 解散房间请求 +class DismissRoomRequest extends $pb.GeneratedMessage { + factory DismissRoomRequest({ + $core.String? roomUuid, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + return result; + } + + DismissRoomRequest._(); + + factory DismissRoomRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory DismissRoomRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'DismissRoomRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DismissRoomRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DismissRoomRequest copyWith(void Function(DismissRoomRequest) updates) => + super.copyWith((message) => updates(message as DismissRoomRequest)) + as DismissRoomRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static DismissRoomRequest create() => DismissRoomRequest._(); + @$core.override + DismissRoomRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static DismissRoomRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static DismissRoomRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); +} + +/// 解散房间响应 +class DismissRoomResponse extends $pb.GeneratedMessage { + factory DismissRoomResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + DismissRoomResponse._(); + + factory DismissRoomResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory DismissRoomResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'DismissRoomResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DismissRoomResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + DismissRoomResponse copyWith(void Function(DismissRoomResponse) updates) => + super.copyWith((message) => updates(message as DismissRoomResponse)) + as DismissRoomResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static DismissRoomResponse create() => DismissRoomResponse._(); + @$core.override + DismissRoomResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static DismissRoomResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static DismissRoomResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +/// 成员状态 +class MemberStatus extends $pb.GeneratedMessage { + factory MemberStatus({ + $core.String? currentLocation, + $core.int? kills, + $core.int? deaths, + $fixnum.Int64? playTime, + }) { + final result = create(); + if (currentLocation != null) result.currentLocation = currentLocation; + if (kills != null) result.kills = kills; + if (deaths != null) result.deaths = deaths; + if (playTime != null) result.playTime = playTime; + return result; + } + + MemberStatus._(); + + factory MemberStatus.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory MemberStatus.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'MemberStatus', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'currentLocation') + ..aI(2, _omitFieldNames ? '' : 'kills') + ..aI(3, _omitFieldNames ? '' : 'deaths') + ..aInt64(4, _omitFieldNames ? '' : 'playTime') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + MemberStatus clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + MemberStatus copyWith(void Function(MemberStatus) updates) => + super.copyWith((message) => updates(message as MemberStatus)) + as MemberStatus; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static MemberStatus create() => MemberStatus._(); + @$core.override + MemberStatus createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static MemberStatus getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static MemberStatus? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get currentLocation => $_getSZ(0); + @$pb.TagNumber(1) + set currentLocation($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasCurrentLocation() => $_has(0); + @$pb.TagNumber(1) + void clearCurrentLocation() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get kills => $_getIZ(1); + @$pb.TagNumber(2) + set kills($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasKills() => $_has(1); + @$pb.TagNumber(2) + void clearKills() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get deaths => $_getIZ(2); + @$pb.TagNumber(3) + set deaths($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasDeaths() => $_has(2); + @$pb.TagNumber(3) + void clearDeaths() => $_clearField(3); + + @$pb.TagNumber(4) + $fixnum.Int64 get playTime => $_getI64(3); + @$pb.TagNumber(4) + set playTime($fixnum.Int64 value) => $_setInt64(3, value); + @$pb.TagNumber(4) + $core.bool hasPlayTime() => $_has(3); + @$pb.TagNumber(4) + void clearPlayTime() => $_clearField(4); +} + +/// 房间成员 +class RoomMember extends $pb.GeneratedMessage { + factory RoomMember({ + $core.String? gameUserId, + $core.String? handleName, + $core.String? avatarUrl, + $fixnum.Int64? joinedAt, + $fixnum.Int64? lastActive, + $core.bool? isOwner, + MemberStatus? status, + }) { + final result = create(); + if (gameUserId != null) result.gameUserId = gameUserId; + if (handleName != null) result.handleName = handleName; + if (avatarUrl != null) result.avatarUrl = avatarUrl; + if (joinedAt != null) result.joinedAt = joinedAt; + if (lastActive != null) result.lastActive = lastActive; + if (isOwner != null) result.isOwner = isOwner; + if (status != null) result.status = status; + return result; + } + + RoomMember._(); + + factory RoomMember.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RoomMember.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RoomMember', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'gameUserId') + ..aOS(2, _omitFieldNames ? '' : 'handleName') + ..aOS(3, _omitFieldNames ? '' : 'avatarUrl') + ..aInt64(4, _omitFieldNames ? '' : 'joinedAt') + ..aInt64(5, _omitFieldNames ? '' : 'lastActive') + ..aOB(6, _omitFieldNames ? '' : 'isOwner') + ..aOM(7, _omitFieldNames ? '' : 'status', + subBuilder: MemberStatus.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RoomMember clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RoomMember copyWith(void Function(RoomMember) updates) => + super.copyWith((message) => updates(message as RoomMember)) as RoomMember; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RoomMember create() => RoomMember._(); + @$core.override + RoomMember createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RoomMember getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RoomMember? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get gameUserId => $_getSZ(0); + @$pb.TagNumber(1) + set gameUserId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasGameUserId() => $_has(0); + @$pb.TagNumber(1) + void clearGameUserId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get handleName => $_getSZ(1); + @$pb.TagNumber(2) + set handleName($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasHandleName() => $_has(1); + @$pb.TagNumber(2) + void clearHandleName() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get avatarUrl => $_getSZ(2); + @$pb.TagNumber(3) + set avatarUrl($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasAvatarUrl() => $_has(2); + @$pb.TagNumber(3) + void clearAvatarUrl() => $_clearField(3); + + @$pb.TagNumber(4) + $fixnum.Int64 get joinedAt => $_getI64(3); + @$pb.TagNumber(4) + set joinedAt($fixnum.Int64 value) => $_setInt64(3, value); + @$pb.TagNumber(4) + $core.bool hasJoinedAt() => $_has(3); + @$pb.TagNumber(4) + void clearJoinedAt() => $_clearField(4); + + @$pb.TagNumber(5) + $fixnum.Int64 get lastActive => $_getI64(4); + @$pb.TagNumber(5) + set lastActive($fixnum.Int64 value) => $_setInt64(4, value); + @$pb.TagNumber(5) + $core.bool hasLastActive() => $_has(4); + @$pb.TagNumber(5) + void clearLastActive() => $_clearField(5); + + @$pb.TagNumber(6) + $core.bool get isOwner => $_getBF(5); + @$pb.TagNumber(6) + set isOwner($core.bool value) => $_setBool(5, value); + @$pb.TagNumber(6) + $core.bool hasIsOwner() => $_has(5); + @$pb.TagNumber(6) + void clearIsOwner() => $_clearField(6); + + @$pb.TagNumber(7) + MemberStatus get status => $_getN(6); + @$pb.TagNumber(7) + set status(MemberStatus value) => $_setField(7, value); + @$pb.TagNumber(7) + $core.bool hasStatus() => $_has(6); + @$pb.TagNumber(7) + void clearStatus() => $_clearField(7); + @$pb.TagNumber(7) + MemberStatus ensureStatus() => $_ensure(6); +} + +/// 房间详情 +class RoomInfo extends $pb.GeneratedMessage { + factory RoomInfo({ + $core.String? roomUuid, + $core.String? ownerGameId, + $core.String? mainTagId, + $core.String? subTagId, + $core.int? targetMembers, + $core.bool? hasPassword, + $fixnum.Int64? createdAt, + $core.int? currentMembers, + $core.Iterable<$core.String>? socialLinks, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (ownerGameId != null) result.ownerGameId = ownerGameId; + if (mainTagId != null) result.mainTagId = mainTagId; + if (subTagId != null) result.subTagId = subTagId; + if (targetMembers != null) result.targetMembers = targetMembers; + if (hasPassword != null) result.hasPassword = hasPassword; + if (createdAt != null) result.createdAt = createdAt; + if (currentMembers != null) result.currentMembers = currentMembers; + if (socialLinks != null) result.socialLinks.addAll(socialLinks); + return result; + } + + RoomInfo._(); + + factory RoomInfo.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RoomInfo.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RoomInfo', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aOS(2, _omitFieldNames ? '' : 'ownerGameId') + ..aOS(3, _omitFieldNames ? '' : 'mainTagId') + ..aOS(4, _omitFieldNames ? '' : 'subTagId') + ..aI(5, _omitFieldNames ? '' : 'targetMembers') + ..aOB(6, _omitFieldNames ? '' : 'hasPassword') + ..aInt64(7, _omitFieldNames ? '' : 'createdAt') + ..aI(8, _omitFieldNames ? '' : 'currentMembers') + ..pPS(9, _omitFieldNames ? '' : 'socialLinks') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RoomInfo clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RoomInfo copyWith(void Function(RoomInfo) updates) => + super.copyWith((message) => updates(message as RoomInfo)) as RoomInfo; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RoomInfo create() => RoomInfo._(); + @$core.override + RoomInfo createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RoomInfo getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static RoomInfo? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get ownerGameId => $_getSZ(1); + @$pb.TagNumber(2) + set ownerGameId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasOwnerGameId() => $_has(1); + @$pb.TagNumber(2) + void clearOwnerGameId() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get mainTagId => $_getSZ(2); + @$pb.TagNumber(3) + set mainTagId($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasMainTagId() => $_has(2); + @$pb.TagNumber(3) + void clearMainTagId() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get subTagId => $_getSZ(3); + @$pb.TagNumber(4) + set subTagId($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasSubTagId() => $_has(3); + @$pb.TagNumber(4) + void clearSubTagId() => $_clearField(4); + + @$pb.TagNumber(5) + $core.int get targetMembers => $_getIZ(4); + @$pb.TagNumber(5) + set targetMembers($core.int value) => $_setSignedInt32(4, value); + @$pb.TagNumber(5) + $core.bool hasTargetMembers() => $_has(4); + @$pb.TagNumber(5) + void clearTargetMembers() => $_clearField(5); + + @$pb.TagNumber(6) + $core.bool get hasPassword => $_getBF(5); + @$pb.TagNumber(6) + set hasPassword($core.bool value) => $_setBool(5, value); + @$pb.TagNumber(6) + $core.bool hasHasPassword() => $_has(5); + @$pb.TagNumber(6) + void clearHasPassword() => $_clearField(6); + + @$pb.TagNumber(7) + $fixnum.Int64 get createdAt => $_getI64(6); + @$pb.TagNumber(7) + set createdAt($fixnum.Int64 value) => $_setInt64(6, value); + @$pb.TagNumber(7) + $core.bool hasCreatedAt() => $_has(6); + @$pb.TagNumber(7) + void clearCreatedAt() => $_clearField(7); + + @$pb.TagNumber(8) + $core.int get currentMembers => $_getIZ(7); + @$pb.TagNumber(8) + set currentMembers($core.int value) => $_setSignedInt32(7, value); + @$pb.TagNumber(8) + $core.bool hasCurrentMembers() => $_has(7); + @$pb.TagNumber(8) + void clearCurrentMembers() => $_clearField(8); + + @$pb.TagNumber(9) + $pb.PbList<$core.String> get socialLinks => $_getList(8); +} + +/// 获取房间详情请求 +class GetRoomInfoRequest extends $pb.GeneratedMessage { + factory GetRoomInfoRequest({ + $core.String? roomUuid, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + return result; + } + + GetRoomInfoRequest._(); + + factory GetRoomInfoRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetRoomInfoRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetRoomInfoRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomInfoRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomInfoRequest copyWith(void Function(GetRoomInfoRequest) updates) => + super.copyWith((message) => updates(message as GetRoomInfoRequest)) + as GetRoomInfoRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetRoomInfoRequest create() => GetRoomInfoRequest._(); + @$core.override + GetRoomInfoRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetRoomInfoRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetRoomInfoRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); +} + +/// 获取房间详情响应 +class GetRoomInfoResponse extends $pb.GeneratedMessage { + factory GetRoomInfoResponse({ + RoomInfo? room, + }) { + final result = create(); + if (room != null) result.room = room; + return result; + } + + GetRoomInfoResponse._(); + + factory GetRoomInfoResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetRoomInfoResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetRoomInfoResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'room', + subBuilder: RoomInfo.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomInfoResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomInfoResponse copyWith(void Function(GetRoomInfoResponse) updates) => + super.copyWith((message) => updates(message as GetRoomInfoResponse)) + as GetRoomInfoResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetRoomInfoResponse create() => GetRoomInfoResponse._(); + @$core.override + GetRoomInfoResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetRoomInfoResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetRoomInfoResponse? _defaultInstance; + + @$pb.TagNumber(1) + RoomInfo get room => $_getN(0); + @$pb.TagNumber(1) + set room(RoomInfo value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasRoom() => $_has(0); + @$pb.TagNumber(1) + void clearRoom() => $_clearField(1); + @$pb.TagNumber(1) + RoomInfo ensureRoom() => $_ensure(0); +} + +/// 获取房间成员请求 +class GetRoomMembersRequest extends $pb.GeneratedMessage { + factory GetRoomMembersRequest({ + $core.String? roomUuid, + $core.int? page, + $core.int? pageSize, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (page != null) result.page = page; + if (pageSize != null) result.pageSize = pageSize; + return result; + } + + GetRoomMembersRequest._(); + + factory GetRoomMembersRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetRoomMembersRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetRoomMembersRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aI(2, _omitFieldNames ? '' : 'page') + ..aI(3, _omitFieldNames ? '' : 'pageSize') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomMembersRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomMembersRequest copyWith( + void Function(GetRoomMembersRequest) updates) => + super.copyWith((message) => updates(message as GetRoomMembersRequest)) + as GetRoomMembersRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetRoomMembersRequest create() => GetRoomMembersRequest._(); + @$core.override + GetRoomMembersRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetRoomMembersRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetRoomMembersRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get page => $_getIZ(1); + @$pb.TagNumber(2) + set page($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasPage() => $_has(1); + @$pb.TagNumber(2) + void clearPage() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get pageSize => $_getIZ(2); + @$pb.TagNumber(3) + set pageSize($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasPageSize() => $_has(2); + @$pb.TagNumber(3) + void clearPageSize() => $_clearField(3); +} + +/// 获取房间成员响应 +class GetRoomMembersResponse extends $pb.GeneratedMessage { + factory GetRoomMembersResponse({ + $core.Iterable? members, + $core.int? total, + }) { + final result = create(); + if (members != null) result.members.addAll(members); + if (total != null) result.total = total; + return result; + } + + GetRoomMembersResponse._(); + + factory GetRoomMembersResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetRoomMembersResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetRoomMembersResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..pPM(1, _omitFieldNames ? '' : 'members', + subBuilder: RoomMember.create) + ..aI(2, _omitFieldNames ? '' : 'total') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomMembersResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetRoomMembersResponse copyWith( + void Function(GetRoomMembersResponse) updates) => + super.copyWith((message) => updates(message as GetRoomMembersResponse)) + as GetRoomMembersResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetRoomMembersResponse create() => GetRoomMembersResponse._(); + @$core.override + GetRoomMembersResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetRoomMembersResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetRoomMembersResponse? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList get members => $_getList(0); + + @$pb.TagNumber(2) + $core.int get total => $_getIZ(1); + @$pb.TagNumber(2) + set total($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasTotal() => $_has(1); + @$pb.TagNumber(2) + void clearTotal() => $_clearField(2); +} + +/// 心跳请求 +class HeartbeatRequest extends $pb.GeneratedMessage { + factory HeartbeatRequest({ + $core.String? roomUuid, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + return result; + } + + HeartbeatRequest._(); + + factory HeartbeatRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory HeartbeatRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'HeartbeatRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HeartbeatRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HeartbeatRequest copyWith(void Function(HeartbeatRequest) updates) => + super.copyWith((message) => updates(message as HeartbeatRequest)) + as HeartbeatRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static HeartbeatRequest create() => HeartbeatRequest._(); + @$core.override + HeartbeatRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static HeartbeatRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static HeartbeatRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); +} + +/// 心跳响应 +class HeartbeatResponse extends $pb.GeneratedMessage { + factory HeartbeatResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + HeartbeatResponse._(); + + factory HeartbeatResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory HeartbeatResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'HeartbeatResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HeartbeatResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + HeartbeatResponse copyWith(void Function(HeartbeatResponse) updates) => + super.copyWith((message) => updates(message as HeartbeatResponse)) + as HeartbeatResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static HeartbeatResponse create() => HeartbeatResponse._(); + @$core.override + HeartbeatResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static HeartbeatResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static HeartbeatResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +/// 获取我的房间请求 +class GetMyRoomRequest extends $pb.GeneratedMessage { + factory GetMyRoomRequest() => create(); + + GetMyRoomRequest._(); + + factory GetMyRoomRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetMyRoomRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetMyRoomRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetMyRoomRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetMyRoomRequest copyWith(void Function(GetMyRoomRequest) updates) => + super.copyWith((message) => updates(message as GetMyRoomRequest)) + as GetMyRoomRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetMyRoomRequest create() => GetMyRoomRequest._(); + @$core.override + GetMyRoomRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetMyRoomRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetMyRoomRequest? _defaultInstance; +} + +/// 获取我的房间响应 +class GetMyRoomResponse extends $pb.GeneratedMessage { + factory GetMyRoomResponse({ + RoomInfo? room, + $core.bool? hasRoom_2, + }) { + final result = create(); + if (room != null) result.room = room; + if (hasRoom_2 != null) result.hasRoom_2 = hasRoom_2; + return result; + } + + GetMyRoomResponse._(); + + factory GetMyRoomResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetMyRoomResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetMyRoomResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOM(1, _omitFieldNames ? '' : 'room', + subBuilder: RoomInfo.create) + ..aOB(2, _omitFieldNames ? '' : 'hasRoom') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetMyRoomResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetMyRoomResponse copyWith(void Function(GetMyRoomResponse) updates) => + super.copyWith((message) => updates(message as GetMyRoomResponse)) + as GetMyRoomResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetMyRoomResponse create() => GetMyRoomResponse._(); + @$core.override + GetMyRoomResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetMyRoomResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetMyRoomResponse? _defaultInstance; + + @$pb.TagNumber(1) + RoomInfo get room => $_getN(0); + @$pb.TagNumber(1) + set room(RoomInfo value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasRoom() => $_has(0); + @$pb.TagNumber(1) + void clearRoom() => $_clearField(1); + @$pb.TagNumber(1) + RoomInfo ensureRoom() => $_ensure(0); + + @$pb.TagNumber(2) + $core.bool get hasRoom_2 => $_getBF(1); + @$pb.TagNumber(2) + set hasRoom_2($core.bool value) => $_setBool(1, value); + @$pb.TagNumber(2) + $core.bool hasHasRoom_2() => $_has(1); + @$pb.TagNumber(2) + void clearHasRoom_2() => $_clearField(2); +} + +/// 更新房间请求 +class UpdateRoomRequest extends $pb.GeneratedMessage { + factory UpdateRoomRequest({ + $core.String? roomUuid, + $core.int? targetMembers, + $core.String? password, + $core.String? mainTagId, + $core.String? subTagId, + $core.Iterable<$core.String>? socialLinks, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (targetMembers != null) result.targetMembers = targetMembers; + if (password != null) result.password = password; + if (mainTagId != null) result.mainTagId = mainTagId; + if (subTagId != null) result.subTagId = subTagId; + if (socialLinks != null) result.socialLinks.addAll(socialLinks); + return result; + } + + UpdateRoomRequest._(); + + factory UpdateRoomRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UpdateRoomRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UpdateRoomRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aI(2, _omitFieldNames ? '' : 'targetMembers') + ..aOS(3, _omitFieldNames ? '' : 'password') + ..aOS(4, _omitFieldNames ? '' : 'mainTagId') + ..aOS(5, _omitFieldNames ? '' : 'subTagId') + ..pPS(6, _omitFieldNames ? '' : 'socialLinks') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UpdateRoomRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UpdateRoomRequest copyWith(void Function(UpdateRoomRequest) updates) => + super.copyWith((message) => updates(message as UpdateRoomRequest)) + as UpdateRoomRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UpdateRoomRequest create() => UpdateRoomRequest._(); + @$core.override + UpdateRoomRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UpdateRoomRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UpdateRoomRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get targetMembers => $_getIZ(1); + @$pb.TagNumber(2) + set targetMembers($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasTargetMembers() => $_has(1); + @$pb.TagNumber(2) + void clearTargetMembers() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get password => $_getSZ(2); + @$pb.TagNumber(3) + set password($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasPassword() => $_has(2); + @$pb.TagNumber(3) + void clearPassword() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get mainTagId => $_getSZ(3); + @$pb.TagNumber(4) + set mainTagId($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasMainTagId() => $_has(3); + @$pb.TagNumber(4) + void clearMainTagId() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get subTagId => $_getSZ(4); + @$pb.TagNumber(5) + set subTagId($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasSubTagId() => $_has(4); + @$pb.TagNumber(5) + void clearSubTagId() => $_clearField(5); + + @$pb.TagNumber(6) + $pb.PbList<$core.String> get socialLinks => $_getList(5); +} + +/// 更新房间响应 +class UpdateRoomResponse extends $pb.GeneratedMessage { + factory UpdateRoomResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + UpdateRoomResponse._(); + + factory UpdateRoomResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory UpdateRoomResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'UpdateRoomResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UpdateRoomResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + UpdateRoomResponse copyWith(void Function(UpdateRoomResponse) updates) => + super.copyWith((message) => updates(message as UpdateRoomResponse)) + as UpdateRoomResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static UpdateRoomResponse create() => UpdateRoomResponse._(); + @$core.override + UpdateRoomResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static UpdateRoomResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static UpdateRoomResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +/// 踢出成员请求 +class KickMemberRequest extends $pb.GeneratedMessage { + factory KickMemberRequest({ + $core.String? roomUuid, + $core.String? targetGameUserId, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (targetGameUserId != null) result.targetGameUserId = targetGameUserId; + return result; + } + + KickMemberRequest._(); + + factory KickMemberRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory KickMemberRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'KickMemberRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aOS(2, _omitFieldNames ? '' : 'targetGameUserId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + KickMemberRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + KickMemberRequest copyWith(void Function(KickMemberRequest) updates) => + super.copyWith((message) => updates(message as KickMemberRequest)) + as KickMemberRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static KickMemberRequest create() => KickMemberRequest._(); + @$core.override + KickMemberRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static KickMemberRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static KickMemberRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get targetGameUserId => $_getSZ(1); + @$pb.TagNumber(2) + set targetGameUserId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasTargetGameUserId() => $_has(1); + @$pb.TagNumber(2) + void clearTargetGameUserId() => $_clearField(2); +} + +/// 踢出成员响应 +class KickMemberResponse extends $pb.GeneratedMessage { + factory KickMemberResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + KickMemberResponse._(); + + factory KickMemberResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory KickMemberResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'KickMemberResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + KickMemberResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + KickMemberResponse copyWith(void Function(KickMemberResponse) updates) => + super.copyWith((message) => updates(message as KickMemberResponse)) + as KickMemberResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static KickMemberResponse create() => KickMemberResponse._(); + @$core.override + KickMemberResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static KickMemberResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static KickMemberResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +/// 设置状态请求 +class SetStatusRequest extends $pb.GeneratedMessage { + factory SetStatusRequest({ + $core.String? roomUuid, + MemberStatus? status, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (status != null) result.status = status; + return result; + } + + SetStatusRequest._(); + + factory SetStatusRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SetStatusRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SetStatusRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aOM(2, _omitFieldNames ? '' : 'status', + subBuilder: MemberStatus.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SetStatusRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SetStatusRequest copyWith(void Function(SetStatusRequest) updates) => + super.copyWith((message) => updates(message as SetStatusRequest)) + as SetStatusRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SetStatusRequest create() => SetStatusRequest._(); + @$core.override + SetStatusRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SetStatusRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SetStatusRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + MemberStatus get status => $_getN(1); + @$pb.TagNumber(2) + set status(MemberStatus value) => $_setField(2, value); + @$pb.TagNumber(2) + $core.bool hasStatus() => $_has(1); + @$pb.TagNumber(2) + void clearStatus() => $_clearField(2); + @$pb.TagNumber(2) + MemberStatus ensureStatus() => $_ensure(1); +} + +/// 设置状态响应 +class SetStatusResponse extends $pb.GeneratedMessage { + factory SetStatusResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + SetStatusResponse._(); + + factory SetStatusResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SetStatusResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SetStatusResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SetStatusResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SetStatusResponse copyWith(void Function(SetStatusResponse) updates) => + super.copyWith((message) => updates(message as SetStatusResponse)) + as SetStatusResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SetStatusResponse create() => SetStatusResponse._(); + @$core.override + SetStatusResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SetStatusResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SetStatusResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +/// 发送信号请求 +class SendSignalRequest extends $pb.GeneratedMessage { + factory SendSignalRequest({ + $core.String? roomUuid, + $core.String? signalId, + $core.Iterable<$core.MapEntry<$core.String, $core.String>>? params, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (signalId != null) result.signalId = signalId; + if (params != null) result.params.addEntries(params); + return result; + } + + SendSignalRequest._(); + + factory SendSignalRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SendSignalRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SendSignalRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aOS(2, _omitFieldNames ? '' : 'signalId') + ..m<$core.String, $core.String>(3, _omitFieldNames ? '' : 'params', + entryClassName: 'SendSignalRequest.ParamsEntry', + keyFieldType: $pb.PbFieldType.OS, + valueFieldType: $pb.PbFieldType.OS, + packageName: const $pb.PackageName('partroom')) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SendSignalRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SendSignalRequest copyWith(void Function(SendSignalRequest) updates) => + super.copyWith((message) => updates(message as SendSignalRequest)) + as SendSignalRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SendSignalRequest create() => SendSignalRequest._(); + @$core.override + SendSignalRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SendSignalRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SendSignalRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get signalId => $_getSZ(1); + @$pb.TagNumber(2) + set signalId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasSignalId() => $_has(1); + @$pb.TagNumber(2) + void clearSignalId() => $_clearField(2); + + @$pb.TagNumber(3) + $pb.PbMap<$core.String, $core.String> get params => $_getMap(2); +} + +/// 发送信号响应 +class SendSignalResponse extends $pb.GeneratedMessage { + factory SendSignalResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + SendSignalResponse._(); + + factory SendSignalResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory SendSignalResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'SendSignalResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SendSignalResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + SendSignalResponse copyWith(void Function(SendSignalResponse) updates) => + super.copyWith((message) => updates(message as SendSignalResponse)) + as SendSignalResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static SendSignalResponse create() => SendSignalResponse._(); + @$core.override + SendSignalResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static SendSignalResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static SendSignalResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +/// 房间事件 +class RoomEvent extends $pb.GeneratedMessage { + factory RoomEvent({ + RoomEventType? type, + $core.String? roomUuid, + $fixnum.Int64? timestamp, + RoomMember? member, + $core.String? signalId, + $core.String? signalSender, + $core.Iterable<$core.MapEntry<$core.String, $core.String>>? signalParams, + RoomInfo? roomInfo, + }) { + final result = create(); + if (type != null) result.type = type; + if (roomUuid != null) result.roomUuid = roomUuid; + if (timestamp != null) result.timestamp = timestamp; + if (member != null) result.member = member; + if (signalId != null) result.signalId = signalId; + if (signalSender != null) result.signalSender = signalSender; + if (signalParams != null) result.signalParams.addEntries(signalParams); + if (roomInfo != null) result.roomInfo = roomInfo; + return result; + } + + RoomEvent._(); + + factory RoomEvent.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RoomEvent.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RoomEvent', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aE(1, _omitFieldNames ? '' : 'type', + enumValues: RoomEventType.values) + ..aOS(2, _omitFieldNames ? '' : 'roomUuid') + ..aInt64(3, _omitFieldNames ? '' : 'timestamp') + ..aOM(4, _omitFieldNames ? '' : 'member', + subBuilder: RoomMember.create) + ..aOS(5, _omitFieldNames ? '' : 'signalId') + ..aOS(6, _omitFieldNames ? '' : 'signalSender') + ..m<$core.String, $core.String>(7, _omitFieldNames ? '' : 'signalParams', + entryClassName: 'RoomEvent.SignalParamsEntry', + keyFieldType: $pb.PbFieldType.OS, + valueFieldType: $pb.PbFieldType.OS, + packageName: const $pb.PackageName('partroom')) + ..aOM(8, _omitFieldNames ? '' : 'roomInfo', + subBuilder: RoomInfo.create) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RoomEvent clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RoomEvent copyWith(void Function(RoomEvent) updates) => + super.copyWith((message) => updates(message as RoomEvent)) as RoomEvent; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RoomEvent create() => RoomEvent._(); + @$core.override + RoomEvent createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RoomEvent getDefault() => + _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor(create); + static RoomEvent? _defaultInstance; + + @$pb.TagNumber(1) + RoomEventType get type => $_getN(0); + @$pb.TagNumber(1) + set type(RoomEventType value) => $_setField(1, value); + @$pb.TagNumber(1) + $core.bool hasType() => $_has(0); + @$pb.TagNumber(1) + void clearType() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get roomUuid => $_getSZ(1); + @$pb.TagNumber(2) + set roomUuid($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasRoomUuid() => $_has(1); + @$pb.TagNumber(2) + void clearRoomUuid() => $_clearField(2); + + @$pb.TagNumber(3) + $fixnum.Int64 get timestamp => $_getI64(2); + @$pb.TagNumber(3) + set timestamp($fixnum.Int64 value) => $_setInt64(2, value); + @$pb.TagNumber(3) + $core.bool hasTimestamp() => $_has(2); + @$pb.TagNumber(3) + void clearTimestamp() => $_clearField(3); + + /// 根据事件类型使用不同的字段 + @$pb.TagNumber(4) + RoomMember get member => $_getN(3); + @$pb.TagNumber(4) + set member(RoomMember value) => $_setField(4, value); + @$pb.TagNumber(4) + $core.bool hasMember() => $_has(3); + @$pb.TagNumber(4) + void clearMember() => $_clearField(4); + @$pb.TagNumber(4) + RoomMember ensureMember() => $_ensure(3); + + @$pb.TagNumber(5) + $core.String get signalId => $_getSZ(4); + @$pb.TagNumber(5) + set signalId($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasSignalId() => $_has(4); + @$pb.TagNumber(5) + void clearSignalId() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get signalSender => $_getSZ(5); + @$pb.TagNumber(6) + set signalSender($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasSignalSender() => $_has(5); + @$pb.TagNumber(6) + void clearSignalSender() => $_clearField(6); + + @$pb.TagNumber(7) + $pb.PbMap<$core.String, $core.String> get signalParams => $_getMap(6); + + @$pb.TagNumber(8) + RoomInfo get roomInfo => $_getN(7); + @$pb.TagNumber(8) + set roomInfo(RoomInfo value) => $_setField(8, value); + @$pb.TagNumber(8) + $core.bool hasRoomInfo() => $_has(7); + @$pb.TagNumber(8) + void clearRoomInfo() => $_clearField(8); + @$pb.TagNumber(8) + RoomInfo ensureRoomInfo() => $_ensure(7); +} + +/// 监听房间事件请求 +class ListenRoomEventsRequest extends $pb.GeneratedMessage { + factory ListenRoomEventsRequest({ + $core.String? roomUuid, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + return result; + } + + ListenRoomEventsRequest._(); + + factory ListenRoomEventsRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ListenRoomEventsRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ListenRoomEventsRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ListenRoomEventsRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ListenRoomEventsRequest copyWith( + void Function(ListenRoomEventsRequest) updates) => + super.copyWith((message) => updates(message as ListenRoomEventsRequest)) + as ListenRoomEventsRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ListenRoomEventsRequest create() => ListenRoomEventsRequest._(); + @$core.override + ListenRoomEventsRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ListenRoomEventsRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ListenRoomEventsRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); +} + +/// 转移房主请求 +class TransferOwnershipRequest extends $pb.GeneratedMessage { + factory TransferOwnershipRequest({ + $core.String? roomUuid, + $core.String? targetGameUserId, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (targetGameUserId != null) result.targetGameUserId = targetGameUserId; + return result; + } + + TransferOwnershipRequest._(); + + factory TransferOwnershipRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TransferOwnershipRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TransferOwnershipRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aOS(2, _omitFieldNames ? '' : 'targetGameUserId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TransferOwnershipRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TransferOwnershipRequest copyWith( + void Function(TransferOwnershipRequest) updates) => + super.copyWith((message) => updates(message as TransferOwnershipRequest)) + as TransferOwnershipRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TransferOwnershipRequest create() => TransferOwnershipRequest._(); + @$core.override + TransferOwnershipRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TransferOwnershipRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TransferOwnershipRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get targetGameUserId => $_getSZ(1); + @$pb.TagNumber(2) + set targetGameUserId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasTargetGameUserId() => $_has(1); + @$pb.TagNumber(2) + void clearTargetGameUserId() => $_clearField(2); +} + +/// 转移房主响应 +class TransferOwnershipResponse extends $pb.GeneratedMessage { + factory TransferOwnershipResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + TransferOwnershipResponse._(); + + factory TransferOwnershipResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TransferOwnershipResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TransferOwnershipResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TransferOwnershipResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TransferOwnershipResponse copyWith( + void Function(TransferOwnershipResponse) updates) => + super.copyWith((message) => updates(message as TransferOwnershipResponse)) + as TransferOwnershipResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TransferOwnershipResponse create() => TransferOwnershipResponse._(); + @$core.override + TransferOwnershipResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TransferOwnershipResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TransferOwnershipResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +/// 被踢出成员 +class KickedMember extends $pb.GeneratedMessage { + factory KickedMember({ + $core.String? gameUserId, + $core.String? handleName, + $core.String? avatarUrl, + $fixnum.Int64? joinedAt, + $fixnum.Int64? kickedAt, + }) { + final result = create(); + if (gameUserId != null) result.gameUserId = gameUserId; + if (handleName != null) result.handleName = handleName; + if (avatarUrl != null) result.avatarUrl = avatarUrl; + if (joinedAt != null) result.joinedAt = joinedAt; + if (kickedAt != null) result.kickedAt = kickedAt; + return result; + } + + KickedMember._(); + + factory KickedMember.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory KickedMember.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'KickedMember', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'gameUserId') + ..aOS(2, _omitFieldNames ? '' : 'handleName') + ..aOS(3, _omitFieldNames ? '' : 'avatarUrl') + ..aInt64(4, _omitFieldNames ? '' : 'joinedAt') + ..aInt64(5, _omitFieldNames ? '' : 'kickedAt') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + KickedMember clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + KickedMember copyWith(void Function(KickedMember) updates) => + super.copyWith((message) => updates(message as KickedMember)) + as KickedMember; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static KickedMember create() => KickedMember._(); + @$core.override + KickedMember createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static KickedMember getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static KickedMember? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get gameUserId => $_getSZ(0); + @$pb.TagNumber(1) + set gameUserId($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasGameUserId() => $_has(0); + @$pb.TagNumber(1) + void clearGameUserId() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get handleName => $_getSZ(1); + @$pb.TagNumber(2) + set handleName($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasHandleName() => $_has(1); + @$pb.TagNumber(2) + void clearHandleName() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get avatarUrl => $_getSZ(2); + @$pb.TagNumber(3) + set avatarUrl($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasAvatarUrl() => $_has(2); + @$pb.TagNumber(3) + void clearAvatarUrl() => $_clearField(3); + + @$pb.TagNumber(4) + $fixnum.Int64 get joinedAt => $_getI64(3); + @$pb.TagNumber(4) + set joinedAt($fixnum.Int64 value) => $_setInt64(3, value); + @$pb.TagNumber(4) + $core.bool hasJoinedAt() => $_has(3); + @$pb.TagNumber(4) + void clearJoinedAt() => $_clearField(4); + + @$pb.TagNumber(5) + $fixnum.Int64 get kickedAt => $_getI64(4); + @$pb.TagNumber(5) + set kickedAt($fixnum.Int64 value) => $_setInt64(4, value); + @$pb.TagNumber(5) + $core.bool hasKickedAt() => $_has(4); + @$pb.TagNumber(5) + void clearKickedAt() => $_clearField(5); +} + +/// 获取被踢出成员请求 +class GetKickedMembersRequest extends $pb.GeneratedMessage { + factory GetKickedMembersRequest({ + $core.String? roomUuid, + $core.int? page, + $core.int? pageSize, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (page != null) result.page = page; + if (pageSize != null) result.pageSize = pageSize; + return result; + } + + GetKickedMembersRequest._(); + + factory GetKickedMembersRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetKickedMembersRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetKickedMembersRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aI(2, _omitFieldNames ? '' : 'page') + ..aI(3, _omitFieldNames ? '' : 'pageSize') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetKickedMembersRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetKickedMembersRequest copyWith( + void Function(GetKickedMembersRequest) updates) => + super.copyWith((message) => updates(message as GetKickedMembersRequest)) + as GetKickedMembersRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetKickedMembersRequest create() => GetKickedMembersRequest._(); + @$core.override + GetKickedMembersRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetKickedMembersRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetKickedMembersRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.int get page => $_getIZ(1); + @$pb.TagNumber(2) + set page($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasPage() => $_has(1); + @$pb.TagNumber(2) + void clearPage() => $_clearField(2); + + @$pb.TagNumber(3) + $core.int get pageSize => $_getIZ(2); + @$pb.TagNumber(3) + set pageSize($core.int value) => $_setSignedInt32(2, value); + @$pb.TagNumber(3) + $core.bool hasPageSize() => $_has(2); + @$pb.TagNumber(3) + void clearPageSize() => $_clearField(3); +} + +/// 获取被踢出成员响应 +class GetKickedMembersResponse extends $pb.GeneratedMessage { + factory GetKickedMembersResponse({ + $core.Iterable? members, + $core.int? total, + }) { + final result = create(); + if (members != null) result.members.addAll(members); + if (total != null) result.total = total; + return result; + } + + GetKickedMembersResponse._(); + + factory GetKickedMembersResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory GetKickedMembersResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'GetKickedMembersResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..pPM(1, _omitFieldNames ? '' : 'members', + subBuilder: KickedMember.create) + ..aI(2, _omitFieldNames ? '' : 'total') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetKickedMembersResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + GetKickedMembersResponse copyWith( + void Function(GetKickedMembersResponse) updates) => + super.copyWith((message) => updates(message as GetKickedMembersResponse)) + as GetKickedMembersResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static GetKickedMembersResponse create() => GetKickedMembersResponse._(); + @$core.override + GetKickedMembersResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static GetKickedMembersResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static GetKickedMembersResponse? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbList get members => $_getList(0); + + @$pb.TagNumber(2) + $core.int get total => $_getIZ(1); + @$pb.TagNumber(2) + set total($core.int value) => $_setSignedInt32(1, value); + @$pb.TagNumber(2) + $core.bool hasTotal() => $_has(1); + @$pb.TagNumber(2) + void clearTotal() => $_clearField(2); +} + +/// 移除被踢出成员请求 +class RemoveKickedMemberRequest extends $pb.GeneratedMessage { + factory RemoveKickedMemberRequest({ + $core.String? roomUuid, + $core.String? gameUserId, + }) { + final result = create(); + if (roomUuid != null) result.roomUuid = roomUuid; + if (gameUserId != null) result.gameUserId = gameUserId; + return result; + } + + RemoveKickedMemberRequest._(); + + factory RemoveKickedMemberRequest.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RemoveKickedMemberRequest.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RemoveKickedMemberRequest', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'roomUuid') + ..aOS(2, _omitFieldNames ? '' : 'gameUserId') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RemoveKickedMemberRequest clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RemoveKickedMemberRequest copyWith( + void Function(RemoveKickedMemberRequest) updates) => + super.copyWith((message) => updates(message as RemoveKickedMemberRequest)) + as RemoveKickedMemberRequest; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RemoveKickedMemberRequest create() => RemoveKickedMemberRequest._(); + @$core.override + RemoveKickedMemberRequest createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RemoveKickedMemberRequest getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RemoveKickedMemberRequest? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get roomUuid => $_getSZ(0); + @$pb.TagNumber(1) + set roomUuid($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasRoomUuid() => $_has(0); + @$pb.TagNumber(1) + void clearRoomUuid() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get gameUserId => $_getSZ(1); + @$pb.TagNumber(2) + set gameUserId($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasGameUserId() => $_has(1); + @$pb.TagNumber(2) + void clearGameUserId() => $_clearField(2); +} + +/// 移除被踢出成员响应 +class RemoveKickedMemberResponse extends $pb.GeneratedMessage { + factory RemoveKickedMemberResponse({ + $core.bool? success, + }) { + final result = create(); + if (success != null) result.success = success; + return result; + } + + RemoveKickedMemberResponse._(); + + factory RemoveKickedMemberResponse.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory RemoveKickedMemberResponse.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'RemoveKickedMemberResponse', + package: const $pb.PackageName(_omitMessageNames ? '' : 'partroom'), + createEmptyInstance: create) + ..aOB(1, _omitFieldNames ? '' : 'success') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RemoveKickedMemberResponse clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + RemoveKickedMemberResponse copyWith( + void Function(RemoveKickedMemberResponse) updates) => + super.copyWith( + (message) => updates(message as RemoveKickedMemberResponse)) + as RemoveKickedMemberResponse; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static RemoveKickedMemberResponse create() => RemoveKickedMemberResponse._(); + @$core.override + RemoveKickedMemberResponse createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static RemoveKickedMemberResponse getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static RemoveKickedMemberResponse? _defaultInstance; + + @$pb.TagNumber(1) + $core.bool get success => $_getBF(0); + @$pb.TagNumber(1) + set success($core.bool value) => $_setBool(0, value); + @$pb.TagNumber(1) + $core.bool hasSuccess() => $_has(0); + @$pb.TagNumber(1) + void clearSuccess() => $_clearField(1); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/lib/generated/proto/partroom/partroom.pbenum.dart b/lib/generated/proto/partroom/partroom.pbenum.dart new file mode 100644 index 0000000..f919bb5 --- /dev/null +++ b/lib/generated/proto/partroom/partroom.pbenum.dart @@ -0,0 +1,56 @@ +// This is a generated file - do not edit. +// +// Generated from proto/partroom/partroom.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +/// 房间事件类型 +class RoomEventType extends $pb.ProtobufEnum { + static const RoomEventType MEMBER_JOINED = + RoomEventType._(0, _omitEnumNames ? '' : 'MEMBER_JOINED'); + static const RoomEventType MEMBER_LEFT = + RoomEventType._(1, _omitEnumNames ? '' : 'MEMBER_LEFT'); + static const RoomEventType OWNER_CHANGED = + RoomEventType._(2, _omitEnumNames ? '' : 'OWNER_CHANGED'); + static const RoomEventType ROOM_UPDATED = + RoomEventType._(3, _omitEnumNames ? '' : 'ROOM_UPDATED'); + static const RoomEventType MEMBER_STATUS_UPDATED = + RoomEventType._(4, _omitEnumNames ? '' : 'MEMBER_STATUS_UPDATED'); + static const RoomEventType SIGNAL_BROADCAST = + RoomEventType._(5, _omitEnumNames ? '' : 'SIGNAL_BROADCAST'); + static const RoomEventType ROOM_DISMISSED = + RoomEventType._(6, _omitEnumNames ? '' : 'ROOM_DISMISSED'); + static const RoomEventType MEMBER_KICKED = + RoomEventType._(7, _omitEnumNames ? '' : 'MEMBER_KICKED'); + + static const $core.List values = [ + MEMBER_JOINED, + MEMBER_LEFT, + OWNER_CHANGED, + ROOM_UPDATED, + MEMBER_STATUS_UPDATED, + SIGNAL_BROADCAST, + ROOM_DISMISSED, + MEMBER_KICKED, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 7); + static RoomEventType? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const RoomEventType._(super.value, super.name); +} + +const $core.bool _omitEnumNames = + $core.bool.fromEnvironment('protobuf.omit_enum_names'); diff --git a/lib/generated/proto/partroom/partroom.pbgrpc.dart b/lib/generated/proto/partroom/partroom.pbgrpc.dart new file mode 100644 index 0000000..a00f136 --- /dev/null +++ b/lib/generated/proto/partroom/partroom.pbgrpc.dart @@ -0,0 +1,543 @@ +// This is a generated file - do not edit. +// +// Generated from proto/partroom/partroom.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names + +import 'dart:async' as $async; +import 'dart:core' as $core; + +import 'package:grpc/service_api.dart' as $grpc; +import 'package:protobuf/protobuf.dart' as $pb; + +import 'partroom.pb.dart' as $0; + +export 'partroom.pb.dart'; + +/// 房间服务 +@$pb.GrpcServiceName('partroom.PartRoomService') +class PartRoomServiceClient extends $grpc.Client { + /// The hostname for this service. + static const $core.String defaultHost = ''; + + /// OAuth scopes needed for the client. + static const $core.List<$core.String> oauthScopes = [ + '', + ]; + + PartRoomServiceClient(super.channel, {super.options, super.interceptors}); + + /// 获取房间列表(匿名接口) + $grpc.ResponseFuture<$0.GetRoomListResponse> getRoomList( + $0.GetRoomListRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getRoomList, request, options: options); + } + + /// 创建房间(需认证) + $grpc.ResponseFuture<$0.CreateRoomResponse> createRoom( + $0.CreateRoomRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$createRoom, request, options: options); + } + + /// 加入房间(需认证) + $grpc.ResponseFuture<$0.JoinRoomResponse> joinRoom( + $0.JoinRoomRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$joinRoom, request, options: options); + } + + /// 离开房间(需认证) + $grpc.ResponseFuture<$0.LeaveRoomResponse> leaveRoom( + $0.LeaveRoomRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$leaveRoom, request, options: options); + } + + /// 解散房间(需认证,仅房主) + $grpc.ResponseFuture<$0.DismissRoomResponse> dismissRoom( + $0.DismissRoomRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$dismissRoom, request, options: options); + } + + /// 获取房间详情(需认证) + $grpc.ResponseFuture<$0.GetRoomInfoResponse> getRoomInfo( + $0.GetRoomInfoRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getRoomInfo, request, options: options); + } + + /// 获取房间成员列表(需认证) + $grpc.ResponseFuture<$0.GetRoomMembersResponse> getRoomMembers( + $0.GetRoomMembersRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getRoomMembers, request, options: options); + } + + /// 心跳(需认证) + $grpc.ResponseFuture<$0.HeartbeatResponse> heartbeat( + $0.HeartbeatRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$heartbeat, request, options: options); + } + + /// 获取当前用户所在房间(需认证) + $grpc.ResponseFuture<$0.GetMyRoomResponse> getMyRoom( + $0.GetMyRoomRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getMyRoom, request, options: options); + } + + /// 更新房间信息(需认证,仅房主) + $grpc.ResponseFuture<$0.UpdateRoomResponse> updateRoom( + $0.UpdateRoomRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$updateRoom, request, options: options); + } + + /// 踢出成员(需认证,仅房主) + $grpc.ResponseFuture<$0.KickMemberResponse> kickMember( + $0.KickMemberRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$kickMember, request, options: options); + } + + /// 设置状态(需认证) + $grpc.ResponseFuture<$0.SetStatusResponse> setStatus( + $0.SetStatusRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$setStatus, request, options: options); + } + + /// 发送信号(需认证) + $grpc.ResponseFuture<$0.SendSignalResponse> sendSignal( + $0.SendSignalRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$sendSignal, request, options: options); + } + + /// 监听房间事件流(需认证) + $grpc.ResponseStream<$0.RoomEvent> listenRoomEvents( + $0.ListenRoomEventsRequest request, { + $grpc.CallOptions? options, + }) { + return $createStreamingCall( + _$listenRoomEvents, $async.Stream.fromIterable([request]), + options: options); + } + + /// 转移房主(需认证,仅房主) + $grpc.ResponseFuture<$0.TransferOwnershipResponse> transferOwnership( + $0.TransferOwnershipRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$transferOwnership, request, options: options); + } + + /// 获取被踢出成员列表(需认证,仅房主) + $grpc.ResponseFuture<$0.GetKickedMembersResponse> getKickedMembers( + $0.GetKickedMembersRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$getKickedMembers, request, options: options); + } + + /// 移除被踢出成员(需认证,仅房主) + $grpc.ResponseFuture<$0.RemoveKickedMemberResponse> removeKickedMember( + $0.RemoveKickedMemberRequest request, { + $grpc.CallOptions? options, + }) { + return $createUnaryCall(_$removeKickedMember, request, options: options); + } + + // method descriptors + + static final _$getRoomList = + $grpc.ClientMethod<$0.GetRoomListRequest, $0.GetRoomListResponse>( + '/partroom.PartRoomService/GetRoomList', + ($0.GetRoomListRequest value) => value.writeToBuffer(), + $0.GetRoomListResponse.fromBuffer); + static final _$createRoom = + $grpc.ClientMethod<$0.CreateRoomRequest, $0.CreateRoomResponse>( + '/partroom.PartRoomService/CreateRoom', + ($0.CreateRoomRequest value) => value.writeToBuffer(), + $0.CreateRoomResponse.fromBuffer); + static final _$joinRoom = + $grpc.ClientMethod<$0.JoinRoomRequest, $0.JoinRoomResponse>( + '/partroom.PartRoomService/JoinRoom', + ($0.JoinRoomRequest value) => value.writeToBuffer(), + $0.JoinRoomResponse.fromBuffer); + static final _$leaveRoom = + $grpc.ClientMethod<$0.LeaveRoomRequest, $0.LeaveRoomResponse>( + '/partroom.PartRoomService/LeaveRoom', + ($0.LeaveRoomRequest value) => value.writeToBuffer(), + $0.LeaveRoomResponse.fromBuffer); + static final _$dismissRoom = + $grpc.ClientMethod<$0.DismissRoomRequest, $0.DismissRoomResponse>( + '/partroom.PartRoomService/DismissRoom', + ($0.DismissRoomRequest value) => value.writeToBuffer(), + $0.DismissRoomResponse.fromBuffer); + static final _$getRoomInfo = + $grpc.ClientMethod<$0.GetRoomInfoRequest, $0.GetRoomInfoResponse>( + '/partroom.PartRoomService/GetRoomInfo', + ($0.GetRoomInfoRequest value) => value.writeToBuffer(), + $0.GetRoomInfoResponse.fromBuffer); + static final _$getRoomMembers = + $grpc.ClientMethod<$0.GetRoomMembersRequest, $0.GetRoomMembersResponse>( + '/partroom.PartRoomService/GetRoomMembers', + ($0.GetRoomMembersRequest value) => value.writeToBuffer(), + $0.GetRoomMembersResponse.fromBuffer); + static final _$heartbeat = + $grpc.ClientMethod<$0.HeartbeatRequest, $0.HeartbeatResponse>( + '/partroom.PartRoomService/Heartbeat', + ($0.HeartbeatRequest value) => value.writeToBuffer(), + $0.HeartbeatResponse.fromBuffer); + static final _$getMyRoom = + $grpc.ClientMethod<$0.GetMyRoomRequest, $0.GetMyRoomResponse>( + '/partroom.PartRoomService/GetMyRoom', + ($0.GetMyRoomRequest value) => value.writeToBuffer(), + $0.GetMyRoomResponse.fromBuffer); + static final _$updateRoom = + $grpc.ClientMethod<$0.UpdateRoomRequest, $0.UpdateRoomResponse>( + '/partroom.PartRoomService/UpdateRoom', + ($0.UpdateRoomRequest value) => value.writeToBuffer(), + $0.UpdateRoomResponse.fromBuffer); + static final _$kickMember = + $grpc.ClientMethod<$0.KickMemberRequest, $0.KickMemberResponse>( + '/partroom.PartRoomService/KickMember', + ($0.KickMemberRequest value) => value.writeToBuffer(), + $0.KickMemberResponse.fromBuffer); + static final _$setStatus = + $grpc.ClientMethod<$0.SetStatusRequest, $0.SetStatusResponse>( + '/partroom.PartRoomService/SetStatus', + ($0.SetStatusRequest value) => value.writeToBuffer(), + $0.SetStatusResponse.fromBuffer); + static final _$sendSignal = + $grpc.ClientMethod<$0.SendSignalRequest, $0.SendSignalResponse>( + '/partroom.PartRoomService/SendSignal', + ($0.SendSignalRequest value) => value.writeToBuffer(), + $0.SendSignalResponse.fromBuffer); + static final _$listenRoomEvents = + $grpc.ClientMethod<$0.ListenRoomEventsRequest, $0.RoomEvent>( + '/partroom.PartRoomService/ListenRoomEvents', + ($0.ListenRoomEventsRequest value) => value.writeToBuffer(), + $0.RoomEvent.fromBuffer); + static final _$transferOwnership = $grpc.ClientMethod< + $0.TransferOwnershipRequest, $0.TransferOwnershipResponse>( + '/partroom.PartRoomService/TransferOwnership', + ($0.TransferOwnershipRequest value) => value.writeToBuffer(), + $0.TransferOwnershipResponse.fromBuffer); + static final _$getKickedMembers = $grpc.ClientMethod< + $0.GetKickedMembersRequest, $0.GetKickedMembersResponse>( + '/partroom.PartRoomService/GetKickedMembers', + ($0.GetKickedMembersRequest value) => value.writeToBuffer(), + $0.GetKickedMembersResponse.fromBuffer); + static final _$removeKickedMember = $grpc.ClientMethod< + $0.RemoveKickedMemberRequest, $0.RemoveKickedMemberResponse>( + '/partroom.PartRoomService/RemoveKickedMember', + ($0.RemoveKickedMemberRequest value) => value.writeToBuffer(), + $0.RemoveKickedMemberResponse.fromBuffer); +} + +@$pb.GrpcServiceName('partroom.PartRoomService') +abstract class PartRoomServiceBase extends $grpc.Service { + $core.String get $name => 'partroom.PartRoomService'; + + PartRoomServiceBase() { + $addMethod( + $grpc.ServiceMethod<$0.GetRoomListRequest, $0.GetRoomListResponse>( + 'GetRoomList', + getRoomList_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.GetRoomListRequest.fromBuffer(value), + ($0.GetRoomListResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.CreateRoomRequest, $0.CreateRoomResponse>( + 'CreateRoom', + createRoom_Pre, + false, + false, + ($core.List<$core.int> value) => $0.CreateRoomRequest.fromBuffer(value), + ($0.CreateRoomResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.JoinRoomRequest, $0.JoinRoomResponse>( + 'JoinRoom', + joinRoom_Pre, + false, + false, + ($core.List<$core.int> value) => $0.JoinRoomRequest.fromBuffer(value), + ($0.JoinRoomResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.LeaveRoomRequest, $0.LeaveRoomResponse>( + 'LeaveRoom', + leaveRoom_Pre, + false, + false, + ($core.List<$core.int> value) => $0.LeaveRoomRequest.fromBuffer(value), + ($0.LeaveRoomResponse value) => value.writeToBuffer())); + $addMethod( + $grpc.ServiceMethod<$0.DismissRoomRequest, $0.DismissRoomResponse>( + 'DismissRoom', + dismissRoom_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.DismissRoomRequest.fromBuffer(value), + ($0.DismissRoomResponse value) => value.writeToBuffer())); + $addMethod( + $grpc.ServiceMethod<$0.GetRoomInfoRequest, $0.GetRoomInfoResponse>( + 'GetRoomInfo', + getRoomInfo_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.GetRoomInfoRequest.fromBuffer(value), + ($0.GetRoomInfoResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.GetRoomMembersRequest, + $0.GetRoomMembersResponse>( + 'GetRoomMembers', + getRoomMembers_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.GetRoomMembersRequest.fromBuffer(value), + ($0.GetRoomMembersResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.HeartbeatRequest, $0.HeartbeatResponse>( + 'Heartbeat', + heartbeat_Pre, + false, + false, + ($core.List<$core.int> value) => $0.HeartbeatRequest.fromBuffer(value), + ($0.HeartbeatResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.GetMyRoomRequest, $0.GetMyRoomResponse>( + 'GetMyRoom', + getMyRoom_Pre, + false, + false, + ($core.List<$core.int> value) => $0.GetMyRoomRequest.fromBuffer(value), + ($0.GetMyRoomResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.UpdateRoomRequest, $0.UpdateRoomResponse>( + 'UpdateRoom', + updateRoom_Pre, + false, + false, + ($core.List<$core.int> value) => $0.UpdateRoomRequest.fromBuffer(value), + ($0.UpdateRoomResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.KickMemberRequest, $0.KickMemberResponse>( + 'KickMember', + kickMember_Pre, + false, + false, + ($core.List<$core.int> value) => $0.KickMemberRequest.fromBuffer(value), + ($0.KickMemberResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.SetStatusRequest, $0.SetStatusResponse>( + 'SetStatus', + setStatus_Pre, + false, + false, + ($core.List<$core.int> value) => $0.SetStatusRequest.fromBuffer(value), + ($0.SetStatusResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.SendSignalRequest, $0.SendSignalResponse>( + 'SendSignal', + sendSignal_Pre, + false, + false, + ($core.List<$core.int> value) => $0.SendSignalRequest.fromBuffer(value), + ($0.SendSignalResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.ListenRoomEventsRequest, $0.RoomEvent>( + 'ListenRoomEvents', + listenRoomEvents_Pre, + false, + true, + ($core.List<$core.int> value) => + $0.ListenRoomEventsRequest.fromBuffer(value), + ($0.RoomEvent value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.TransferOwnershipRequest, + $0.TransferOwnershipResponse>( + 'TransferOwnership', + transferOwnership_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.TransferOwnershipRequest.fromBuffer(value), + ($0.TransferOwnershipResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.GetKickedMembersRequest, + $0.GetKickedMembersResponse>( + 'GetKickedMembers', + getKickedMembers_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.GetKickedMembersRequest.fromBuffer(value), + ($0.GetKickedMembersResponse value) => value.writeToBuffer())); + $addMethod($grpc.ServiceMethod<$0.RemoveKickedMemberRequest, + $0.RemoveKickedMemberResponse>( + 'RemoveKickedMember', + removeKickedMember_Pre, + false, + false, + ($core.List<$core.int> value) => + $0.RemoveKickedMemberRequest.fromBuffer(value), + ($0.RemoveKickedMemberResponse value) => value.writeToBuffer())); + } + + $async.Future<$0.GetRoomListResponse> getRoomList_Pre($grpc.ServiceCall $call, + $async.Future<$0.GetRoomListRequest> $request) async { + return getRoomList($call, await $request); + } + + $async.Future<$0.GetRoomListResponse> getRoomList( + $grpc.ServiceCall call, $0.GetRoomListRequest request); + + $async.Future<$0.CreateRoomResponse> createRoom_Pre($grpc.ServiceCall $call, + $async.Future<$0.CreateRoomRequest> $request) async { + return createRoom($call, await $request); + } + + $async.Future<$0.CreateRoomResponse> createRoom( + $grpc.ServiceCall call, $0.CreateRoomRequest request); + + $async.Future<$0.JoinRoomResponse> joinRoom_Pre($grpc.ServiceCall $call, + $async.Future<$0.JoinRoomRequest> $request) async { + return joinRoom($call, await $request); + } + + $async.Future<$0.JoinRoomResponse> joinRoom( + $grpc.ServiceCall call, $0.JoinRoomRequest request); + + $async.Future<$0.LeaveRoomResponse> leaveRoom_Pre($grpc.ServiceCall $call, + $async.Future<$0.LeaveRoomRequest> $request) async { + return leaveRoom($call, await $request); + } + + $async.Future<$0.LeaveRoomResponse> leaveRoom( + $grpc.ServiceCall call, $0.LeaveRoomRequest request); + + $async.Future<$0.DismissRoomResponse> dismissRoom_Pre($grpc.ServiceCall $call, + $async.Future<$0.DismissRoomRequest> $request) async { + return dismissRoom($call, await $request); + } + + $async.Future<$0.DismissRoomResponse> dismissRoom( + $grpc.ServiceCall call, $0.DismissRoomRequest request); + + $async.Future<$0.GetRoomInfoResponse> getRoomInfo_Pre($grpc.ServiceCall $call, + $async.Future<$0.GetRoomInfoRequest> $request) async { + return getRoomInfo($call, await $request); + } + + $async.Future<$0.GetRoomInfoResponse> getRoomInfo( + $grpc.ServiceCall call, $0.GetRoomInfoRequest request); + + $async.Future<$0.GetRoomMembersResponse> getRoomMembers_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.GetRoomMembersRequest> $request) async { + return getRoomMembers($call, await $request); + } + + $async.Future<$0.GetRoomMembersResponse> getRoomMembers( + $grpc.ServiceCall call, $0.GetRoomMembersRequest request); + + $async.Future<$0.HeartbeatResponse> heartbeat_Pre($grpc.ServiceCall $call, + $async.Future<$0.HeartbeatRequest> $request) async { + return heartbeat($call, await $request); + } + + $async.Future<$0.HeartbeatResponse> heartbeat( + $grpc.ServiceCall call, $0.HeartbeatRequest request); + + $async.Future<$0.GetMyRoomResponse> getMyRoom_Pre($grpc.ServiceCall $call, + $async.Future<$0.GetMyRoomRequest> $request) async { + return getMyRoom($call, await $request); + } + + $async.Future<$0.GetMyRoomResponse> getMyRoom( + $grpc.ServiceCall call, $0.GetMyRoomRequest request); + + $async.Future<$0.UpdateRoomResponse> updateRoom_Pre($grpc.ServiceCall $call, + $async.Future<$0.UpdateRoomRequest> $request) async { + return updateRoom($call, await $request); + } + + $async.Future<$0.UpdateRoomResponse> updateRoom( + $grpc.ServiceCall call, $0.UpdateRoomRequest request); + + $async.Future<$0.KickMemberResponse> kickMember_Pre($grpc.ServiceCall $call, + $async.Future<$0.KickMemberRequest> $request) async { + return kickMember($call, await $request); + } + + $async.Future<$0.KickMemberResponse> kickMember( + $grpc.ServiceCall call, $0.KickMemberRequest request); + + $async.Future<$0.SetStatusResponse> setStatus_Pre($grpc.ServiceCall $call, + $async.Future<$0.SetStatusRequest> $request) async { + return setStatus($call, await $request); + } + + $async.Future<$0.SetStatusResponse> setStatus( + $grpc.ServiceCall call, $0.SetStatusRequest request); + + $async.Future<$0.SendSignalResponse> sendSignal_Pre($grpc.ServiceCall $call, + $async.Future<$0.SendSignalRequest> $request) async { + return sendSignal($call, await $request); + } + + $async.Future<$0.SendSignalResponse> sendSignal( + $grpc.ServiceCall call, $0.SendSignalRequest request); + + $async.Stream<$0.RoomEvent> listenRoomEvents_Pre($grpc.ServiceCall $call, + $async.Future<$0.ListenRoomEventsRequest> $request) async* { + yield* listenRoomEvents($call, await $request); + } + + $async.Stream<$0.RoomEvent> listenRoomEvents( + $grpc.ServiceCall call, $0.ListenRoomEventsRequest request); + + $async.Future<$0.TransferOwnershipResponse> transferOwnership_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.TransferOwnershipRequest> $request) async { + return transferOwnership($call, await $request); + } + + $async.Future<$0.TransferOwnershipResponse> transferOwnership( + $grpc.ServiceCall call, $0.TransferOwnershipRequest request); + + $async.Future<$0.GetKickedMembersResponse> getKickedMembers_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.GetKickedMembersRequest> $request) async { + return getKickedMembers($call, await $request); + } + + $async.Future<$0.GetKickedMembersResponse> getKickedMembers( + $grpc.ServiceCall call, $0.GetKickedMembersRequest request); + + $async.Future<$0.RemoveKickedMemberResponse> removeKickedMember_Pre( + $grpc.ServiceCall $call, + $async.Future<$0.RemoveKickedMemberRequest> $request) async { + return removeKickedMember($call, await $request); + } + + $async.Future<$0.RemoveKickedMemberResponse> removeKickedMember( + $grpc.ServiceCall call, $0.RemoveKickedMemberRequest request); +} diff --git a/lib/generated/proto/partroom/partroom.pbjson.dart b/lib/generated/proto/partroom/partroom.pbjson.dart new file mode 100644 index 0000000..acf8c45 --- /dev/null +++ b/lib/generated/proto/partroom/partroom.pbjson.dart @@ -0,0 +1,759 @@ +// This is a generated file - do not edit. +// +// Generated from proto/partroom/partroom.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use roomEventTypeDescriptor instead') +const RoomEventType$json = { + '1': 'RoomEventType', + '2': [ + {'1': 'MEMBER_JOINED', '2': 0}, + {'1': 'MEMBER_LEFT', '2': 1}, + {'1': 'OWNER_CHANGED', '2': 2}, + {'1': 'ROOM_UPDATED', '2': 3}, + {'1': 'MEMBER_STATUS_UPDATED', '2': 4}, + {'1': 'SIGNAL_BROADCAST', '2': 5}, + {'1': 'ROOM_DISMISSED', '2': 6}, + {'1': 'MEMBER_KICKED', '2': 7}, + ], +}; + +/// Descriptor for `RoomEventType`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List roomEventTypeDescriptor = $convert.base64Decode( + 'Cg1Sb29tRXZlbnRUeXBlEhEKDU1FTUJFUl9KT0lORUQQABIPCgtNRU1CRVJfTEVGVBABEhEKDU' + '9XTkVSX0NIQU5HRUQQAhIQCgxST09NX1VQREFURUQQAxIZChVNRU1CRVJfU1RBVFVTX1VQREFU' + 'RUQQBBIUChBTSUdOQUxfQlJPQURDQVNUEAUSEgoOUk9PTV9ESVNNSVNTRUQQBhIRCg1NRU1CRV' + 'JfS0lDS0VEEAc='); + +@$core.Deprecated('Use roomListItemDescriptor instead') +const RoomListItem$json = { + '1': 'RoomListItem', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + {'1': 'owner_game_id', '3': 2, '4': 1, '5': 9, '10': 'ownerGameId'}, + {'1': 'owner_handle_name', '3': 3, '4': 1, '5': 9, '10': 'ownerHandleName'}, + {'1': 'owner_avatar', '3': 4, '4': 1, '5': 9, '10': 'ownerAvatar'}, + {'1': 'main_tag_id', '3': 5, '4': 1, '5': 9, '10': 'mainTagId'}, + {'1': 'sub_tag_id', '3': 6, '4': 1, '5': 9, '10': 'subTagId'}, + {'1': 'created_at', '3': 7, '4': 1, '5': 3, '10': 'createdAt'}, + {'1': 'owner_last_active', '3': 8, '4': 1, '5': 3, '10': 'ownerLastActive'}, + {'1': 'current_members', '3': 9, '4': 1, '5': 5, '10': 'currentMembers'}, + {'1': 'target_members', '3': 10, '4': 1, '5': 5, '10': 'targetMembers'}, + {'1': 'has_password', '3': 11, '4': 1, '5': 8, '10': 'hasPassword'}, + {'1': 'social_links', '3': 12, '4': 3, '5': 9, '10': 'socialLinks'}, + ], +}; + +/// Descriptor for `RoomListItem`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List roomListItemDescriptor = $convert.base64Decode( + 'CgxSb29tTGlzdEl0ZW0SGwoJcm9vbV91dWlkGAEgASgJUghyb29tVXVpZBIiCg1vd25lcl9nYW' + '1lX2lkGAIgASgJUgtvd25lckdhbWVJZBIqChFvd25lcl9oYW5kbGVfbmFtZRgDIAEoCVIPb3du' + 'ZXJIYW5kbGVOYW1lEiEKDG93bmVyX2F2YXRhchgEIAEoCVILb3duZXJBdmF0YXISHgoLbWFpbl' + '90YWdfaWQYBSABKAlSCW1haW5UYWdJZBIcCgpzdWJfdGFnX2lkGAYgASgJUghzdWJUYWdJZBId' + 'CgpjcmVhdGVkX2F0GAcgASgDUgljcmVhdGVkQXQSKgoRb3duZXJfbGFzdF9hY3RpdmUYCCABKA' + 'NSD293bmVyTGFzdEFjdGl2ZRInCg9jdXJyZW50X21lbWJlcnMYCSABKAVSDmN1cnJlbnRNZW1i' + 'ZXJzEiUKDnRhcmdldF9tZW1iZXJzGAogASgFUg10YXJnZXRNZW1iZXJzEiEKDGhhc19wYXNzd2' + '9yZBgLIAEoCFILaGFzUGFzc3dvcmQSIQoMc29jaWFsX2xpbmtzGAwgAygJUgtzb2NpYWxMaW5r' + 'cw=='); + +@$core.Deprecated('Use getRoomListRequestDescriptor instead') +const GetRoomListRequest$json = { + '1': 'GetRoomListRequest', + '2': [ + {'1': 'main_tag_id', '3': 1, '4': 1, '5': 9, '10': 'mainTagId'}, + {'1': 'sub_tag_id', '3': 2, '4': 1, '5': 9, '10': 'subTagId'}, + {'1': 'search_owner_name', '3': 3, '4': 1, '5': 9, '10': 'searchOwnerName'}, + {'1': 'page', '3': 4, '4': 1, '5': 5, '10': 'page'}, + {'1': 'page_size', '3': 5, '4': 1, '5': 5, '10': 'pageSize'}, + ], +}; + +/// Descriptor for `GetRoomListRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getRoomListRequestDescriptor = $convert.base64Decode( + 'ChJHZXRSb29tTGlzdFJlcXVlc3QSHgoLbWFpbl90YWdfaWQYASABKAlSCW1haW5UYWdJZBIcCg' + 'pzdWJfdGFnX2lkGAIgASgJUghzdWJUYWdJZBIqChFzZWFyY2hfb3duZXJfbmFtZRgDIAEoCVIP' + 'c2VhcmNoT3duZXJOYW1lEhIKBHBhZ2UYBCABKAVSBHBhZ2USGwoJcGFnZV9zaXplGAUgASgFUg' + 'hwYWdlU2l6ZQ=='); + +@$core.Deprecated('Use getRoomListResponseDescriptor instead') +const GetRoomListResponse$json = { + '1': 'GetRoomListResponse', + '2': [ + { + '1': 'rooms', + '3': 1, + '4': 3, + '5': 11, + '6': '.partroom.RoomListItem', + '10': 'rooms' + }, + {'1': 'total', '3': 2, '4': 1, '5': 5, '10': 'total'}, + {'1': 'page', '3': 3, '4': 1, '5': 5, '10': 'page'}, + {'1': 'page_size', '3': 4, '4': 1, '5': 5, '10': 'pageSize'}, + ], +}; + +/// Descriptor for `GetRoomListResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getRoomListResponseDescriptor = $convert.base64Decode( + 'ChNHZXRSb29tTGlzdFJlc3BvbnNlEiwKBXJvb21zGAEgAygLMhYucGFydHJvb20uUm9vbUxpc3' + 'RJdGVtUgVyb29tcxIUCgV0b3RhbBgCIAEoBVIFdG90YWwSEgoEcGFnZRgDIAEoBVIEcGFnZRIb' + 'CglwYWdlX3NpemUYBCABKAVSCHBhZ2VTaXpl'); + +@$core.Deprecated('Use createRoomRequestDescriptor instead') +const CreateRoomRequest$json = { + '1': 'CreateRoomRequest', + '2': [ + {'1': 'main_tag_id', '3': 1, '4': 1, '5': 9, '10': 'mainTagId'}, + {'1': 'sub_tag_id', '3': 2, '4': 1, '5': 9, '10': 'subTagId'}, + {'1': 'target_members', '3': 3, '4': 1, '5': 5, '10': 'targetMembers'}, + {'1': 'has_password', '3': 4, '4': 1, '5': 8, '10': 'hasPassword'}, + {'1': 'password', '3': 5, '4': 1, '5': 9, '10': 'password'}, + {'1': 'social_links', '3': 6, '4': 3, '5': 9, '10': 'socialLinks'}, + ], +}; + +/// Descriptor for `CreateRoomRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List createRoomRequestDescriptor = $convert.base64Decode( + 'ChFDcmVhdGVSb29tUmVxdWVzdBIeCgttYWluX3RhZ19pZBgBIAEoCVIJbWFpblRhZ0lkEhwKCn' + 'N1Yl90YWdfaWQYAiABKAlSCHN1YlRhZ0lkEiUKDnRhcmdldF9tZW1iZXJzGAMgASgFUg10YXJn' + 'ZXRNZW1iZXJzEiEKDGhhc19wYXNzd29yZBgEIAEoCFILaGFzUGFzc3dvcmQSGgoIcGFzc3dvcm' + 'QYBSABKAlSCHBhc3N3b3JkEiEKDHNvY2lhbF9saW5rcxgGIAMoCVILc29jaWFsTGlua3M='); + +@$core.Deprecated('Use createRoomResponseDescriptor instead') +const CreateRoomResponse$json = { + '1': 'CreateRoomResponse', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + ], +}; + +/// Descriptor for `CreateRoomResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List createRoomResponseDescriptor = + $convert.base64Decode( + 'ChJDcmVhdGVSb29tUmVzcG9uc2USGwoJcm9vbV91dWlkGAEgASgJUghyb29tVXVpZA=='); + +@$core.Deprecated('Use joinRoomRequestDescriptor instead') +const JoinRoomRequest$json = { + '1': 'JoinRoomRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + {'1': 'password', '3': 2, '4': 1, '5': 9, '10': 'password'}, + ], +}; + +/// Descriptor for `JoinRoomRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List joinRoomRequestDescriptor = $convert.base64Decode( + 'Cg9Kb2luUm9vbVJlcXVlc3QSGwoJcm9vbV91dWlkGAEgASgJUghyb29tVXVpZBIaCghwYXNzd2' + '9yZBgCIAEoCVIIcGFzc3dvcmQ='); + +@$core.Deprecated('Use joinRoomResponseDescriptor instead') +const JoinRoomResponse$json = { + '1': 'JoinRoomResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `JoinRoomResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List joinRoomResponseDescriptor = $convert.base64Decode( + 'ChBKb2luUm9vbVJlc3BvbnNlEhgKB3N1Y2Nlc3MYASABKAhSB3N1Y2Nlc3M='); + +@$core.Deprecated('Use leaveRoomRequestDescriptor instead') +const LeaveRoomRequest$json = { + '1': 'LeaveRoomRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + ], +}; + +/// Descriptor for `LeaveRoomRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List leaveRoomRequestDescriptor = $convert.base64Decode( + 'ChBMZWF2ZVJvb21SZXF1ZXN0EhsKCXJvb21fdXVpZBgBIAEoCVIIcm9vbVV1aWQ='); + +@$core.Deprecated('Use leaveRoomResponseDescriptor instead') +const LeaveRoomResponse$json = { + '1': 'LeaveRoomResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `LeaveRoomResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List leaveRoomResponseDescriptor = $convert.base64Decode( + 'ChFMZWF2ZVJvb21SZXNwb25zZRIYCgdzdWNjZXNzGAEgASgIUgdzdWNjZXNz'); + +@$core.Deprecated('Use dismissRoomRequestDescriptor instead') +const DismissRoomRequest$json = { + '1': 'DismissRoomRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + ], +}; + +/// Descriptor for `DismissRoomRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List dismissRoomRequestDescriptor = + $convert.base64Decode( + 'ChJEaXNtaXNzUm9vbVJlcXVlc3QSGwoJcm9vbV91dWlkGAEgASgJUghyb29tVXVpZA=='); + +@$core.Deprecated('Use dismissRoomResponseDescriptor instead') +const DismissRoomResponse$json = { + '1': 'DismissRoomResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `DismissRoomResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List dismissRoomResponseDescriptor = + $convert.base64Decode( + 'ChNEaXNtaXNzUm9vbVJlc3BvbnNlEhgKB3N1Y2Nlc3MYASABKAhSB3N1Y2Nlc3M='); + +@$core.Deprecated('Use memberStatusDescriptor instead') +const MemberStatus$json = { + '1': 'MemberStatus', + '2': [ + {'1': 'current_location', '3': 1, '4': 1, '5': 9, '10': 'currentLocation'}, + {'1': 'kills', '3': 2, '4': 1, '5': 5, '10': 'kills'}, + {'1': 'deaths', '3': 3, '4': 1, '5': 5, '10': 'deaths'}, + {'1': 'play_time', '3': 4, '4': 1, '5': 3, '10': 'playTime'}, + ], +}; + +/// Descriptor for `MemberStatus`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List memberStatusDescriptor = $convert.base64Decode( + 'CgxNZW1iZXJTdGF0dXMSKQoQY3VycmVudF9sb2NhdGlvbhgBIAEoCVIPY3VycmVudExvY2F0aW' + '9uEhQKBWtpbGxzGAIgASgFUgVraWxscxIWCgZkZWF0aHMYAyABKAVSBmRlYXRocxIbCglwbGF5' + 'X3RpbWUYBCABKANSCHBsYXlUaW1l'); + +@$core.Deprecated('Use roomMemberDescriptor instead') +const RoomMember$json = { + '1': 'RoomMember', + '2': [ + {'1': 'game_user_id', '3': 1, '4': 1, '5': 9, '10': 'gameUserId'}, + {'1': 'handle_name', '3': 2, '4': 1, '5': 9, '10': 'handleName'}, + {'1': 'avatar_url', '3': 3, '4': 1, '5': 9, '10': 'avatarUrl'}, + {'1': 'joined_at', '3': 4, '4': 1, '5': 3, '10': 'joinedAt'}, + {'1': 'last_active', '3': 5, '4': 1, '5': 3, '10': 'lastActive'}, + {'1': 'is_owner', '3': 6, '4': 1, '5': 8, '10': 'isOwner'}, + { + '1': 'status', + '3': 7, + '4': 1, + '5': 11, + '6': '.partroom.MemberStatus', + '10': 'status' + }, + ], +}; + +/// Descriptor for `RoomMember`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List roomMemberDescriptor = $convert.base64Decode( + 'CgpSb29tTWVtYmVyEiAKDGdhbWVfdXNlcl9pZBgBIAEoCVIKZ2FtZVVzZXJJZBIfCgtoYW5kbG' + 'VfbmFtZRgCIAEoCVIKaGFuZGxlTmFtZRIdCgphdmF0YXJfdXJsGAMgASgJUglhdmF0YXJVcmwS' + 'GwoJam9pbmVkX2F0GAQgASgDUghqb2luZWRBdBIfCgtsYXN0X2FjdGl2ZRgFIAEoA1IKbGFzdE' + 'FjdGl2ZRIZCghpc19vd25lchgGIAEoCFIHaXNPd25lchIuCgZzdGF0dXMYByABKAsyFi5wYXJ0' + 'cm9vbS5NZW1iZXJTdGF0dXNSBnN0YXR1cw=='); + +@$core.Deprecated('Use roomInfoDescriptor instead') +const RoomInfo$json = { + '1': 'RoomInfo', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + {'1': 'owner_game_id', '3': 2, '4': 1, '5': 9, '10': 'ownerGameId'}, + {'1': 'main_tag_id', '3': 3, '4': 1, '5': 9, '10': 'mainTagId'}, + {'1': 'sub_tag_id', '3': 4, '4': 1, '5': 9, '10': 'subTagId'}, + {'1': 'target_members', '3': 5, '4': 1, '5': 5, '10': 'targetMembers'}, + {'1': 'has_password', '3': 6, '4': 1, '5': 8, '10': 'hasPassword'}, + {'1': 'created_at', '3': 7, '4': 1, '5': 3, '10': 'createdAt'}, + {'1': 'current_members', '3': 8, '4': 1, '5': 5, '10': 'currentMembers'}, + {'1': 'social_links', '3': 9, '4': 3, '5': 9, '10': 'socialLinks'}, + ], +}; + +/// Descriptor for `RoomInfo`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List roomInfoDescriptor = $convert.base64Decode( + 'CghSb29tSW5mbxIbCglyb29tX3V1aWQYASABKAlSCHJvb21VdWlkEiIKDW93bmVyX2dhbWVfaW' + 'QYAiABKAlSC293bmVyR2FtZUlkEh4KC21haW5fdGFnX2lkGAMgASgJUgltYWluVGFnSWQSHAoK' + 'c3ViX3RhZ19pZBgEIAEoCVIIc3ViVGFnSWQSJQoOdGFyZ2V0X21lbWJlcnMYBSABKAVSDXRhcm' + 'dldE1lbWJlcnMSIQoMaGFzX3Bhc3N3b3JkGAYgASgIUgtoYXNQYXNzd29yZBIdCgpjcmVhdGVk' + 'X2F0GAcgASgDUgljcmVhdGVkQXQSJwoPY3VycmVudF9tZW1iZXJzGAggASgFUg5jdXJyZW50TW' + 'VtYmVycxIhCgxzb2NpYWxfbGlua3MYCSADKAlSC3NvY2lhbExpbmtz'); + +@$core.Deprecated('Use getRoomInfoRequestDescriptor instead') +const GetRoomInfoRequest$json = { + '1': 'GetRoomInfoRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + ], +}; + +/// Descriptor for `GetRoomInfoRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getRoomInfoRequestDescriptor = + $convert.base64Decode( + 'ChJHZXRSb29tSW5mb1JlcXVlc3QSGwoJcm9vbV91dWlkGAEgASgJUghyb29tVXVpZA=='); + +@$core.Deprecated('Use getRoomInfoResponseDescriptor instead') +const GetRoomInfoResponse$json = { + '1': 'GetRoomInfoResponse', + '2': [ + { + '1': 'room', + '3': 1, + '4': 1, + '5': 11, + '6': '.partroom.RoomInfo', + '10': 'room' + }, + ], +}; + +/// Descriptor for `GetRoomInfoResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getRoomInfoResponseDescriptor = $convert.base64Decode( + 'ChNHZXRSb29tSW5mb1Jlc3BvbnNlEiYKBHJvb20YASABKAsyEi5wYXJ0cm9vbS5Sb29tSW5mb1' + 'IEcm9vbQ=='); + +@$core.Deprecated('Use getRoomMembersRequestDescriptor instead') +const GetRoomMembersRequest$json = { + '1': 'GetRoomMembersRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + {'1': 'page', '3': 2, '4': 1, '5': 5, '10': 'page'}, + {'1': 'page_size', '3': 3, '4': 1, '5': 5, '10': 'pageSize'}, + ], +}; + +/// Descriptor for `GetRoomMembersRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getRoomMembersRequestDescriptor = $convert.base64Decode( + 'ChVHZXRSb29tTWVtYmVyc1JlcXVlc3QSGwoJcm9vbV91dWlkGAEgASgJUghyb29tVXVpZBISCg' + 'RwYWdlGAIgASgFUgRwYWdlEhsKCXBhZ2Vfc2l6ZRgDIAEoBVIIcGFnZVNpemU='); + +@$core.Deprecated('Use getRoomMembersResponseDescriptor instead') +const GetRoomMembersResponse$json = { + '1': 'GetRoomMembersResponse', + '2': [ + { + '1': 'members', + '3': 1, + '4': 3, + '5': 11, + '6': '.partroom.RoomMember', + '10': 'members' + }, + {'1': 'total', '3': 2, '4': 1, '5': 5, '10': 'total'}, + ], +}; + +/// Descriptor for `GetRoomMembersResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getRoomMembersResponseDescriptor = + $convert.base64Decode( + 'ChZHZXRSb29tTWVtYmVyc1Jlc3BvbnNlEi4KB21lbWJlcnMYASADKAsyFC5wYXJ0cm9vbS5Sb2' + '9tTWVtYmVyUgdtZW1iZXJzEhQKBXRvdGFsGAIgASgFUgV0b3RhbA=='); + +@$core.Deprecated('Use heartbeatRequestDescriptor instead') +const HeartbeatRequest$json = { + '1': 'HeartbeatRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + ], +}; + +/// Descriptor for `HeartbeatRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List heartbeatRequestDescriptor = $convert.base64Decode( + 'ChBIZWFydGJlYXRSZXF1ZXN0EhsKCXJvb21fdXVpZBgBIAEoCVIIcm9vbVV1aWQ='); + +@$core.Deprecated('Use heartbeatResponseDescriptor instead') +const HeartbeatResponse$json = { + '1': 'HeartbeatResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `HeartbeatResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List heartbeatResponseDescriptor = $convert.base64Decode( + 'ChFIZWFydGJlYXRSZXNwb25zZRIYCgdzdWNjZXNzGAEgASgIUgdzdWNjZXNz'); + +@$core.Deprecated('Use getMyRoomRequestDescriptor instead') +const GetMyRoomRequest$json = { + '1': 'GetMyRoomRequest', +}; + +/// Descriptor for `GetMyRoomRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getMyRoomRequestDescriptor = + $convert.base64Decode('ChBHZXRNeVJvb21SZXF1ZXN0'); + +@$core.Deprecated('Use getMyRoomResponseDescriptor instead') +const GetMyRoomResponse$json = { + '1': 'GetMyRoomResponse', + '2': [ + { + '1': 'room', + '3': 1, + '4': 1, + '5': 11, + '6': '.partroom.RoomInfo', + '10': 'room' + }, + {'1': 'has_room', '3': 2, '4': 1, '5': 8, '10': 'hasRoom'}, + ], +}; + +/// Descriptor for `GetMyRoomResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getMyRoomResponseDescriptor = $convert.base64Decode( + 'ChFHZXRNeVJvb21SZXNwb25zZRImCgRyb29tGAEgASgLMhIucGFydHJvb20uUm9vbUluZm9SBH' + 'Jvb20SGQoIaGFzX3Jvb20YAiABKAhSB2hhc1Jvb20='); + +@$core.Deprecated('Use updateRoomRequestDescriptor instead') +const UpdateRoomRequest$json = { + '1': 'UpdateRoomRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + {'1': 'target_members', '3': 2, '4': 1, '5': 5, '10': 'targetMembers'}, + {'1': 'password', '3': 3, '4': 1, '5': 9, '10': 'password'}, + {'1': 'main_tag_id', '3': 4, '4': 1, '5': 9, '10': 'mainTagId'}, + {'1': 'sub_tag_id', '3': 5, '4': 1, '5': 9, '10': 'subTagId'}, + {'1': 'social_links', '3': 6, '4': 3, '5': 9, '10': 'socialLinks'}, + ], +}; + +/// Descriptor for `UpdateRoomRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List updateRoomRequestDescriptor = $convert.base64Decode( + 'ChFVcGRhdGVSb29tUmVxdWVzdBIbCglyb29tX3V1aWQYASABKAlSCHJvb21VdWlkEiUKDnRhcm' + 'dldF9tZW1iZXJzGAIgASgFUg10YXJnZXRNZW1iZXJzEhoKCHBhc3N3b3JkGAMgASgJUghwYXNz' + 'd29yZBIeCgttYWluX3RhZ19pZBgEIAEoCVIJbWFpblRhZ0lkEhwKCnN1Yl90YWdfaWQYBSABKA' + 'lSCHN1YlRhZ0lkEiEKDHNvY2lhbF9saW5rcxgGIAMoCVILc29jaWFsTGlua3M='); + +@$core.Deprecated('Use updateRoomResponseDescriptor instead') +const UpdateRoomResponse$json = { + '1': 'UpdateRoomResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `UpdateRoomResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List updateRoomResponseDescriptor = + $convert.base64Decode( + 'ChJVcGRhdGVSb29tUmVzcG9uc2USGAoHc3VjY2VzcxgBIAEoCFIHc3VjY2Vzcw=='); + +@$core.Deprecated('Use kickMemberRequestDescriptor instead') +const KickMemberRequest$json = { + '1': 'KickMemberRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + { + '1': 'target_game_user_id', + '3': 2, + '4': 1, + '5': 9, + '10': 'targetGameUserId' + }, + ], +}; + +/// Descriptor for `KickMemberRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List kickMemberRequestDescriptor = $convert.base64Decode( + 'ChFLaWNrTWVtYmVyUmVxdWVzdBIbCglyb29tX3V1aWQYASABKAlSCHJvb21VdWlkEi0KE3Rhcm' + 'dldF9nYW1lX3VzZXJfaWQYAiABKAlSEHRhcmdldEdhbWVVc2VySWQ='); + +@$core.Deprecated('Use kickMemberResponseDescriptor instead') +const KickMemberResponse$json = { + '1': 'KickMemberResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `KickMemberResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List kickMemberResponseDescriptor = + $convert.base64Decode( + 'ChJLaWNrTWVtYmVyUmVzcG9uc2USGAoHc3VjY2VzcxgBIAEoCFIHc3VjY2Vzcw=='); + +@$core.Deprecated('Use setStatusRequestDescriptor instead') +const SetStatusRequest$json = { + '1': 'SetStatusRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + { + '1': 'status', + '3': 2, + '4': 1, + '5': 11, + '6': '.partroom.MemberStatus', + '10': 'status' + }, + ], +}; + +/// Descriptor for `SetStatusRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List setStatusRequestDescriptor = $convert.base64Decode( + 'ChBTZXRTdGF0dXNSZXF1ZXN0EhsKCXJvb21fdXVpZBgBIAEoCVIIcm9vbVV1aWQSLgoGc3RhdH' + 'VzGAIgASgLMhYucGFydHJvb20uTWVtYmVyU3RhdHVzUgZzdGF0dXM='); + +@$core.Deprecated('Use setStatusResponseDescriptor instead') +const SetStatusResponse$json = { + '1': 'SetStatusResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `SetStatusResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List setStatusResponseDescriptor = $convert.base64Decode( + 'ChFTZXRTdGF0dXNSZXNwb25zZRIYCgdzdWNjZXNzGAEgASgIUgdzdWNjZXNz'); + +@$core.Deprecated('Use sendSignalRequestDescriptor instead') +const SendSignalRequest$json = { + '1': 'SendSignalRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + {'1': 'signal_id', '3': 2, '4': 1, '5': 9, '10': 'signalId'}, + { + '1': 'params', + '3': 3, + '4': 3, + '5': 11, + '6': '.partroom.SendSignalRequest.ParamsEntry', + '10': 'params' + }, + ], + '3': [SendSignalRequest_ParamsEntry$json], +}; + +@$core.Deprecated('Use sendSignalRequestDescriptor instead') +const SendSignalRequest_ParamsEntry$json = { + '1': 'ParamsEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'}, + ], + '7': {'7': true}, +}; + +/// Descriptor for `SendSignalRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sendSignalRequestDescriptor = $convert.base64Decode( + 'ChFTZW5kU2lnbmFsUmVxdWVzdBIbCglyb29tX3V1aWQYASABKAlSCHJvb21VdWlkEhsKCXNpZ2' + '5hbF9pZBgCIAEoCVIIc2lnbmFsSWQSPwoGcGFyYW1zGAMgAygLMicucGFydHJvb20uU2VuZFNp' + 'Z25hbFJlcXVlc3QuUGFyYW1zRW50cnlSBnBhcmFtcxo5CgtQYXJhbXNFbnRyeRIQCgNrZXkYAS' + 'ABKAlSA2tleRIUCgV2YWx1ZRgCIAEoCVIFdmFsdWU6AjgB'); + +@$core.Deprecated('Use sendSignalResponseDescriptor instead') +const SendSignalResponse$json = { + '1': 'SendSignalResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `SendSignalResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List sendSignalResponseDescriptor = + $convert.base64Decode( + 'ChJTZW5kU2lnbmFsUmVzcG9uc2USGAoHc3VjY2VzcxgBIAEoCFIHc3VjY2Vzcw=='); + +@$core.Deprecated('Use roomEventDescriptor instead') +const RoomEvent$json = { + '1': 'RoomEvent', + '2': [ + { + '1': 'type', + '3': 1, + '4': 1, + '5': 14, + '6': '.partroom.RoomEventType', + '10': 'type' + }, + {'1': 'room_uuid', '3': 2, '4': 1, '5': 9, '10': 'roomUuid'}, + {'1': 'timestamp', '3': 3, '4': 1, '5': 3, '10': 'timestamp'}, + { + '1': 'member', + '3': 4, + '4': 1, + '5': 11, + '6': '.partroom.RoomMember', + '10': 'member' + }, + {'1': 'signal_id', '3': 5, '4': 1, '5': 9, '10': 'signalId'}, + {'1': 'signal_sender', '3': 6, '4': 1, '5': 9, '10': 'signalSender'}, + { + '1': 'signal_params', + '3': 7, + '4': 3, + '5': 11, + '6': '.partroom.RoomEvent.SignalParamsEntry', + '10': 'signalParams' + }, + { + '1': 'room_info', + '3': 8, + '4': 1, + '5': 11, + '6': '.partroom.RoomInfo', + '10': 'roomInfo' + }, + ], + '3': [RoomEvent_SignalParamsEntry$json], +}; + +@$core.Deprecated('Use roomEventDescriptor instead') +const RoomEvent_SignalParamsEntry$json = { + '1': 'SignalParamsEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'}, + {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'}, + ], + '7': {'7': true}, +}; + +/// Descriptor for `RoomEvent`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List roomEventDescriptor = $convert.base64Decode( + 'CglSb29tRXZlbnQSKwoEdHlwZRgBIAEoDjIXLnBhcnRyb29tLlJvb21FdmVudFR5cGVSBHR5cG' + 'USGwoJcm9vbV91dWlkGAIgASgJUghyb29tVXVpZBIcCgl0aW1lc3RhbXAYAyABKANSCXRpbWVz' + 'dGFtcBIsCgZtZW1iZXIYBCABKAsyFC5wYXJ0cm9vbS5Sb29tTWVtYmVyUgZtZW1iZXISGwoJc2' + 'lnbmFsX2lkGAUgASgJUghzaWduYWxJZBIjCg1zaWduYWxfc2VuZGVyGAYgASgJUgxzaWduYWxT' + 'ZW5kZXISSgoNc2lnbmFsX3BhcmFtcxgHIAMoCzIlLnBhcnRyb29tLlJvb21FdmVudC5TaWduYW' + 'xQYXJhbXNFbnRyeVIMc2lnbmFsUGFyYW1zEi8KCXJvb21faW5mbxgIIAEoCzISLnBhcnRyb29t' + 'LlJvb21JbmZvUghyb29tSW5mbxo/ChFTaWduYWxQYXJhbXNFbnRyeRIQCgNrZXkYASABKAlSA2' + 'tleRIUCgV2YWx1ZRgCIAEoCVIFdmFsdWU6AjgB'); + +@$core.Deprecated('Use listenRoomEventsRequestDescriptor instead') +const ListenRoomEventsRequest$json = { + '1': 'ListenRoomEventsRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + ], +}; + +/// Descriptor for `ListenRoomEventsRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List listenRoomEventsRequestDescriptor = + $convert.base64Decode( + 'ChdMaXN0ZW5Sb29tRXZlbnRzUmVxdWVzdBIbCglyb29tX3V1aWQYASABKAlSCHJvb21VdWlk'); + +@$core.Deprecated('Use transferOwnershipRequestDescriptor instead') +const TransferOwnershipRequest$json = { + '1': 'TransferOwnershipRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + { + '1': 'target_game_user_id', + '3': 2, + '4': 1, + '5': 9, + '10': 'targetGameUserId' + }, + ], +}; + +/// Descriptor for `TransferOwnershipRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List transferOwnershipRequestDescriptor = + $convert.base64Decode( + 'ChhUcmFuc2Zlck93bmVyc2hpcFJlcXVlc3QSGwoJcm9vbV91dWlkGAEgASgJUghyb29tVXVpZB' + 'ItChN0YXJnZXRfZ2FtZV91c2VyX2lkGAIgASgJUhB0YXJnZXRHYW1lVXNlcklk'); + +@$core.Deprecated('Use transferOwnershipResponseDescriptor instead') +const TransferOwnershipResponse$json = { + '1': 'TransferOwnershipResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `TransferOwnershipResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List transferOwnershipResponseDescriptor = + $convert.base64Decode( + 'ChlUcmFuc2Zlck93bmVyc2hpcFJlc3BvbnNlEhgKB3N1Y2Nlc3MYASABKAhSB3N1Y2Nlc3M='); + +@$core.Deprecated('Use kickedMemberDescriptor instead') +const KickedMember$json = { + '1': 'KickedMember', + '2': [ + {'1': 'game_user_id', '3': 1, '4': 1, '5': 9, '10': 'gameUserId'}, + {'1': 'handle_name', '3': 2, '4': 1, '5': 9, '10': 'handleName'}, + {'1': 'avatar_url', '3': 3, '4': 1, '5': 9, '10': 'avatarUrl'}, + {'1': 'joined_at', '3': 4, '4': 1, '5': 3, '10': 'joinedAt'}, + {'1': 'kicked_at', '3': 5, '4': 1, '5': 3, '10': 'kickedAt'}, + ], +}; + +/// Descriptor for `KickedMember`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List kickedMemberDescriptor = $convert.base64Decode( + 'CgxLaWNrZWRNZW1iZXISIAoMZ2FtZV91c2VyX2lkGAEgASgJUgpnYW1lVXNlcklkEh8KC2hhbm' + 'RsZV9uYW1lGAIgASgJUgpoYW5kbGVOYW1lEh0KCmF2YXRhcl91cmwYAyABKAlSCWF2YXRhclVy' + 'bBIbCglqb2luZWRfYXQYBCABKANSCGpvaW5lZEF0EhsKCWtpY2tlZF9hdBgFIAEoA1IIa2lja2' + 'VkQXQ='); + +@$core.Deprecated('Use getKickedMembersRequestDescriptor instead') +const GetKickedMembersRequest$json = { + '1': 'GetKickedMembersRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + {'1': 'page', '3': 2, '4': 1, '5': 5, '10': 'page'}, + {'1': 'page_size', '3': 3, '4': 1, '5': 5, '10': 'pageSize'}, + ], +}; + +/// Descriptor for `GetKickedMembersRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getKickedMembersRequestDescriptor = + $convert.base64Decode( + 'ChdHZXRLaWNrZWRNZW1iZXJzUmVxdWVzdBIbCglyb29tX3V1aWQYASABKAlSCHJvb21VdWlkEh' + 'IKBHBhZ2UYAiABKAVSBHBhZ2USGwoJcGFnZV9zaXplGAMgASgFUghwYWdlU2l6ZQ=='); + +@$core.Deprecated('Use getKickedMembersResponseDescriptor instead') +const GetKickedMembersResponse$json = { + '1': 'GetKickedMembersResponse', + '2': [ + { + '1': 'members', + '3': 1, + '4': 3, + '5': 11, + '6': '.partroom.KickedMember', + '10': 'members' + }, + {'1': 'total', '3': 2, '4': 1, '5': 5, '10': 'total'}, + ], +}; + +/// Descriptor for `GetKickedMembersResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List getKickedMembersResponseDescriptor = + $convert.base64Decode( + 'ChhHZXRLaWNrZWRNZW1iZXJzUmVzcG9uc2USMAoHbWVtYmVycxgBIAMoCzIWLnBhcnRyb29tLk' + 'tpY2tlZE1lbWJlclIHbWVtYmVycxIUCgV0b3RhbBgCIAEoBVIFdG90YWw='); + +@$core.Deprecated('Use removeKickedMemberRequestDescriptor instead') +const RemoveKickedMemberRequest$json = { + '1': 'RemoveKickedMemberRequest', + '2': [ + {'1': 'room_uuid', '3': 1, '4': 1, '5': 9, '10': 'roomUuid'}, + {'1': 'game_user_id', '3': 2, '4': 1, '5': 9, '10': 'gameUserId'}, + ], +}; + +/// Descriptor for `RemoveKickedMemberRequest`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List removeKickedMemberRequestDescriptor = + $convert.base64Decode( + 'ChlSZW1vdmVLaWNrZWRNZW1iZXJSZXF1ZXN0EhsKCXJvb21fdXVpZBgBIAEoCVIIcm9vbVV1aW' + 'QSIAoMZ2FtZV91c2VyX2lkGAIgASgJUgpnYW1lVXNlcklk'); + +@$core.Deprecated('Use removeKickedMemberResponseDescriptor instead') +const RemoveKickedMemberResponse$json = { + '1': 'RemoveKickedMemberResponse', + '2': [ + {'1': 'success', '3': 1, '4': 1, '5': 8, '10': 'success'}, + ], +}; + +/// Descriptor for `RemoveKickedMemberResponse`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List removeKickedMemberResponseDescriptor = + $convert.base64Decode( + 'ChpSZW1vdmVLaWNrZWRNZW1iZXJSZXNwb25zZRIYCgdzdWNjZXNzGAEgASgIUgdzdWNjZXNz'); diff --git a/lib/provider/party_room.dart b/lib/provider/party_room.dart new file mode 100644 index 0000000..f58c277 --- /dev/null +++ b/lib/provider/party_room.dart @@ -0,0 +1,889 @@ +import 'dart:async'; +import 'package:fixnum/fixnum.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:grpc/grpc.dart'; +import 'package:hive_ce/hive.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:starcitizen_doctor/common/conf/url_conf.dart'; +import 'package:starcitizen_doctor/common/utils/provider.dart'; +import 'package:starcitizen_doctor/common/utils/log.dart'; +import 'package:starcitizen_doctor/generated/proto/auth/auth.pbgrpc.dart' as auth; +import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pbgrpc.dart' as partroom; +import 'package:starcitizen_doctor/generated/proto/common/common.pbgrpc.dart' as common; + +part 'party_room.freezed.dart'; + +part 'party_room.g.dart'; + +/// PartyRoom 认证状态 +@freezed +sealed class PartyRoomAuthState with _$PartyRoomAuthState { + const factory PartyRoomAuthState({ + @Default('') String uuid, + @Default('') String secretKey, + @Default(false) bool isLoggedIn, + auth.GameUserInfo? userInfo, + DateTime? lastLoginTime, + }) = _PartyRoomAuthState; +} + +/// PartyRoom 房间状态 +@freezed +sealed class PartyRoomState with _$PartyRoomState { + const factory PartyRoomState({ + partroom.RoomInfo? currentRoom, + @Default([]) List members, + @Default([]) List tags, + @Default([]) List signalTypes, + @Default(false) bool isInRoom, + @Default(false) bool isOwner, + String? roomUuid, + @Default([]) List recentEvents, + }) = _PartyRoomState; +} + +/// PartyRoom gRPC 客户端状态 +@freezed +sealed class PartyRoomClientState with _$PartyRoomClientState { + const factory PartyRoomClientState({ + ClientChannel? channel, + auth.AuthServiceClient? authClient, + partroom.PartRoomServiceClient? roomClient, + common.CommonServiceClient? commonClient, + @Default(false) bool isConnected, + @Default('') String serverAddress, + @Default(0) int serverPort, + }) = _PartyRoomClientState; +} + +/// PartyRoom 完整状态 +@freezed +sealed class PartyRoomFullState with _$PartyRoomFullState { + const factory PartyRoomFullState({ + required PartyRoomAuthState auth, + required PartyRoomState room, + required PartyRoomClientState client, + }) = _PartyRoomFullState; +} + +/// PartyRoom Provider +@riverpod +class PartyRoom extends _$PartyRoom { + static const String _boxName = 'party_room_conf'; + static const String _secretKeyKey = 'party_room_secret_key'; + + Box? _confBox; + StreamSubscription? _eventStreamSubscription; + Timer? _heartbeatTimer; + bool _disposed = false; + + @override + PartyRoomFullState build() { + ref.onDispose(() { + _disposed = true; + _cleanup(); + }); + + state = const PartyRoomFullState( + auth: PartyRoomAuthState(), + room: PartyRoomState(), + client: PartyRoomClientState(), + ); + + // 初始化 + _initialize(); + + ref.keepAlive(); + return state; + } + + /// 初始化 + Future _initialize() async { + try { + _confBox = await Hive.openBox(_boxName); + + // 加载保存的认证信息 + final uuid = appGlobalState.deviceUUID; + if (uuid?.isEmpty ?? true) { + throw Exception('Device UUID is not available'); + } + final secretKey = _confBox?.get(_secretKeyKey, defaultValue: ''); + state = state.copyWith( + auth: state.auth.copyWith(uuid: uuid!, secretKey: secretKey), + ); + + dPrint('[PartyRoom] Initialized with UUID: ${state.auth.uuid}'); + } catch (e) { + dPrint('[PartyRoom] Initialize error: $e'); + } + } + + /// 连接到服务器 + Future connect() async { + try { + // 关闭现有连接 + await disconnect(); + + final serverAddress = URLConf.partyRoomServerAddress; + final serverPort = URLConf.partyRoomServerPort; + + final channel = ClientChannel( + serverAddress, + port: serverPort, + options: const ChannelOptions(credentials: ChannelCredentials.insecure()), + ); + + final authClient = auth.AuthServiceClient(channel); + final roomClient = partroom.PartRoomServiceClient(channel); + final commonClient = common.CommonServiceClient(channel); + + // 测试连接 + await authClient.status(auth.StatusRequest()); + + state = state.copyWith( + client: state.client.copyWith( + channel: channel, + authClient: authClient, + roomClient: roomClient, + commonClient: commonClient, + isConnected: true, + serverAddress: serverAddress, + serverPort: serverPort, + ), + ); + + dPrint('[PartyRoom] Connected to $serverAddress:$serverPort'); + } catch (e) { + dPrint('[PartyRoom] Connect error: $e'); + rethrow; + } + } + + /// 断开连接 + Future disconnect() async { + await _stopHeartbeat(); + await _stopEventStream(); + + await state.client.channel?.shutdown(); + + state = state.copyWith(client: const PartyRoomClientState()); + + dPrint('[PartyRoom] Disconnected'); + } + + /// 获取认证 CallOptions + CallOptions _getAuthCallOptions() { + return CallOptions(metadata: {'uuid': state.auth.uuid, 'secret-key': state.auth.secretKey}); + } + + // ========== 认证相关方法 ========== + + /// 登录 + Future login() async { + try { + final client = state.client.authClient; + if (client == null) throw Exception('Not connected to server'); + + final response = await client.login(auth.LoginRequest(), options: _getAuthCallOptions()); + + state = state.copyWith( + auth: state.auth.copyWith( + isLoggedIn: true, + userInfo: response.userInfo, + lastLoginTime: DateTime.fromMillisecondsSinceEpoch(response.lastLoginTime.toInt()), + ), + ); + + dPrint('[PartyRoom] Logged in as ${response.userInfo.gameUserId}'); + + // 登录后检查是否有房间 + await _checkMyRoom(); + } catch (e) { + dPrint('[PartyRoom] Login error: $e'); + rethrow; + } + } + + /// 预注册 + Future preRegister(String gameUserId) async { + try { + final client = state.client.authClient; + if (client == null) throw Exception('Not connected to server'); + + final response = await client.preRegister(auth.PreRegisterRequest(uuid: state.auth.uuid, gameUserId: gameUserId)); + + dPrint('[PartyRoom] PreRegister verification code: ${response.verificationCode}'); + return response; + } catch (e) { + dPrint('[PartyRoom] PreRegister error: $e'); + rethrow; + } + } + + /// 注册 + Future register(String gameUserId) async { + try { + final client = state.client.authClient; + if (client == null) throw Exception('Not connected to server'); + + final response = await client.register(auth.RegisterRequest(uuid: state.auth.uuid, gameUserId: gameUserId)); + + // 保存 secretKey + await _confBox?.put(_secretKeyKey, response.partyRoomSecretKey); + + state = state.copyWith( + auth: state.auth.copyWith(secretKey: response.partyRoomSecretKey, userInfo: response.userInfo), + ); + + dPrint('[PartyRoom] Registered successfully'); + } catch (e) { + dPrint('[PartyRoom] Register error: $e'); + rethrow; + } + } + + /// 注销 + Future unregister() async { + try { + final client = state.client.authClient; + if (client == null) throw Exception('Not connected to server'); + + await client.unregister(auth.UnregisterRequest(), options: _getAuthCallOptions()); + + // 清除本地认证信息 + await _confBox?.delete(_secretKeyKey); + + state = state.copyWith( + auth: state.auth.copyWith(secretKey: '', isLoggedIn: false, userInfo: null), + room: const PartyRoomState(), + ); + + dPrint('[PartyRoom] Unregistered successfully'); + } catch (e) { + dPrint('[PartyRoom] Unregister error: $e'); + rethrow; + } + } + + // ========== 房间相关方法 ========== + + /// 加载标签和信号类型 + Future loadTags() async { + try { + final roomClient = state.client.roomClient; + final commonClient = state.client.commonClient; + if (roomClient == null || commonClient == null) throw Exception('Not connected to server'); + + final response = await commonClient.getTags(common.GetTagsRequest()); + final signalTypesResponse = await commonClient.getSignalTypes(common.GetSignalTypesRequest()); + + state = state.copyWith( + room: state.room.copyWith(tags: response.tags, signalTypes: signalTypesResponse.signals), + ); + + dPrint( + '[PartyRoom] Tags and SignalTypes loaded: ${response.tags.length} tags, ${signalTypesResponse.signals.length} signal types', + ); + } catch (e) { + dPrint('[PartyRoom] LoadTags error: $e'); + rethrow; + } + } + + /// 获取房间列表 + Future getRoomList({ + String? mainTagId, + String? subTagId, + String? searchOwnerName, + int page = 1, + int pageSize = 20, + }) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final response = await client.getRoomList( + partroom.GetRoomListRequest( + mainTagId: mainTagId ?? '', + subTagId: subTagId ?? '', + searchOwnerName: searchOwnerName ?? '', + page: page, + pageSize: pageSize, + ), + ); + + return response; + } catch (e) { + dPrint('[PartyRoom] GetRoomList error: $e'); + rethrow; + } + } + + /// 创建房间 + Future createRoom({ + required String mainTagId, + String? subTagId, + required int targetMembers, + bool hasPassword = false, + String? password, + List? socialLinks, + }) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final response = await client.createRoom( + partroom.CreateRoomRequest( + mainTagId: mainTagId, + subTagId: subTagId ?? '', + targetMembers: targetMembers, + hasPassword: hasPassword, + password_5: password ?? '', + socialLinks: socialLinks ?? [], + ), + options: _getAuthCallOptions(), + ); + + state = state.copyWith(room: state.room.copyWith(roomUuid: response.roomUuid, isInRoom: true, isOwner: true)); + + dPrint('[PartyRoom] Room created: ${response.roomUuid}'); + + // 获取房间详情 + await getRoomInfo(response.roomUuid); + + // 启动心跳和事件监听 + await _startHeartbeat(response.roomUuid); + await _startEventStream(response.roomUuid); + } catch (e) { + dPrint('[PartyRoom] CreateRoom error: $e'); + rethrow; + } + } + + /// 加入房间 + Future joinRoom(String roomUuid, {String? password}) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + await client.joinRoom( + partroom.JoinRoomRequest(roomUuid: roomUuid, password: password ?? ''), + options: _getAuthCallOptions(), + ); + + state = state.copyWith(room: state.room.copyWith(roomUuid: roomUuid, isInRoom: true, isOwner: false)); + + dPrint('[PartyRoom] Joined room: $roomUuid'); + + // 获取房间详情 + await getRoomInfo(roomUuid); + + // 启动心跳和事件监听 + await _startHeartbeat(roomUuid); + await _startEventStream(roomUuid); + } catch (e) { + dPrint('[PartyRoom] JoinRoom error: $e'); + rethrow; + } + } + + /// 离开房间 + Future leaveRoom() async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final roomUuid = state.room.roomUuid; + if (roomUuid == null) return; + + await client.leaveRoom(partroom.LeaveRoomRequest(roomUuid: roomUuid), options: _getAuthCallOptions()); + + await _stopHeartbeat(); + await _stopEventStream(); + + state = state.copyWith(room: const PartyRoomState()); + + dPrint('[PartyRoom] Left room: $roomUuid'); + } catch (e) { + dPrint('[PartyRoom] LeaveRoom error: $e'); + rethrow; + } + } + + /// 解散房间 + Future dismissRoom() async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final roomUuid = state.room.roomUuid; + if (roomUuid == null) return; + + await client.dismissRoom(partroom.DismissRoomRequest(roomUuid: roomUuid), options: _getAuthCallOptions()); + + await _stopHeartbeat(); + await _stopEventStream(); + + state = state.copyWith(room: const PartyRoomState()); + + dPrint('[PartyRoom] Dismissed room: $roomUuid'); + } catch (e) { + dPrint('[PartyRoom] DismissRoom error: $e'); + rethrow; + } + } + + /// 获取房间详情 + Future getRoomInfo(String roomUuid) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final response = await client.getRoomInfo( + partroom.GetRoomInfoRequest(roomUuid: roomUuid), + options: _getAuthCallOptions(), + ); + + // 检查是否为房主 + final isOwner = response.room.ownerGameId == state.auth.userInfo?.gameUserId; + + state = state.copyWith( + room: state.room.copyWith(currentRoom: response.room, isOwner: isOwner), + ); + + // 同时获取成员列表 + await getRoomMembers(roomUuid); + + dPrint('[PartyRoom] Room info loaded'); + } catch (e) { + dPrint('[PartyRoom] GetRoomInfo error: $e'); + rethrow; + } + } + + /// 获取房间成员 + Future getRoomMembers(String roomUuid, {int page = 1, int pageSize = 100}) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final response = await client.getRoomMembers( + partroom.GetRoomMembersRequest(roomUuid: roomUuid, page: page, pageSize: pageSize), + options: _getAuthCallOptions(), + ); + + state = state.copyWith(room: state.room.copyWith(members: response.members)); + + dPrint('[PartyRoom] Loaded ${response.members.length} members'); + } catch (e) { + dPrint('[PartyRoom] GetRoomMembers error: $e'); + rethrow; + } + } + + /// 检查当前用户所在房间 + Future _checkMyRoom() async { + try { + final client = state.client.roomClient; + if (client == null) return; + + final response = await client.getMyRoom(partroom.GetMyRoomRequest(), options: _getAuthCallOptions()); + + if (response.hasRoom() && response.room.roomUuid.isNotEmpty) { + final isOwner = response.room.ownerGameId == state.auth.userInfo?.gameUserId; + + state = state.copyWith( + room: state.room.copyWith( + currentRoom: response.room, + roomUuid: response.room.roomUuid, + isInRoom: true, + isOwner: isOwner, + ), + ); + + dPrint('[PartyRoom] Rejoined room: ${response.room.roomUuid}'); + + // 重新启动心跳和事件监听 + await _startHeartbeat(response.room.roomUuid); + await _startEventStream(response.room.roomUuid); + await getRoomMembers(response.room.roomUuid); + } + } catch (e) { + dPrint('[PartyRoom] CheckMyRoom error: $e'); + } + } + + /// 更新房间信息 + Future updateRoom({ + int? targetMembers, + String? password, + String? mainTagId, + String? subTagId, + List? socialLinks, + }) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final roomUuid = state.room.roomUuid; + if (roomUuid == null) return; + + final request = partroom.UpdateRoomRequest( + roomUuid: roomUuid, + targetMembers: targetMembers ?? state.room.currentRoom?.targetMembers ?? 0, + mainTagId: mainTagId ?? state.room.currentRoom?.mainTagId ?? '', + subTagId: subTagId ?? state.room.currentRoom?.subTagId ?? '', + ); + + if (password != null) { + request.password = password; + } + + if (socialLinks != null) { + request.socialLinks.addAll(socialLinks); + } else if (state.room.currentRoom?.socialLinks != null) { + request.socialLinks.addAll(state.room.currentRoom!.socialLinks); + } + + await client.updateRoom(request, options: _getAuthCallOptions()); + + // 刷新房间信息 + await getRoomInfo(roomUuid); + + dPrint('[PartyRoom] Room updated'); + } catch (e) { + dPrint('[PartyRoom] UpdateRoom error: $e'); + rethrow; + } + } + + /// 踢出成员 + Future kickMember(String targetGameUserId) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final roomUuid = state.room.roomUuid; + if (roomUuid == null) return; + + await client.kickMember( + partroom.KickMemberRequest(roomUuid: roomUuid, targetGameUserId: targetGameUserId), + options: _getAuthCallOptions(), + ); + + dPrint('[PartyRoom] Member kicked: $targetGameUserId'); + + // 刷新成员列表 + await getRoomMembers(roomUuid); + } catch (e) { + dPrint('[PartyRoom] KickMember error: $e'); + rethrow; + } + } + + /// 设置状态 + Future setStatus({String? currentLocation, int? kills, int? deaths, int? playTime}) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final roomUuid = state.room.roomUuid; + if (roomUuid == null) return; + + await client.setStatus( + partroom.SetStatusRequest( + roomUuid: roomUuid, + status: partroom.MemberStatus( + currentLocation: currentLocation ?? '', + kills: kills ?? 0, + deaths: deaths ?? 0, + playTime: Int64(playTime ?? 0), + ), + ), + options: _getAuthCallOptions(), + ); + + dPrint('[PartyRoom] Status updated'); + } catch (e) { + dPrint('[PartyRoom] SetStatus error: $e'); + rethrow; + } + } + + /// 发送信号 + Future sendSignal(String signalId, {Map? params}) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final roomUuid = state.room.roomUuid; + if (roomUuid == null) return; + + // 验证信号类型是否有效 + final validSignalIds = state.room.signalTypes.map((s) => s.id).toList(); + if (validSignalIds.isNotEmpty && !validSignalIds.contains(signalId)) { + throw Exception('Invalid signal ID: $signalId. Valid IDs: ${validSignalIds.join(", ")}'); + } + + final request = partroom.SendSignalRequest(roomUuid: roomUuid, signalId: signalId); + + if (params != null) { + request.params.addAll(params); + } + + await client.sendSignal(request, options: _getAuthCallOptions()); + + dPrint('[PartyRoom] Signal sent: $signalId'); + } catch (e) { + dPrint('[PartyRoom] SendSignal error: $e'); + rethrow; + } + } + + /// 转移房主 + Future transferOwnership(String targetGameUserId) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final roomUuid = state.room.roomUuid; + if (roomUuid == null) return; + + await client.transferOwnership( + partroom.TransferOwnershipRequest(roomUuid: roomUuid, targetGameUserId: targetGameUserId), + options: _getAuthCallOptions(), + ); + + // 更新房主状态 + state = state.copyWith(room: state.room.copyWith(isOwner: false)); + + dPrint('[PartyRoom] Ownership transferred to: $targetGameUserId'); + + // 刷新房间信息 + await getRoomInfo(roomUuid); + } catch (e) { + dPrint('[PartyRoom] TransferOwnership error: $e'); + rethrow; + } + } + + /// 获取被踢出成员列表 + Future getKickedMembers({int page = 1, int pageSize = 20}) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final roomUuid = state.room.roomUuid; + if (roomUuid == null) throw Exception('Not in a room'); + + final response = await client.getKickedMembers( + partroom.GetKickedMembersRequest(roomUuid: roomUuid, page: page, pageSize: pageSize), + options: _getAuthCallOptions(), + ); + + return response; + } catch (e) { + dPrint('[PartyRoom] GetKickedMembers error: $e'); + rethrow; + } + } + + /// 移除被踢出成员(解除黑名单) + Future removeKickedMember(String gameUserId) async { + try { + final client = state.client.roomClient; + if (client == null) throw Exception('Not connected to server'); + + final roomUuid = state.room.roomUuid; + if (roomUuid == null) return; + + await client.removeKickedMember( + partroom.RemoveKickedMemberRequest(roomUuid: roomUuid, gameUserId: gameUserId), + options: _getAuthCallOptions(), + ); + + dPrint('[PartyRoom] Kicked member removed: $gameUserId'); + } catch (e) { + dPrint('[PartyRoom] RemoveKickedMember error: $e'); + rethrow; + } + } + + // ========== 心跳和事件流 ========== + + /// 启动心跳 + Future _startHeartbeat(String roomUuid) async { + await _stopHeartbeat(); + + _heartbeatTimer = Timer.periodic(const Duration(seconds: 30), (timer) async { + if (_disposed || state.room.roomUuid == null) { + timer.cancel(); + return; + } + + try { + final client = state.client.roomClient; + if (client == null) return; + + await client.heartbeat(partroom.HeartbeatRequest(roomUuid: roomUuid), options: _getAuthCallOptions()); + + dPrint('[PartyRoom] Heartbeat sent'); + } catch (e) { + dPrint('[PartyRoom] Heartbeat error: $e'); + } + }); + + dPrint('[PartyRoom] Heartbeat started'); + } + + /// 停止心跳 + Future _stopHeartbeat() async { + _heartbeatTimer?.cancel(); + _heartbeatTimer = null; + dPrint('[PartyRoom] Heartbeat stopped'); + } + + /// 启动事件流监听 + Future _startEventStream(String roomUuid) async { + await _stopEventStream(); + + try { + final client = state.client.roomClient; + if (client == null) return; + + final stream = client.listenRoomEvents( + partroom.ListenRoomEventsRequest(roomUuid: roomUuid), + options: _getAuthCallOptions(), + ); + + _eventStreamSubscription = stream.listen( + (event) { + _handleRoomEvent(event); + }, + onError: (error) { + dPrint('[PartyRoom] Event stream error: $error'); + }, + onDone: () { + dPrint('[PartyRoom] Event stream closed'); + }, + ); + + dPrint('[PartyRoom] Event stream started'); + } catch (e) { + dPrint('[PartyRoom] StartEventStream error: $e'); + } + } + + /// 停止事件流监听 + Future _stopEventStream() async { + await _eventStreamSubscription?.cancel(); + _eventStreamSubscription = null; + dPrint('[PartyRoom] Event stream stopped'); + } + + /// 处理房间事件 + void _handleRoomEvent(partroom.RoomEvent event) { + dPrint('[PartyRoom] Event received: ${event.type}'); + + // 添加到最近事件列表(保留最近 50 条) + final recentEvents = [...state.room.recentEvents, event]; + if (recentEvents.length > 50) { + recentEvents.removeAt(0); + } + + state = state.copyWith(room: state.room.copyWith(recentEvents: recentEvents)); + + // 根据事件类型处理 + switch (event.type) { + case partroom.RoomEventType.MEMBER_JOINED: + case partroom.RoomEventType.MEMBER_LEFT: + case partroom.RoomEventType.MEMBER_KICKED: + case partroom.RoomEventType.MEMBER_STATUS_UPDATED: + // 刷新成员列表 + if (state.room.roomUuid != null) { + getRoomMembers(state.room.roomUuid!); + } + break; + + case partroom.RoomEventType.OWNER_CHANGED: + // 检查是否自己成为房主 + final isOwner = event.member.gameUserId == state.auth.userInfo?.gameUserId; + state = state.copyWith(room: state.room.copyWith(isOwner: isOwner)); + // 刷新房间信息 + if (state.room.roomUuid != null) { + getRoomInfo(state.room.roomUuid!); + } + break; + + case partroom.RoomEventType.ROOM_UPDATED: + // 刷新房间信息 + if (state.room.roomUuid != null) { + getRoomInfo(state.room.roomUuid!); + } + break; + + case partroom.RoomEventType.ROOM_DISMISSED: + // 房间被解散 + _stopHeartbeat(); + _stopEventStream(); + state = state.copyWith(room: const PartyRoomState()); + break; + + case partroom.RoomEventType.SIGNAL_BROADCAST: + // 信号广播,UI 层可以监听 state.room.recentEvents 来处理 + break; + + default: + break; + } + } + + // ========== 通用服务方法 ========== + + /// 获取服务器时间 + Future getServerTime() async { + try { + final client = state.client.commonClient; + if (client == null) throw Exception('Not connected to server'); + + return await client.getServerTime(common.GetServerTimeRequest()); + } catch (e) { + dPrint('[PartyRoom] GetServerTime error: $e'); + rethrow; + } + } + + /// 获取版本信息 + Future getVersion() async { + try { + final client = state.client.commonClient; + if (client == null) throw Exception('Not connected to server'); + + return await client.getVersion(common.GetVersionRequest()); + } catch (e) { + dPrint('[PartyRoom] GetVersion error: $e'); + rethrow; + } + } + + /// 获取信号类型列表 + Future getSignalTypes() async { + try { + final client = state.client.commonClient; + if (client == null) throw Exception('Not connected to server'); + + return await client.getSignalTypes(common.GetSignalTypesRequest()); + } catch (e) { + dPrint('[PartyRoom] GetSignalTypes error: $e'); + rethrow; + } + } + + // ========== 清理 ========== + + void _cleanup() { + _stopHeartbeat(); + _stopEventStream(); + _confBox?.close(); + } +} diff --git a/lib/provider/party_room.freezed.dart b/lib/provider/party_room.freezed.dart new file mode 100644 index 0000000..2cf8331 --- /dev/null +++ b/lib/provider/party_room.freezed.dart @@ -0,0 +1,1153 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'party_room.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$PartyRoomAuthState { + + String get uuid; String get secretKey; bool get isLoggedIn; auth.GameUserInfo? get userInfo; DateTime? get lastLoginTime; +/// Create a copy of PartyRoomAuthState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PartyRoomAuthStateCopyWith get copyWith => _$PartyRoomAuthStateCopyWithImpl(this as PartyRoomAuthState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomAuthState&&(identical(other.uuid, uuid) || other.uuid == uuid)&&(identical(other.secretKey, secretKey) || other.secretKey == secretKey)&&(identical(other.isLoggedIn, isLoggedIn) || other.isLoggedIn == isLoggedIn)&&(identical(other.userInfo, userInfo) || other.userInfo == userInfo)&&(identical(other.lastLoginTime, lastLoginTime) || other.lastLoginTime == lastLoginTime)); +} + + +@override +int get hashCode => Object.hash(runtimeType,uuid,secretKey,isLoggedIn,userInfo,lastLoginTime); + +@override +String toString() { + return 'PartyRoomAuthState(uuid: $uuid, secretKey: $secretKey, isLoggedIn: $isLoggedIn, userInfo: $userInfo, lastLoginTime: $lastLoginTime)'; +} + + +} + +/// @nodoc +abstract mixin class $PartyRoomAuthStateCopyWith<$Res> { + factory $PartyRoomAuthStateCopyWith(PartyRoomAuthState value, $Res Function(PartyRoomAuthState) _then) = _$PartyRoomAuthStateCopyWithImpl; +@useResult +$Res call({ + String uuid, String secretKey, bool isLoggedIn, auth.GameUserInfo? userInfo, DateTime? lastLoginTime +}); + + + + +} +/// @nodoc +class _$PartyRoomAuthStateCopyWithImpl<$Res> + implements $PartyRoomAuthStateCopyWith<$Res> { + _$PartyRoomAuthStateCopyWithImpl(this._self, this._then); + + final PartyRoomAuthState _self; + final $Res Function(PartyRoomAuthState) _then; + +/// Create a copy of PartyRoomAuthState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? uuid = null,Object? secretKey = null,Object? isLoggedIn = null,Object? userInfo = freezed,Object? lastLoginTime = freezed,}) { + return _then(_self.copyWith( +uuid: null == uuid ? _self.uuid : uuid // ignore: cast_nullable_to_non_nullable +as String,secretKey: null == secretKey ? _self.secretKey : secretKey // ignore: cast_nullable_to_non_nullable +as String,isLoggedIn: null == isLoggedIn ? _self.isLoggedIn : isLoggedIn // ignore: cast_nullable_to_non_nullable +as bool,userInfo: freezed == userInfo ? _self.userInfo : userInfo // ignore: cast_nullable_to_non_nullable +as auth.GameUserInfo?,lastLoginTime: freezed == lastLoginTime ? _self.lastLoginTime : lastLoginTime // ignore: cast_nullable_to_non_nullable +as DateTime?, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PartyRoomAuthState]. +extension PartyRoomAuthStatePatterns on PartyRoomAuthState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PartyRoomAuthState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PartyRoomAuthState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PartyRoomAuthState value) $default,){ +final _that = this; +switch (_that) { +case _PartyRoomAuthState(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PartyRoomAuthState value)? $default,){ +final _that = this; +switch (_that) { +case _PartyRoomAuthState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String uuid, String secretKey, bool isLoggedIn, auth.GameUserInfo? userInfo, DateTime? lastLoginTime)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PartyRoomAuthState() when $default != null: +return $default(_that.uuid,_that.secretKey,_that.isLoggedIn,_that.userInfo,_that.lastLoginTime);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String uuid, String secretKey, bool isLoggedIn, auth.GameUserInfo? userInfo, DateTime? lastLoginTime) $default,) {final _that = this; +switch (_that) { +case _PartyRoomAuthState(): +return $default(_that.uuid,_that.secretKey,_that.isLoggedIn,_that.userInfo,_that.lastLoginTime);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String uuid, String secretKey, bool isLoggedIn, auth.GameUserInfo? userInfo, DateTime? lastLoginTime)? $default,) {final _that = this; +switch (_that) { +case _PartyRoomAuthState() when $default != null: +return $default(_that.uuid,_that.secretKey,_that.isLoggedIn,_that.userInfo,_that.lastLoginTime);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _PartyRoomAuthState implements PartyRoomAuthState { + const _PartyRoomAuthState({this.uuid = '', this.secretKey = '', this.isLoggedIn = false, this.userInfo, this.lastLoginTime}); + + +@override@JsonKey() final String uuid; +@override@JsonKey() final String secretKey; +@override@JsonKey() final bool isLoggedIn; +@override final auth.GameUserInfo? userInfo; +@override final DateTime? lastLoginTime; + +/// Create a copy of PartyRoomAuthState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PartyRoomAuthStateCopyWith<_PartyRoomAuthState> get copyWith => __$PartyRoomAuthStateCopyWithImpl<_PartyRoomAuthState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomAuthState&&(identical(other.uuid, uuid) || other.uuid == uuid)&&(identical(other.secretKey, secretKey) || other.secretKey == secretKey)&&(identical(other.isLoggedIn, isLoggedIn) || other.isLoggedIn == isLoggedIn)&&(identical(other.userInfo, userInfo) || other.userInfo == userInfo)&&(identical(other.lastLoginTime, lastLoginTime) || other.lastLoginTime == lastLoginTime)); +} + + +@override +int get hashCode => Object.hash(runtimeType,uuid,secretKey,isLoggedIn,userInfo,lastLoginTime); + +@override +String toString() { + return 'PartyRoomAuthState(uuid: $uuid, secretKey: $secretKey, isLoggedIn: $isLoggedIn, userInfo: $userInfo, lastLoginTime: $lastLoginTime)'; +} + + +} + +/// @nodoc +abstract mixin class _$PartyRoomAuthStateCopyWith<$Res> implements $PartyRoomAuthStateCopyWith<$Res> { + factory _$PartyRoomAuthStateCopyWith(_PartyRoomAuthState value, $Res Function(_PartyRoomAuthState) _then) = __$PartyRoomAuthStateCopyWithImpl; +@override @useResult +$Res call({ + String uuid, String secretKey, bool isLoggedIn, auth.GameUserInfo? userInfo, DateTime? lastLoginTime +}); + + + + +} +/// @nodoc +class __$PartyRoomAuthStateCopyWithImpl<$Res> + implements _$PartyRoomAuthStateCopyWith<$Res> { + __$PartyRoomAuthStateCopyWithImpl(this._self, this._then); + + final _PartyRoomAuthState _self; + final $Res Function(_PartyRoomAuthState) _then; + +/// Create a copy of PartyRoomAuthState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? uuid = null,Object? secretKey = null,Object? isLoggedIn = null,Object? userInfo = freezed,Object? lastLoginTime = freezed,}) { + return _then(_PartyRoomAuthState( +uuid: null == uuid ? _self.uuid : uuid // ignore: cast_nullable_to_non_nullable +as String,secretKey: null == secretKey ? _self.secretKey : secretKey // ignore: cast_nullable_to_non_nullable +as String,isLoggedIn: null == isLoggedIn ? _self.isLoggedIn : isLoggedIn // ignore: cast_nullable_to_non_nullable +as bool,userInfo: freezed == userInfo ? _self.userInfo : userInfo // ignore: cast_nullable_to_non_nullable +as auth.GameUserInfo?,lastLoginTime: freezed == lastLoginTime ? _self.lastLoginTime : lastLoginTime // ignore: cast_nullable_to_non_nullable +as DateTime?, + )); +} + + +} + +/// @nodoc +mixin _$PartyRoomState { + + partroom.RoomInfo? get currentRoom; List get members; List get tags; List get signalTypes; bool get isInRoom; bool get isOwner; String? get roomUuid; List get recentEvents; +/// Create a copy of PartyRoomState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PartyRoomStateCopyWith get copyWith => _$PartyRoomStateCopyWithImpl(this as PartyRoomState, _$identity); + + + +@override +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)); +} + + +@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)); + +@override +String toString() { + return 'PartyRoomState(currentRoom: $currentRoom, members: $members, tags: $tags, signalTypes: $signalTypes, isInRoom: $isInRoom, isOwner: $isOwner, roomUuid: $roomUuid, recentEvents: $recentEvents)'; +} + + +} + +/// @nodoc +abstract mixin class $PartyRoomStateCopyWith<$Res> { + factory $PartyRoomStateCopyWith(PartyRoomState value, $Res Function(PartyRoomState) _then) = _$PartyRoomStateCopyWithImpl; +@useResult +$Res call({ + partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents +}); + + + + +} +/// @nodoc +class _$PartyRoomStateCopyWithImpl<$Res> + implements $PartyRoomStateCopyWith<$Res> { + _$PartyRoomStateCopyWithImpl(this._self, this._then); + + final PartyRoomState _self; + final $Res Function(PartyRoomState) _then; + +/// Create a copy of PartyRoomState +/// 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,}) { + return _then(_self.copyWith( +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 List,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable +as List,signalTypes: null == signalTypes ? _self.signalTypes : signalTypes // ignore: cast_nullable_to_non_nullable +as List,isInRoom: null == isInRoom ? _self.isInRoom : isInRoom // 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 String?,recentEvents: null == recentEvents ? _self.recentEvents : recentEvents // ignore: cast_nullable_to_non_nullable +as List, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PartyRoomState]. +extension PartyRoomStatePatterns on PartyRoomState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PartyRoomState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PartyRoomState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PartyRoomState value) $default,){ +final _that = this; +switch (_that) { +case _PartyRoomState(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PartyRoomState value)? $default,){ +final _that = this; +switch (_that) { +case _PartyRoomState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +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 orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents) $default,) {final _that = this; +switch (_that) { +case _PartyRoomState(): +return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents)? $default,) {final _that = this; +switch (_that) { +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 null; + +} +} + +} + +/// @nodoc + + +class _PartyRoomState implements PartyRoomState { + const _PartyRoomState({this.currentRoom, final List members = const [], final List tags = const [], final List signalTypes = const [], this.isInRoom = false, this.isOwner = false, this.roomUuid, final List recentEvents = const []}): _members = members,_tags = tags,_signalTypes = signalTypes,_recentEvents = recentEvents; + + +@override final partroom.RoomInfo? currentRoom; + final List _members; +@override@JsonKey() List get members { + if (_members is EqualUnmodifiableListView) return _members; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_members); +} + + final List _tags; +@override@JsonKey() List get tags { + if (_tags is EqualUnmodifiableListView) return _tags; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tags); +} + + final List _signalTypes; +@override@JsonKey() List get signalTypes { + if (_signalTypes is EqualUnmodifiableListView) return _signalTypes; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_signalTypes); +} + +@override@JsonKey() final bool isInRoom; +@override@JsonKey() final bool isOwner; +@override final String? roomUuid; + final List _recentEvents; +@override@JsonKey() List get recentEvents { + if (_recentEvents is EqualUnmodifiableListView) return _recentEvents; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_recentEvents); +} + + +/// Create a copy of PartyRoomState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PartyRoomStateCopyWith<_PartyRoomState> get copyWith => __$PartyRoomStateCopyWithImpl<_PartyRoomState>(this, _$identity); + + + +@override +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)); +} + + +@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)); + +@override +String toString() { + return 'PartyRoomState(currentRoom: $currentRoom, members: $members, tags: $tags, signalTypes: $signalTypes, isInRoom: $isInRoom, isOwner: $isOwner, roomUuid: $roomUuid, recentEvents: $recentEvents)'; +} + + +} + +/// @nodoc +abstract mixin class _$PartyRoomStateCopyWith<$Res> implements $PartyRoomStateCopyWith<$Res> { + factory _$PartyRoomStateCopyWith(_PartyRoomState value, $Res Function(_PartyRoomState) _then) = __$PartyRoomStateCopyWithImpl; +@override @useResult +$Res call({ + partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents +}); + + + + +} +/// @nodoc +class __$PartyRoomStateCopyWithImpl<$Res> + implements _$PartyRoomStateCopyWith<$Res> { + __$PartyRoomStateCopyWithImpl(this._self, this._then); + + final _PartyRoomState _self; + final $Res Function(_PartyRoomState) _then; + +/// Create a copy of PartyRoomState +/// 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,}) { + return _then(_PartyRoomState( +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 List,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable +as List,signalTypes: null == signalTypes ? _self._signalTypes : signalTypes // ignore: cast_nullable_to_non_nullable +as List,isInRoom: null == isInRoom ? _self.isInRoom : isInRoom // 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 String?,recentEvents: null == recentEvents ? _self._recentEvents : recentEvents // ignore: cast_nullable_to_non_nullable +as List, + )); +} + + +} + +/// @nodoc +mixin _$PartyRoomClientState { + + ClientChannel? get channel; auth.AuthServiceClient? get authClient; partroom.PartRoomServiceClient? get roomClient; common.CommonServiceClient? get commonClient; bool get isConnected; String get serverAddress; int get serverPort; +/// Create a copy of PartyRoomClientState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PartyRoomClientStateCopyWith get copyWith => _$PartyRoomClientStateCopyWithImpl(this as PartyRoomClientState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomClientState&&(identical(other.channel, channel) || other.channel == channel)&&(identical(other.authClient, authClient) || other.authClient == authClient)&&(identical(other.roomClient, roomClient) || other.roomClient == roomClient)&&(identical(other.commonClient, commonClient) || other.commonClient == commonClient)&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected)&&(identical(other.serverAddress, serverAddress) || other.serverAddress == serverAddress)&&(identical(other.serverPort, serverPort) || other.serverPort == serverPort)); +} + + +@override +int get hashCode => Object.hash(runtimeType,channel,authClient,roomClient,commonClient,isConnected,serverAddress,serverPort); + +@override +String toString() { + return 'PartyRoomClientState(channel: $channel, authClient: $authClient, roomClient: $roomClient, commonClient: $commonClient, isConnected: $isConnected, serverAddress: $serverAddress, serverPort: $serverPort)'; +} + + +} + +/// @nodoc +abstract mixin class $PartyRoomClientStateCopyWith<$Res> { + factory $PartyRoomClientStateCopyWith(PartyRoomClientState value, $Res Function(PartyRoomClientState) _then) = _$PartyRoomClientStateCopyWithImpl; +@useResult +$Res call({ + ClientChannel? channel, auth.AuthServiceClient? authClient, partroom.PartRoomServiceClient? roomClient, common.CommonServiceClient? commonClient, bool isConnected, String serverAddress, int serverPort +}); + + + + +} +/// @nodoc +class _$PartyRoomClientStateCopyWithImpl<$Res> + implements $PartyRoomClientStateCopyWith<$Res> { + _$PartyRoomClientStateCopyWithImpl(this._self, this._then); + + final PartyRoomClientState _self; + final $Res Function(PartyRoomClientState) _then; + +/// Create a copy of PartyRoomClientState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? channel = freezed,Object? authClient = freezed,Object? roomClient = freezed,Object? commonClient = freezed,Object? isConnected = null,Object? serverAddress = null,Object? serverPort = null,}) { + return _then(_self.copyWith( +channel: freezed == channel ? _self.channel : channel // ignore: cast_nullable_to_non_nullable +as ClientChannel?,authClient: freezed == authClient ? _self.authClient : authClient // ignore: cast_nullable_to_non_nullable +as auth.AuthServiceClient?,roomClient: freezed == roomClient ? _self.roomClient : roomClient // ignore: cast_nullable_to_non_nullable +as partroom.PartRoomServiceClient?,commonClient: freezed == commonClient ? _self.commonClient : commonClient // ignore: cast_nullable_to_non_nullable +as common.CommonServiceClient?,isConnected: null == isConnected ? _self.isConnected : isConnected // ignore: cast_nullable_to_non_nullable +as bool,serverAddress: null == serverAddress ? _self.serverAddress : serverAddress // ignore: cast_nullable_to_non_nullable +as String,serverPort: null == serverPort ? _self.serverPort : serverPort // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PartyRoomClientState]. +extension PartyRoomClientStatePatterns on PartyRoomClientState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PartyRoomClientState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PartyRoomClientState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PartyRoomClientState value) $default,){ +final _that = this; +switch (_that) { +case _PartyRoomClientState(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PartyRoomClientState value)? $default,){ +final _that = this; +switch (_that) { +case _PartyRoomClientState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( ClientChannel? channel, auth.AuthServiceClient? authClient, partroom.PartRoomServiceClient? roomClient, common.CommonServiceClient? commonClient, bool isConnected, String serverAddress, int serverPort)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PartyRoomClientState() when $default != null: +return $default(_that.channel,_that.authClient,_that.roomClient,_that.commonClient,_that.isConnected,_that.serverAddress,_that.serverPort);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( ClientChannel? channel, auth.AuthServiceClient? authClient, partroom.PartRoomServiceClient? roomClient, common.CommonServiceClient? commonClient, bool isConnected, String serverAddress, int serverPort) $default,) {final _that = this; +switch (_that) { +case _PartyRoomClientState(): +return $default(_that.channel,_that.authClient,_that.roomClient,_that.commonClient,_that.isConnected,_that.serverAddress,_that.serverPort);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( ClientChannel? channel, auth.AuthServiceClient? authClient, partroom.PartRoomServiceClient? roomClient, common.CommonServiceClient? commonClient, bool isConnected, String serverAddress, int serverPort)? $default,) {final _that = this; +switch (_that) { +case _PartyRoomClientState() when $default != null: +return $default(_that.channel,_that.authClient,_that.roomClient,_that.commonClient,_that.isConnected,_that.serverAddress,_that.serverPort);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _PartyRoomClientState implements PartyRoomClientState { + const _PartyRoomClientState({this.channel, this.authClient, this.roomClient, this.commonClient, this.isConnected = false, this.serverAddress = '', this.serverPort = 0}); + + +@override final ClientChannel? channel; +@override final auth.AuthServiceClient? authClient; +@override final partroom.PartRoomServiceClient? roomClient; +@override final common.CommonServiceClient? commonClient; +@override@JsonKey() final bool isConnected; +@override@JsonKey() final String serverAddress; +@override@JsonKey() final int serverPort; + +/// Create a copy of PartyRoomClientState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PartyRoomClientStateCopyWith<_PartyRoomClientState> get copyWith => __$PartyRoomClientStateCopyWithImpl<_PartyRoomClientState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomClientState&&(identical(other.channel, channel) || other.channel == channel)&&(identical(other.authClient, authClient) || other.authClient == authClient)&&(identical(other.roomClient, roomClient) || other.roomClient == roomClient)&&(identical(other.commonClient, commonClient) || other.commonClient == commonClient)&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected)&&(identical(other.serverAddress, serverAddress) || other.serverAddress == serverAddress)&&(identical(other.serverPort, serverPort) || other.serverPort == serverPort)); +} + + +@override +int get hashCode => Object.hash(runtimeType,channel,authClient,roomClient,commonClient,isConnected,serverAddress,serverPort); + +@override +String toString() { + return 'PartyRoomClientState(channel: $channel, authClient: $authClient, roomClient: $roomClient, commonClient: $commonClient, isConnected: $isConnected, serverAddress: $serverAddress, serverPort: $serverPort)'; +} + + +} + +/// @nodoc +abstract mixin class _$PartyRoomClientStateCopyWith<$Res> implements $PartyRoomClientStateCopyWith<$Res> { + factory _$PartyRoomClientStateCopyWith(_PartyRoomClientState value, $Res Function(_PartyRoomClientState) _then) = __$PartyRoomClientStateCopyWithImpl; +@override @useResult +$Res call({ + ClientChannel? channel, auth.AuthServiceClient? authClient, partroom.PartRoomServiceClient? roomClient, common.CommonServiceClient? commonClient, bool isConnected, String serverAddress, int serverPort +}); + + + + +} +/// @nodoc +class __$PartyRoomClientStateCopyWithImpl<$Res> + implements _$PartyRoomClientStateCopyWith<$Res> { + __$PartyRoomClientStateCopyWithImpl(this._self, this._then); + + final _PartyRoomClientState _self; + final $Res Function(_PartyRoomClientState) _then; + +/// Create a copy of PartyRoomClientState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? channel = freezed,Object? authClient = freezed,Object? roomClient = freezed,Object? commonClient = freezed,Object? isConnected = null,Object? serverAddress = null,Object? serverPort = null,}) { + return _then(_PartyRoomClientState( +channel: freezed == channel ? _self.channel : channel // ignore: cast_nullable_to_non_nullable +as ClientChannel?,authClient: freezed == authClient ? _self.authClient : authClient // ignore: cast_nullable_to_non_nullable +as auth.AuthServiceClient?,roomClient: freezed == roomClient ? _self.roomClient : roomClient // ignore: cast_nullable_to_non_nullable +as partroom.PartRoomServiceClient?,commonClient: freezed == commonClient ? _self.commonClient : commonClient // ignore: cast_nullable_to_non_nullable +as common.CommonServiceClient?,isConnected: null == isConnected ? _self.isConnected : isConnected // ignore: cast_nullable_to_non_nullable +as bool,serverAddress: null == serverAddress ? _self.serverAddress : serverAddress // ignore: cast_nullable_to_non_nullable +as String,serverPort: null == serverPort ? _self.serverPort : serverPort // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +/// @nodoc +mixin _$PartyRoomFullState { + + PartyRoomAuthState get auth; PartyRoomState get room; PartyRoomClientState get client; +/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PartyRoomFullStateCopyWith get copyWith => _$PartyRoomFullStateCopyWithImpl(this as PartyRoomFullState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomFullState&&(identical(other.auth, auth) || other.auth == auth)&&(identical(other.room, room) || other.room == room)&&(identical(other.client, client) || other.client == client)); +} + + +@override +int get hashCode => Object.hash(runtimeType,auth,room,client); + +@override +String toString() { + return 'PartyRoomFullState(auth: $auth, room: $room, client: $client)'; +} + + +} + +/// @nodoc +abstract mixin class $PartyRoomFullStateCopyWith<$Res> { + factory $PartyRoomFullStateCopyWith(PartyRoomFullState value, $Res Function(PartyRoomFullState) _then) = _$PartyRoomFullStateCopyWithImpl; +@useResult +$Res call({ + PartyRoomAuthState auth, PartyRoomState room, PartyRoomClientState client +}); + + +$PartyRoomAuthStateCopyWith<$Res> get auth;$PartyRoomStateCopyWith<$Res> get room;$PartyRoomClientStateCopyWith<$Res> get client; + +} +/// @nodoc +class _$PartyRoomFullStateCopyWithImpl<$Res> + implements $PartyRoomFullStateCopyWith<$Res> { + _$PartyRoomFullStateCopyWithImpl(this._self, this._then); + + final PartyRoomFullState _self; + final $Res Function(PartyRoomFullState) _then; + +/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? auth = null,Object? room = null,Object? client = null,}) { + return _then(_self.copyWith( +auth: null == auth ? _self.auth : auth // ignore: cast_nullable_to_non_nullable +as PartyRoomAuthState,room: null == room ? _self.room : room // ignore: cast_nullable_to_non_nullable +as PartyRoomState,client: null == client ? _self.client : client // ignore: cast_nullable_to_non_nullable +as PartyRoomClientState, + )); +} +/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PartyRoomAuthStateCopyWith<$Res> get auth { + + return $PartyRoomAuthStateCopyWith<$Res>(_self.auth, (value) { + return _then(_self.copyWith(auth: value)); + }); +}/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PartyRoomStateCopyWith<$Res> get room { + + return $PartyRoomStateCopyWith<$Res>(_self.room, (value) { + return _then(_self.copyWith(room: value)); + }); +}/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PartyRoomClientStateCopyWith<$Res> get client { + + return $PartyRoomClientStateCopyWith<$Res>(_self.client, (value) { + return _then(_self.copyWith(client: value)); + }); +} +} + + +/// Adds pattern-matching-related methods to [PartyRoomFullState]. +extension PartyRoomFullStatePatterns on PartyRoomFullState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PartyRoomFullState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PartyRoomFullState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PartyRoomFullState value) $default,){ +final _that = this; +switch (_that) { +case _PartyRoomFullState(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PartyRoomFullState value)? $default,){ +final _that = this; +switch (_that) { +case _PartyRoomFullState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( PartyRoomAuthState auth, PartyRoomState room, PartyRoomClientState client)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PartyRoomFullState() when $default != null: +return $default(_that.auth,_that.room,_that.client);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( PartyRoomAuthState auth, PartyRoomState room, PartyRoomClientState client) $default,) {final _that = this; +switch (_that) { +case _PartyRoomFullState(): +return $default(_that.auth,_that.room,_that.client);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( PartyRoomAuthState auth, PartyRoomState room, PartyRoomClientState client)? $default,) {final _that = this; +switch (_that) { +case _PartyRoomFullState() when $default != null: +return $default(_that.auth,_that.room,_that.client);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _PartyRoomFullState implements PartyRoomFullState { + const _PartyRoomFullState({required this.auth, required this.room, required this.client}); + + +@override final PartyRoomAuthState auth; +@override final PartyRoomState room; +@override final PartyRoomClientState client; + +/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PartyRoomFullStateCopyWith<_PartyRoomFullState> get copyWith => __$PartyRoomFullStateCopyWithImpl<_PartyRoomFullState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomFullState&&(identical(other.auth, auth) || other.auth == auth)&&(identical(other.room, room) || other.room == room)&&(identical(other.client, client) || other.client == client)); +} + + +@override +int get hashCode => Object.hash(runtimeType,auth,room,client); + +@override +String toString() { + return 'PartyRoomFullState(auth: $auth, room: $room, client: $client)'; +} + + +} + +/// @nodoc +abstract mixin class _$PartyRoomFullStateCopyWith<$Res> implements $PartyRoomFullStateCopyWith<$Res> { + factory _$PartyRoomFullStateCopyWith(_PartyRoomFullState value, $Res Function(_PartyRoomFullState) _then) = __$PartyRoomFullStateCopyWithImpl; +@override @useResult +$Res call({ + PartyRoomAuthState auth, PartyRoomState room, PartyRoomClientState client +}); + + +@override $PartyRoomAuthStateCopyWith<$Res> get auth;@override $PartyRoomStateCopyWith<$Res> get room;@override $PartyRoomClientStateCopyWith<$Res> get client; + +} +/// @nodoc +class __$PartyRoomFullStateCopyWithImpl<$Res> + implements _$PartyRoomFullStateCopyWith<$Res> { + __$PartyRoomFullStateCopyWithImpl(this._self, this._then); + + final _PartyRoomFullState _self; + final $Res Function(_PartyRoomFullState) _then; + +/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? auth = null,Object? room = null,Object? client = null,}) { + return _then(_PartyRoomFullState( +auth: null == auth ? _self.auth : auth // ignore: cast_nullable_to_non_nullable +as PartyRoomAuthState,room: null == room ? _self.room : room // ignore: cast_nullable_to_non_nullable +as PartyRoomState,client: null == client ? _self.client : client // ignore: cast_nullable_to_non_nullable +as PartyRoomClientState, + )); +} + +/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PartyRoomAuthStateCopyWith<$Res> get auth { + + return $PartyRoomAuthStateCopyWith<$Res>(_self.auth, (value) { + return _then(_self.copyWith(auth: value)); + }); +}/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PartyRoomStateCopyWith<$Res> get room { + + return $PartyRoomStateCopyWith<$Res>(_self.room, (value) { + return _then(_self.copyWith(room: value)); + }); +}/// Create a copy of PartyRoomFullState +/// with the given fields replaced by the non-null parameter values. +@override +@pragma('vm:prefer-inline') +$PartyRoomClientStateCopyWith<$Res> get client { + + return $PartyRoomClientStateCopyWith<$Res>(_self.client, (value) { + return _then(_self.copyWith(client: value)); + }); +} +} + +// dart format on diff --git a/lib/provider/party_room.g.dart b/lib/provider/party_room.g.dart new file mode 100644 index 0000000..70dc68c --- /dev/null +++ b/lib/provider/party_room.g.dart @@ -0,0 +1,68 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'party_room.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning +/// PartyRoom Provider + +@ProviderFor(PartyRoom) +const partyRoomProvider = PartyRoomProvider._(); + +/// PartyRoom Provider +final class PartyRoomProvider + extends $NotifierProvider { + /// PartyRoom Provider + const PartyRoomProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'partyRoomProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$partyRoomHash(); + + @$internal + @override + PartyRoom create() => PartyRoom(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PartyRoomFullState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$partyRoomHash() => r'2c521709721292458d5459359cac376f123ec226'; + +/// PartyRoom Provider + +abstract class _$PartyRoom extends $Notifier { + PartyRoomFullState build(); + @$mustCallSuper + @override + void runBuild() { + final created = build(); + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + PartyRoomFullState, + Object?, + Object? + >; + element.handleValue(ref, created); + } +} diff --git a/lib/ui/home/input_method/input_method_dialog_ui_model.g.dart b/lib/ui/home/input_method/input_method_dialog_ui_model.g.dart index 6270440..9ef43ba 100644 --- a/lib/ui/home/input_method/input_method_dialog_ui_model.g.dart +++ b/lib/ui/home/input_method/input_method_dialog_ui_model.g.dart @@ -43,7 +43,7 @@ final class InputMethodDialogUIModelProvider } String _$inputMethodDialogUIModelHash() => - r'f216c1a5b6d68b3924af7b351314c618dcac80b5'; + r'51f1708f22a90f7c2f879ad3d2a87a8e2f81b9e9'; abstract class _$InputMethodDialogUIModel extends $Notifier { diff --git a/lib/ui/home/localization/localization_ui_model.dart b/lib/ui/home/localization/localization_ui_model.dart index 2954421..40a029b 100644 --- a/lib/ui/home/localization/localization_ui_model.dart +++ b/lib/ui/home/localization/localization_ui_model.dart @@ -63,10 +63,13 @@ class LocalizationUIModel extends _$LocalizationUIModel { String get _scInstallPath => ref.read(homeUIModelProvider).scInstalledPath!; + bool _isDisposed = false; + @override LocalizationUIState build() { state = LocalizationUIState(selectedLanguage: languageSupport.keys.first); ref.onDispose(() { + _isDisposed = true; _customizeDirListenSub?.cancel(); _customizeDirListenSub = null; }); @@ -83,6 +86,7 @@ class LocalizationUIModel extends _$LocalizationUIModel { state = state.copyWith(selectedLanguage: lang); // fix for ui performance await Future.delayed(Duration(milliseconds: 250)); + if (_isDisposed) return; await _loadData(); } @@ -113,6 +117,7 @@ class LocalizationUIModel extends _$LocalizationUIModel { for (var lang in languageSupport.keys) { final l = await Api.getScLocalizationData(lang).unwrap(); if (l != null) { + if (_isDisposed) return; if (lang == state.selectedLanguage) { final apiLocalizationData = {}; for (var element in l) { @@ -123,6 +128,7 @@ class LocalizationUIModel extends _$LocalizationUIModel { apiLocalizationData[element.versionName ?? ""] = element; } } + if (_isDisposed) return; state = state.copyWith(apiLocalizationData: apiLocalizationData); } final map = {}; @@ -464,7 +470,7 @@ class LocalizationUIModel extends _$LocalizationUIModel { dPrint( "_updateStatus updateStatus: $patchStatus , isInstalledAdvanced: $isInstalledAdvanced ,installedCommunityInputMethodSupportVersion: $installedCommunityInputMethodSupportVersion"); - + if (_isDisposed) return; state = state.copyWith( patchStatus: patchStatus, isInstalledAdvanced: isInstalledAdvanced, diff --git a/lib/ui/home/localization/localization_ui_model.g.dart b/lib/ui/home/localization/localization_ui_model.g.dart index eee7afd..ad9ffca 100644 --- a/lib/ui/home/localization/localization_ui_model.g.dart +++ b/lib/ui/home/localization/localization_ui_model.g.dart @@ -42,7 +42,7 @@ final class LocalizationUIModelProvider } String _$localizationUIModelHash() => - r'3d3f0ed7fa3631eca4e10d456c437f6fca8eedff'; + r'122f9f85da6e112165f4ff88667b45cf3cf3f43e'; abstract class _$LocalizationUIModel extends $Notifier { LocalizationUIState build(); diff --git a/lib/ui/index_ui.dart b/lib/ui/index_ui.dart index 98fe9bb..203c3ce 100644 --- a/lib/ui/index_ui.dart +++ b/lib/ui/index_ui.dart @@ -6,6 +6,7 @@ import 'package:starcitizen_doctor/app.dart'; import 'package:starcitizen_doctor/common/conf/conf.dart'; import 'package:starcitizen_doctor/provider/aria2c.dart'; import 'package:starcitizen_doctor/ui/home/home_ui_model.dart'; +import 'package:starcitizen_doctor/ui/party_room/party_room_ui.dart'; import 'package:starcitizen_doctor/ui/settings/settings_ui_model.dart'; import 'package:starcitizen_doctor/widgets/widgets.dart'; import 'package:window_manager/window_manager.dart'; @@ -13,6 +14,7 @@ import 'package:window_manager/window_manager.dart'; import 'about/about_ui.dart'; import 'home/home_ui.dart'; import 'nav/nav_ui.dart'; +import 'party_room/party_room_ui_model.dart'; import 'settings/settings_ui.dart'; import 'tools/tools_ui.dart'; @@ -21,58 +23,52 @@ class IndexUI extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + ref.watch(appGlobalModelProvider); // pre init child + ref.watch(homeUIModelProvider.select((value) => null)); ref.watch(settingsUIModelProvider.select((value) => null)); - ref.watch(appGlobalModelProvider); + ref.watch(partyRoomUIModelProvider.select((value) => null)); final curIndex = useState(0); return NavigationView( appBar: NavigationAppBar( - automaticallyImplyLeading: false, - title: () { - return DragToMoveArea( - child: Align( - alignment: AlignmentDirectional.centerStart, - child: Row( - children: [ - Image.asset( - "assets/app_logo_mini.png", - width: 20, - height: 20, - fit: BoxFit.cover, - ), - const SizedBox(width: 12), - Text(S.current.app_index_version_info(ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")), - ], - ), + automaticallyImplyLeading: false, + title: () { + return DragToMoveArea( + child: Align( + alignment: AlignmentDirectional.centerStart, + child: Row( + children: [ + Image.asset("assets/app_logo_mini.png", width: 20, height: 20, fit: BoxFit.cover), + const SizedBox(width: 12), + Text(S.current.app_index_version_info(ConstConf.appVersion, ConstConf.isMSE ? "" : " Dev")), + ], ), - ); - }(), - actions: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - IconButton( - icon: Stack( - children: [ - Padding( - padding: const EdgeInsets.all(6), - child: Icon( - FluentIcons.installation, - size: 22, - color: Colors.white.withValues(alpha: .6), - ), - ), - _makeAria2TaskNumWidget() - ], - ), - onPressed: () => _goDownloader(context), - // onPressed: model.goDownloader + ), + ); + }(), + actions: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + icon: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(6), + child: Icon(FluentIcons.installation, size: 22, color: Colors.white.withValues(alpha: .6)), + ), + _makeAria2TaskNumWidget(), + ], ), - const SizedBox(width: 24), - const WindowButtons() - ], - )), + onPressed: () => _goDownloader(context), + // onPressed: model.goDownloader + ), + const SizedBox(width: 24), + const WindowButtons(), + ], + ), + ), pane: NavigationPane( key: Key("NavigationPane_${S.current.app_language_code}"), selected: curIndex.value, @@ -86,21 +82,13 @@ class IndexUI extends HookConsumerWidget { } Map get pageMenus => { - FluentIcons.home: ( - S.current.app_index_menu_home, - const HomeUI(), - ), - FluentIcons.toolbox: ( - S.current.app_index_menu_tools, - const ToolsUI(), - ), - FluentIcons.power_apps: ((S.current.nav_title), const NavUI()), - FluentIcons.settings: (S.current.app_index_menu_settings, const SettingsUI()), - FluentIcons.info: ( - S.current.app_index_menu_about, - const AboutUI(), - ), - }; + FluentIcons.home: (S.current.app_index_menu_home, const HomeUI()), + FluentIcons.game: (S.current.app_index_menu_lobby, const PartyRoomUI()), + FluentIcons.toolbox: (S.current.app_index_menu_tools, const ToolsUI()), + FluentIcons.power_apps: ((S.current.nav_title), const NavUI()), + FluentIcons.settings: (S.current.app_index_menu_settings, const SettingsUI()), + FluentIcons.info: (S.current.app_index_menu_about, const AboutUI()), + }; List getNavigationPaneItems(ValueNotifier curIndexState) { // width = 64 @@ -116,10 +104,7 @@ class IndexUI extends HookConsumerWidget { children: [ Icon(kv.key, size: 18), const SizedBox(height: 3), - Text( - kv.value.$1, - style: const TextStyle(fontSize: 11), - ) + Text(kv.value.$1, style: const TextStyle(fontSize: 11)), ], ), ), @@ -144,22 +129,14 @@ class IndexUI extends HookConsumerWidget { return const SizedBox(); } return Positioned( - bottom: 0, - right: 0, - child: Container( - decoration: BoxDecoration( - color: Colors.red, - borderRadius: BorderRadius.circular(12), - ), - padding: const EdgeInsets.only(left: 6, right: 6, bottom: 1.5, top: 1.5), - child: Text( - "${aria2cState.aria2TotalTaskNum}", - style: const TextStyle( - fontSize: 8, - color: Colors.white, - ), - ), - )); + bottom: 0, + right: 0, + child: Container( + decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(12)), + padding: const EdgeInsets.only(left: 6, right: 6, bottom: 1.5, top: 1.5), + child: Text("${aria2cState.aria2TotalTaskNum}", style: const TextStyle(fontSize: 8, color: Colors.white)), + ), + ); }, ); } @@ -167,4 +144,4 @@ class IndexUI extends HookConsumerWidget { void _goDownloader(BuildContext context) { context.push('/index/downloader'); } -} \ No newline at end of file +} diff --git a/lib/ui/nav/nav_state.dart b/lib/ui/nav/nav_state.dart index b7196f1..864bc67 100644 --- a/lib/ui/nav/nav_state.dart +++ b/lib/ui/nav/nav_state.dart @@ -33,8 +33,10 @@ class Nav extends _$Nav { if (!_mounted) return; try { final r = await UDBNavApi.getNavItems(pageNo: pageNo); + if (!_mounted) return; state = state.copyWith(items: r.docs, errorInfo: ""); } catch (e) { + if (!_mounted) return; state = state.copyWith(errorInfo: e.toString()); } } diff --git a/lib/ui/nav/nav_state.g.dart b/lib/ui/nav/nav_state.g.dart index e5b764e..3ab26d9 100644 --- a/lib/ui/nav/nav_state.g.dart +++ b/lib/ui/nav/nav_state.g.dart @@ -40,7 +40,7 @@ final class NavProvider extends $NotifierProvider { } } -String _$navHash() => r'00c4da8fdd37214cda179a81ece3676add7aab53'; +String _$navHash() => r'bd3773505d6ae53f577ac6f3bfe711b320faa524'; abstract class _$Nav extends $Notifier { NavState build(); diff --git a/lib/ui/party_room/party_room_ui.dart b/lib/ui/party_room/party_room_ui.dart index c86f270..a00ee4d 100644 --- a/lib/ui/party_room/party_room_ui.dart +++ b/lib/ui/party_room/party_room_ui.dart @@ -1,41 +1,32 @@ -import 'package:flutter/material.dart'; +import 'package:fluent_ui/fluent_ui.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:starcitizen_doctor/generated/l10n.dart'; -import 'package:url_launcher/url_launcher_string.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/widgets/party_room_connect_page.dart'; +import 'package:starcitizen_doctor/ui/party_room/widgets/party_room_list_page.dart'; +import 'package:starcitizen_doctor/ui/party_room/widgets/party_room_detail_page.dart'; +import 'package:starcitizen_doctor/ui/party_room/widgets/party_room_register_page.dart'; class PartyRoomUI extends HookConsumerWidget { const PartyRoomUI({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - S.current.lobby_online_lobby_coming_soon, - style: const TextStyle(fontSize: 20), - ), - const SizedBox(height: 12), - GestureDetector( - onTap: () { - launchUrlString("https://wj.qq.com/s2/14112124/f4c8/"); - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text(S.current.lobby_invitation_to_participate), - Text( - S.current.lobby_survey, - style: const TextStyle( - color: Colors.blue, - ), - ) - ], - ), - ), - ], - ), - ); + final partyRoomState = ref.watch(partyRoomProvider); + ref.watch(partyRoomUIModelProvider.select((_) => null)); + // 根据状态显示不同页面 + if (!partyRoomState.client.isConnected) { + return const PartyRoomConnectPage(); + } + + if (!partyRoomState.auth.isLoggedIn) { + return const PartyRoomRegisterPage(); + } + + if (partyRoomState.room.isInRoom) { + return const PartyRoomDetailPage(); + } + + return const PartyRoomListPage(); } } diff --git a/lib/ui/party_room/party_room_ui_model.dart b/lib/ui/party_room/party_room_ui_model.dart new file mode 100644 index 0000000..d7d72c4 --- /dev/null +++ b/lib/ui/party_room/party_room_ui_model.dart @@ -0,0 +1,262 @@ +import 'dart:async'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:starcitizen_doctor/common/utils/log.dart'; +import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.dart'; +import 'package:starcitizen_doctor/provider/party_room.dart'; + +part 'party_room_ui_model.freezed.dart'; + +part 'party_room_ui_model.g.dart'; + +@freezed +sealed class PartyRoomUIState with _$PartyRoomUIState { + const factory PartyRoomUIState({ + @Default(false) bool isConnecting, + @Default(false) bool showRoomList, + @Default([]) List roomListItems, + @Default(1) int currentPage, + @Default(20) int pageSize, + @Default(0) int totalRooms, + String? selectedMainTagId, + String? selectedSubTagId, + @Default('') String searchOwnerName, + @Default(false) bool isLoading, + String? errorMessage, + @Default('') String preRegisterCode, + @Default('') String registerGameUserId, + @Default(false) bool isReconnecting, + @Default(0) int reconnectAttempts, + }) = _PartyRoomUIState; +} + +@riverpod +class PartyRoomUIModel extends _$PartyRoomUIModel { + Timer? _reconnectTimer; + + @override + PartyRoomUIState build() { + state = const PartyRoomUIState(); + ref.listen(partyRoomProvider, (previous, next) { + _handleConnectionStateChange(previous, next); + }); + + connectToServer(); + + // 在 dispose 时清理定时器 + ref.onDispose(() { + _reconnectTimer?.cancel(); + }); + + return state; + } + + /// 处理连接状态变化 + void _handleConnectionStateChange(PartyRoomFullState? previous, PartyRoomFullState next) { + // 检测断线:之前已连接但现在未连接 + if (previous != null && previous.client.isConnected && !next.client.isConnected && !state.isReconnecting) { + dPrint('[PartyRoomUI] Connection lost, starting reconnection...'); + _startReconnection(); + } + } + + /// 开始断线重连 + Future _startReconnection() async { + if (state.isReconnecting) return; + + state = state.copyWith(isReconnecting: true, reconnectAttempts: 0); + + try { + // 尝试重新连接和登录 + await _attemptReconnect(); + } catch (e) { + dPrint('[PartyRoomUI] Reconnection failed: $e'); + state = state.copyWith(isReconnecting: false, errorMessage: '重连失败: $e'); + } + } + + /// 尝试重新连接 + Future _attemptReconnect() async { + const maxAttempts = 5; + const baseDelay = Duration(seconds: 2); + + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + state = state.copyWith(reconnectAttempts: attempt); + dPrint('[PartyRoomUI] Reconnection attempt $attempt/$maxAttempts'); + + try { + final partyRoom = ref.read(partyRoomProvider.notifier); + + // 重新连接 + await partyRoom.connect(); + + // 重新登录 + await partyRoom.login(); + + // 重新加载标签和房间列表 + await partyRoom.loadTags(); + if (state.showRoomList) { + await loadRoomList(); + } + + // 重连成功 + state = state.copyWith(isReconnecting: false, reconnectAttempts: 0, errorMessage: null); + + dPrint('[PartyRoomUI] Reconnection successful'); + return; + } catch (e) { + dPrint('[PartyRoomUI] Reconnection attempt $attempt failed: $e'); + + if (attempt < maxAttempts) { + // 使用指数退避策略 + final delay = baseDelay * (1 << (attempt - 1)); + dPrint('[PartyRoomUI] Waiting ${delay.inSeconds}s before next attempt...'); + await Future.delayed(delay); + } + } + } + + // 所有重连尝试都失败 + state = state.copyWith(isReconnecting: false, errorMessage: '重连失败,已尝试 $maxAttempts 次'); + throw Exception('Max reconnection attempts reached'); + } + + /// 连接到服务器 + Future connectToServer() async { + state = state.copyWith(isConnecting: true, errorMessage: null); + await Future.delayed(Duration(seconds: 1)); + try { + final partyRoom = ref.read(partyRoomProvider.notifier); + await partyRoom.connect(); + + // 尝试登录 + try { + await partyRoom.login(); + // 登录成功,加载标签和房间列表 + await partyRoom.loadTags(); + await loadRoomList(); + state = state.copyWith(showRoomList: true); + } catch (e) { + // 未注册,保持在连接状态 + dPrint('[PartyRoomUI] Login failed, need register: $e'); + } + + state = state.copyWith(isConnecting: false); + } catch (e) { + state = state.copyWith(isConnecting: false, errorMessage: '连接失败: $e'); + rethrow; + } + } + + /// 请求注册验证码 + Future requestPreRegister(String gameUserId) async { + state = state.copyWith(isLoading: true, errorMessage: null, registerGameUserId: gameUserId); + + try { + final partyRoom = ref.read(partyRoomProvider.notifier); + final response = await partyRoom.preRegister(gameUserId); + + state = state.copyWith(isLoading: false, preRegisterCode: response.verificationCode); + } catch (e) { + state = state.copyWith(isLoading: false, errorMessage: '获取验证码失败: $e'); + rethrow; + } + } + + /// 完成注册 + Future completeRegister() async { + if (state.registerGameUserId.isEmpty) { + throw Exception('游戏ID不能为空'); + } + + state = state.copyWith(isLoading: true, errorMessage: null); + + try { + final partyRoom = ref.read(partyRoomProvider.notifier); + await partyRoom.register(state.registerGameUserId); + + // 注册成功,登录并加载数据 + await partyRoom.login(); + await partyRoom.loadTags(); + await loadRoomList(); + + state = state.copyWith(isLoading: false, showRoomList: true, preRegisterCode: '', registerGameUserId: ''); + } catch (e) { + state = state.copyWith(isLoading: false, errorMessage: '注册失败: $e'); + rethrow; + } + } + + /// 加载房间列表 + Future loadRoomList({ + String? mainTagId, + String? subTagId, + String? searchName, + int? page, + bool append = false, + }) async { + try { + state = state.copyWith(isLoading: true); + + // 更新筛选条件 + if (mainTagId != null) state = state.copyWith(selectedMainTagId: mainTagId); + if (subTagId != null) state = state.copyWith(selectedSubTagId: subTagId); + if (searchName != null) state = state.copyWith(searchOwnerName: searchName); + if (page != null) state = state.copyWith(currentPage: page); + + final partyRoom = ref.read(partyRoomProvider.notifier); + final response = await partyRoom.getRoomList( + mainTagId: state.selectedMainTagId, + subTagId: state.selectedSubTagId, + searchOwnerName: state.searchOwnerName, + page: state.currentPage, + pageSize: state.pageSize, + ); + + // 追加模式:合并数据,否则替换数据 + final newRooms = append ? [...state.roomListItems, ...response.rooms] : response.rooms; + + state = state.copyWith(isLoading: false, roomListItems: newRooms, totalRooms: response.total, errorMessage: null); + } catch (e) { + state = state.copyWith(isLoading: false, errorMessage: '加载房间列表失败: $e'); + } + } + + /// 加载更多房间(无限滑动) + Future loadMoreRooms() async { + final totalPages = (state.totalRooms / state.pageSize).ceil(); + if (state.currentPage >= totalPages || state.isLoading) return; + + await loadRoomList(page: state.currentPage + 1, append: true); + } + + /// 刷新房间列表 + Future refreshRoomList() async { + state = state.copyWith(currentPage: 1, roomListItems: []); + await loadRoomList(page: 1); + } + + /// 清除错误消息 + void clearError() { + state = state.copyWith(errorMessage: null); + } + + /// 断开连接 + Future disconnect() async { + final partyRoom = ref.read(partyRoomProvider.notifier); + await partyRoom.disconnect(); + + state = const PartyRoomUIState(); + } + + void setSelectedMainTagId(String? value) { + state = state.copyWith(selectedMainTagId: value); + refreshRoomList(); + } + + void dismissRoom() { + ref.read(partyRoomProvider.notifier).dismissRoom(); + ref.read(partyRoomProvider.notifier).loadTags(); + } +} diff --git a/lib/ui/party_room/party_room_ui_model.freezed.dart b/lib/ui/party_room/party_room_ui_model.freezed.dart new file mode 100644 index 0000000..99fd3ba --- /dev/null +++ b/lib/ui/party_room/party_room_ui_model.freezed.dart @@ -0,0 +1,313 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'party_room_ui_model.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$PartyRoomUIState { + + bool get isConnecting; bool get showRoomList; List get roomListItems; int get currentPage; int get pageSize; int get totalRooms; String? get selectedMainTagId; String? get selectedSubTagId; String get searchOwnerName; bool get isLoading; String? get errorMessage; String get preRegisterCode; String get registerGameUserId; bool get isReconnecting; int get reconnectAttempts; +/// Create a copy of PartyRoomUIState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PartyRoomUIStateCopyWith get copyWith => _$PartyRoomUIStateCopyWithImpl(this as PartyRoomUIState, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other.roomListItems, roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)); +} + + +@override +int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts); + +@override +String toString() { + return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts)'; +} + + +} + +/// @nodoc +abstract mixin class $PartyRoomUIStateCopyWith<$Res> { + factory $PartyRoomUIStateCopyWith(PartyRoomUIState value, $Res Function(PartyRoomUIState) _then) = _$PartyRoomUIStateCopyWithImpl; +@useResult +$Res call({ + bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts +}); + + + + +} +/// @nodoc +class _$PartyRoomUIStateCopyWithImpl<$Res> + implements $PartyRoomUIStateCopyWith<$Res> { + _$PartyRoomUIStateCopyWithImpl(this._self, this._then); + + final PartyRoomUIState _self; + final $Res Function(PartyRoomUIState) _then; + +/// Create a copy of PartyRoomUIState +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') @override $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,}) { + return _then(_self.copyWith( +isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable +as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable +as bool,roomListItems: null == roomListItems ? _self.roomListItems : roomListItems // ignore: cast_nullable_to_non_nullable +as List,currentPage: null == currentPage ? _self.currentPage : currentPage // ignore: cast_nullable_to_non_nullable +as int,pageSize: null == pageSize ? _self.pageSize : pageSize // ignore: cast_nullable_to_non_nullable +as int,totalRooms: null == totalRooms ? _self.totalRooms : totalRooms // ignore: cast_nullable_to_non_nullable +as int,selectedMainTagId: freezed == selectedMainTagId ? _self.selectedMainTagId : selectedMainTagId // ignore: cast_nullable_to_non_nullable +as String?,selectedSubTagId: freezed == selectedSubTagId ? _self.selectedSubTagId : selectedSubTagId // ignore: cast_nullable_to_non_nullable +as String?,searchOwnerName: null == searchOwnerName ? _self.searchOwnerName : searchOwnerName // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String?,preRegisterCode: null == preRegisterCode ? _self.preRegisterCode : preRegisterCode // ignore: cast_nullable_to_non_nullable +as String,registerGameUserId: null == registerGameUserId ? _self.registerGameUserId : registerGameUserId // ignore: cast_nullable_to_non_nullable +as String,isReconnecting: null == isReconnecting ? _self.isReconnecting : isReconnecting // ignore: cast_nullable_to_non_nullable +as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts : reconnectAttempts // ignore: cast_nullable_to_non_nullable +as int, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PartyRoomUIState]. +extension PartyRoomUIStatePatterns on PartyRoomUIState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PartyRoomUIState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PartyRoomUIState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PartyRoomUIState value) $default,){ +final _that = this; +switch (_that) { +case _PartyRoomUIState(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PartyRoomUIState value)? $default,){ +final _that = this; +switch (_that) { +case _PartyRoomUIState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts)? $default,{required TResult orElse(),}) {final _that = this; +switch (_that) { +case _PartyRoomUIState() when $default != null: +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts) $default,) {final _that = this; +switch (_that) { +case _PartyRoomUIState(): +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts)? $default,) {final _that = this; +switch (_that) { +case _PartyRoomUIState() when $default != null: +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts);case _: + return null; + +} +} + +} + +/// @nodoc + + +class _PartyRoomUIState implements PartyRoomUIState { + const _PartyRoomUIState({this.isConnecting = false, this.showRoomList = false, final List roomListItems = const [], this.currentPage = 1, this.pageSize = 20, this.totalRooms = 0, this.selectedMainTagId, this.selectedSubTagId, this.searchOwnerName = '', this.isLoading = false, this.errorMessage, this.preRegisterCode = '', this.registerGameUserId = '', this.isReconnecting = false, this.reconnectAttempts = 0}): _roomListItems = roomListItems; + + +@override@JsonKey() final bool isConnecting; +@override@JsonKey() final bool showRoomList; + final List _roomListItems; +@override@JsonKey() List get roomListItems { + if (_roomListItems is EqualUnmodifiableListView) return _roomListItems; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_roomListItems); +} + +@override@JsonKey() final int currentPage; +@override@JsonKey() final int pageSize; +@override@JsonKey() final int totalRooms; +@override final String? selectedMainTagId; +@override final String? selectedSubTagId; +@override@JsonKey() final String searchOwnerName; +@override@JsonKey() final bool isLoading; +@override final String? errorMessage; +@override@JsonKey() final String preRegisterCode; +@override@JsonKey() final String registerGameUserId; +@override@JsonKey() final bool isReconnecting; +@override@JsonKey() final int reconnectAttempts; + +/// Create a copy of PartyRoomUIState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PartyRoomUIStateCopyWith<_PartyRoomUIState> get copyWith => __$PartyRoomUIStateCopyWithImpl<_PartyRoomUIState>(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other._roomListItems, _roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)); +} + + +@override +int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(_roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts); + +@override +String toString() { + return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts)'; +} + + +} + +/// @nodoc +abstract mixin class _$PartyRoomUIStateCopyWith<$Res> implements $PartyRoomUIStateCopyWith<$Res> { + factory _$PartyRoomUIStateCopyWith(_PartyRoomUIState value, $Res Function(_PartyRoomUIState) _then) = __$PartyRoomUIStateCopyWithImpl; +@override @useResult +$Res call({ + bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts +}); + + + + +} +/// @nodoc +class __$PartyRoomUIStateCopyWithImpl<$Res> + implements _$PartyRoomUIStateCopyWith<$Res> { + __$PartyRoomUIStateCopyWithImpl(this._self, this._then); + + final _PartyRoomUIState _self; + final $Res Function(_PartyRoomUIState) _then; + +/// Create a copy of PartyRoomUIState +/// with the given fields replaced by the non-null parameter values. +@override @pragma('vm:prefer-inline') $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,}) { + return _then(_PartyRoomUIState( +isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable +as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable +as bool,roomListItems: null == roomListItems ? _self._roomListItems : roomListItems // ignore: cast_nullable_to_non_nullable +as List,currentPage: null == currentPage ? _self.currentPage : currentPage // ignore: cast_nullable_to_non_nullable +as int,pageSize: null == pageSize ? _self.pageSize : pageSize // ignore: cast_nullable_to_non_nullable +as int,totalRooms: null == totalRooms ? _self.totalRooms : totalRooms // ignore: cast_nullable_to_non_nullable +as int,selectedMainTagId: freezed == selectedMainTagId ? _self.selectedMainTagId : selectedMainTagId // ignore: cast_nullable_to_non_nullable +as String?,selectedSubTagId: freezed == selectedSubTagId ? _self.selectedSubTagId : selectedSubTagId // ignore: cast_nullable_to_non_nullable +as String?,searchOwnerName: null == searchOwnerName ? _self.searchOwnerName : searchOwnerName // ignore: cast_nullable_to_non_nullable +as String,isLoading: null == isLoading ? _self.isLoading : isLoading // ignore: cast_nullable_to_non_nullable +as bool,errorMessage: freezed == errorMessage ? _self.errorMessage : errorMessage // ignore: cast_nullable_to_non_nullable +as String?,preRegisterCode: null == preRegisterCode ? _self.preRegisterCode : preRegisterCode // ignore: cast_nullable_to_non_nullable +as String,registerGameUserId: null == registerGameUserId ? _self.registerGameUserId : registerGameUserId // ignore: cast_nullable_to_non_nullable +as String,isReconnecting: null == isReconnecting ? _self.isReconnecting : isReconnecting // ignore: cast_nullable_to_non_nullable +as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts : reconnectAttempts // ignore: cast_nullable_to_non_nullable +as int, + )); +} + + +} + +// dart format on diff --git a/lib/ui/party_room/party_room_ui_model.g.dart b/lib/ui/party_room/party_room_ui_model.g.dart new file mode 100644 index 0000000..4901ef5 --- /dev/null +++ b/lib/ui/party_room/party_room_ui_model.g.dart @@ -0,0 +1,63 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'party_room_ui_model.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(PartyRoomUIModel) +const partyRoomUIModelProvider = PartyRoomUIModelProvider._(); + +final class PartyRoomUIModelProvider + extends $NotifierProvider { + const PartyRoomUIModelProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'partyRoomUIModelProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$partyRoomUIModelHash(); + + @$internal + @override + PartyRoomUIModel create() => PartyRoomUIModel(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PartyRoomUIState value) { + return $ProviderOverride( + origin: this, + providerOverride: $SyncValueProvider(value), + ); + } +} + +String _$partyRoomUIModelHash() => r'262069d02bbc7d76fe6797c6c744bdf848122492'; + +abstract class _$PartyRoomUIModel extends $Notifier { + PartyRoomUIState build(); + @$mustCallSuper + @override + void runBuild() { + final created = build(); + final ref = this.ref as $Ref; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier, + PartyRoomUIState, + Object?, + Object? + >; + element.handleValue(ref, created); + } +} diff --git a/lib/ui/party_room/widgets/create_room_dialog.dart b/lib/ui/party_room/widgets/create_room_dialog.dart new file mode 100644 index 0000000..7fbc5ef --- /dev/null +++ b/lib/ui/party_room/widgets/create_room_dialog.dart @@ -0,0 +1,190 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/provider/party_room.dart'; + +/// 创建房间对话框 +class CreateRoomDialog extends HookConsumerWidget { + const CreateRoomDialog({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final partyRoomState = ref.watch(partyRoomProvider); + final partyRoom = ref.read(partyRoomProvider.notifier); + + final selectedMainTag = useState(null); + final selectedSubTag = useState(null); + final targetMembersController = useTextEditingController(text: '6'); + final hasPassword = useState(false); + final passwordController = useTextEditingController(); + final socialLinksController = useTextEditingController(); + final isCreating = useState(false); + + return ContentDialog( + constraints: const BoxConstraints(maxWidth: 500), + title: const Text('创建房间'), + content: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + InfoLabel( + label: '房间类型', + child: ComboBox( + placeholder: const Text('选择主标签'), + value: selectedMainTag.value, + items: partyRoomState.room.tags.map((tag) { + return ComboBoxItem(value: tag.id, child: Text(tag.name)); + }).toList(), + onChanged: (value) { + selectedMainTag.value = value; + selectedSubTag.value = null; + }, + ), + ), + const SizedBox(height: 12), + + if (selectedMainTag.value != null) ...[ + InfoLabel( + label: '子标签 (可选)', + child: ComboBox( + placeholder: const Text('选择子标签'), + value: selectedSubTag.value, + items: [ + const ComboBoxItem(value: null, child: Text('无')), + ...partyRoomState.room.tags.firstWhere((tag) => tag.id == selectedMainTag.value).subTags.map(( + subTag, + ) { + return ComboBoxItem(value: subTag.id, child: Text(subTag.name)); + }), + ], + onChanged: (value) { + selectedSubTag.value = value; + }, + ), + ), + const SizedBox(height: 12), + ], + + InfoLabel( + label: '目标人数 (2-600)', + child: TextBox( + controller: targetMembersController, + placeholder: '输入目标人数', + keyboardType: TextInputType.number, + ), + ), + const SizedBox(height: 12), + + Row( + children: [ + Checkbox( + checked: hasPassword.value, + onChanged: (value) { + hasPassword.value = value ?? false; + }, + content: const Text('设置密码'), + ), + ], + ), + if (hasPassword.value) ...[ + const SizedBox(height: 8), + InfoLabel( + label: '房间密码', + child: TextBox(controller: passwordController, placeholder: '输入密码', obscureText: true), + ), + ], + const SizedBox(height: 12), + + InfoLabel( + label: '社交链接 (可选)', + child: TextBox(controller: socialLinksController, placeholder: 'https://discord.gg/xxxxx', maxLines: 1), + ), + ], + ), + ), + actions: [ + FilledButton( + onPressed: isCreating.value + ? null + : () async { + final mainTagId = selectedMainTag.value; + if (mainTagId == null || mainTagId.isEmpty) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('提示'), + content: const Text('请选择房间类型'), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + return; + } + + final targetMembers = int.tryParse(targetMembersController.text); + if (targetMembers == null || targetMembers < 2 || targetMembers > 600) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('提示'), + content: const Text('目标人数必须在 2-600 之间'), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + return; + } + + if (hasPassword.value && passwordController.text.trim().isEmpty) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('提示'), + content: const Text('请输入密码'), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + return; + } + + final socialLinks = socialLinksController.text + .split('\n') + .where((link) => link.trim().isNotEmpty && link.trim().startsWith('http')) + .toList(); + + isCreating.value = true; + try { + 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) { + Navigator.pop(context); + } + } catch (e) { + isCreating.value = false; + if (context.mounted) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('创建失败'), + content: Text(e.toString()), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + } + } + }, + child: isCreating.value + ? const SizedBox(width: 16, height: 16, child: ProgressRing(strokeWidth: 2)) + : const Text('创建'), + ), + Button(onPressed: isCreating.value ? null : () => Navigator.pop(context), child: const Text('取消')), + ], + ); + } +} diff --git a/lib/ui/party_room/widgets/party_room_connect_page.dart b/lib/ui/party_room/widgets/party_room_connect_page.dart new file mode 100644 index 0000000..077d79a --- /dev/null +++ b/lib/ui/party_room/widgets/party_room_connect_page.dart @@ -0,0 +1,98 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/ui/party_room/party_room_ui_model.dart'; + +/// 连接服务器页面 +class PartyRoomConnectPage extends HookConsumerWidget { + const PartyRoomConnectPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final uiModel = ref.read(partyRoomUIModelProvider.notifier); + final uiState = ref.watch(partyRoomUIModelProvider); + + return ScaffoldPage( + padding: EdgeInsets.zero, + content: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.black.withValues(alpha: 0.3), Colors.black.withValues(alpha: 0.6)], + ), + ), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Logo 或图标 + Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: const Color(0xFF1E3A5F).withValues(alpha: 0.6), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow(color: const Color(0xFF4A9EFF).withValues(alpha: 0.3), blurRadius: 30, spreadRadius: 5), + ], + ), + child: const Icon(FluentIcons.group, size: 64, color: Color(0xFF4A9EFF)), + ), + const SizedBox(height: 32), + + // 标题 + const Text( + '组队大厅', + style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0), letterSpacing: 2), + ), + const SizedBox(height: 12), + + // 副标题 + Text('正在连接服务器...', style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: 0.7))), + const SizedBox(height: 32), + + // 加载动画 + const SizedBox(width: 40, height: 40, child: ProgressRing(strokeWidth: 3)), + const SizedBox(height: 32), + + if (uiState.errorMessage != null) ...[ + Container( + constraints: const BoxConstraints(maxWidth: 400), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF3D1E1E).withValues(alpha: 0.8), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFFFF6B6B), width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Row( + children: [ + Icon(FluentIcons.error_badge, color: Color(0xFFFF6B6B), size: 16), + SizedBox(width: 8), + Text( + '连接失败', + style: TextStyle(color: Color(0xFFFF6B6B), fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 8), + Text(uiState.errorMessage!, style: const TextStyle(color: Color(0xFFE0E0E0))), + const SizedBox(height: 12), + FilledButton( + onPressed: () async { + await uiModel.connectToServer(); + }, + child: const Text('重试'), + ), + ], + ), + ), + ], + ], + ), + ), + ), + ); + } +} diff --git a/lib/ui/party_room/widgets/party_room_detail_page.dart b/lib/ui/party_room/widgets/party_room_detail_page.dart new file mode 100644 index 0000000..14927e2 --- /dev/null +++ b/lib/ui/party_room/widgets/party_room_detail_page.dart @@ -0,0 +1,689 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/common/conf/url_conf.dart'; +import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.dart' as partroom; +import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.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/widgets/src/cache_image.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +/// 房间详情页面 (Discord 样式) +class PartyRoomDetailPage extends ConsumerStatefulWidget { + const PartyRoomDetailPage({super.key}); + + @override + ConsumerState createState() => _PartyRoomDetailPageState(); +} + +class _PartyRoomDetailPageState extends ConsumerState { + final ScrollController _scrollController = ScrollController(); + int _lastEventCount = 0; + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _scrollToBottom() { + if (_scrollController.hasClients) { + Future.delayed(const Duration(milliseconds: 100), () { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + }); + } + } + + @override + Widget build(BuildContext context) { + final partyRoomState = ref.watch(partyRoomProvider); + final partyRoom = ref.read(partyRoomProvider.notifier); + final room = partyRoomState.room.currentRoom; + final members = partyRoomState.room.members; + final isOwner = partyRoomState.room.isOwner; + final events = partyRoomState.room.recentEvents; + + // 检测消息数量变化,触发滚动 + if (events.length != _lastEventCount) { + _lastEventCount = events.length; + if (events.isNotEmpty) { + _scrollToBottom(); + } + } + + return ScaffoldPage( + padding: EdgeInsets.zero, + content: Row( + children: [ + // 左侧成员列表 (类似 Discord 侧边栏) + Container( + width: 240, + decoration: BoxDecoration( + color: Color(0xFF232428).withValues(alpha: .3), + border: Border(right: BorderSide(color: Colors.black.withValues(alpha: 0.3), width: 1)), + ), + child: Column( + children: [ + // 房间信息头部 + _buildRoomHeader(context, room, members, isOwner, partyRoom), + const Divider( + style: DividerThemeData(thickness: 1, decoration: BoxDecoration(color: Color(0xFF1E1F22))), + ), + // 成员列表 + Expanded(child: _buildMembersSidebar(context, ref, members, isOwner, partyRoom)), + ], + ), + ), + // 右侧消息区域 + Expanded( + child: Column( + children: [ + // 消息列表 + Expanded(child: _buildMessageList(context, events, _scrollController, ref)), + // 信号发送按钮 + _buildSignalSender(context, ref, partyRoom, room), + ], + ), + ), + ], + ), + ); + } + + // 房间信息头部 + Widget _buildRoomHeader(BuildContext context, dynamic room, List members, bool isOwner, PartyRoom partyRoom) { + return Container( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(FluentIcons.room, size: 16, color: Color(0xFFB5BAC1)), + const SizedBox(width: 8), + Expanded( + child: Text( + room?.ownerGameId ?? '房间', + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + const Icon(FluentIcons.group, size: 12, color: Color(0xFF80848E)), + const SizedBox(width: 4), + Text( + '${members.length}/${room?.targetMembers ?? 0} 成员', + style: const TextStyle(fontSize: 11, color: Color(0xFF80848E)), + ), + ], + ), + if (isOwner) ...[ + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: Button( + onPressed: () async { + final confirmed = await showDialog( + 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 ...[ + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: Button( + onPressed: () async { + await partyRoom.leaveRoom(); + }, + style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFF404249))), + child: const Text('离开房间', style: TextStyle(color: Color(0xFFB5BAC1))), + ), + ), + ], + ], + ), + ); + } + + IconData _getSocialIcon(String link) { + if (link.contains('qq.com')) return FontAwesomeIcons.qq; + if (link.contains('discord')) return FontAwesomeIcons.discord; + if (link.contains('kook')) return FluentIcons.chat; + return FluentIcons.link; + } + + String _getSocialName(String link) { + if (link.contains('discord')) return 'Discord'; + if (link.contains('kook')) return 'KOOK'; + if (link.contains('qq')) return 'QQ'; + return '链接'; + } + + // 成员侧边栏 + Widget _buildMembersSidebar(BuildContext context, WidgetRef ref, List members, bool isOwner, PartyRoom partyRoom) { + if (members.isEmpty) { + return Center( + child: Text('暂无成员', style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 12)), + ); + } + + return ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: members.length, + itemBuilder: (context, index) { + final member = members[index]; + return _buildMemberItem(context, ref, member, isOwner, partyRoom); + }, + ); + } + + Widget _buildMemberItem(BuildContext context, WidgetRef ref, RoomMember member, bool isOwner, PartyRoom partyRoom) { + final avatarUrl = member.avatarUrl.isNotEmpty ? '${URLConf.rsiAvatarBaseUrl}${member.avatarUrl}' : null; + + return Container( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), + child: HoverButton( + onPressed: isOwner && !member.isOwner ? () => _showMemberContextMenu(context, member, partyRoom) : null, + builder: (context, states) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: states.isHovered ? const Color(0xFF404249) : Colors.transparent, + borderRadius: BorderRadius.circular(4), + ), + child: Row( + children: [ + // 头像 + makeUserAvatar(member.handleName, avatarUrl: avatarUrl, size: 32), + const SizedBox(width: 8), + // 名称和状态 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: Text( + member.handleName.isNotEmpty ? member.handleName : member.gameUserId, + style: TextStyle( + fontSize: 13, + color: member.isOwner ? const Color(0xFFFAA81A) : const Color(0xFFDBDEE1), + fontWeight: member.isOwner ? FontWeight.bold : FontWeight.normal, + ), + overflow: TextOverflow.ellipsis, + ), + ), + if (member.isOwner) ...[ + const SizedBox(width: 4), + const Icon(FluentIcons.crown, size: 10, color: Color(0xFFFAA81A)), + ], + ], + ), + if (member.status.currentLocation.isNotEmpty) + Text( + member.status.currentLocation, + style: const TextStyle(fontSize: 10, color: Color(0xFF80848E)), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + // 状态指示器 + Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Color(0xFF23A559), // 在线绿色 + shape: BoxShape.circle, + ), + ), + ], + ), + ); + }, + ), + ); + } + + Widget makeUserAvatar(String memberName, {String? avatarUrl, double size = 32}) { + return SizedBox( + width: size, + height: size, + child: avatarUrl == null + ? CircleAvatar( + radius: 16, + backgroundColor: const Color(0xFF5865F2), + child: Text( + memberName.toUpperCase(), + style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), + ), + ) + : ClipRRect( + borderRadius: BorderRadius.circular(100), + child: CacheNetImage(url: avatarUrl), + ), + ); + } + + void _showMemberContextMenu(BuildContext context, dynamic member, PartyRoom partyRoom) async { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: Text(member.handleName.isNotEmpty ? member.handleName : member.gameUserId), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + FilledButton( + onPressed: () async { + Navigator.pop(context); + final confirmed = await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('转移房主'), + content: Text( + '确定要将房主转移给 ${member.handleName.isNotEmpty ? member.handleName : member.gameUserId} 吗?', + ), + actions: [ + Button(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)), + FilledButton(child: const Text('转移'), onPressed: () => Navigator.pop(context, true)), + ], + ), + ); + if (confirmed == true) { + await partyRoom.transferOwnership(member.gameUserId); + } + }, + child: const Text('转移房主'), + ), + const SizedBox(height: 8), + Button( + onPressed: () async { + Navigator.pop(context); + final confirmed = await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('踢出成员'), + content: Text('确定要踢出 ${member.handleName.isNotEmpty ? member.handleName : member.gameUserId} 吗?'), + actions: [ + Button(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)), + FilledButton( + style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFFDA373C))), + child: const Text('踢出'), + onPressed: () => Navigator.pop(context, true), + ), + ], + ), + ); + if (confirmed == true) { + await partyRoom.kickMember(member.gameUserId); + } + }, + style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFFDA373C))), + child: const Text('踢出成员', style: TextStyle(color: Colors.white)), + ), + ], + ), + actions: [Button(child: const Text('关闭'), onPressed: () => Navigator.pop(context))], + ), + ); + } + + // 消息列表 + Widget _buildMessageList(BuildContext context, List events, ScrollController scrollController, WidgetRef ref) { + final partyRoomState = ref.watch(partyRoomProvider); + final room = partyRoomState.room.currentRoom; + final hasSocialLinks = room != null && room.socialLinks.isNotEmpty; + + // 计算总项数:社交链接消息(如果有)+ 事件消息 + final totalItems = (hasSocialLinks ? 1 : 0) + events.length; + + if (totalItems == 0) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(FluentIcons.chat, size: 64, color: Color(0xFF404249)), + const SizedBox(height: 16), + Text('暂无消息', style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 14)), + const SizedBox(height: 4), + Text('发送一条信号开始对话吧!', style: TextStyle(color: Colors.white.withValues(alpha: 0.3), fontSize: 12)), + ], + ), + ); + } + + return ListView.builder( + controller: scrollController, + padding: const EdgeInsets.all(16), + itemCount: totalItems, + itemBuilder: (context, index) { + // 第一条消息显示社交链接(如果有) + if (hasSocialLinks && index == 0) { + return _buildSocialLinksMessage(room); + } + + // 其他消息显示事件 + final eventIndex = hasSocialLinks ? index - 1 : index; + final event = events[eventIndex]; + return _buildMessageItem(event, ref); + }, + ); + } + + // 社交链接系统消息 + Widget _buildSocialLinksMessage(dynamic room) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFF2B2D31), + border: Border.all(color: const Color(0xFF5865F2).withValues(alpha: 0.3)), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: const BoxDecoration(color: Color(0xFF5865F2), shape: BoxShape.circle), + child: const Icon(FluentIcons.info, size: 14, color: Colors.white), + ), + const SizedBox(width: 12), + const Expanded( + child: Text( + '该房间包含第三方社交连接,点击加入一起开黑吧~', + style: TextStyle(fontSize: 14, color: Color(0xFFDBDEE1), fontWeight: FontWeight.w500), + ), + ), + ], + ), + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 8, + children: room.socialLinks.map((link) { + return HyperlinkButton( + onPressed: () => launchUrlString(link), + style: ButtonStyle( + padding: WidgetStateProperty.all(const EdgeInsets.symmetric(horizontal: 12, vertical: 8)), + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.isHovered) return const Color(0xFF4752C4); + return const Color(0xFF5865F2); + }), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(_getSocialIcon(link), size: 16, color: Colors.white), + const SizedBox(width: 8), + Text( + _getSocialName(link), + style: const TextStyle(fontSize: 13, color: Colors.white, fontWeight: FontWeight.w500), + ), + ], + ), + ); + }).toList(), + ), + ], + ), + ); + } + + Widget _buildMessageItem(dynamic event, WidgetRef ref) { + final roomEvent = event as partroom.RoomEvent; + final isSignal = roomEvent.type == partroom.RoomEventType.SIGNAL_BROADCAST; + final userName = _getEventUserName(roomEvent); + final avatarUrl = _getEventAvatarUrl(roomEvent); + + return Container( + margin: const EdgeInsets.only(bottom: 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + makeUserAvatar(userName, avatarUrl: avatarUrl, size: 28), + const SizedBox(width: 12), + // 消息内容 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + userName, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isSignal ? Colors.white : const Color(0xFF80848E), + ), + ), + const SizedBox(width: 8), + Text( + _formatTime(roomEvent.timestamp), + style: const TextStyle(fontSize: 11, color: Color(0xFF80848E)), + ), + ], + ), + const SizedBox(height: 4), + Text( + _getEventText(roomEvent, ref), + style: TextStyle( + fontSize: 14, + color: isSignal ? const Color(0xFFDBDEE1) : const Color(0xFF949BA4), + fontStyle: isSignal ? FontStyle.normal : FontStyle.italic, + ), + ), + ], + ), + ), + ], + ), + ); + } + + String _getEventUserName(partroom.RoomEvent event) { + switch (event.type) { + case partroom.RoomEventType.SIGNAL_BROADCAST: + return event.signalSender.isNotEmpty ? event.signalSender : '未知用户'; + case partroom.RoomEventType.MEMBER_JOINED: + case partroom.RoomEventType.MEMBER_LEFT: + case partroom.RoomEventType.MEMBER_KICKED: + return event.hasMember() && event.member.handleName.isNotEmpty + ? event.member.handleName + : event.hasMember() + ? event.member.gameUserId + : '未知用户'; + case partroom.RoomEventType.OWNER_CHANGED: + return event.hasMember() && event.member.handleName.isNotEmpty ? event.member.handleName : '新房主'; + default: + return '系统'; + } + } + + String? _getEventAvatarUrl(partroom.RoomEvent event) { + if (event.type == partroom.RoomEventType.SIGNAL_BROADCAST || + event.type == partroom.RoomEventType.MEMBER_JOINED || + event.type == partroom.RoomEventType.MEMBER_LEFT || + event.type == partroom.RoomEventType.MEMBER_KICKED || + event.type == partroom.RoomEventType.OWNER_CHANGED) { + if (event.hasMember() && event.member.avatarUrl.isNotEmpty) { + return '${URLConf.rsiAvatarBaseUrl}${event.member.avatarUrl}'; + } + } + return null; + } + + // 信号发送器 + Widget _buildSignalSender(BuildContext context, WidgetRef ref, PartyRoom partyRoom, dynamic room) { + final partyRoomState = ref.watch(partyRoomProvider); + final signalTypes = partyRoomState.room.signalTypes.where((s) => !s.isSpecial).toList(); + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF2B2D31).withValues(alpha: .4), + border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.3))), + ), + child: Row( + children: [ + const Spacer(), + DropDownButton( + leading: const Icon(FluentIcons.send, size: 16), + title: Text(signalTypes.isEmpty ? '加载中...' : '发送信号'), + disabled: signalTypes.isEmpty || room == null, + items: signalTypes.map((signal) { + return MenuFlyoutItem( + leading: const Icon(FluentIcons.radio_bullet, size: 16), + text: Text(signal.name.isNotEmpty ? signal.name : signal.id), + onPressed: () => _sendSignal(context, ref, partyRoom, room, signal), + ); + }).toList(), + ), + ], + ), + ); + } + + Future _sendSignal( + BuildContext context, + WidgetRef ref, + PartyRoom partyRoom, + dynamic room, + dynamic signal, + ) async { + if (room == null) return; + + try { + await partyRoom.sendSignal(signal.id); + + // 发送成功后,显示在消息列表中 + if (context.mounted) { + // 信号已发送,会通过事件流更新到消息列表 + } + } catch (e) { + // 显示错误提示 + if (context.mounted) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('发送失败'), + content: Text(e.toString()), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + } + } + } + + String _getEventText(partroom.RoomEvent event, WidgetRef ref) { + final partyRoomState = ref.read(partyRoomProvider); + final signalTypes = partyRoomState.room.signalTypes; + switch (event.type) { + case partroom.RoomEventType.SIGNAL_BROADCAST: + // 从 signalTypes 提取信号名称 + final signalType = signalTypes.where((s) => s.id == event.signalId).firstOrNull; + // 显示信号ID和参数 + if (event.signalId.isNotEmpty) { + if (event.signalParams.isNotEmpty) { + final params = event.signalParams; + return "signalId: ${event.signalId},params:$params"; + } + } + return signalType?.name ?? event.signalId; + + case partroom.RoomEventType.MEMBER_JOINED: + return '加入了房间'; + + case partroom.RoomEventType.MEMBER_LEFT: + return '离开了房间'; + + case partroom.RoomEventType.OWNER_CHANGED: + return '成为了新房主'; + + case partroom.RoomEventType.ROOM_UPDATED: + return '房间信息已更新'; + + case partroom.RoomEventType.MEMBER_STATUS_UPDATED: + if (event.hasMember()) { + final member = event.member; + final name = member.handleName.isNotEmpty ? member.handleName : member.gameUserId; + if (member.hasStatus() && member.status.currentLocation.isNotEmpty) { + return '$name 更新了状态: ${member.status.currentLocation}'; + } + return '$name 更新了状态'; + } + return '成员状态已更新'; + + case partroom.RoomEventType.ROOM_DISMISSED: + return '房间已解散'; + + case partroom.RoomEventType.MEMBER_KICKED: + return '被踢出房间'; + + default: + return '未知事件'; + } + } + + String _formatTime(dynamic timestamp) { + try { + final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt() * 1000); + final now = DateTime.now(); + final diff = now.difference(date); + + if (diff.inMinutes < 1) { + return '刚刚'; + } else if (diff.inMinutes < 60) { + return '${diff.inMinutes} 分钟前'; + } else if (diff.inHours < 24) { + return '${diff.inHours} 小时前'; + } else { + return '${diff.inDays} 天前'; + } + } catch (e) { + return ''; + } + } +} diff --git a/lib/ui/party_room/widgets/party_room_list_page.dart b/lib/ui/party_room/widgets/party_room_list_page.dart new file mode 100644 index 0000000..6dcf57d --- /dev/null +++ b/lib/ui/party_room/widgets/party_room_list_page.dart @@ -0,0 +1,371 @@ +import 'dart:ui'; + +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; +import 'package:flutter_tilt/flutter_tilt.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/common/conf/url_conf.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/widgets/create_room_dialog.dart'; +import 'package:starcitizen_doctor/widgets/widgets.dart'; + +/// 房间列表页面 +class PartyRoomListPage extends HookConsumerWidget { + const PartyRoomListPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final uiModel = ref.read(partyRoomUIModelProvider.notifier); + final uiState = ref.watch(partyRoomUIModelProvider); + final partyRoomState = ref.watch(partyRoomProvider); + final partyRoom = ref.read(partyRoomProvider.notifier); + + final searchController = useTextEditingController(); + final scrollController = useScrollController(); + + useEffect(() { + // 初次加载房间列表 + Future.microtask(() => uiModel.loadRoomList()); + return null; + }, []); + + // 无限滑动监听 + useEffect(() { + void onScroll() { + if (scrollController.position.pixels >= scrollController.position.maxScrollExtent - 200) { + // 距离底部200px时开始加载 + final totalPages = (uiState.totalRooms / uiState.pageSize).ceil(); + if (!uiState.isLoading && uiState.currentPage < totalPages && uiState.errorMessage == null) { + uiModel.loadMoreRooms(); + } + } + } + + scrollController.addListener(onScroll); + return () => scrollController.removeListener(onScroll); + }, [uiState.isLoading, uiState.currentPage, uiState.totalRooms]); + + return ScaffoldPage( + padding: EdgeInsets.zero, + content: Column( + children: [ + // 筛选栏 + Container( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Expanded( + child: TextBox( + controller: searchController, + placeholder: '搜索房主名称...', + prefix: const Padding(padding: EdgeInsets.only(left: 8), child: Icon(FluentIcons.search)), + onSubmitted: (value) { + uiModel.loadRoomList(searchName: value, page: 1); + }, + ), + ), + const SizedBox(width: 12), + _buildTagFilter(context, ref, uiState, partyRoomState), + const SizedBox(width: 12), + IconButton(icon: const Icon(FluentIcons.refresh), onPressed: () => uiModel.refreshRoomList()), + const SizedBox(width: 12), + FilledButton( + onPressed: () => _showCreateRoomDialog(context, ref), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [Icon(FluentIcons.add, size: 16), SizedBox(width: 8), Text('创建房间')], + ), + ), + ], + ), + ), + + // 房间列表 + Expanded(child: _buildRoomList(context, ref, uiState, partyRoom, scrollController)), + ], + ), + ); + } + + Widget _buildTagFilter( + BuildContext context, + WidgetRef ref, + PartyRoomUIState uiState, + PartyRoomFullState partyRoomState, + ) { + final tags = partyRoomState.room.tags; + + return ComboBox( + placeholder: const Text('选择标签'), + value: uiState.selectedMainTagId, + items: [ + const ComboBoxItem(value: null, child: Text('全部标签')), + ...tags.map((tag) => ComboBoxItem(value: tag.id, child: Text(tag.name))), + ], + onChanged: (value) { + ref.read(partyRoomUIModelProvider.notifier).setSelectedMainTagId(value); + }, + ); + } + + Widget _buildRoomList( + BuildContext context, + WidgetRef ref, + PartyRoomUIState uiState, + PartyRoom partyRoom, + ScrollController scrollController, + ) { + if (uiState.isLoading && uiState.roomListItems.isEmpty) { + return const Center(child: ProgressRing()); + } + + if (uiState.errorMessage != null && uiState.roomListItems.isEmpty) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(FluentIcons.error, size: 48, color: Color(0xFFFF6B6B)), + const SizedBox(height: 16), + Text(uiState.errorMessage!, style: const TextStyle(color: Color(0xFFE0E0E0))), + const SizedBox(height: 16), + FilledButton( + onPressed: () { + ref.read(partyRoomUIModelProvider.notifier).refreshRoomList(); + }, + child: const Text('重试'), + ), + ], + ), + ); + } + + if (uiState.roomListItems.isEmpty) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(FluentIcons.room, size: 48, color: Colors.grey.withValues(alpha: 0.6)), + const SizedBox(height: 16), + Text('暂无房间', style: TextStyle(color: Colors.white.withValues(alpha: 0.7))), + const SizedBox(height: 8), + Text('成为第一个创建房间的人吧!', style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.5))), + const SizedBox(height: 16), + FilledButton(onPressed: () => _showCreateRoomDialog(context, ref), child: const Text('创建房间')), + ], + ), + ); + } + + final totalPages = (uiState.totalRooms / uiState.pageSize).ceil(); + final hasMore = uiState.currentPage < totalPages; + + return MasonryGridView.count( + controller: scrollController, + crossAxisCount: 3, + mainAxisSpacing: 12, + crossAxisSpacing: 12, + itemCount: uiState.roomListItems.length + (hasMore || uiState.isLoading ? 1 : 0), + padding: const EdgeInsets.all(16), + itemBuilder: (context, index) { + // 显示加载更多指示器 + if (index == uiState.roomListItems.length) { + return Container( + padding: const EdgeInsets.all(24), + child: Center( + child: uiState.isLoading + ? const ProgressRing() + : Text('已加载全部房间', style: TextStyle(color: Colors.white.withValues(alpha: 0.5))), + ), + ); + } + + final room = uiState.roomListItems[index]; + return _buildRoomCard(context, ref, partyRoom, room, index); + }, + ); + } + + Widget _buildRoomCard(BuildContext context, WidgetRef ref, PartyRoom partyRoom, dynamic room, int index) { + final avatarUrl = room.ownerAvatar.isNotEmpty ? '${URLConf.rsiAvatarBaseUrl}${room.ownerAvatar}' : ''; + + return GridItemAnimator( + index: index, + child: GestureDetector( + onTap: () => _joinRoom(context, ref, partyRoom, room), + child: Tilt( + shadowConfig: const ShadowConfig(maxIntensity: .3), + borderRadius: BorderRadius.circular(12), + clipBehavior: Clip.hardEdge, + child: Container( + decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)), + clipBehavior: Clip.hardEdge, + child: Stack( + children: [ + // 背景图片 + if (avatarUrl.isNotEmpty) + Positioned.fill( + child: CacheNetImage(url: avatarUrl, fit: BoxFit.cover), + ), + // 黑色遮罩 + Positioned.fill( + child: Container(decoration: BoxDecoration(color: Colors.black.withValues(alpha: 0.6))), + ), + // 模糊效果 + Positioned.fill( + child: ClipRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 15.0, sigmaY: 15.0), + child: Container(color: Colors.transparent), + ), + ), + ), + // 内容 + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + // 头像和房主信息 + Row( + children: [ + CircleAvatar( + radius: 24, + backgroundColor: const Color(0xFF4A9EFF).withValues(alpha: 0.5), + backgroundImage: avatarUrl.isNotEmpty ? NetworkImage(avatarUrl) : null, + child: avatarUrl.isEmpty ? const Icon(FluentIcons.contact, color: Colors.white) : null, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: Text( + room.ownerHandleName.isNotEmpty ? room.ownerHandleName : room.ownerGameId, + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.white, + fontSize: 14, + ), + overflow: TextOverflow.ellipsis, + ), + ), + if (room.hasPassword) ...[ + const SizedBox(width: 4), + Icon(FluentIcons.lock, size: 12, color: Colors.white.withValues(alpha: 0.7)), + ], + ], + ), + const SizedBox(height: 2), + Row( + children: [ + Icon(FluentIcons.group, size: 11, color: Colors.white.withValues(alpha: 0.6)), + const SizedBox(width: 4), + Text( + '${room.currentMembers}/${room.targetMembers}', + style: TextStyle(fontSize: 11, color: Colors.white.withValues(alpha: 0.7)), + ), + ], + ), + ], + ), + ), + ], + ), + const SizedBox(height: 12), + // 标签和时间 + Wrap( + spacing: 6, + runSpacing: 6, + children: [ + if (room.mainTagId.isNotEmpty) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFF4A9EFF).withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + room.mainTagId, + style: const TextStyle(fontSize: 11, color: Color(0xFF4A9EFF)), + ), + ), + if (room.socialLinks.isNotEmpty) + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: Colors.green.withValues(alpha: 0.2), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(FluentIcons.link, size: 10, color: Colors.green.withValues(alpha: 0.8)), + const SizedBox(width: 4), + Text( + '${room.socialLinks.length}', + style: TextStyle(fontSize: 11, color: Colors.green.withValues(alpha: 0.9)), + ), + ], + ), + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + Future _showCreateRoomDialog(BuildContext context, WidgetRef ref) async { + await showDialog(context: context, builder: (context) => const CreateRoomDialog()); + } + + Future _joinRoom(BuildContext context, WidgetRef ref, PartyRoom partyRoom, dynamic room) async { + String? password; + + if (room.hasPassword) { + password = await showDialog( + context: context, + builder: (context) { + final passwordController = TextEditingController(); + return ContentDialog( + title: const Text('输入房间密码'), + content: TextBox(controller: passwordController, placeholder: '请输入密码', obscureText: true), + actions: [ + Button(child: const Text('取消'), onPressed: () => Navigator.pop(context)), + FilledButton(child: const Text('加入'), onPressed: () => Navigator.pop(context, passwordController.text)), + ], + ); + }, + ); + + if (password == null) return; + } + + try { + await partyRoom.joinRoom(room.roomUuid, password: password); + } catch (e) { + if (context.mounted) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('加入失败'), + content: Text(e.toString()), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + } + } + } +} diff --git a/lib/ui/party_room/widgets/party_room_register_page.dart b/lib/ui/party_room/widgets/party_room_register_page.dart new file mode 100644 index 0000000..467043f --- /dev/null +++ b/lib/ui/party_room/widgets/party_room_register_page.dart @@ -0,0 +1,364 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/ui/party_room/party_room_ui_model.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +/// 注册页面 +class PartyRoomRegisterPage extends HookConsumerWidget { + const PartyRoomRegisterPage({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final uiModel = ref.read(partyRoomUIModelProvider.notifier); + final uiState = ref.watch(partyRoomUIModelProvider); + + final gameIdController = useTextEditingController(); + final currentStep = useState(0); + + return ScaffoldPage( + padding: EdgeInsets.zero, + content: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width * .6, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Expanded( + child: Text( + '注册账号', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + ), + ], + ), + const SizedBox(height: 24), + + if (uiState.errorMessage != null) ...[ + InfoBar( + title: const Text('错误'), + content: Text(uiState.errorMessage!), + severity: InfoBarSeverity.error, + onClose: () => uiModel.clearError(), + ), + const SizedBox(height: 16), + ], + + // 步骤指示器 + Row( + children: [ + _buildStepIndicator( + context, + number: 1, + title: '输入游戏ID', + isActive: currentStep.value == 0, + isCompleted: currentStep.value > 0, + ), + const Expanded(child: Divider()), + _buildStepIndicator( + context, + number: 2, + title: '验证RSI账号', + isActive: currentStep.value == 1, + isCompleted: currentStep.value > 1, + ), + const Expanded(child: Divider()), + _buildStepIndicator( + context, + number: 3, + title: '完成注册', + isActive: currentStep.value == 2, + isCompleted: false, + ), + ], + ), + const SizedBox(height: 24), + + if (currentStep.value == 0) ..._buildStep1(context, uiModel, uiState, gameIdController, currentStep), + + if (currentStep.value == 1) ..._buildStep2(context, uiModel, uiState, gameIdController, currentStep), + + if (currentStep.value == 2) ..._buildStep3(context, uiModel, uiState, currentStep), + + const SizedBox(height: 16), + const Divider(), + const SizedBox(height: 16), + + InfoBar( + title: const Text('关于账号验证'), + content: const Text('接下来,您需要在 RSI 账号简介中添加验证码以证明账号所有权,验证通过后,您可以移除该验证码。'), + severity: InfoBarSeverity.info, + ), + ], + ), + ), + ), + ); + } + + static Widget _buildStepIndicator( + BuildContext context, { + required int number, + required String title, + required bool isActive, + required bool isCompleted, + }) { + return Column( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: isCompleted + ? const Color(0xFF4CAF50) + : isActive + ? const Color(0xFF4A9EFF) + : Colors.grey.withValues(alpha: 0.3), + shape: BoxShape.circle, + ), + child: Center( + child: isCompleted + ? const Icon(FluentIcons.check_mark, size: 16, color: Colors.white) + : Text( + '$number', + style: TextStyle( + color: isActive ? Colors.white : Colors.grey.withValues(alpha: 0.7), + fontWeight: FontWeight.bold, + ), + ), + ), + ), + const SizedBox(height: 4), + Text( + title, + style: TextStyle( + fontSize: 11, + color: isActive ? const Color(0xFF4A9EFF) : Colors.grey.withValues(alpha: 0.7), + fontWeight: isActive ? FontWeight.bold : FontWeight.normal, + ), + ), + ], + ); + } + + static List _buildStep1( + BuildContext context, + PartyRoomUIModel uiModel, + PartyRoomUIState uiState, + TextEditingController gameIdController, + ValueNotifier currentStep, + ) { + return [ + const Text( + '步骤 1: 输入您的游戏ID', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + const SizedBox(height: 12), + Text( + '请输入您在星际公民中的游戏ID(Handle),' + '这是您在游戏中使用的唯一标识符。', + style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: 0.6)), + ), + const SizedBox(height: 16), + + TextBox( + controller: gameIdController, + placeholder: '例如: Citizen123', + enabled: !uiState.isLoading, + onSubmitted: (value) async { + if (value.trim().isEmpty) return; + await _requestVerificationCode(uiModel, uiState, value.trim(), currentStep); + }, + ), + const SizedBox(height: 16), + + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Button( + onPressed: () { + launchUrlString('https://robertsspaceindustries.com/en/account/dashboard'); + }, + child: const Text('查看我的游戏ID'), + ), + const SizedBox(width: 8), + FilledButton( + onPressed: uiState.isLoading + ? null + : () async { + final gameId = gameIdController.text.trim(); + if (gameId.isEmpty) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('提示'), + content: const Text('请输入游戏ID'), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + return; + } + await _requestVerificationCode(uiModel, uiState, gameId, currentStep); + }, + child: uiState.isLoading + ? const SizedBox(width: 16, height: 16, child: ProgressRing(strokeWidth: 2)) + : const Text('下一步'), + ), + ], + ), + ]; + } + + static Future _requestVerificationCode( + PartyRoomUIModel uiModel, + PartyRoomUIState uiState, + String gameId, + ValueNotifier currentStep, + ) async { + try { + await uiModel.requestPreRegister(gameId); + currentStep.value = 1; + } catch (e) { + // 错误已在 state 中设置 + } + } + + static List _buildStep2( + BuildContext context, + PartyRoomUIModel uiModel, + PartyRoomUIState uiState, + TextEditingController gameIdController, + ValueNotifier currentStep, + ) { + return [ + const Text( + '步骤 2: 验证 RSI 账号', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + const SizedBox(height: 12), + Text('请按照以下步骤完成账号验证:', style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: 0.6))), + const SizedBox(height: 16), + + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF1E3A5F).withValues(alpha: 0.3), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: const Color(0xFF4A9EFF).withValues(alpha: 0.3)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '1. 复制以下验证码:', + style: TextStyle(fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + const SizedBox(height: 8), + Row( + children: [ + SelectableText( + 'SCB:${uiState.preRegisterCode}', + style: const TextStyle( + fontSize: 16, + fontFamily: 'monospace', + fontWeight: FontWeight.bold, + color: Color(0xFF4A9EFF), + ), + ), + SizedBox(width: 12), + Button( + child: Icon(FluentIcons.copy), + onPressed: () { + Clipboard.setData(ClipboardData(text: 'SCB:${uiState.preRegisterCode}')); + }, + ), + ], + ), + const SizedBox(height: 16), + const Text( + '2. 访问您的 RSI 账号资设置页', + style: TextStyle(fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + const SizedBox(height: 8), + Button( + onPressed: () { + launchUrlString('https://robertsspaceindustries.com/en/account/profile'); + }, + child: const Text('打开资料页'), + ), + const SizedBox(height: 16), + const Text( + '3. 编辑您的个人简介,将验证码添加到简介中', + style: TextStyle(fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + const SizedBox(height: 8), + Text( + '在简介的任意位置添加验证码即可,验证码30分钟内有效', + style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.5)), + ), + ], + ), + ), + const SizedBox(height: 16), + + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Button( + onPressed: () { + currentStep.value = 0; + }, + child: const Text('上一步'), + ), + FilledButton( + onPressed: uiState.isLoading + ? null + : () async { + await _completeRegistration(uiModel, currentStep); + }, + child: uiState.isLoading + ? const SizedBox(width: 16, height: 16, child: ProgressRing(strokeWidth: 2)) + : const Text('我已添加,验证并注册'), + ), + ], + ), + ]; + } + + static Future _completeRegistration(PartyRoomUIModel uiModel, ValueNotifier currentStep) async { + try { + await uiModel.completeRegister(); + currentStep.value = 2; + } catch (e) { + // 错误已在 state 中设置 + } + } + + static List _buildStep3( + BuildContext context, + PartyRoomUIModel uiModel, + PartyRoomUIState uiState, + ValueNotifier currentStep, + ) { + return [ + Center( + child: Column( + children: [ + const Icon(FluentIcons.completed_solid, size: 64, color: Color(0xFF4CAF50)), + const SizedBox(height: 16), + const Text( + '注册成功!', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Color(0xFFE0E0E0)), + ), + const SizedBox(height: 8), + Text('您已成功注册组队大厅,现在可以开始使用了', style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: 0.6))), + ], + ), + ), + ]; + } +} diff --git a/pubspec.lock b/pubspec.lock index b652b5f..385e751 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -78,10 +78,10 @@ packages: dependency: transitive description: name: build - sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d + sha256: dfb67ccc9a78c642193e0c2d94cb9e48c2c818b3178a86097d644acdcde6a8d9 url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.0.2" build_cli_annotations: dependency: transitive description: @@ -106,30 +106,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" - build_resolvers: - dependency: transitive - description: - name: build_resolvers - sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46 - url: "https://pub.dev" - source: hosted - version: "3.0.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30 + sha256: "7b5b569f3df370590a85029148d6fc66c7d0201fc6f1847c07dd85d365ae9fcd" url: "https://pub.dev" source: hosted - version: "2.7.1" - build_runner_core: - dependency: transitive - description: - name: build_runner_core - sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b" - url: "https://pub.dev" - source: hosted - version: "9.3.1" + version: "2.10.3" built_collection: dependency: transitive description: @@ -606,6 +590,22 @@ packages: url: "https://pub.dev" source: hosted version: "17.0.0" + google_identity_services_web: + dependency: transitive + description: + name: google_identity_services_web + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" + url: "https://pub.dev" + source: hosted + version: "0.3.3+1" + googleapis_auth: + dependency: transitive + description: + name: googleapis_auth + sha256: b81fe352cc4a330b3710d2b7ad258d9bcef6f909bb759b306bf42973a7d046db + url: "https://pub.dev" + source: hosted + version: "2.0.0" graphs: dependency: transitive description: @@ -614,6 +614,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" + grpc: + dependency: "direct main" + description: + name: grpc + sha256: d5711432e7fcb41d23f88461651d503db98661af73ac7df211ace9c92fd03753 + url: "https://pub.dev" + source: hosted + version: "5.0.0" hexcolor: dependency: "direct main" description: @@ -670,6 +678,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.6.0" + http2: + dependency: transitive + description: + name: http2 + sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa" + url: "https://pub.dev" + source: hosted + version: "2.3.1" http_client_helper: dependency: transitive description: @@ -1022,6 +1038,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + protobuf: + dependency: "direct main" + description: + name: protobuf + sha256: "2fcc8a202ca7ec17dab7c97d6b6d91cf03aa07fe6f65f8afbb6dfa52cc5bd902" + url: "https://pub.dev" + source: hosted + version: "5.1.0" pub_semver: dependency: transitive description: @@ -1385,14 +1409,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.12" - timing: - dependency: transitive - description: - name: timing - sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" - source: hosted - version: "1.0.2" translator: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e7fc839..1d4646e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -58,7 +58,7 @@ dependencies: aria2: git: https://github.com/xkeyC/dart_aria2_rpc.git # path: ../../xkeyC/dart_aria2_rpc - intl: any + intl: ^0.20.2 synchronized: ^3.4.0 super_sliver_list: ^0.4.1 file: ^7.0.1 @@ -71,6 +71,10 @@ dependencies: path: ^1.9.1 crypto: ^3.0.7 xml: ^6.6.1 + + # gRPC and protobuf + grpc: ^5.0.0 + protobuf: ^5.1.0 dependency_overrides: http: ^1.6.0 intl: ^0.20.2 @@ -80,7 +84,7 @@ dev_dependencies: sdk: flutter flutter_lints: ^6.0.0 msix: ^3.16.12 - build_runner: 2.7.1 + build_runner: 2.10.3 freezed: ^3.2.3 json_serializable: ^6.11.1 riverpod_generator: ^3.0.3 diff --git a/update_grpc.bat b/update_grpc.bat deleted file mode 100644 index 1c3ec48..0000000 --- a/update_grpc.bat +++ /dev/null @@ -1 +0,0 @@ -protoc --dart_out=grpc:lib/generated/grpc/party_room_server -I../party_room_server/protos/ ../party_room_server/protos/*.proto From 0983ebe21fdc824830f3a775112dffc16ff56ebf Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Wed, 19 Nov 2025 11:53:45 +0800 Subject: [PATCH 02/11] fix: macos Support --- .gitignore | 2 + .vscode/settings.json | 3 + lib/common/conf/url_conf.dart | 9 +- lib/generated/l10n.dart | 7 +- lib/provider/party_room.dart | 6 +- lib/ui/settings/settings_ui_model.g.dart | 2 +- macos/Podfile.lock | 42 ++- macos/Runner.xcodeproj/project.pbxproj | 12 +- .../xcshareddata/xcschemes/Runner.xcscheme | 1 + macos/Runner/AppDelegate.swift | 6 +- rust/Cargo.lock | 258 ++++++++---------- rust/build.rs | 8 + rust_builder/macos/rust_builder.podspec | 5 +- 13 files changed, 184 insertions(+), 177 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 rust/build.rs diff --git a/.gitignore b/.gitignore index 3cfe813..39ab636 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,11 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ # IntelliJ related diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1395495 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "dart.flutterSdkPath": ".fvm/versions/stable" +} \ No newline at end of file diff --git a/lib/common/conf/url_conf.dart b/lib/common/conf/url_conf.dart index c46a977..9ae9191 100644 --- a/lib/common/conf/url_conf.dart +++ b/lib/common/conf/url_conf.dart @@ -10,6 +10,11 @@ class URLConf { static String newsApiHome = "https://scbox.citizenwiki.cn"; static const String analyticsApiHome = "https://scbox.org"; + /// PartyRoom Server + static const String partyRoomServerAddress = "partyroom.grpc.scbox.xkeyc.cn"; + static const int partyRoomServerPort = 443; + + static bool isUrlCheckPass = false; /// URLS @@ -32,10 +37,6 @@ class URLConf { static String get devReleaseUrl => "$gitApiHome/SCToolBox/Release/releases"; - /// PartyRoom Server - static const String partyRoomServerAddress = "localhost"; - static const int partyRoomServerPort = 50051; - /// RSI Avatar Base URL static const String rsiAvatarBaseUrl = "https://robertsspaceindustries.com"; diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 7477333..a15e33e 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -28,10 +28,9 @@ class S { static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); static Future load(Locale locale) { - final name = - (locale.countryCode?.isEmpty ?? false) - ? locale.languageCode - : locale.toString(); + final name = (locale.countryCode?.isEmpty ?? false) + ? locale.languageCode + : locale.toString(); final localeName = Intl.canonicalizedLocale(name); return initializeMessages(localeName).then((_) { Intl.defaultLocale = localeName; diff --git a/lib/provider/party_room.dart b/lib/provider/party_room.dart index f58c277..b527255 100644 --- a/lib/provider/party_room.dart +++ b/lib/provider/party_room.dart @@ -127,11 +127,7 @@ class PartyRoom extends _$PartyRoom { final serverAddress = URLConf.partyRoomServerAddress; final serverPort = URLConf.partyRoomServerPort; - final channel = ClientChannel( - serverAddress, - port: serverPort, - options: const ChannelOptions(credentials: ChannelCredentials.insecure()), - ); + final channel = ClientChannel(serverAddress, port: serverPort); final authClient = auth.AuthServiceClient(channel); final roomClient = partroom.PartRoomServiceClient(channel); diff --git a/lib/ui/settings/settings_ui_model.g.dart b/lib/ui/settings/settings_ui_model.g.dart index 82f5ddf..4fd2682 100644 --- a/lib/ui/settings/settings_ui_model.g.dart +++ b/lib/ui/settings/settings_ui_model.g.dart @@ -41,7 +41,7 @@ final class SettingsUIModelProvider } } -String _$settingsUIModelHash() => r'5c08c56bf5464ef44bee8edb8c18c08d4217f135'; +String _$settingsUIModelHash() => r'd19104d924f018a9230548d0372692fc344adacd'; abstract class _$SettingsUIModel extends $Notifier { SettingsUIState build(); diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 1b97e58..6b59aa0 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,8 +1,12 @@ PODS: + - desktop_multi_window (0.0.1): + - FlutterMacOS - desktop_webview_window (0.0.1): - FlutterMacOS - device_info_plus (0.0.1): - FlutterMacOS + - file_picker (0.0.1): + - FlutterMacOS - FlutterMacOS (1.0.0) - macos_window_utils (1.0.0): - FlutterMacOS @@ -11,29 +15,35 @@ PODS: - FlutterMacOS - rust_builder (0.0.1): - FlutterMacOS - - screen_retriever (0.0.1): + - screen_retriever_macos (0.0.1): - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS - - window_manager (0.2.0): + - window_manager (0.5.0): - FlutterMacOS DEPENDENCIES: + - desktop_multi_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos`) - desktop_webview_window (from `Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos`) - device_info_plus (from `Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos`) + - file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - rust_builder (from `Flutter/ephemeral/.symlinks/plugins/rust_builder/macos`) - - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) + - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) EXTERNAL SOURCES: + desktop_multi_window: + :path: Flutter/ephemeral/.symlinks/plugins/desktop_multi_window/macos desktop_webview_window: :path: Flutter/ephemeral/.symlinks/plugins/desktop_webview_window/macos device_info_plus: :path: Flutter/ephemeral/.symlinks/plugins/device_info_plus/macos + file_picker: + :path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos FlutterMacOS: :path: Flutter/ephemeral macos_window_utils: @@ -42,24 +52,26 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin rust_builder: :path: Flutter/ephemeral/.symlinks/plugins/rust_builder/macos - screen_retriever: - :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos + screen_retriever_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos window_manager: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - desktop_webview_window: d4365e71bcd4e1aa0c14cf0377aa24db0c16a7e2 - device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - macos_window_utils: 933f91f64805e2eb91a5bd057cf97cd097276663 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - rust_builder: 4b521d57bf67224da65f32b529be8fab420fca32 - screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 - window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 + desktop_multi_window: 93667594ccc4b88d91a97972fd3b1b89667fa80a + desktop_webview_window: 7e37af677d6d19294cb433d9b1d878ef78dffa4d + device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 + file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + macos_window_utils: 23f54331a0fd51eea9e0ed347253bf48fd379d1d + path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880 + rust_builder: 20181fc33fd408f6ec1135f518edbd4f5d3a6dc5 + screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f + url_launcher_macos: f87a979182d112f911de6820aefddaf56ee9fbfd + window_manager: b729e31d38fb04905235df9ea896128991cad99e PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj index df23c3e..1245a15 100644 --- a/macos/Runner.xcodeproj/project.pbxproj +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -544,7 +544,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -567,7 +567,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 13.5; PRODUCT_BUNDLE_IDENTIFIER = com.xkeyc.tools.sctoolbox; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; @@ -629,7 +629,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -679,7 +679,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -702,7 +702,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 13.5; PRODUCT_BUNDLE_IDENTIFIER = com.xkeyc.tools.sctoolbox; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -725,7 +725,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 13.5; PRODUCT_BUNDLE_IDENTIFIER = com.xkeyc.tools.sctoolbox; PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 4e26a6f..93a85d5 100644 --- a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -59,6 +59,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index d53ef64..b3c1761 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,9 +1,13 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 5bc9c55..7bd436b 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -33,9 +33,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -109,22 +109,22 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -179,9 +179,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.32" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" +checksum = "93c1f86859c1af3d514fa19e8323147ff10ea98684e6c7b307912509f50e67b2" dependencies = [ "compression-codecs", "compression-core", @@ -411,9 +411,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "castaway" @@ -426,9 +426,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.43" +version = "1.2.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "739eb0f94557554b3ca9a86d2d37bebd49c5e6d0c1d2bda35ba5bdac830befc2" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" dependencies = [ "find-msvc-tools", "shlex", @@ -460,9 +460,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.50" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" +checksum = "aa8120877db0e5c011242f96806ce3c94e0737ab8108532a76a3300a01db2ab8" dependencies = [ "clap_builder", "clap_derive", @@ -470,9 +470,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.50" +version = "4.5.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" +checksum = "02576b399397b659c26064fbc92a75fede9d18ffd5f80ca1cd74ddab167016e1" dependencies = [ "anstream", "anstyle", @@ -548,9 +548,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.31" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" +checksum = "680dc087785c5230f8e8843e2e57ac7c1c90488b6a91b88caa265410568f441b" dependencies = [ "compression-core", "flate2", @@ -559,9 +559,9 @@ dependencies = [ [[package]] name = "compression-core" -version = "0.4.29" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" +checksum = "3a9b614a5787ef0c8802a55766480563cb3a93b435898c422ed2a359cf811582" [[package]] name = "concurrent-queue" @@ -707,9 +707,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", @@ -845,9 +845,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", "serde_core", @@ -1067,9 +1067,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "flate2" @@ -1257,9 +1257,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.9" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1441,9 +1441,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" dependencies = [ "atomic-waker", "bytes", @@ -1496,9 +1496,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" +checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" dependencies = [ "base64 0.22.1", "bytes", @@ -1546,9 +1546,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", @@ -1559,9 +1559,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", @@ -1572,11 +1572,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -1587,42 +1586,38 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" dependencies = [ - "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -1706,9 +1701,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.8" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" dependencies = [ "memchr", "serde", @@ -1755,9 +1750,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" +checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" dependencies = [ "once_cell", "wasm-bindgen", @@ -1794,9 +1789,9 @@ checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" [[package]] name = "litrs" @@ -1827,9 +1822,9 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "mac-notification-sys" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "119c8490084af61b44c9eda9d626475847a186737c0378c85e32d77c33a01cd4" +checksum = "4ee70bb2bba058d58e252d2944582d634fc884fc9c489a966d428dedcf653e97" dependencies = [ "cc", "objc2", @@ -2176,9 +2171,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.74" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ad14dd45412269e1a30f52ad8f0664f0f4f4a89ee8fe28c3b3527021ebb654" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags", "cfg-if", @@ -2208,9 +2203,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.110" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -2389,9 +2384,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" dependencies = [ "zerovec", ] @@ -2511,9 +2506,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -2700,9 +2695,9 @@ dependencies = [ [[package]] name = "resolv-conf" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" +checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" [[package]] name = "ring" @@ -2778,9 +2773,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.34" +version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9586e9ee2b4f8fab52a0048ca7334d7024eef48e2cb9407e3497bb7cab7fa7" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ "once_cell", "ring", @@ -2792,9 +2787,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" dependencies = [ "web-time", "zeroize", @@ -2802,9 +2797,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.7" +version = "0.103.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" dependencies = [ "ring", "rustls-pki-types", @@ -2855,9 +2850,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ "dyn-clone", "ref-cast", @@ -2968,9 +2963,9 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.15.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa66c845eee442168b2c8134fec70ac50dc20e760769c8ba0ad1319ca1959b04" +checksum = "10574371d41b0d9b2cff89418eda27da52bcaff2cc8741db26382a77c29131f1" dependencies = [ "base64 0.22.1", "chrono", @@ -2978,7 +2973,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.12.0", "schemars 0.9.0", - "schemars 1.0.4", + "schemars 1.1.0", "serde_core", "serde_json", "serde_with_macros", @@ -2987,9 +2982,9 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.15.1" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91a903660542fced4e99881aa481bdbaec1634568ee02e0b8bd57c64cb38955" +checksum = "08a72d8216842fdd57820dc78d840bef99248e35fb2554ff923319e60f2d686b" dependencies = [ "darling 0.21.3", "proc-macro2", @@ -3125,9 +3120,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.108" +version = "2.0.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" dependencies = [ "proc-macro2", "quote", @@ -3308,9 +3303,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" dependencies = [ "displaydoc", "zerovec", @@ -3413,9 +3408,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.16" +version = "0.7.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" dependencies = [ "bytes", "futures-core", @@ -3577,9 +3572,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-normalization-alignments" @@ -3740,9 +3735,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" +checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ "cfg-if", "once_cell", @@ -3751,25 +3746,11 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - [[package]] name = "wasm-bindgen-futures" -version = "0.4.54" +version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" +checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ "cfg-if", "js-sys", @@ -3780,9 +3761,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" +checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3790,22 +3771,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" +checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.104" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" +checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" dependencies = [ "unicode-ident", ] @@ -3840,9 +3821,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.81" +version = "0.3.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" +checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" dependencies = [ "js-sys", "wasm-bindgen", @@ -3869,9 +3850,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" dependencies = [ "rustls-pki-types", ] @@ -4070,13 +4051,13 @@ dependencies = [ [[package]] name = "windows-registry" -version = "0.5.3" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -4391,9 +4372,9 @@ checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "xattr" @@ -4407,11 +4388,10 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -4419,9 +4399,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", @@ -4539,9 +4519,9 @@ checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" dependencies = [ "displaydoc", "yoke", @@ -4550,9 +4530,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" dependencies = [ "yoke", "zerofrom", @@ -4561,9 +4541,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", diff --git a/rust/build.rs b/rust/build.rs new file mode 100644 index 0000000..a64d8c5 --- /dev/null +++ b/rust/build.rs @@ -0,0 +1,8 @@ +fn main() { + #[cfg(target_os = "macos")] + { + println!("cargo:rustc-link-lib=framework=CoreServices"); + println!("cargo:rustc-link-lib=framework=AppKit"); + println!("cargo:rustc-link-lib=framework=Foundation"); + } +} diff --git a/rust_builder/macos/rust_builder.podspec b/rust_builder/macos/rust_builder.podspec index ff19726..2438fe0 100644 --- a/rust_builder/macos/rust_builder.podspec +++ b/rust_builder/macos/rust_builder.podspec @@ -19,9 +19,10 @@ A new Flutter FFI plugin project. # `../src/*` so that the C sources can be shared among all target platforms. s.source = { :path => '.' } s.source_files = 'Classes/**/*' - s.frameworks = 'SystemConfiguration' + s.frameworks = 'SystemConfiguration', 'CoreFoundation', 'CoreServices' s.dependency 'FlutterMacOS' - s.platform = :osx, '10.11' + s.libraries = 'c++' + s.platform = :osx, '13.5' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' From a6a5a117bcd3c8d0c53eb66bbf895b9b0915781a Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Wed, 19 Nov 2025 17:18:04 +0800 Subject: [PATCH 03/11] feat: update UI --- lib/provider/party_room.dart | 39 +- lib/provider/party_room.freezed.dart | 38 +- lib/provider/party_room.g.dart | 2 +- lib/ui/party_room/party_room_ui.dart | 32 +- lib/ui/party_room/party_room_ui_model.dart | 10 + .../party_room_ui_model.freezed.dart | 43 +- lib/ui/party_room/party_room_ui_model.g.dart | 2 +- .../widgets/create_room_dialog.dart | 220 ++++-- .../detail/party_room_detail_page.dart | 101 +++ .../widgets/detail/party_room_header.dart | 112 +++ .../detail/party_room_member_list.dart | 252 +++++++ .../detail/party_room_message_list.dart | 305 ++++++++ .../detail/party_room_signal_sender.dart | 63 ++ .../widgets/party_room_detail_page.dart | 689 ------------------ .../widgets/party_room_list_page.dart | 126 +++- pubspec.lock | 8 + pubspec.yaml | 1 + 17 files changed, 1201 insertions(+), 842 deletions(-) create mode 100644 lib/ui/party_room/widgets/detail/party_room_detail_page.dart create mode 100644 lib/ui/party_room/widgets/detail/party_room_header.dart create mode 100644 lib/ui/party_room/widgets/detail/party_room_member_list.dart create mode 100644 lib/ui/party_room/widgets/detail/party_room_message_list.dart create mode 100644 lib/ui/party_room/widgets/detail/party_room_signal_sender.dart delete mode 100644 lib/ui/party_room/widgets/party_room_detail_page.dart diff --git a/lib/provider/party_room.dart b/lib/provider/party_room.dart index b527255..8850ca3 100644 --- a/lib/provider/party_room.dart +++ b/lib/provider/party_room.dart @@ -33,8 +33,8 @@ sealed class PartyRoomState with _$PartyRoomState { const factory PartyRoomState({ partroom.RoomInfo? currentRoom, @Default([]) List members, - @Default([]) List tags, - @Default([]) List signalTypes, + @Default({}) Map tags, + @Default({}) Map signalTypes, @Default(false) bool isInRoom, @Default(false) bool isOwner, String? roomUuid, @@ -249,10 +249,9 @@ class PartyRoom extends _$PartyRoom { // 清除本地认证信息 await _confBox?.delete(_secretKeyKey); - state = state.copyWith( - auth: state.auth.copyWith(secretKey: '', isLoggedIn: false, userInfo: null), - room: const PartyRoomState(), - ); + _dismissRoom(); + + state = state.copyWith(auth: state.auth.copyWith(secretKey: '', isLoggedIn: false, userInfo: null)); dPrint('[PartyRoom] Unregistered successfully'); } catch (e) { @@ -273,13 +272,15 @@ class PartyRoom extends _$PartyRoom { final response = await commonClient.getTags(common.GetTagsRequest()); final signalTypesResponse = await commonClient.getSignalTypes(common.GetSignalTypesRequest()); + // 转换为 Map + final tagsMap = {for (var tag in response.tags) tag.id: tag}; + final signalTypesMap = {for (var signal in signalTypesResponse.signals) signal.id: signal}; + state = state.copyWith( - room: state.room.copyWith(tags: response.tags, signalTypes: signalTypesResponse.signals), + room: state.room.copyWith(tags: tagsMap, signalTypes: signalTypesMap), ); - dPrint( - '[PartyRoom] Tags and SignalTypes loaded: ${response.tags.length} tags, ${signalTypesResponse.signals.length} signal types', - ); + dPrint('[PartyRoom] Tags and SignalTypes loaded: ${tagsMap.length} tags, ${signalTypesMap.length} signal types'); } catch (e) { dPrint('[PartyRoom] LoadTags error: $e'); rethrow; @@ -397,7 +398,7 @@ class PartyRoom extends _$PartyRoom { await _stopHeartbeat(); await _stopEventStream(); - state = state.copyWith(room: const PartyRoomState()); + _dismissRoom(); dPrint('[PartyRoom] Left room: $roomUuid'); } catch (e) { @@ -420,7 +421,7 @@ class PartyRoom extends _$PartyRoom { await _stopHeartbeat(); await _stopEventStream(); - state = state.copyWith(room: const PartyRoomState()); + _dismissRoom(); dPrint('[PartyRoom] Dismissed room: $roomUuid'); } catch (e) { @@ -616,9 +617,8 @@ class PartyRoom extends _$PartyRoom { if (roomUuid == null) return; // 验证信号类型是否有效 - final validSignalIds = state.room.signalTypes.map((s) => s.id).toList(); - if (validSignalIds.isNotEmpty && !validSignalIds.contains(signalId)) { - throw Exception('Invalid signal ID: $signalId. Valid IDs: ${validSignalIds.join(", ")}'); + if (state.room.signalTypes.isNotEmpty && !state.room.signalTypes.containsKey(signalId)) { + throw Exception('Invalid signal ID: $signalId. Valid IDs: ${state.room.signalTypes.keys.join(", ")}'); } final request = partroom.SendSignalRequest(roomUuid: roomUuid, signalId: signalId); @@ -822,7 +822,7 @@ class PartyRoom extends _$PartyRoom { // 房间被解散 _stopHeartbeat(); _stopEventStream(); - state = state.copyWith(room: const PartyRoomState()); + _dismissRoom(); break; case partroom.RoomEventType.SIGNAL_BROADCAST: @@ -877,6 +877,13 @@ class PartyRoom extends _$PartyRoom { // ========== 清理 ========== + /// 重置房间状态(保留 tags 和 signalTypes) + void _dismissRoom() { + state = state.copyWith( + room: PartyRoomState(tags: state.room.tags, signalTypes: state.room.signalTypes), + ); + } + void _cleanup() { _stopHeartbeat(); _stopEventStream(); diff --git a/lib/provider/party_room.freezed.dart b/lib/provider/party_room.freezed.dart index 2cf8331..c77b670 100644 --- a/lib/provider/party_room.freezed.dart +++ b/lib/provider/party_room.freezed.dart @@ -277,7 +277,7 @@ as DateTime?, /// @nodoc mixin _$PartyRoomState { - partroom.RoomInfo? get currentRoom; List get members; List get tags; List get signalTypes; bool get isInRoom; bool get isOwner; String? get roomUuid; List get recentEvents; + partroom.RoomInfo? get currentRoom; List get members; Map get tags; Map get signalTypes; bool get isInRoom; bool get isOwner; String? get roomUuid; List get recentEvents; /// Create a copy of PartyRoomState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -308,7 +308,7 @@ abstract mixin class $PartyRoomStateCopyWith<$Res> { factory $PartyRoomStateCopyWith(PartyRoomState value, $Res Function(PartyRoomState) _then) = _$PartyRoomStateCopyWithImpl; @useResult $Res call({ - partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents + partroom.RoomInfo? currentRoom, List members, Map tags, Map signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents }); @@ -330,8 +330,8 @@ class _$PartyRoomStateCopyWithImpl<$Res> 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 List,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable -as List,signalTypes: null == signalTypes ? _self.signalTypes : signalTypes // ignore: cast_nullable_to_non_nullable -as List,isInRoom: null == isInRoom ? _self.isInRoom : isInRoom // ignore: cast_nullable_to_non_nullable +as Map,signalTypes: null == signalTypes ? _self.signalTypes : signalTypes // ignore: cast_nullable_to_non_nullable +as Map,isInRoom: null == isInRoom ? _self.isInRoom : isInRoom // 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 String?,recentEvents: null == recentEvents ? _self.recentEvents : recentEvents // ignore: cast_nullable_to_non_nullable @@ -417,7 +417,7 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( partroom.RoomInfo? currentRoom, List members, Map tags, Map signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _PartyRoomState() when $default != null: return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents);case _: @@ -438,7 +438,7 @@ return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_th /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( partroom.RoomInfo? currentRoom, List members, Map tags, Map signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents) $default,) {final _that = this; switch (_that) { case _PartyRoomState(): return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents);} @@ -455,7 +455,7 @@ return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_th /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( partroom.RoomInfo? currentRoom, List members, Map tags, Map signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents)? $default,) {final _that = this; switch (_that) { case _PartyRoomState() when $default != null: return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_that.isInRoom,_that.isOwner,_that.roomUuid,_that.recentEvents);case _: @@ -470,7 +470,7 @@ return $default(_that.currentRoom,_that.members,_that.tags,_that.signalTypes,_th class _PartyRoomState implements PartyRoomState { - const _PartyRoomState({this.currentRoom, final List members = const [], final List tags = const [], final List signalTypes = const [], this.isInRoom = false, this.isOwner = false, this.roomUuid, final List recentEvents = const []}): _members = members,_tags = tags,_signalTypes = signalTypes,_recentEvents = recentEvents; + const _PartyRoomState({this.currentRoom, final List members = const [], final Map tags = const {}, final Map signalTypes = const {}, this.isInRoom = false, this.isOwner = false, this.roomUuid, final List recentEvents = const []}): _members = members,_tags = tags,_signalTypes = signalTypes,_recentEvents = recentEvents; @override final partroom.RoomInfo? currentRoom; @@ -481,18 +481,18 @@ class _PartyRoomState implements PartyRoomState { return EqualUnmodifiableListView(_members); } - final List _tags; -@override@JsonKey() List get tags { - if (_tags is EqualUnmodifiableListView) return _tags; + final Map _tags; +@override@JsonKey() Map get tags { + if (_tags is EqualUnmodifiableMapView) return _tags; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_tags); + return EqualUnmodifiableMapView(_tags); } - final List _signalTypes; -@override@JsonKey() List get signalTypes { - if (_signalTypes is EqualUnmodifiableListView) return _signalTypes; + final Map _signalTypes; +@override@JsonKey() Map get signalTypes { + if (_signalTypes is EqualUnmodifiableMapView) return _signalTypes; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_signalTypes); + return EqualUnmodifiableMapView(_signalTypes); } @override@JsonKey() final bool isInRoom; @@ -536,7 +536,7 @@ abstract mixin class _$PartyRoomStateCopyWith<$Res> implements $PartyRoomStateCo factory _$PartyRoomStateCopyWith(_PartyRoomState value, $Res Function(_PartyRoomState) _then) = __$PartyRoomStateCopyWithImpl; @override @useResult $Res call({ - partroom.RoomInfo? currentRoom, List members, List tags, List signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents + partroom.RoomInfo? currentRoom, List members, Map tags, Map signalTypes, bool isInRoom, bool isOwner, String? roomUuid, List recentEvents }); @@ -558,8 +558,8 @@ class __$PartyRoomStateCopyWithImpl<$Res> 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 List,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable -as List,signalTypes: null == signalTypes ? _self._signalTypes : signalTypes // ignore: cast_nullable_to_non_nullable -as List,isInRoom: null == isInRoom ? _self.isInRoom : isInRoom // ignore: cast_nullable_to_non_nullable +as Map,signalTypes: null == signalTypes ? _self._signalTypes : signalTypes // ignore: cast_nullable_to_non_nullable +as Map,isInRoom: null == isInRoom ? _self.isInRoom : isInRoom // 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 String?,recentEvents: null == recentEvents ? _self._recentEvents : recentEvents // ignore: cast_nullable_to_non_nullable diff --git a/lib/provider/party_room.g.dart b/lib/provider/party_room.g.dart index 70dc68c..0a8b49d 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'2c521709721292458d5459359cac376f123ec226'; +String _$partyRoomHash() => r'f427838c330942d59faf614f420236dc5a699381'; /// PartyRoom Provider diff --git a/lib/ui/party_room/party_room_ui.dart b/lib/ui/party_room/party_room_ui.dart index a00ee4d..ab90615 100644 --- a/lib/ui/party_room/party_room_ui.dart +++ b/lib/ui/party_room/party_room_ui.dart @@ -1,10 +1,11 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:local_hero/local_hero.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/widgets/party_room_connect_page.dart'; import 'package:starcitizen_doctor/ui/party_room/widgets/party_room_list_page.dart'; -import 'package:starcitizen_doctor/ui/party_room/widgets/party_room_detail_page.dart'; +import 'package:starcitizen_doctor/ui/party_room/widgets/detail/party_room_detail_page.dart'; import 'package:starcitizen_doctor/ui/party_room/widgets/party_room_register_page.dart'; class PartyRoomUI extends HookConsumerWidget { @@ -13,20 +14,27 @@ class PartyRoomUI extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final partyRoomState = ref.watch(partyRoomProvider); - ref.watch(partyRoomUIModelProvider.select((_) => null)); + final uiState = ref.watch(partyRoomUIModelProvider); + + Widget widget = const PartyRoomListPage(); + // 根据状态显示不同页面 if (!partyRoomState.client.isConnected) { - return const PartyRoomConnectPage(); + widget = PartyRoomConnectPage(); + } else if (!partyRoomState.auth.isLoggedIn) { + widget = PartyRoomRegisterPage(); + } else if (partyRoomState.room.isInRoom && !uiState.isMinimized) { + widget = PartyRoomDetailPage(); } - if (!partyRoomState.auth.isLoggedIn) { - return const PartyRoomRegisterPage(); - } - - if (partyRoomState.room.isInRoom) { - return const PartyRoomDetailPage(); - } - - return const PartyRoomListPage(); + return LocalHeroScope( + duration: Duration(milliseconds: 180), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 230), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + child: widget, + ), + ); } } diff --git a/lib/ui/party_room/party_room_ui_model.dart b/lib/ui/party_room/party_room_ui_model.dart index d7d72c4..c1086ac 100644 --- a/lib/ui/party_room/party_room_ui_model.dart +++ b/lib/ui/party_room/party_room_ui_model.dart @@ -28,6 +28,7 @@ sealed class PartyRoomUIState with _$PartyRoomUIState { @Default('') String registerGameUserId, @Default(false) bool isReconnecting, @Default(0) int reconnectAttempts, + @Default(false) bool isMinimized, }) = _PartyRoomUIState; } @@ -40,6 +41,11 @@ class PartyRoomUIModel extends _$PartyRoomUIModel { state = const PartyRoomUIState(); ref.listen(partyRoomProvider, (previous, next) { _handleConnectionStateChange(previous, next); + + // 如果房间被解散或离开房间,重置最小化状态 + if (previous?.room.isInRoom == true && !next.room.isInRoom) { + state = state.copyWith(isMinimized: false); + } }); connectToServer(); @@ -259,4 +265,8 @@ class PartyRoomUIModel extends _$PartyRoomUIModel { ref.read(partyRoomProvider.notifier).dismissRoom(); ref.read(partyRoomProvider.notifier).loadTags(); } + + void setMinimized(bool minimized) { + state = state.copyWith(isMinimized: minimized); + } } diff --git a/lib/ui/party_room/party_room_ui_model.freezed.dart b/lib/ui/party_room/party_room_ui_model.freezed.dart index 99fd3ba..1dd6a9d 100644 --- a/lib/ui/party_room/party_room_ui_model.freezed.dart +++ b/lib/ui/party_room/party_room_ui_model.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$PartyRoomUIState { - bool get isConnecting; bool get showRoomList; List get roomListItems; int get currentPage; int get pageSize; int get totalRooms; String? get selectedMainTagId; String? get selectedSubTagId; String get searchOwnerName; bool get isLoading; String? get errorMessage; String get preRegisterCode; String get registerGameUserId; bool get isReconnecting; int get reconnectAttempts; + bool get isConnecting; bool get showRoomList; List get roomListItems; int get currentPage; int get pageSize; int get totalRooms; String? get selectedMainTagId; String? get selectedSubTagId; String get searchOwnerName; bool get isLoading; String? get errorMessage; String get preRegisterCode; String get registerGameUserId; bool get isReconnecting; int get reconnectAttempts; bool get isMinimized; /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -25,16 +25,16 @@ $PartyRoomUIStateCopyWith get copyWith => _$PartyRoomUIStateCo @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other.roomListItems, roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other.roomListItems, roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)); } @override -int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts); +int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized); @override String toString() { - return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts)'; + return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized)'; } @@ -45,7 +45,7 @@ abstract mixin class $PartyRoomUIStateCopyWith<$Res> { factory $PartyRoomUIStateCopyWith(PartyRoomUIState value, $Res Function(PartyRoomUIState) _then) = _$PartyRoomUIStateCopyWithImpl; @useResult $Res call({ - bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts + bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized }); @@ -62,7 +62,7 @@ class _$PartyRoomUIStateCopyWithImpl<$Res> /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,}) { return _then(_self.copyWith( isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable @@ -79,7 +79,8 @@ as String?,preRegisterCode: null == preRegisterCode ? _self.preRegisterCode : pr as String,registerGameUserId: null == registerGameUserId ? _self.registerGameUserId : registerGameUserId // ignore: cast_nullable_to_non_nullable as String,isReconnecting: null == isReconnecting ? _self.isReconnecting : isReconnecting // ignore: cast_nullable_to_non_nullable as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts : reconnectAttempts // ignore: cast_nullable_to_non_nullable -as int, +as int,isMinimized: null == isMinimized ? _self.isMinimized : isMinimized // ignore: cast_nullable_to_non_nullable +as bool, )); } @@ -161,10 +162,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _PartyRoomUIState() when $default != null: -return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts);case _: +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized);case _: return orElse(); } @@ -182,10 +183,10 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that. /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized) $default,) {final _that = this; switch (_that) { case _PartyRoomUIState(): -return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts);} +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized);} } /// A variant of `when` that fallback to returning `null` /// @@ -199,10 +200,10 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that. /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized)? $default,) {final _that = this; switch (_that) { case _PartyRoomUIState() when $default != null: -return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts);case _: +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized);case _: return null; } @@ -214,7 +215,7 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that. class _PartyRoomUIState implements PartyRoomUIState { - const _PartyRoomUIState({this.isConnecting = false, this.showRoomList = false, final List roomListItems = const [], this.currentPage = 1, this.pageSize = 20, this.totalRooms = 0, this.selectedMainTagId, this.selectedSubTagId, this.searchOwnerName = '', this.isLoading = false, this.errorMessage, this.preRegisterCode = '', this.registerGameUserId = '', this.isReconnecting = false, this.reconnectAttempts = 0}): _roomListItems = roomListItems; + const _PartyRoomUIState({this.isConnecting = false, this.showRoomList = false, final List roomListItems = const [], this.currentPage = 1, this.pageSize = 20, this.totalRooms = 0, this.selectedMainTagId, this.selectedSubTagId, this.searchOwnerName = '', this.isLoading = false, this.errorMessage, this.preRegisterCode = '', this.registerGameUserId = '', this.isReconnecting = false, this.reconnectAttempts = 0, this.isMinimized = false}): _roomListItems = roomListItems; @override@JsonKey() final bool isConnecting; @@ -238,6 +239,7 @@ class _PartyRoomUIState implements PartyRoomUIState { @override@JsonKey() final String registerGameUserId; @override@JsonKey() final bool isReconnecting; @override@JsonKey() final int reconnectAttempts; +@override@JsonKey() final bool isMinimized; /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. @@ -249,16 +251,16 @@ _$PartyRoomUIStateCopyWith<_PartyRoomUIState> get copyWith => __$PartyRoomUIStat @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other._roomListItems, _roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other._roomListItems, _roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)); } @override -int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(_roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts); +int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(_roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized); @override String toString() { - return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts)'; + return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized)'; } @@ -269,7 +271,7 @@ abstract mixin class _$PartyRoomUIStateCopyWith<$Res> implements $PartyRoomUISta factory _$PartyRoomUIStateCopyWith(_PartyRoomUIState value, $Res Function(_PartyRoomUIState) _then) = __$PartyRoomUIStateCopyWithImpl; @override @useResult $Res call({ - bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts + bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized }); @@ -286,7 +288,7 @@ class __$PartyRoomUIStateCopyWithImpl<$Res> /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,}) { return _then(_PartyRoomUIState( isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable @@ -303,7 +305,8 @@ as String?,preRegisterCode: null == preRegisterCode ? _self.preRegisterCode : pr as String,registerGameUserId: null == registerGameUserId ? _self.registerGameUserId : registerGameUserId // ignore: cast_nullable_to_non_nullable as String,isReconnecting: null == isReconnecting ? _self.isReconnecting : isReconnecting // ignore: cast_nullable_to_non_nullable as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts : reconnectAttempts // ignore: cast_nullable_to_non_nullable -as int, +as int,isMinimized: null == isMinimized ? _self.isMinimized : isMinimized // ignore: cast_nullable_to_non_nullable +as bool, )); } 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 4901ef5..7160239 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'262069d02bbc7d76fe6797c6c744bdf848122492'; +String _$partyRoomUIModelHash() => r'0e86aeb2bf3524907836e9951b04c062c84327a6'; abstract class _$PartyRoomUIModel extends $Notifier { PartyRoomUIState build(); diff --git a/lib/ui/party_room/widgets/create_room_dialog.dart b/lib/ui/party_room/widgets/create_room_dialog.dart index 7fbc5ef..db82cd9 100644 --- a/lib/ui/party_room/widgets/create_room_dialog.dart +++ b/lib/ui/party_room/widgets/create_room_dialog.dart @@ -20,87 +20,149 @@ class CreateRoomDialog extends HookConsumerWidget { final socialLinksController = useTextEditingController(); final isCreating = useState(false); + // 获取选中的主标签 + final selectedMainTagData = selectedMainTag.value != null ? partyRoomState.room.tags[selectedMainTag.value] : null; + return ContentDialog( - constraints: const BoxConstraints(maxWidth: 500), + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.4), title: const Text('创建房间'), - content: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - InfoLabel( - label: '房间类型', - child: ComboBox( - placeholder: const Text('选择主标签'), - value: selectedMainTag.value, - items: partyRoomState.room.tags.map((tag) { - return ComboBoxItem(value: tag.id, child: Text(tag.name)); - }).toList(), - onChanged: (value) { - selectedMainTag.value = value; - selectedSubTag.value = null; - }, - ), - ), - const SizedBox(height: 12), + content: SizedBox( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + children: [ + InfoLabel( + label: '房间类型', + child: ComboBox( + placeholder: const Text('选择主标签'), + value: selectedMainTag.value, + isExpanded: true, + items: partyRoomState.room.tags.values.map((tag) { + return ComboBoxItem( + value: tag.id, + child: Row( + children: [ + if (tag.color.isNotEmpty) + Container( + width: 12, + height: 12, + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: _parseColor(tag.color), + borderRadius: BorderRadius.circular(2), + ), + ), + Text(tag.name), + if (tag.info.isNotEmpty) + Padding( + padding: const EdgeInsets.only(left: 8), + child: Text(tag.info, style: TextStyle(fontSize: 11, color: Colors.grey[100])), + ), + ], + ), + ); + }).toList(), + onChanged: (value) { + selectedMainTag.value = value; + selectedSubTag.value = null; + }, + ), + ), - if (selectedMainTag.value != null) ...[ + const SizedBox(height: 12), + + // 子标签 - 始终显示,避免布局跳动 + InfoLabel( + label: '子标签 (可选)', + child: ComboBox( + placeholder: const Text('选择子标签'), + value: selectedSubTag.value, + isExpanded: true, + items: [ + const ComboBoxItem(value: null, child: Text('无')), + if (selectedMainTagData != null) + ...selectedMainTagData.subTags.map((subTag) { + return ComboBoxItem( + value: subTag.id, + child: Row( + children: [ + if (subTag.color.isNotEmpty) + Container( + width: 12, + height: 12, + margin: const EdgeInsets.only(right: 8), + decoration: BoxDecoration( + color: _parseColor(subTag.color), + borderRadius: BorderRadius.circular(2), + ), + ), + Text(subTag.name), + if (subTag.info.isNotEmpty) + Padding( + padding: const EdgeInsets.only(left: 8), + child: Text(subTag.info, style: TextStyle(fontSize: 11, color: Colors.grey[100])), + ), + ], + ), + ); + }), + ], + onChanged: selectedMainTagData != null + ? (value) { + selectedSubTag.value = value; + } + : null, + ), + ), + ], + ), + const SizedBox(height: 16), InfoLabel( - label: '子标签 (可选)', - child: ComboBox( - placeholder: const Text('选择子标签'), - value: selectedSubTag.value, - items: [ - const ComboBoxItem(value: null, child: Text('无')), - ...partyRoomState.room.tags.firstWhere((tag) => tag.id == selectedMainTag.value).subTags.map(( - subTag, - ) { - return ComboBoxItem(value: subTag.id, child: Text(subTag.name)); - }), - ], - onChanged: (value) { - selectedSubTag.value = value; - }, + label: '目标人数 (2-600)', + child: TextBox( + controller: targetMembersController, + placeholder: '输入目标人数', + keyboardType: TextInputType.number, ), ), - const SizedBox(height: 12), - ], + const SizedBox(height: 16), - InfoLabel( - label: '目标人数 (2-600)', - child: TextBox( - controller: targetMembersController, - placeholder: '输入目标人数', - keyboardType: TextInputType.number, + Row( + children: [ + Checkbox( + checked: hasPassword.value, + onChanged: (value) { + hasPassword.value = value ?? false; + }, + content: const Text('设置密码'), + ), + ], ), - ), - const SizedBox(height: 12), - - Row( - children: [ - Checkbox( - checked: hasPassword.value, - onChanged: (value) { - hasPassword.value = value ?? false; - }, - content: const Text('设置密码'), - ), - ], - ), - if (hasPassword.value) ...[ const SizedBox(height: 8), + + // 密码输入框 - 始终显示,避免布局跳动 InfoLabel( label: '房间密码', - child: TextBox(controller: passwordController, placeholder: '输入密码', obscureText: true), + child: TextBox( + controller: passwordController, + placeholder: hasPassword.value ? '输入密码' : '未启用密码', + obscureText: hasPassword.value, + maxLines: 1, + maxLength: 12, + enabled: hasPassword.value, + ), + ), + const SizedBox(height: 16), + + InfoLabel( + label: '社交链接 (可选)', + child: TextBox(controller: socialLinksController, placeholder: 'https://discord.gg/xxxxx', maxLines: 1), ), ], - const SizedBox(height: 12), - - InfoLabel( - label: '社交链接 (可选)', - child: TextBox(controller: socialLinksController, placeholder: 'https://discord.gg/xxxxx', maxLines: 1), - ), - ], + ), ), ), actions: [ @@ -187,4 +249,26 @@ class CreateRoomDialog extends HookConsumerWidget { ], ); } + + /// 解析颜色字符串 + Color _parseColor(String colorStr) { + if (colorStr.isEmpty) return Colors.grey; + + try { + // 移除 # 前缀 + String hexColor = colorStr.replaceAll('#', ''); + + // 如果是3位或6位,添加 alpha 通道 + if (hexColor.length == 3) { + hexColor = hexColor.split('').map((c) => '$c$c').join(); + } + if (hexColor.length == 6) { + hexColor = 'FF$hexColor'; + } + + return Color(int.parse(hexColor, radix: 16)); + } catch (e) { + return Colors.grey; + } + } } diff --git a/lib/ui/party_room/widgets/detail/party_room_detail_page.dart b/lib/ui/party_room/widgets/detail/party_room_detail_page.dart new file mode 100644 index 0000000..72beaf3 --- /dev/null +++ b/lib/ui/party_room/widgets/detail/party_room_detail_page.dart @@ -0,0 +1,101 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/provider/party_room.dart'; +import 'package:starcitizen_doctor/ui/party_room/widgets/detail/party_room_message_list.dart'; + +import 'party_room_header.dart'; +import 'party_room_member_list.dart'; +import 'party_room_signal_sender.dart'; + +/// 房间详情页面 (Discord 样式) +class PartyRoomDetailPage extends ConsumerStatefulWidget { + const PartyRoomDetailPage({super.key}); + + @override + ConsumerState createState() => _PartyRoomDetailPageState(); +} + +class _PartyRoomDetailPageState extends ConsumerState { + final ScrollController _scrollController = ScrollController(); + int _lastEventCount = 0; + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + void _scrollToBottom() { + if (_scrollController.hasClients) { + Future.delayed(const Duration(milliseconds: 100), () { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 300), + curve: Curves.easeOut, + ); + } + }); + } + } + + @override + Widget build(BuildContext context) { + final partyRoomState = ref.watch(partyRoomProvider); + final partyRoom = ref.read(partyRoomProvider.notifier); + final room = partyRoomState.room.currentRoom; + final members = partyRoomState.room.members; + final isOwner = partyRoomState.room.isOwner; + final events = partyRoomState.room.recentEvents; + + // 检测消息数量变化,触发滚动 + if (events.length != _lastEventCount) { + _lastEventCount = events.length; + if (events.isNotEmpty) { + _scrollToBottom(); + } + } + + return ScaffoldPage( + padding: EdgeInsets.zero, + content: Row( + children: [ + // 左侧成员列表 (类似 Discord 侧边栏) + Container( + width: 240, + decoration: BoxDecoration( + color: Color(0xFF232428).withValues(alpha: .3), + border: Border(right: BorderSide(color: Colors.black.withValues(alpha: 0.3), width: 1)), + ), + child: Column( + children: [ + // 房间信息头部 + PartyRoomHeader(room: room, members: members, isOwner: isOwner, partyRoom: partyRoom), + const Divider( + style: DividerThemeData(thickness: 1, decoration: BoxDecoration(color: Color(0xFF1E1F22))), + ), + // 成员列表 + Expanded( + child: PartyRoomMemberList(members: members, isOwner: isOwner, partyRoom: partyRoom), + ), + ], + ), + ), + // 右侧消息区域 + Expanded( + child: Column( + children: [ + // 消息列表 + Expanded( + child: PartyRoomMessageList(events: events, scrollController: _scrollController), + ), + // 信号发送按钮 + PartyRoomSignalSender(partyRoom: partyRoom, room: room), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/ui/party_room/widgets/detail/party_room_header.dart b/lib/ui/party_room/widgets/detail/party_room_header.dart new file mode 100644 index 0000000..04e2f3a --- /dev/null +++ b/lib/ui/party_room/widgets/detail/party_room_header.dart @@ -0,0 +1,112 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/provider/party_room.dart'; +import 'package:starcitizen_doctor/ui/party_room/party_room_ui_model.dart'; + +/// 房间信息头部组件 +class PartyRoomHeader extends ConsumerWidget { + final dynamic room; + final List members; + final bool isOwner; + final PartyRoom partyRoom; + + const PartyRoomHeader({ + super.key, + required this.room, + required this.members, + required this.isOwner, + required this.partyRoom, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Container( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + IconButton( + icon: const Icon(FluentIcons.back, size: 16, color: Color(0xFFB5BAC1)), + onPressed: () { + ref.read(partyRoomUIModelProvider.notifier).setMinimized(true); + }, + ), + const SizedBox(width: 8), + const Icon(FluentIcons.room, size: 16, color: Color(0xFFB5BAC1)), + const SizedBox(width: 8), + Expanded( + child: Text( + room?.ownerGameId ?? '房间', + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + children: [ + const Icon(FluentIcons.group, size: 12, color: Color(0xFF80848E)), + const SizedBox(width: 4), + Text( + '${members.length}/${room?.targetMembers ?? 0} 成员', + style: const TextStyle(fontSize: 11, color: Color(0xFF80848E)), + ), + ], + ), + if (isOwner) ...[ + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: Button( + onPressed: () async { + final confirmed = await showDialog( + 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 ...[ + const SizedBox(height: 8), + SizedBox( + width: double.infinity, + child: Button( + onPressed: () async { + await partyRoom.leaveRoom(); + }, + style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFF404249))), + child: const Text('离开房间', style: TextStyle(color: Color(0xFFB5BAC1))), + ), + ), + ], + ], + ), + ); + } +} 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 new file mode 100644 index 0000000..f1d12c4 --- /dev/null +++ b/lib/ui/party_room/widgets/detail/party_room_member_list.dart @@ -0,0 +1,252 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:local_hero/local_hero.dart'; +import 'package:starcitizen_doctor/common/conf/url_conf.dart'; +import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.dart'; +import 'package:starcitizen_doctor/provider/party_room.dart'; +import 'package:starcitizen_doctor/widgets/src/cache_image.dart'; + +/// 成员列表侧边栏 +class PartyRoomMemberList extends ConsumerWidget { + final List members; + final bool isOwner; + final PartyRoom partyRoom; + + const PartyRoomMemberList({super.key, required this.members, required this.isOwner, required this.partyRoom}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + if (members.isEmpty) { + return Center( + child: Text('暂无成员', style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 12)), + ); + } + + return ListView.builder( + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: members.length, + itemBuilder: (context, index) { + final member = members[index]; + return PartyRoomMemberItem(member: member, isOwner: isOwner, partyRoom: partyRoom); + }, + ); + } +} + +/// 成员列表项 +class PartyRoomMemberItem extends ConsumerWidget { + final RoomMember member; + final bool isOwner; + final PartyRoom partyRoom; + + const PartyRoomMemberItem({super.key, required this.member, required this.isOwner, required this.partyRoom}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final avatarUrl = member.avatarUrl.isNotEmpty ? '${URLConf.rsiAvatarBaseUrl}${member.avatarUrl}' : null; + final partyRoomState = ref.watch(partyRoomProvider); + final currentUserId = partyRoomState.auth.userInfo?.gameUserId ?? ''; + final isSelf = member.gameUserId == currentUserId; + final flyoutController = FlyoutController(); + + return FlyoutTarget( + controller: flyoutController, + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), + child: GestureDetector( + onSecondaryTapUp: (details) => + _showMemberContextMenu(context, member, partyRoom, isOwner, isSelf, flyoutController), + child: HoverButton( + onPressed: null, + builder: (context, states) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: states.isHovered ? const Color(0xFF404249) : Colors.transparent, + borderRadius: BorderRadius.circular(4), + ), + child: Row( + children: [ + // 头像 + _buildUserAvatar(member.handleName, avatarUrl: avatarUrl, size: 32, isOwner: isOwner), + const SizedBox(width: 8), + // 名称和状态 + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Flexible( + child: Text( + member.handleName.isNotEmpty ? member.handleName : member.gameUserId, + style: TextStyle( + fontSize: 13, + color: member.isOwner ? const Color(0xFFFAA81A) : const Color(0xFFDBDEE1), + fontWeight: member.isOwner ? FontWeight.bold : FontWeight.normal, + ), + overflow: TextOverflow.ellipsis, + ), + ), + if (member.isOwner) ...[ + const SizedBox(width: 4), + const Icon(FluentIcons.crown, size: 10, color: Color(0xFFFAA81A)), + ], + ], + ), + if (member.status.currentLocation.isNotEmpty) + Text( + member.status.currentLocation, + style: const TextStyle(fontSize: 10, color: Color(0xFF80848E)), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + // 状态指示器 + Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Color(0xFF23A559), // 在线绿色 + shape: BoxShape.circle, + ), + ), + ], + ), + ); + }, + ), + ), + ), + ); + } + + void _showMemberContextMenu( + BuildContext context, + RoomMember member, + PartyRoom partyRoom, + bool isOwner, + bool isSelf, + FlyoutController controller, + ) { + final menuItems = [ + // 复制ID - 所有用户可用 + MenuFlyoutItem( + leading: const Icon(FluentIcons.copy, size: 16), + text: const Text('复制用户ID'), + onPressed: () async { + await Clipboard.setData(ClipboardData(text: member.gameUserId)); + }, + ), + ]; + + // 房主专属功能 - 不能对自己和其他房主使用 + if (isOwner && !member.isOwner && !isSelf) { + menuItems.addAll([ + const MenuFlyoutSeparator(), + MenuFlyoutItem( + leading: const Icon(FluentIcons.people, size: 16), + text: const Text('转移房主'), + onPressed: () async { + final confirmed = await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('转移房主'), + content: Text('确定要将房主转移给 ${member.handleName.isNotEmpty ? member.handleName : member.gameUserId} 吗?'), + actions: [ + Button(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)), + FilledButton(child: const Text('转移'), onPressed: () => Navigator.pop(context, true)), + ], + ), + ); + if (confirmed == true && context.mounted) { + try { + await partyRoom.transferOwnership(member.gameUserId); + } catch (e) { + if (context.mounted) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('操作失败'), + content: Text('转移房主失败:$e'), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + } + } + } + }, + ), + MenuFlyoutItem( + leading: const Icon(FluentIcons.remove_from_shopping_list, size: 16), + text: const Text('踢出成员'), + onPressed: () async { + final confirmed = await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('踢出成员'), + content: Text('确定要踢出 ${member.handleName.isNotEmpty ? member.handleName : member.gameUserId} 吗?'), + actions: [ + Button(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)), + FilledButton( + style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFFDA373C))), + child: const Text('踢出'), + onPressed: () => Navigator.pop(context, true), + ), + ], + ), + ); + if (confirmed == true && context.mounted) { + try { + await partyRoom.kickMember(member.gameUserId); + } catch (e) { + if (context.mounted) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('操作失败'), + content: Text('踢出成员失败:$e'), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + } + } + } + }, + ), + ]); + } + + controller.showFlyout( + autoModeConfiguration: FlyoutAutoConfiguration(preferredMode: FlyoutPlacementMode.bottomCenter), + barrierColor: Colors.transparent, + builder: (context) { + return MenuFlyout(items: menuItems); + }, + ); + } + + Widget _buildUserAvatar(String memberName, {String? avatarUrl, bool isOwner = false, double size = 32}) { + final avatarWidget = SizedBox( + width: size, + height: size, + child: avatarUrl == null + ? CircleAvatar( + radius: 16, + backgroundColor: const Color(0xFF5865F2), + child: Text( + memberName.toUpperCase(), + style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), + ), + ) + : ClipRRect( + borderRadius: BorderRadius.circular(100), + child: CacheNetImage(url: avatarUrl), + ), + ); + if (isOwner) return LocalHero(tag: 'party_room_detail_hero', child: avatarWidget); + return avatarWidget; + } +} 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 new file mode 100644 index 0000000..d275c95 --- /dev/null +++ b/lib/ui/party_room/widgets/detail/party_room_message_list.dart @@ -0,0 +1,305 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/common/conf/url_conf.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/widgets/src/cache_image.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +/// 消息列表组件 +class PartyRoomMessageList extends ConsumerWidget { + final List events; + final ScrollController scrollController; + + const PartyRoomMessageList({super.key, required this.events, required this.scrollController}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final partyRoomState = ref.watch(partyRoomProvider); + final room = partyRoomState.room.currentRoom; + final hasSocialLinks = room != null && room.socialLinks.isNotEmpty; + + // 计算总项数:社交链接消息(如果有)+ 事件消息 + final totalItems = (hasSocialLinks ? 1 : 0) + events.length; + + if (totalItems == 0) { + return Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(FluentIcons.chat, size: 64, color: Color(0xFF404249)), + const SizedBox(height: 16), + Text('暂无消息', style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 14)), + const SizedBox(height: 4), + Text('发送一条信号开始对话吧!', style: TextStyle(color: Colors.white.withValues(alpha: 0.3), fontSize: 12)), + ], + ), + ); + } + + return ListView.builder( + controller: scrollController, + padding: const EdgeInsets.all(16), + itemCount: totalItems, + itemBuilder: (context, index) { + // 第一条消息显示社交链接(如果有) + if (hasSocialLinks && index == 0) { + return _buildSocialLinksMessage(room); + } + + // 其他消息显示事件 + final eventIndex = hasSocialLinks ? index - 1 : index; + final event = events[eventIndex]; + return _MessageItem(event: event); + }, + ); + } + + Widget _buildSocialLinksMessage(dynamic room) { + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: const Color(0xFF2B2D31), + border: Border.all(color: const Color(0xFF5865F2).withValues(alpha: 0.3)), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: const BoxDecoration(color: Color(0xFF5865F2), shape: BoxShape.circle), + child: const Icon(FluentIcons.info, size: 14, color: Colors.white), + ), + const SizedBox(width: 12), + const Expanded( + child: Text( + '该房间包含第三方社交连接,点击加入一起开黑吧~', + style: TextStyle(fontSize: 14, color: Color(0xFFDBDEE1), fontWeight: FontWeight.w500), + ), + ), + ], + ), + const SizedBox(height: 12), + Wrap( + spacing: 8, + runSpacing: 8, + children: room.socialLinks.map((link) { + return HyperlinkButton( + onPressed: () => launchUrlString(link), + style: ButtonStyle( + padding: WidgetStateProperty.all(const EdgeInsets.symmetric(horizontal: 12, vertical: 8)), + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.isHovered) return const Color(0xFF4752C4); + return const Color(0xFF5865F2); + }), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(_getSocialIcon(link), size: 16, color: Colors.white), + const SizedBox(width: 8), + Text( + _getSocialName(link), + style: const TextStyle(fontSize: 13, color: Colors.white, fontWeight: FontWeight.w500), + ), + ], + ), + ); + }).toList(), + ), + ], + ), + ); + } + + IconData _getSocialIcon(String link) { + if (link.contains('qq.com')) return FontAwesomeIcons.qq; + if (link.contains('discord')) return FontAwesomeIcons.discord; + if (link.contains('kook')) return FluentIcons.chat; + return FluentIcons.link; + } + + String _getSocialName(String link) { + if (link.contains('discord')) return 'Discord'; + if (link.contains('kook')) return 'KOOK'; + if (link.contains('qq')) return 'QQ'; + return '链接'; + } +} + +/// 消息项 +class _MessageItem extends ConsumerWidget { + final dynamic event; + + const _MessageItem({required this.event}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final roomEvent = event as partroom.RoomEvent; + final isSignal = roomEvent.type == partroom.RoomEventType.SIGNAL_BROADCAST; + final userName = _getEventUserName(roomEvent); + final avatarUrl = _getEventAvatarUrl(roomEvent); + + 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: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: isSignal ? Colors.white : const Color(0xFF80848E), + ), + ), + const SizedBox(width: 8), + Text( + _formatTime(roomEvent.timestamp), + style: const TextStyle(fontSize: 11, color: Color(0xFF80848E)), + ), + ], + ), + const SizedBox(height: 4), + Text( + _getEventText(roomEvent, ref), + style: TextStyle( + fontSize: 14, + color: isSignal ? const Color(0xFFDBDEE1) : const Color(0xFF949BA4), + fontStyle: isSignal ? FontStyle.normal : FontStyle.italic, + ), + ), + ], + ), + ), + ], + ), + ); + } + + String _getEventUserName(partroom.RoomEvent event) { + switch (event.type) { + case partroom.RoomEventType.SIGNAL_BROADCAST: + return event.signalSender.isNotEmpty ? event.signalSender : '未知用户'; + case partroom.RoomEventType.MEMBER_JOINED: + case partroom.RoomEventType.MEMBER_LEFT: + case partroom.RoomEventType.MEMBER_KICKED: + return event.hasMember() && event.member.handleName.isNotEmpty + ? event.member.handleName + : event.hasMember() + ? event.member.gameUserId + : '未知用户'; + case partroom.RoomEventType.OWNER_CHANGED: + return event.hasMember() && event.member.handleName.isNotEmpty ? event.member.handleName : '新房主'; + default: + return '系统'; + } + } + + String? _getEventAvatarUrl(partroom.RoomEvent event) { + if (event.type == partroom.RoomEventType.SIGNAL_BROADCAST || + event.type == partroom.RoomEventType.MEMBER_JOINED || + event.type == partroom.RoomEventType.MEMBER_LEFT || + event.type == partroom.RoomEventType.MEMBER_KICKED || + event.type == partroom.RoomEventType.OWNER_CHANGED) { + if (event.hasMember() && event.member.avatarUrl.isNotEmpty) { + return '${URLConf.rsiAvatarBaseUrl}${event.member.avatarUrl}'; + } + } + return null; + } + + String _getEventText(partroom.RoomEvent event, WidgetRef ref) { + final partyRoomState = ref.read(partyRoomProvider); + final signalTypes = partyRoomState.room.signalTypes; + switch (event.type) { + case partroom.RoomEventType.SIGNAL_BROADCAST: + final signalType = signalTypes[event.signalId]; + if (event.signalId.isNotEmpty) { + if (event.signalParams.isNotEmpty) { + final params = event.signalParams; + return "signalId: ${event.signalId},params:$params"; + } + } + return signalType?.name ?? event.signalId; + case partroom.RoomEventType.MEMBER_JOINED: + return '加入了房间'; + case partroom.RoomEventType.MEMBER_LEFT: + return '离开了房间'; + case partroom.RoomEventType.OWNER_CHANGED: + return '成为了新房主'; + case partroom.RoomEventType.ROOM_UPDATED: + return '房间信息已更新'; + case partroom.RoomEventType.MEMBER_STATUS_UPDATED: + if (event.hasMember()) { + final member = event.member; + final name = member.handleName.isNotEmpty ? member.handleName : member.gameUserId; + if (member.hasStatus() && member.status.currentLocation.isNotEmpty) { + return '$name 更新了状态: ${member.status.currentLocation}'; + } + return '$name 更新了状态'; + } + return '成员状态已更新'; + case partroom.RoomEventType.ROOM_DISMISSED: + return '房间已解散'; + case partroom.RoomEventType.MEMBER_KICKED: + return '被踢出房间'; + default: + return '未知事件'; + } + } + + String _formatTime(dynamic timestamp) { + try { + final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt() * 1000); + final now = DateTime.now(); + final diff = now.difference(date); + + if (diff.inMinutes < 1) { + return '刚刚'; + } else if (diff.inMinutes < 60) { + return '${diff.inMinutes} 分钟前'; + } else if (diff.inHours < 24) { + return '${diff.inHours} 小时前'; + } else { + return '${diff.inDays} 天前'; + } + } catch (e) { + return ''; + } + } + + Widget _buildUserAvatar(String memberName, {String? avatarUrl, double size = 32}) { + return SizedBox( + width: size, + height: size, + child: avatarUrl == null + ? CircleAvatar( + radius: 16, + backgroundColor: const Color(0xFF5865F2), + child: Text( + memberName.toUpperCase(), + style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), + ), + ) + : ClipRRect( + borderRadius: BorderRadius.circular(100), + child: CacheNetImage(url: avatarUrl), + ), + ); + } +} diff --git a/lib/ui/party_room/widgets/detail/party_room_signal_sender.dart b/lib/ui/party_room/widgets/detail/party_room_signal_sender.dart new file mode 100644 index 0000000..a4eaf65 --- /dev/null +++ b/lib/ui/party_room/widgets/detail/party_room_signal_sender.dart @@ -0,0 +1,63 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/provider/party_room.dart'; + +/// 信号发送器组件 +class PartyRoomSignalSender extends ConsumerWidget { + final PartyRoom partyRoom; + final dynamic room; + + const PartyRoomSignalSender({super.key, required this.partyRoom, required this.room}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final partyRoomState = ref.watch(partyRoomProvider); + final signalTypes = partyRoomState.room.signalTypes.values.where((s) => !s.isSpecial).toList(); + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: const Color(0xFF2B2D31).withValues(alpha: .4), + border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.3))), + ), + child: Row( + children: [ + const Spacer(), + DropDownButton( + leading: const Icon(FluentIcons.send, size: 16), + title: Text(signalTypes.isEmpty ? '加载中...' : '发送信号'), + disabled: signalTypes.isEmpty || room == null, + items: signalTypes.map((signal) { + return MenuFlyoutItem( + leading: const Icon(FluentIcons.radio_bullet, size: 16), + text: Text(signal.name.isNotEmpty ? signal.name : signal.id), + onPressed: () => _sendSignal(context, signal), + ); + }).toList(), + ), + ], + ), + ); + } + + Future _sendSignal(BuildContext context, dynamic signal) async { + if (room == null) return; + + try { + await partyRoom.sendSignal(signal.id); + // 信号已发送,会通过事件流更新到消息列表 + } catch (e) { + // 显示错误提示 + if (context.mounted) { + await showDialog( + context: context, + builder: (context) => ContentDialog( + title: const Text('发送失败'), + content: Text(e.toString()), + actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], + ), + ); + } + } + } +} diff --git a/lib/ui/party_room/widgets/party_room_detail_page.dart b/lib/ui/party_room/widgets/party_room_detail_page.dart deleted file mode 100644 index 14927e2..0000000 --- a/lib/ui/party_room/widgets/party_room_detail_page.dart +++ /dev/null @@ -1,689 +0,0 @@ -import 'package:fluent_ui/fluent_ui.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:starcitizen_doctor/common/conf/url_conf.dart'; -import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.dart' as partroom; -import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.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/widgets/src/cache_image.dart'; -import 'package:url_launcher/url_launcher_string.dart'; - -/// 房间详情页面 (Discord 样式) -class PartyRoomDetailPage extends ConsumerStatefulWidget { - const PartyRoomDetailPage({super.key}); - - @override - ConsumerState createState() => _PartyRoomDetailPageState(); -} - -class _PartyRoomDetailPageState extends ConsumerState { - final ScrollController _scrollController = ScrollController(); - int _lastEventCount = 0; - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - void _scrollToBottom() { - if (_scrollController.hasClients) { - Future.delayed(const Duration(milliseconds: 100), () { - if (_scrollController.hasClients) { - _scrollController.animateTo( - _scrollController.position.maxScrollExtent, - duration: const Duration(milliseconds: 300), - curve: Curves.easeOut, - ); - } - }); - } - } - - @override - Widget build(BuildContext context) { - final partyRoomState = ref.watch(partyRoomProvider); - final partyRoom = ref.read(partyRoomProvider.notifier); - final room = partyRoomState.room.currentRoom; - final members = partyRoomState.room.members; - final isOwner = partyRoomState.room.isOwner; - final events = partyRoomState.room.recentEvents; - - // 检测消息数量变化,触发滚动 - if (events.length != _lastEventCount) { - _lastEventCount = events.length; - if (events.isNotEmpty) { - _scrollToBottom(); - } - } - - return ScaffoldPage( - padding: EdgeInsets.zero, - content: Row( - children: [ - // 左侧成员列表 (类似 Discord 侧边栏) - Container( - width: 240, - decoration: BoxDecoration( - color: Color(0xFF232428).withValues(alpha: .3), - border: Border(right: BorderSide(color: Colors.black.withValues(alpha: 0.3), width: 1)), - ), - child: Column( - children: [ - // 房间信息头部 - _buildRoomHeader(context, room, members, isOwner, partyRoom), - const Divider( - style: DividerThemeData(thickness: 1, decoration: BoxDecoration(color: Color(0xFF1E1F22))), - ), - // 成员列表 - Expanded(child: _buildMembersSidebar(context, ref, members, isOwner, partyRoom)), - ], - ), - ), - // 右侧消息区域 - Expanded( - child: Column( - children: [ - // 消息列表 - Expanded(child: _buildMessageList(context, events, _scrollController, ref)), - // 信号发送按钮 - _buildSignalSender(context, ref, partyRoom, room), - ], - ), - ), - ], - ), - ); - } - - // 房间信息头部 - Widget _buildRoomHeader(BuildContext context, dynamic room, List members, bool isOwner, PartyRoom partyRoom) { - return Container( - padding: const EdgeInsets.all(12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Icon(FluentIcons.room, size: 16, color: Color(0xFFB5BAC1)), - const SizedBox(width: 8), - Expanded( - child: Text( - room?.ownerGameId ?? '房间', - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white), - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - const SizedBox(height: 8), - Row( - children: [ - const Icon(FluentIcons.group, size: 12, color: Color(0xFF80848E)), - const SizedBox(width: 4), - Text( - '${members.length}/${room?.targetMembers ?? 0} 成员', - style: const TextStyle(fontSize: 11, color: Color(0xFF80848E)), - ), - ], - ), - if (isOwner) ...[ - const SizedBox(height: 8), - SizedBox( - width: double.infinity, - child: Button( - onPressed: () async { - final confirmed = await showDialog( - 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 ...[ - const SizedBox(height: 8), - SizedBox( - width: double.infinity, - child: Button( - onPressed: () async { - await partyRoom.leaveRoom(); - }, - style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFF404249))), - child: const Text('离开房间', style: TextStyle(color: Color(0xFFB5BAC1))), - ), - ), - ], - ], - ), - ); - } - - IconData _getSocialIcon(String link) { - if (link.contains('qq.com')) return FontAwesomeIcons.qq; - if (link.contains('discord')) return FontAwesomeIcons.discord; - if (link.contains('kook')) return FluentIcons.chat; - return FluentIcons.link; - } - - String _getSocialName(String link) { - if (link.contains('discord')) return 'Discord'; - if (link.contains('kook')) return 'KOOK'; - if (link.contains('qq')) return 'QQ'; - return '链接'; - } - - // 成员侧边栏 - Widget _buildMembersSidebar(BuildContext context, WidgetRef ref, List members, bool isOwner, PartyRoom partyRoom) { - if (members.isEmpty) { - return Center( - child: Text('暂无成员', style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 12)), - ); - } - - return ListView.builder( - padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: members.length, - itemBuilder: (context, index) { - final member = members[index]; - return _buildMemberItem(context, ref, member, isOwner, partyRoom); - }, - ); - } - - Widget _buildMemberItem(BuildContext context, WidgetRef ref, RoomMember member, bool isOwner, PartyRoom partyRoom) { - final avatarUrl = member.avatarUrl.isNotEmpty ? '${URLConf.rsiAvatarBaseUrl}${member.avatarUrl}' : null; - - return Container( - margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), - child: HoverButton( - onPressed: isOwner && !member.isOwner ? () => _showMemberContextMenu(context, member, partyRoom) : null, - builder: (context, states) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: states.isHovered ? const Color(0xFF404249) : Colors.transparent, - borderRadius: BorderRadius.circular(4), - ), - child: Row( - children: [ - // 头像 - makeUserAvatar(member.handleName, avatarUrl: avatarUrl, size: 32), - const SizedBox(width: 8), - // 名称和状态 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Flexible( - child: Text( - member.handleName.isNotEmpty ? member.handleName : member.gameUserId, - style: TextStyle( - fontSize: 13, - color: member.isOwner ? const Color(0xFFFAA81A) : const Color(0xFFDBDEE1), - fontWeight: member.isOwner ? FontWeight.bold : FontWeight.normal, - ), - overflow: TextOverflow.ellipsis, - ), - ), - if (member.isOwner) ...[ - const SizedBox(width: 4), - const Icon(FluentIcons.crown, size: 10, color: Color(0xFFFAA81A)), - ], - ], - ), - if (member.status.currentLocation.isNotEmpty) - Text( - member.status.currentLocation, - style: const TextStyle(fontSize: 10, color: Color(0xFF80848E)), - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - // 状态指示器 - Container( - width: 8, - height: 8, - decoration: const BoxDecoration( - color: Color(0xFF23A559), // 在线绿色 - shape: BoxShape.circle, - ), - ), - ], - ), - ); - }, - ), - ); - } - - Widget makeUserAvatar(String memberName, {String? avatarUrl, double size = 32}) { - return SizedBox( - width: size, - height: size, - child: avatarUrl == null - ? CircleAvatar( - radius: 16, - backgroundColor: const Color(0xFF5865F2), - child: Text( - memberName.toUpperCase(), - style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), - ), - ) - : ClipRRect( - borderRadius: BorderRadius.circular(100), - child: CacheNetImage(url: avatarUrl), - ), - ); - } - - void _showMemberContextMenu(BuildContext context, dynamic member, PartyRoom partyRoom) async { - await showDialog( - context: context, - builder: (context) => ContentDialog( - title: Text(member.handleName.isNotEmpty ? member.handleName : member.gameUserId), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - FilledButton( - onPressed: () async { - Navigator.pop(context); - final confirmed = await showDialog( - context: context, - builder: (context) => ContentDialog( - title: const Text('转移房主'), - content: Text( - '确定要将房主转移给 ${member.handleName.isNotEmpty ? member.handleName : member.gameUserId} 吗?', - ), - actions: [ - Button(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)), - FilledButton(child: const Text('转移'), onPressed: () => Navigator.pop(context, true)), - ], - ), - ); - if (confirmed == true) { - await partyRoom.transferOwnership(member.gameUserId); - } - }, - child: const Text('转移房主'), - ), - const SizedBox(height: 8), - Button( - onPressed: () async { - Navigator.pop(context); - final confirmed = await showDialog( - context: context, - builder: (context) => ContentDialog( - title: const Text('踢出成员'), - content: Text('确定要踢出 ${member.handleName.isNotEmpty ? member.handleName : member.gameUserId} 吗?'), - actions: [ - Button(child: const Text('取消'), onPressed: () => Navigator.pop(context, false)), - FilledButton( - style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFFDA373C))), - child: const Text('踢出'), - onPressed: () => Navigator.pop(context, true), - ), - ], - ), - ); - if (confirmed == true) { - await partyRoom.kickMember(member.gameUserId); - } - }, - style: ButtonStyle(backgroundColor: WidgetStateProperty.all(const Color(0xFFDA373C))), - child: const Text('踢出成员', style: TextStyle(color: Colors.white)), - ), - ], - ), - actions: [Button(child: const Text('关闭'), onPressed: () => Navigator.pop(context))], - ), - ); - } - - // 消息列表 - Widget _buildMessageList(BuildContext context, List events, ScrollController scrollController, WidgetRef ref) { - final partyRoomState = ref.watch(partyRoomProvider); - final room = partyRoomState.room.currentRoom; - final hasSocialLinks = room != null && room.socialLinks.isNotEmpty; - - // 计算总项数:社交链接消息(如果有)+ 事件消息 - final totalItems = (hasSocialLinks ? 1 : 0) + events.length; - - if (totalItems == 0) { - return Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(FluentIcons.chat, size: 64, color: Color(0xFF404249)), - const SizedBox(height: 16), - Text('暂无消息', style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 14)), - const SizedBox(height: 4), - Text('发送一条信号开始对话吧!', style: TextStyle(color: Colors.white.withValues(alpha: 0.3), fontSize: 12)), - ], - ), - ); - } - - return ListView.builder( - controller: scrollController, - padding: const EdgeInsets.all(16), - itemCount: totalItems, - itemBuilder: (context, index) { - // 第一条消息显示社交链接(如果有) - if (hasSocialLinks && index == 0) { - return _buildSocialLinksMessage(room); - } - - // 其他消息显示事件 - final eventIndex = hasSocialLinks ? index - 1 : index; - final event = events[eventIndex]; - return _buildMessageItem(event, ref); - }, - ); - } - - // 社交链接系统消息 - Widget _buildSocialLinksMessage(dynamic room) { - return Container( - margin: const EdgeInsets.only(bottom: 16), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: const Color(0xFF2B2D31), - border: Border.all(color: const Color(0xFF5865F2).withValues(alpha: 0.3)), - borderRadius: BorderRadius.circular(8), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(6), - decoration: const BoxDecoration(color: Color(0xFF5865F2), shape: BoxShape.circle), - child: const Icon(FluentIcons.info, size: 14, color: Colors.white), - ), - const SizedBox(width: 12), - const Expanded( - child: Text( - '该房间包含第三方社交连接,点击加入一起开黑吧~', - style: TextStyle(fontSize: 14, color: Color(0xFFDBDEE1), fontWeight: FontWeight.w500), - ), - ), - ], - ), - const SizedBox(height: 12), - Wrap( - spacing: 8, - runSpacing: 8, - children: room.socialLinks.map((link) { - return HyperlinkButton( - onPressed: () => launchUrlString(link), - style: ButtonStyle( - padding: WidgetStateProperty.all(const EdgeInsets.symmetric(horizontal: 12, vertical: 8)), - backgroundColor: WidgetStateProperty.resolveWith((states) { - if (states.isHovered) return const Color(0xFF4752C4); - return const Color(0xFF5865F2); - }), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(_getSocialIcon(link), size: 16, color: Colors.white), - const SizedBox(width: 8), - Text( - _getSocialName(link), - style: const TextStyle(fontSize: 13, color: Colors.white, fontWeight: FontWeight.w500), - ), - ], - ), - ); - }).toList(), - ), - ], - ), - ); - } - - Widget _buildMessageItem(dynamic event, WidgetRef ref) { - final roomEvent = event as partroom.RoomEvent; - final isSignal = roomEvent.type == partroom.RoomEventType.SIGNAL_BROADCAST; - final userName = _getEventUserName(roomEvent); - final avatarUrl = _getEventAvatarUrl(roomEvent); - - return Container( - margin: const EdgeInsets.only(bottom: 16), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - makeUserAvatar(userName, avatarUrl: avatarUrl, size: 28), - const SizedBox(width: 12), - // 消息内容 - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text( - userName, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: isSignal ? Colors.white : const Color(0xFF80848E), - ), - ), - const SizedBox(width: 8), - Text( - _formatTime(roomEvent.timestamp), - style: const TextStyle(fontSize: 11, color: Color(0xFF80848E)), - ), - ], - ), - const SizedBox(height: 4), - Text( - _getEventText(roomEvent, ref), - style: TextStyle( - fontSize: 14, - color: isSignal ? const Color(0xFFDBDEE1) : const Color(0xFF949BA4), - fontStyle: isSignal ? FontStyle.normal : FontStyle.italic, - ), - ), - ], - ), - ), - ], - ), - ); - } - - String _getEventUserName(partroom.RoomEvent event) { - switch (event.type) { - case partroom.RoomEventType.SIGNAL_BROADCAST: - return event.signalSender.isNotEmpty ? event.signalSender : '未知用户'; - case partroom.RoomEventType.MEMBER_JOINED: - case partroom.RoomEventType.MEMBER_LEFT: - case partroom.RoomEventType.MEMBER_KICKED: - return event.hasMember() && event.member.handleName.isNotEmpty - ? event.member.handleName - : event.hasMember() - ? event.member.gameUserId - : '未知用户'; - case partroom.RoomEventType.OWNER_CHANGED: - return event.hasMember() && event.member.handleName.isNotEmpty ? event.member.handleName : '新房主'; - default: - return '系统'; - } - } - - String? _getEventAvatarUrl(partroom.RoomEvent event) { - if (event.type == partroom.RoomEventType.SIGNAL_BROADCAST || - event.type == partroom.RoomEventType.MEMBER_JOINED || - event.type == partroom.RoomEventType.MEMBER_LEFT || - event.type == partroom.RoomEventType.MEMBER_KICKED || - event.type == partroom.RoomEventType.OWNER_CHANGED) { - if (event.hasMember() && event.member.avatarUrl.isNotEmpty) { - return '${URLConf.rsiAvatarBaseUrl}${event.member.avatarUrl}'; - } - } - return null; - } - - // 信号发送器 - Widget _buildSignalSender(BuildContext context, WidgetRef ref, PartyRoom partyRoom, dynamic room) { - final partyRoomState = ref.watch(partyRoomProvider); - final signalTypes = partyRoomState.room.signalTypes.where((s) => !s.isSpecial).toList(); - - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: const Color(0xFF2B2D31).withValues(alpha: .4), - border: Border(top: BorderSide(color: Colors.black.withValues(alpha: 0.3))), - ), - child: Row( - children: [ - const Spacer(), - DropDownButton( - leading: const Icon(FluentIcons.send, size: 16), - title: Text(signalTypes.isEmpty ? '加载中...' : '发送信号'), - disabled: signalTypes.isEmpty || room == null, - items: signalTypes.map((signal) { - return MenuFlyoutItem( - leading: const Icon(FluentIcons.radio_bullet, size: 16), - text: Text(signal.name.isNotEmpty ? signal.name : signal.id), - onPressed: () => _sendSignal(context, ref, partyRoom, room, signal), - ); - }).toList(), - ), - ], - ), - ); - } - - Future _sendSignal( - BuildContext context, - WidgetRef ref, - PartyRoom partyRoom, - dynamic room, - dynamic signal, - ) async { - if (room == null) return; - - try { - await partyRoom.sendSignal(signal.id); - - // 发送成功后,显示在消息列表中 - if (context.mounted) { - // 信号已发送,会通过事件流更新到消息列表 - } - } catch (e) { - // 显示错误提示 - if (context.mounted) { - await showDialog( - context: context, - builder: (context) => ContentDialog( - title: const Text('发送失败'), - content: Text(e.toString()), - actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], - ), - ); - } - } - } - - String _getEventText(partroom.RoomEvent event, WidgetRef ref) { - final partyRoomState = ref.read(partyRoomProvider); - final signalTypes = partyRoomState.room.signalTypes; - switch (event.type) { - case partroom.RoomEventType.SIGNAL_BROADCAST: - // 从 signalTypes 提取信号名称 - final signalType = signalTypes.where((s) => s.id == event.signalId).firstOrNull; - // 显示信号ID和参数 - if (event.signalId.isNotEmpty) { - if (event.signalParams.isNotEmpty) { - final params = event.signalParams; - return "signalId: ${event.signalId},params:$params"; - } - } - return signalType?.name ?? event.signalId; - - case partroom.RoomEventType.MEMBER_JOINED: - return '加入了房间'; - - case partroom.RoomEventType.MEMBER_LEFT: - return '离开了房间'; - - case partroom.RoomEventType.OWNER_CHANGED: - return '成为了新房主'; - - case partroom.RoomEventType.ROOM_UPDATED: - return '房间信息已更新'; - - case partroom.RoomEventType.MEMBER_STATUS_UPDATED: - if (event.hasMember()) { - final member = event.member; - final name = member.handleName.isNotEmpty ? member.handleName : member.gameUserId; - if (member.hasStatus() && member.status.currentLocation.isNotEmpty) { - return '$name 更新了状态: ${member.status.currentLocation}'; - } - return '$name 更新了状态'; - } - return '成员状态已更新'; - - case partroom.RoomEventType.ROOM_DISMISSED: - return '房间已解散'; - - case partroom.RoomEventType.MEMBER_KICKED: - return '被踢出房间'; - - default: - return '未知事件'; - } - } - - String _formatTime(dynamic timestamp) { - try { - final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt() * 1000); - final now = DateTime.now(); - final diff = now.difference(date); - - if (diff.inMinutes < 1) { - return '刚刚'; - } else if (diff.inMinutes < 60) { - return '${diff.inMinutes} 分钟前'; - } else if (diff.inHours < 24) { - return '${diff.inHours} 小时前'; - } else { - return '${diff.inDays} 天前'; - } - } catch (e) { - return ''; - } - } -} diff --git a/lib/ui/party_room/widgets/party_room_list_page.dart b/lib/ui/party_room/widgets/party_room_list_page.dart index 6dcf57d..d21a6e8 100644 --- a/lib/ui/party_room/widgets/party_room_list_page.dart +++ b/lib/ui/party_room/widgets/party_room_list_page.dart @@ -1,11 +1,14 @@ import 'dart:ui'; +import 'package:extended_image/extended_image.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:local_hero/local_hero.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart'; +import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.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/widgets/create_room_dialog.dart'; @@ -86,6 +89,53 @@ class PartyRoomListPage extends HookConsumerWidget { Expanded(child: _buildRoomList(context, ref, uiState, partyRoom, scrollController)), ], ), + bottomBar: _buildFloatingRoomButton(context, ref, partyRoomState), + ); + } + + Widget? _buildFloatingRoomButton(BuildContext context, WidgetRef ref, PartyRoomFullState partyRoomState) { + if (!partyRoomState.room.isInRoom || partyRoomState.room.currentRoom == null) { + return null; + } + + final currentRoom = partyRoomState.room.currentRoom!; + final owner = partyRoomState.room.members.firstWhere( + (m) => m.gameUserId == currentRoom.ownerGameId, + orElse: () => RoomMember(), + ); + final avatarUrl = owner.avatarUrl.isNotEmpty ? '${URLConf.rsiAvatarBaseUrl}${owner.avatarUrl}' : ''; + + return Container( + padding: const EdgeInsets.all(16), + alignment: Alignment.bottomRight, + child: Tooltip( + message: '返回当前房间', + child: GestureDetector( + onTap: () { + ref.read(partyRoomUIModelProvider.notifier).setMinimized(false); + }, + child: LocalHero( + tag: 'party_room_detail_hero', + child: Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: const Color(0xFF2D2D2D), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow(color: Colors.black.withValues(alpha: 0.3), blurRadius: 8, offset: const Offset(0, 4)), + ], + border: Border.all(color: const Color(0xFF4A9EFF), width: 2), + ), + child: ClipOval( + child: avatarUrl.isNotEmpty + ? CacheNetImage(url: avatarUrl, fit: BoxFit.cover) + : const Icon(FluentIcons.group, color: Colors.white), + ), + ), + ), + ), + ), ); } @@ -102,7 +152,7 @@ class PartyRoomListPage extends HookConsumerWidget { value: uiState.selectedMainTagId, items: [ const ComboBoxItem(value: null, child: Text('全部标签')), - ...tags.map((tag) => ComboBoxItem(value: tag.id, child: Text(tag.name))), + ...tags.values.map((tag) => ComboBoxItem(value: tag.id, child: Text(tag.name))), ], onChanged: (value) { ref.read(partyRoomUIModelProvider.notifier).setSelectedMainTagId(value); @@ -189,6 +239,8 @@ class PartyRoomListPage extends HookConsumerWidget { Widget _buildRoomCard(BuildContext context, WidgetRef ref, PartyRoom partyRoom, dynamic room, int index) { final avatarUrl = room.ownerAvatar.isNotEmpty ? '${URLConf.rsiAvatarBaseUrl}${room.ownerAvatar}' : ''; + final partyRoomState = ref.watch(partyRoomProvider); + final isCurrentRoom = partyRoomState.room.isInRoom && partyRoomState.room.roomUuid == room.roomUuid; return GridItemAnimator( index: index, @@ -197,6 +249,7 @@ class PartyRoomListPage extends HookConsumerWidget { child: Tilt( shadowConfig: const ShadowConfig(maxIntensity: .3), borderRadius: BorderRadius.circular(12), + border: isCurrentRoom ? Border.all(color: Colors.green, width: 2) : null, clipBehavior: Clip.hardEdge, child: Container( decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)), @@ -234,7 +287,9 @@ class PartyRoomListPage extends HookConsumerWidget { CircleAvatar( radius: 24, backgroundColor: const Color(0xFF4A9EFF).withValues(alpha: 0.5), - backgroundImage: avatarUrl.isNotEmpty ? NetworkImage(avatarUrl) : null, + backgroundImage: avatarUrl.isNotEmpty + ? ExtendedNetworkImageProvider(avatarUrl, cache: true) + : null, child: avatarUrl.isEmpty ? const Icon(FluentIcons.contact, color: Colors.white) : null, ), const SizedBox(width: 12), @@ -332,29 +387,68 @@ class PartyRoomListPage extends HookConsumerWidget { } Future _joinRoom(BuildContext context, WidgetRef ref, PartyRoom partyRoom, dynamic room) async { + final partyRoomState = ref.read(partyRoomProvider); + + // 如果已经在房间中 + if (partyRoomState.room.isInRoom) { + // 如果点击的是当前房间,直接返回 + if (partyRoomState.room.roomUuid == room.roomUuid) { + ref.read(partyRoomUIModelProvider.notifier).setMinimized(false); + return; + } + + // 如果点击的是其他房间,提示用户 + if (context.mounted) { + final confirmed = await showDialog( + 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 (confirmed != true) return; + } else { + return; + } + + // 退出当前房间 + await partyRoom.leaveRoom(); + } + String? password; if (room.hasPassword) { - password = await showDialog( - context: context, - builder: (context) { - final passwordController = TextEditingController(); - return ContentDialog( - title: const Text('输入房间密码'), - content: TextBox(controller: passwordController, placeholder: '请输入密码', obscureText: true), - actions: [ - Button(child: const Text('取消'), onPressed: () => Navigator.pop(context)), - FilledButton(child: const Text('加入'), onPressed: () => Navigator.pop(context, passwordController.text)), - ], - ); - }, - ); + if (context.mounted) { + password = await showDialog( + context: context, + builder: (context) { + final passwordController = TextEditingController(); + return ContentDialog( + title: const Text('输入房间密码'), + content: TextBox(controller: passwordController, placeholder: '请输入密码', obscureText: true), + actions: [ + Button(child: const Text('取消'), onPressed: () => Navigator.pop(context)), + FilledButton(child: const Text('加入'), onPressed: () => Navigator.pop(context, passwordController.text)), + ], + ); + }, + ); + } else { + return; + } if (password == null) return; } try { await partyRoom.joinRoom(room.roomUuid, password: password); + // 加入成功后,确保不处于最小化状态 + ref.read(partyRoomUIModelProvider.notifier).setMinimized(false); } catch (e) { if (context.mounted) { await showDialog( diff --git a/pubspec.lock b/pubspec.lock index 385e751..08c7c1e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -830,6 +830,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" + local_hero: + dependency: "direct main" + description: + name: local_hero + sha256: "5c85451dd51ecd0e8d3656775fac9a6db82f296f200d9931217186d34fed6089" + url: "https://pub.dev" + source: hosted + version: "0.3.0" logging: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1d4646e..65477e3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,7 @@ dependencies: markdown: ^7.3.0 markdown_widget: ^2.3.2+8 extended_image: ^10.0.1 + local_hero: ^0.3.0 device_info_plus: ^12.2.0 file_picker: ^10.3.6 file_sizes: ^1.0.6 From 2ffb02e62d36cf06aa6bda1cea41edb373779fc9 Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Wed, 19 Nov 2025 18:19:55 +0800 Subject: [PATCH 04/11] feat: ui update --- lib/generated/intl/messages_en.dart | 16 ++ lib/generated/intl/messages_zh_CN.dart | 10 + lib/generated/l10n.dart | 60 ++++++ lib/l10n/intl_en.arb | 15 +- lib/l10n/intl_zh_CN.arb | 8 +- lib/ui/index_ui.dart | 11 + .../index_ui_widgets/user_avatar_widget.dart | 198 ++++++++++++++++++ lib/ui/party_room/party_room_ui.dart | 2 +- lib/ui/party_room/party_room_ui_model.dart | 4 + .../party_room_ui_model.freezed.dart | 39 ++-- lib/ui/party_room/party_room_ui_model.g.dart | 2 +- 11 files changed, 343 insertions(+), 22 deletions(-) create mode 100644 lib/ui/index_ui_widgets/user_avatar_widget.dart diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index e53371f..d163323 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -1718,6 +1718,22 @@ class MessageLookup extends MessageLookupByLibrary { "tools_vehicle_sorting_vehicle": MessageLookupByLibrary.simpleMessage( "Vehicles", ), + "user_action_unregister": MessageLookupByLibrary.simpleMessage( + "Unregister Account", + ), + "user_confirm_unregister_message": MessageLookupByLibrary.simpleMessage( + "Are you sure you want to unregister your account? If you need to log in again, you will need to link your account again.", + ), + "user_confirm_unregister_title": MessageLookupByLibrary.simpleMessage( + "Confirm Unregister", + ), + "user_not_logged_in": MessageLookupByLibrary.simpleMessage("Not Logged In"), + "user_unregister_failed": MessageLookupByLibrary.simpleMessage( + "Account unregistration failed", + ), + "user_unregister_success": MessageLookupByLibrary.simpleMessage( + "Account unregistered successfully", + ), "webview_localization_finished_invitations": MessageLookupByLibrary.simpleMessage("Finished invitations"), "webview_localization_name_member": MessageLookupByLibrary.simpleMessage( diff --git a/lib/generated/intl/messages_zh_CN.dart b/lib/generated/intl/messages_zh_CN.dart index 283f571..d9b947e 100644 --- a/lib/generated/intl/messages_zh_CN.dart +++ b/lib/generated/intl/messages_zh_CN.dart @@ -1472,6 +1472,16 @@ class MessageLookup extends MessageLookupByLibrary { ), "tools_vehicle_sorting_title": MessageLookupByLibrary.simpleMessage("载具排序"), "tools_vehicle_sorting_vehicle": MessageLookupByLibrary.simpleMessage("载具"), + "user_action_unregister": MessageLookupByLibrary.simpleMessage("注销账户"), + "user_confirm_unregister_message": MessageLookupByLibrary.simpleMessage( + "您确定要注销账户吗?此操作不可撤销,如需再次登录,需重新验证 RSI 账号。", + ), + "user_confirm_unregister_title": MessageLookupByLibrary.simpleMessage( + "确认注销", + ), + "user_not_logged_in": MessageLookupByLibrary.simpleMessage("未登录"), + "user_unregister_failed": MessageLookupByLibrary.simpleMessage("账户注销失败"), + "user_unregister_success": MessageLookupByLibrary.simpleMessage("账户注销成功"), "webview_localization_finished_invitations": MessageLookupByLibrary.simpleMessage("已完成的邀请"), "webview_localization_name_member": MessageLookupByLibrary.simpleMessage( diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index a15e33e..249b16c 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -6001,6 +6001,66 @@ class S { args: [], ); } + + /// `Not Logged In` + String get user_not_logged_in { + return Intl.message( + 'Not Logged In', + name: 'user_not_logged_in', + desc: '', + args: [], + ); + } + + /// `Unregister Account` + String get user_action_unregister { + return Intl.message( + 'Unregister Account', + name: 'user_action_unregister', + desc: '', + args: [], + ); + } + + /// `Confirm Unregister` + String get user_confirm_unregister_title { + return Intl.message( + 'Confirm Unregister', + name: 'user_confirm_unregister_title', + desc: '', + args: [], + ); + } + + /// `Are you sure you want to unregister your account? If you need to log in again, you will need to link your account again.` + String get user_confirm_unregister_message { + return Intl.message( + 'Are you sure you want to unregister your account? If you need to log in again, you will need to link your account again.', + name: 'user_confirm_unregister_message', + desc: '', + args: [], + ); + } + + /// `Account unregistered successfully` + String get user_unregister_success { + return Intl.message( + 'Account unregistered successfully', + name: 'user_unregister_success', + desc: '', + args: [], + ); + } + + /// `Account unregistration failed` + String get user_unregister_failed { + return Intl.message( + 'Account unregistration failed', + name: 'user_unregister_failed', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 4772b3d..99e35ca 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1180,5 +1180,18 @@ "tools_vehicle_sorting_search": "Search Vehicles", "@tools_vehicle_sorting_search": {}, "tools_vehicle_sorting_sorted": "Sorted Vehicles", - "@tools_vehicle_sorting_sorted": {} + "@tools_vehicle_sorting_sorted": {}, + "user_not_logged_in": "Not Logged In", + "@user_not_logged_in": {}, + "user_action_unregister": "Unregister Account", + "@user_action_unregister": {}, + "user_confirm_unregister_title": "Confirm Unregister", + "@user_confirm_unregister_title": {}, + "user_confirm_unregister_message": "Are you sure you want to unregister your account? If you need to log in again, you will need to link your account again.", + "@user_confirm_unregister_message": {}, + "user_unregister_success": "Account unregistered successfully", + "@user_unregister_success": {}, + "user_unregister_failed": "Account unregistration failed", + "@user_unregister_failed": {} } + diff --git a/lib/l10n/intl_zh_CN.arb b/lib/l10n/intl_zh_CN.arb index 24402d0..c02efa8 100644 --- a/lib/l10n/intl_zh_CN.arb +++ b/lib/l10n/intl_zh_CN.arb @@ -909,5 +909,11 @@ "tools_vehicle_sorting_info": "将左侧载具拖动到右侧列表中,这将会为载具名称增加 001、002 .. 等前缀,方便您在游戏内 UI 快速定位载具。在右侧列表上下拖动可以调整载具的顺序。", "tools_vehicle_sorting_vehicle": "载具", "tools_vehicle_sorting_search": "搜索载具", - "tools_vehicle_sorting_sorted": "已排序载具" + "tools_vehicle_sorting_sorted": "已排序载具", + "user_not_logged_in": "未登录", + "user_action_unregister": "注销账户", + "user_confirm_unregister_title": "确认注销", + "user_confirm_unregister_message": "您确定要注销账户吗?此操作不可撤销,如需再次登录,需重新验证 RSI 账号。", + "user_unregister_success": "账户注销成功", + "user_unregister_failed": "账户注销失败" } \ No newline at end of file diff --git a/lib/ui/index_ui.dart b/lib/ui/index_ui.dart index 203c3ce..9114519 100644 --- a/lib/ui/index_ui.dart +++ b/lib/ui/index_ui.dart @@ -17,6 +17,7 @@ import 'nav/nav_ui.dart'; import 'party_room/party_room_ui_model.dart'; import 'settings/settings_ui.dart'; import 'tools/tools_ui.dart'; +import 'index_ui_widgets/user_avatar_widget.dart'; class IndexUI extends HookConsumerWidget { const IndexUI({super.key}); @@ -51,6 +52,8 @@ class IndexUI extends HookConsumerWidget { actions: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ + UserAvatarWidget(onTapNavigateToPartyRoom: () => _navigateToPartyRoom(curIndex)), + const SizedBox(width: 12), IconButton( icon: Stack( children: [ @@ -144,4 +147,12 @@ class IndexUI extends HookConsumerWidget { void _goDownloader(BuildContext context) { context.push('/index/downloader'); } + + void _navigateToPartyRoom(ValueNotifier curIndexState) { + // 查找 PartyRoomUI 在菜单中的索引 + final partyRoomIndex = pageMenus.values.toList().indexWhere((element) => element.$2 is PartyRoomUI); + if (partyRoomIndex >= 0) { + curIndexState.value = partyRoomIndex; + } + } } diff --git a/lib/ui/index_ui_widgets/user_avatar_widget.dart b/lib/ui/index_ui_widgets/user_avatar_widget.dart new file mode 100644 index 0000000..221b6c1 --- /dev/null +++ b/lib/ui/index_ui_widgets/user_avatar_widget.dart @@ -0,0 +1,198 @@ +import 'package:fluent_ui/fluent_ui.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:starcitizen_doctor/app.dart'; +import 'package:starcitizen_doctor/common/conf/url_conf.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/widgets/widgets.dart'; + +class UserAvatarWidget extends HookConsumerWidget { + final VoidCallback onTapNavigateToPartyRoom; + + const UserAvatarWidget({super.key, required this.onTapNavigateToPartyRoom}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final partyRoomState = ref.watch(partyRoomProvider); + final uiState = ref.watch(partyRoomUIModelProvider); + final isLoggedIn = partyRoomState.auth.isLoggedIn; + final userName = partyRoomState.auth.userInfo?.gameUserId ?? S.current.user_not_logged_in; + final avatarUrl = partyRoomState.auth.userInfo?.avatarUrl; + final fullAvatarUrl = (avatarUrl != null && avatarUrl.isNotEmpty) ? '${URLConf.rsiAvatarBaseUrl}$avatarUrl' : null; + final uuid = ref.read(appGlobalModelProvider).deviceUUID; + return HoverButton( + onPressed: () { + if (isLoggedIn) { + _showAccountCard(context, ref, userName, fullAvatarUrl, uuid); + } else { + onTapNavigateToPartyRoom(); + } + }, + builder: (BuildContext context, Set states) { + return Container( + decoration: BoxDecoration( + color: states.isHovered ? Colors.white.withValues(alpha: .1) : Colors.transparent, + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // 头像 + Container( + width: 28, + height: 28, + decoration: BoxDecoration( + color: isLoggedIn ? Colors.blue : Colors.grey, + borderRadius: BorderRadius.circular(14), + border: Border.all(color: Colors.white.withValues(alpha: .3), width: 1), + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(14), + child: uiState.isLoggingIn + ? const Padding(padding: EdgeInsets.all(4), child: ProgressRing(strokeWidth: 2)) + : (fullAvatarUrl != null + ? CacheNetImage(url: fullAvatarUrl, fit: BoxFit.cover) + : Center( + child: Icon( + isLoggedIn ? FluentIcons.contact : FluentIcons.unknown, + size: 16, + color: Colors.white, + ), + )), + ), + ), + const SizedBox(width: 8), + // 用户名 + Text( + uiState.isLoggingIn ? S.current.home_title_logging_in : userName, + style: TextStyle(fontSize: 13, color: Colors.white.withValues(alpha: isLoggedIn ? 1.0 : .6)), + ), + ], + ), + ); + }, + ); + } + + void _showAccountCard(BuildContext context, WidgetRef ref, String userName, String? avatarUrl, String? uuid) { + final targetContext = context; + final box = targetContext.findRenderObject() as RenderBox?; + final offset = box?.localToGlobal(Offset.zero) ?? Offset.zero; + + showDialog( + context: context, + barrierDismissible: true, + barrierColor: Colors.transparent, + builder: (BuildContext dialogContext) { + return Stack( + children: [ + // 透明遮罩,点击关闭 + GestureDetector( + onTap: () => Navigator.of(dialogContext).pop(), + child: Container(color: Colors.transparent), + ), + // 账户卡片 + Positioned( + left: offset.dx - 100, + top: offset.dy + (box?.size.height ?? 0) + 8, + child: Container( + width: 360, + decoration: BoxDecoration( + color: FluentTheme.of(context).micaBackgroundColor, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.white.withValues(alpha: .1), width: 1), + boxShadow: [ + BoxShadow(color: Colors.black.withValues(alpha: .3), blurRadius: 20, offset: const Offset(0, 8)), + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 用户信息 + Row( + children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(24)), + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: avatarUrl != null + ? CacheNetImage(url: avatarUrl, fit: BoxFit.cover) + : const Center(child: Icon(FluentIcons.contact, size: 24, color: Colors.white)), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + userName, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Text( + '$uuid', + style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 16), + const Divider(), + const SizedBox(height: 8), + // 注销按钮 + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () async { + Navigator.of(dialogContext).pop(); + await _handleUnregister(context, ref); + }, + style: ButtonStyle(backgroundColor: WidgetStateProperty.all(Colors.red)), + child: Text(S.current.user_action_unregister, style: const TextStyle(color: Colors.white)), + ), + ), + ], + ), + ), + ), + ), + ], + ); + }, + ); + } + + Future _handleUnregister(BuildContext context, WidgetRef ref) async { + final confirmed = await showConfirmDialogs( + context, + S.current.user_confirm_unregister_title, + Text(S.current.user_confirm_unregister_message), + constraints: const BoxConstraints(maxWidth: 400), + ); + + if (confirmed == true) { + try { + final partyRoom = ref.read(partyRoomProvider.notifier); + await partyRoom.unregister(); + if (context.mounted) { + showToast(context, S.current.user_unregister_success); + } + } catch (e) { + if (context.mounted) { + showToast(context, '${S.current.user_unregister_failed}: $e'); + } + } + } + } +} diff --git a/lib/ui/party_room/party_room_ui.dart b/lib/ui/party_room/party_room_ui.dart index ab90615..ab644e0 100644 --- a/lib/ui/party_room/party_room_ui.dart +++ b/lib/ui/party_room/party_room_ui.dart @@ -19,7 +19,7 @@ class PartyRoomUI extends HookConsumerWidget { Widget widget = const PartyRoomListPage(); // 根据状态显示不同页面 - if (!partyRoomState.client.isConnected) { + if (!partyRoomState.client.isConnected || uiState.isLoggingIn) { widget = PartyRoomConnectPage(); } else if (!partyRoomState.auth.isLoggedIn) { widget = PartyRoomRegisterPage(); diff --git a/lib/ui/party_room/party_room_ui_model.dart b/lib/ui/party_room/party_room_ui_model.dart index c1086ac..711787f 100644 --- a/lib/ui/party_room/party_room_ui_model.dart +++ b/lib/ui/party_room/party_room_ui_model.dart @@ -29,6 +29,7 @@ sealed class PartyRoomUIState with _$PartyRoomUIState { @Default(false) bool isReconnecting, @Default(0) int reconnectAttempts, @Default(false) bool isMinimized, + @Default(true) bool isLoggingIn, }) = _PartyRoomUIState; } @@ -138,6 +139,7 @@ class PartyRoomUIModel extends _$PartyRoomUIModel { // 尝试登录 try { + state = state.copyWith(isLoggingIn: true); await partyRoom.login(); // 登录成功,加载标签和房间列表 await partyRoom.loadTags(); @@ -146,6 +148,8 @@ class PartyRoomUIModel extends _$PartyRoomUIModel { } catch (e) { // 未注册,保持在连接状态 dPrint('[PartyRoomUI] Login failed, need register: $e'); + } finally { + state = state.copyWith(isLoggingIn: false); } state = state.copyWith(isConnecting: false); diff --git a/lib/ui/party_room/party_room_ui_model.freezed.dart b/lib/ui/party_room/party_room_ui_model.freezed.dart index 1dd6a9d..4d9cd5f 100644 --- a/lib/ui/party_room/party_room_ui_model.freezed.dart +++ b/lib/ui/party_room/party_room_ui_model.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$PartyRoomUIState { - bool get isConnecting; bool get showRoomList; List get roomListItems; int get currentPage; int get pageSize; int get totalRooms; String? get selectedMainTagId; String? get selectedSubTagId; String get searchOwnerName; bool get isLoading; String? get errorMessage; String get preRegisterCode; String get registerGameUserId; bool get isReconnecting; int get reconnectAttempts; bool get isMinimized; + bool get isConnecting; bool get showRoomList; List get roomListItems; int get currentPage; int get pageSize; int get totalRooms; String? get selectedMainTagId; String? get selectedSubTagId; String get searchOwnerName; bool get isLoading; String? get errorMessage; String get preRegisterCode; String get registerGameUserId; bool get isReconnecting; int get reconnectAttempts; bool get isMinimized; bool get isLoggingIn; /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -25,16 +25,16 @@ $PartyRoomUIStateCopyWith get copyWith => _$PartyRoomUIStateCo @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other.roomListItems, roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other.roomListItems, roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)); } @override -int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized); +int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn); @override String toString() { - return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized)'; + return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn)'; } @@ -45,7 +45,7 @@ abstract mixin class $PartyRoomUIStateCopyWith<$Res> { factory $PartyRoomUIStateCopyWith(PartyRoomUIState value, $Res Function(PartyRoomUIState) _then) = _$PartyRoomUIStateCopyWithImpl; @useResult $Res call({ - bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized + bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn }); @@ -62,7 +62,7 @@ class _$PartyRoomUIStateCopyWithImpl<$Res> /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,}) { return _then(_self.copyWith( isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable @@ -80,6 +80,7 @@ as String,registerGameUserId: null == registerGameUserId ? _self.registerGameUse as String,isReconnecting: null == isReconnecting ? _self.isReconnecting : isReconnecting // ignore: cast_nullable_to_non_nullable as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts : reconnectAttempts // ignore: cast_nullable_to_non_nullable as int,isMinimized: null == isMinimized ? _self.isMinimized : isMinimized // ignore: cast_nullable_to_non_nullable +as bool,isLoggingIn: null == isLoggingIn ? _self.isLoggingIn : isLoggingIn // ignore: cast_nullable_to_non_nullable as bool, )); } @@ -162,10 +163,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _PartyRoomUIState() when $default != null: -return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized);case _: +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn);case _: return orElse(); } @@ -183,10 +184,10 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that. /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn) $default,) {final _that = this; switch (_that) { case _PartyRoomUIState(): -return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized);} +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn);} } /// A variant of `when` that fallback to returning `null` /// @@ -200,10 +201,10 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that. /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn)? $default,) {final _that = this; switch (_that) { case _PartyRoomUIState() when $default != null: -return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized);case _: +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn);case _: return null; } @@ -215,7 +216,7 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that. class _PartyRoomUIState implements PartyRoomUIState { - const _PartyRoomUIState({this.isConnecting = false, this.showRoomList = false, final List roomListItems = const [], this.currentPage = 1, this.pageSize = 20, this.totalRooms = 0, this.selectedMainTagId, this.selectedSubTagId, this.searchOwnerName = '', this.isLoading = false, this.errorMessage, this.preRegisterCode = '', this.registerGameUserId = '', this.isReconnecting = false, this.reconnectAttempts = 0, this.isMinimized = false}): _roomListItems = roomListItems; + const _PartyRoomUIState({this.isConnecting = false, this.showRoomList = false, final List roomListItems = const [], this.currentPage = 1, this.pageSize = 20, this.totalRooms = 0, this.selectedMainTagId, this.selectedSubTagId, this.searchOwnerName = '', this.isLoading = false, this.errorMessage, this.preRegisterCode = '', this.registerGameUserId = '', this.isReconnecting = false, this.reconnectAttempts = 0, this.isMinimized = false, this.isLoggingIn = true}): _roomListItems = roomListItems; @override@JsonKey() final bool isConnecting; @@ -240,6 +241,7 @@ class _PartyRoomUIState implements PartyRoomUIState { @override@JsonKey() final bool isReconnecting; @override@JsonKey() final int reconnectAttempts; @override@JsonKey() final bool isMinimized; +@override@JsonKey() final bool isLoggingIn; /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. @@ -251,16 +253,16 @@ _$PartyRoomUIStateCopyWith<_PartyRoomUIState> get copyWith => __$PartyRoomUIStat @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other._roomListItems, _roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other._roomListItems, _roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)); } @override -int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(_roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized); +int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(_roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn); @override String toString() { - return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized)'; + return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn)'; } @@ -271,7 +273,7 @@ abstract mixin class _$PartyRoomUIStateCopyWith<$Res> implements $PartyRoomUISta factory _$PartyRoomUIStateCopyWith(_PartyRoomUIState value, $Res Function(_PartyRoomUIState) _then) = __$PartyRoomUIStateCopyWithImpl; @override @useResult $Res call({ - bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized + bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn }); @@ -288,7 +290,7 @@ class __$PartyRoomUIStateCopyWithImpl<$Res> /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,}) { return _then(_PartyRoomUIState( isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable @@ -306,6 +308,7 @@ as String,registerGameUserId: null == registerGameUserId ? _self.registerGameUse as String,isReconnecting: null == isReconnecting ? _self.isReconnecting : isReconnecting // ignore: cast_nullable_to_non_nullable as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts : reconnectAttempts // ignore: cast_nullable_to_non_nullable as int,isMinimized: null == isMinimized ? _self.isMinimized : isMinimized // ignore: cast_nullable_to_non_nullable +as bool,isLoggingIn: null == isLoggingIn ? _self.isLoggingIn : isLoggingIn // ignore: cast_nullable_to_non_nullable as bool, )); } 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 7160239..6b26a6a 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'0e86aeb2bf3524907836e9951b04c062c84327a6'; +String _$partyRoomUIModelHash() => r'c5282725c1e078efefe0023dbedfba02091d4d8e'; abstract class _$PartyRoomUIModel extends $Notifier { PartyRoomUIState build(); From 6fda5628ffefc69a65acc1a70a162d01073651c3 Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Wed, 19 Nov 2025 21:14:36 +0800 Subject: [PATCH 05/11] feat: UI update --- lib/generated/l10n.dart | 7 +- lib/provider/party_room.dart | 4 + .../index_ui_widgets/user_avatar_widget.dart | 11 +-- lib/ui/party_room/party_room_ui.dart | 25 +++--- lib/ui/party_room/utils/party_room_utils.dart | 22 +++++ .../widgets/create_room_dialog.dart | 22 +++-- .../detail/party_room_member_list.dart | 2 - .../widgets/party_room_list_page.dart | 80 ++++++++++++------- pubspec.lock | 16 ++-- pubspec.yaml | 2 +- 10 files changed, 126 insertions(+), 65 deletions(-) create mode 100644 lib/ui/party_room/utils/party_room_utils.dart diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 249b16c..0f62308 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -28,9 +28,10 @@ class S { static const AppLocalizationDelegate delegate = AppLocalizationDelegate(); static Future load(Locale locale) { - final name = (locale.countryCode?.isEmpty ?? false) - ? locale.languageCode - : locale.toString(); + final name = + (locale.countryCode?.isEmpty ?? false) + ? locale.languageCode + : locale.toString(); final localeName = Intl.canonicalizedLocale(name); return initializeMessages(localeName).then((_) { Intl.defaultLocale = localeName; diff --git a/lib/provider/party_room.dart b/lib/provider/party_room.dart index 8850ca3..ddf6a3a 100644 --- a/lib/provider/party_room.dart +++ b/lib/provider/party_room.dart @@ -889,4 +889,8 @@ class PartyRoom extends _$PartyRoom { _stopEventStream(); _confBox?.close(); } + + common.Tag? getMainTagById(String mainTagId) { + return state.room.tags[mainTagId]; + } } diff --git a/lib/ui/index_ui_widgets/user_avatar_widget.dart b/lib/ui/index_ui_widgets/user_avatar_widget.dart index 221b6c1..9478ad2 100644 --- a/lib/ui/index_ui_widgets/user_avatar_widget.dart +++ b/lib/ui/index_ui_widgets/user_avatar_widget.dart @@ -1,9 +1,10 @@ +import 'package:fixnum/fixnum.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:starcitizen_doctor/app.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.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/utils/party_room_utils.dart'; import 'package:starcitizen_doctor/widgets/widgets.dart'; class UserAvatarWidget extends HookConsumerWidget { @@ -18,12 +19,12 @@ class UserAvatarWidget extends HookConsumerWidget { final isLoggedIn = partyRoomState.auth.isLoggedIn; final userName = partyRoomState.auth.userInfo?.gameUserId ?? S.current.user_not_logged_in; final avatarUrl = partyRoomState.auth.userInfo?.avatarUrl; + final enlistedDate = partyRoomState.auth.userInfo?.enlistedDate; final fullAvatarUrl = (avatarUrl != null && avatarUrl.isNotEmpty) ? '${URLConf.rsiAvatarBaseUrl}$avatarUrl' : null; - final uuid = ref.read(appGlobalModelProvider).deviceUUID; return HoverButton( onPressed: () { if (isLoggedIn) { - _showAccountCard(context, ref, userName, fullAvatarUrl, uuid); + _showAccountCard(context, ref, userName, fullAvatarUrl, enlistedDate); } else { onTapNavigateToPartyRoom(); } @@ -75,7 +76,7 @@ class UserAvatarWidget extends HookConsumerWidget { ); } - void _showAccountCard(BuildContext context, WidgetRef ref, String userName, String? avatarUrl, String? uuid) { + void _showAccountCard(BuildContext context, WidgetRef ref, String userName, String? avatarUrl, Int64? enlistedDate) { final targetContext = context; final box = targetContext.findRenderObject() as RenderBox?; final offset = box?.localToGlobal(Offset.zero) ?? Offset.zero; @@ -139,7 +140,7 @@ class UserAvatarWidget extends HookConsumerWidget { ), const SizedBox(height: 4), Text( - '$uuid', + '注册时间:${PartyRoomUtils.formatDateTime(enlistedDate)}', style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)), ), ], diff --git a/lib/ui/party_room/party_room_ui.dart b/lib/ui/party_room/party_room_ui.dart index ab644e0..f7cb81a 100644 --- a/lib/ui/party_room/party_room_ui.dart +++ b/lib/ui/party_room/party_room_ui.dart @@ -1,6 +1,5 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:local_hero/local_hero.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/widgets/party_room_connect_page.dart'; @@ -27,14 +26,22 @@ class PartyRoomUI extends HookConsumerWidget { widget = PartyRoomDetailPage(); } - return LocalHeroScope( - duration: Duration(milliseconds: 180), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 230), - switchInCurve: Curves.easeInOut, - switchOutCurve: Curves.easeInOut, - child: widget, - ), + return AnimatedSwitcher( + duration: const Duration(milliseconds: 230), + switchInCurve: Curves.easeInOut, + switchOutCurve: Curves.easeInOut, + transitionBuilder: (Widget child, Animation animation) { + final offsetAnimation = Tween( + begin: const Offset(0, 0.08), + end: Offset.zero, + ).chain(CurveTween(curve: Curves.easeInOut)).animate(animation); + + return SlideTransition( + position: offsetAnimation, + child: FadeTransition(opacity: animation, child: child), + ); + }, + child: widget, ); } } diff --git a/lib/ui/party_room/utils/party_room_utils.dart b/lib/ui/party_room/utils/party_room_utils.dart new file mode 100644 index 0000000..5fa218a --- /dev/null +++ b/lib/ui/party_room/utils/party_room_utils.dart @@ -0,0 +1,22 @@ +import 'package:fixnum/fixnum.dart'; +import 'package:intl/intl.dart'; + +class PartyRoomUtils { + static final DateFormat dateTimeFormatter = DateFormat('yyyy-MM-dd HH:mm:ss'); + + /// rpc Int64 时间戳 转 DateTime + static DateTime? getDateTime(Int64? timestamp) { + if (timestamp == null || timestamp == Int64.ZERO) { + return null; + } + return DateTime.fromMillisecondsSinceEpoch(timestamp.toInt() * 1000); + } + + static String? formatDateTime(Int64? timestamp) { + final dateTime = getDateTime(timestamp); + if (dateTime == null) { + return null; + } + return dateTimeFormatter.format(dateTime); + } +} diff --git a/lib/ui/party_room/widgets/create_room_dialog.dart b/lib/ui/party_room/widgets/create_room_dialog.dart index db82cd9..bc2bc68 100644 --- a/lib/ui/party_room/widgets/create_room_dialog.dart +++ b/lib/ui/party_room/widgets/create_room_dialog.dart @@ -24,7 +24,7 @@ class CreateRoomDialog extends HookConsumerWidget { final selectedMainTagData = selectedMainTag.value != null ? partyRoomState.room.tags[selectedMainTag.value] : null; return ContentDialog( - constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.4), + constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.5), title: const Text('创建房间'), content: SizedBox( child: SingleChildScrollView( @@ -55,11 +55,14 @@ class CreateRoomDialog extends HookConsumerWidget { borderRadius: BorderRadius.circular(2), ), ), - Text(tag.name), + Text(tag.name, style: TextStyle(fontSize: 16)), if (tag.info.isNotEmpty) Padding( padding: const EdgeInsets.only(left: 8), - child: Text(tag.info, style: TextStyle(fontSize: 11, color: Colors.grey[100])), + child: Text( + tag.info, + style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .7)), + ), ), ], ), @@ -99,11 +102,14 @@ class CreateRoomDialog extends HookConsumerWidget { borderRadius: BorderRadius.circular(2), ), ), - Text(subTag.name), + Text(subTag.name, style: TextStyle(fontSize: 16)), if (subTag.info.isNotEmpty) Padding( padding: const EdgeInsets.only(left: 8), - child: Text(subTag.info, style: TextStyle(fontSize: 11, color: Colors.grey[100])), + child: Text( + subTag.info, + style: TextStyle(fontSize: 14, color: Colors.white.withValues(alpha: .7)), + ), ), ], ), @@ -159,7 +165,11 @@ class CreateRoomDialog extends HookConsumerWidget { InfoLabel( label: '社交链接 (可选)', - child: TextBox(controller: socialLinksController, placeholder: 'https://discord.gg/xxxxx', maxLines: 1), + child: TextBox( + controller: socialLinksController, + placeholder: '以 https:// 开头,目前仅支持 qq、discord、kook、oopz 链接', + maxLines: 1, + ), ), ], ), 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 f1d12c4..7c539b0 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 @@ -1,7 +1,6 @@ import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:local_hero/local_hero.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart'; import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.dart'; import 'package:starcitizen_doctor/provider/party_room.dart'; @@ -246,7 +245,6 @@ class PartyRoomMemberItem extends ConsumerWidget { child: CacheNetImage(url: avatarUrl), ), ); - if (isOwner) return LocalHero(tag: 'party_room_detail_hero', child: avatarWidget); return avatarWidget; } } diff --git a/lib/ui/party_room/widgets/party_room_list_page.dart b/lib/ui/party_room/widgets/party_room_list_page.dart index d21a6e8..b687eed 100644 --- a/lib/ui/party_room/widgets/party_room_list_page.dart +++ b/lib/ui/party_room/widgets/party_room_list_page.dart @@ -1,12 +1,13 @@ import 'dart:ui'; +import 'package:animate_do/animate_do.dart'; import 'package:extended_image/extended_image.dart'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; +import 'package:hexcolor/hexcolor.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:local_hero/local_hero.dart'; import 'package:starcitizen_doctor/common/conf/url_conf.dart'; import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.dart'; import 'package:starcitizen_doctor/provider/party_room.dart'; @@ -105,17 +106,17 @@ class PartyRoomListPage extends HookConsumerWidget { ); final avatarUrl = owner.avatarUrl.isNotEmpty ? '${URLConf.rsiAvatarBaseUrl}${owner.avatarUrl}' : ''; - return Container( - padding: const EdgeInsets.all(16), - alignment: Alignment.bottomRight, - child: Tooltip( - message: '返回当前房间', - child: GestureDetector( - onTap: () { - ref.read(partyRoomUIModelProvider.notifier).setMinimized(false); - }, - child: LocalHero( - tag: 'party_room_detail_hero', + return Bounce( + duration: Duration(milliseconds: 230), + child: Container( + padding: const EdgeInsets.all(16), + alignment: Alignment.bottomRight, + child: Tooltip( + message: '返回当前房间', + child: GestureDetector( + onTap: () { + ref.read(partyRoomUIModelProvider.notifier).setMinimized(false); + }, child: Container( width: 56, height: 56, @@ -196,13 +197,22 @@ class PartyRoomListPage extends HookConsumerWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(FluentIcons.room, size: 48, color: Colors.grey.withValues(alpha: 0.6)), + Icon(FluentIcons.room, size: 48, color: Colors.white.withValues(alpha: 0.6)), const SizedBox(height: 16), - Text('暂无房间', style: TextStyle(color: Colors.white.withValues(alpha: 0.7))), + Text( + uiState.searchOwnerName.isNotEmpty + ? '没有找到符合条件的房间' + : uiState.selectedMainTagId != null + ? '当前分类下没有房间' + : '暂无可用房间', + style: TextStyle(color: Colors.white.withValues(alpha: 0.7)), + ), const SizedBox(height: 8), - Text('成为第一个创建房间的人吧!', style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.5))), - const SizedBox(height: 16), - FilledButton(onPressed: () => _showCreateRoomDialog(context, ref), child: const Text('创建房间')), + if (uiState.searchOwnerName.isEmpty) ...[ + Text('成为第一个创建房间的人吧!', style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.5))), + const SizedBox(height: 16), + FilledButton(onPressed: () => _showCreateRoomDialog(context, ref), child: const Text('创建房间')), + ], ], ), ); @@ -237,11 +247,10 @@ class PartyRoomListPage extends HookConsumerWidget { ); } - Widget _buildRoomCard(BuildContext context, WidgetRef ref, PartyRoom partyRoom, dynamic room, int index) { + Widget _buildRoomCard(BuildContext context, WidgetRef ref, PartyRoom partyRoom, RoomListItem room, int index) { final avatarUrl = room.ownerAvatar.isNotEmpty ? '${URLConf.rsiAvatarBaseUrl}${room.ownerAvatar}' : ''; final partyRoomState = ref.watch(partyRoomProvider); final isCurrentRoom = partyRoomState.room.isInRoom && partyRoomState.room.roomUuid == room.roomUuid; - return GridItemAnimator( index: index, child: GestureDetector( @@ -338,18 +347,7 @@ class PartyRoomListPage extends HookConsumerWidget { spacing: 6, runSpacing: 6, children: [ - if (room.mainTagId.isNotEmpty) - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: const Color(0xFF4A9EFF).withValues(alpha: 0.3), - borderRadius: BorderRadius.circular(4), - ), - child: Text( - room.mainTagId, - style: const TextStyle(fontSize: 11, color: Color(0xFF4A9EFF)), - ), - ), + makeTagContainer(partyRoom, room), if (room.socialLinks.isNotEmpty) Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), @@ -382,6 +380,26 @@ class PartyRoomListPage extends HookConsumerWidget { ); } + Widget makeTagContainer(PartyRoom partyRoom, RoomListItem room) { + final tag = partyRoom.getMainTagById(room.mainTagId); + final subTag = tag?.subTags.where((e) => e.id == room.subTagId).firstOrNull; + + Widget buildContainer(String name, Color color) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration(color: color, borderRadius: BorderRadius.circular(4)), + child: Text(name, style: const TextStyle(fontSize: 11, color: Colors.white)), + ); + } + + return Row( + children: [ + buildContainer(tag?.name ?? "<${tag?.id}>", HexColor(tag?.color ?? "#0096FF")), + if (subTag != null) ...[const SizedBox(width: 4), buildContainer(subTag.name, HexColor(subTag.color))], + ], + ); + } + Future _showCreateRoomDialog(BuildContext context, WidgetRef ref) async { await showDialog(context: context, builder: (context) => const CreateRoomDialog()); } diff --git a/pubspec.lock b/pubspec.lock index 08c7c1e..f6b7688 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -33,6 +33,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.13.7" + animate_do: + dependency: "direct main" + description: + name: animate_do + sha256: e5c8b92e8495cba5adfff17c0b017d50f46b2766226e9faaf68bc08c91aef034 + url: "https://pub.dev" + source: hosted + version: "4.2.0" archive: dependency: "direct main" description: @@ -830,14 +838,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.0" - local_hero: - dependency: "direct main" - description: - name: local_hero - sha256: "5c85451dd51ecd0e8d3656775fac9a6db82f296f200d9931217186d34fed6089" - url: "https://pub.dev" - source: hosted - version: "0.3.0" logging: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 65477e3..40e606e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: markdown: ^7.3.0 markdown_widget: ^2.3.2+8 extended_image: ^10.0.1 - local_hero: ^0.3.0 + animate_do: ^4.2.0 device_info_plus: ^12.2.0 file_picker: ^10.3.6 file_sizes: ^1.0.6 From f6340337dbec97f51e21067794dd98048e5aae5e Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Wed, 19 Nov 2025 21:37:32 +0800 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E6=B8=B8?= =?UTF-8?q?=E5=AE=A2=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/provider/party_room.g.dart | 2 +- lib/ui/party_room/party_room_ui.dart | 3 +- lib/ui/party_room/party_room_ui_model.dart | 19 ++++- .../party_room_ui_model.freezed.dart | 39 +++++----- lib/ui/party_room/party_room_ui_model.g.dart | 2 +- .../widgets/party_room_list_page.dart | 71 +++++++++++++++++++ .../widgets/party_room_register_page.dart | 6 ++ lib/ui/settings/settings_ui_model.g.dart | 2 +- 8 files changed, 119 insertions(+), 25 deletions(-) diff --git a/lib/provider/party_room.g.dart b/lib/provider/party_room.g.dart index 0a8b49d..5106025 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'f427838c330942d59faf614f420236dc5a699381'; +String _$partyRoomHash() => r'02cdd156995799411eb47107d5c197f43e78629e'; /// PartyRoom Provider diff --git a/lib/ui/party_room/party_room_ui.dart b/lib/ui/party_room/party_room_ui.dart index f7cb81a..bc19a47 100644 --- a/lib/ui/party_room/party_room_ui.dart +++ b/lib/ui/party_room/party_room_ui.dart @@ -20,7 +20,8 @@ class PartyRoomUI extends HookConsumerWidget { // 根据状态显示不同页面 if (!partyRoomState.client.isConnected || uiState.isLoggingIn) { widget = PartyRoomConnectPage(); - } else if (!partyRoomState.auth.isLoggedIn) { + } else if (!partyRoomState.auth.isLoggedIn && !uiState.isGuestMode) { + // 非游客模式且未登录,显示注册页面 widget = PartyRoomRegisterPage(); } else if (partyRoomState.room.isInRoom && !uiState.isMinimized) { widget = PartyRoomDetailPage(); diff --git a/lib/ui/party_room/party_room_ui_model.dart b/lib/ui/party_room/party_room_ui_model.dart index 711787f..f782256 100644 --- a/lib/ui/party_room/party_room_ui_model.dart +++ b/lib/ui/party_room/party_room_ui_model.dart @@ -30,6 +30,7 @@ sealed class PartyRoomUIState with _$PartyRoomUIState { @Default(0) int reconnectAttempts, @Default(false) bool isMinimized, @Default(true) bool isLoggingIn, + @Default(true) bool isGuestMode, }) = _PartyRoomUIState; } @@ -137,12 +138,14 @@ class PartyRoomUIModel extends _$PartyRoomUIModel { final partyRoom = ref.read(partyRoomProvider.notifier); await partyRoom.connect(); - // 尝试登录 + // 加载标签(游客和登录用户都需要) + await partyRoom.loadTags(); + + // 非游客模式:尝试登录 try { state = state.copyWith(isLoggingIn: true); await partyRoom.login(); - // 登录成功,加载标签和房间列表 - await partyRoom.loadTags(); + // 登录成功,加载房间列表 await loadRoomList(); state = state.copyWith(showRoomList: true); } catch (e) { @@ -247,6 +250,16 @@ class PartyRoomUIModel extends _$PartyRoomUIModel { await loadRoomList(page: 1); } + /// 进入游客模式 + void enterGuestMode() { + state = state.copyWith(isGuestMode: true, showRoomList: false); + } + + /// 退出游客模式(进入登录/注册流程) + void exitGuestMode() { + state = state.copyWith(isGuestMode: false, showRoomList: false); + } + /// 清除错误消息 void clearError() { state = state.copyWith(errorMessage: null); diff --git a/lib/ui/party_room/party_room_ui_model.freezed.dart b/lib/ui/party_room/party_room_ui_model.freezed.dart index 4d9cd5f..e03d205 100644 --- a/lib/ui/party_room/party_room_ui_model.freezed.dart +++ b/lib/ui/party_room/party_room_ui_model.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$PartyRoomUIState { - bool get isConnecting; bool get showRoomList; List get roomListItems; int get currentPage; int get pageSize; int get totalRooms; String? get selectedMainTagId; String? get selectedSubTagId; String get searchOwnerName; bool get isLoading; String? get errorMessage; String get preRegisterCode; String get registerGameUserId; bool get isReconnecting; int get reconnectAttempts; bool get isMinimized; bool get isLoggingIn; + bool get isConnecting; bool get showRoomList; List get roomListItems; int get currentPage; int get pageSize; int get totalRooms; String? get selectedMainTagId; String? get selectedSubTagId; String get searchOwnerName; bool get isLoading; String? get errorMessage; String get preRegisterCode; String get registerGameUserId; bool get isReconnecting; int get reconnectAttempts; bool get isMinimized; bool get isLoggingIn; bool get isGuestMode; /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -25,16 +25,16 @@ $PartyRoomUIStateCopyWith get copyWith => _$PartyRoomUIStateCo @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other.roomListItems, roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other.roomListItems, roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)&&(identical(other.isGuestMode, isGuestMode) || other.isGuestMode == isGuestMode)); } @override -int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn); +int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn,isGuestMode); @override String toString() { - return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn)'; + return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn, isGuestMode: $isGuestMode)'; } @@ -45,7 +45,7 @@ abstract mixin class $PartyRoomUIStateCopyWith<$Res> { factory $PartyRoomUIStateCopyWith(PartyRoomUIState value, $Res Function(PartyRoomUIState) _then) = _$PartyRoomUIStateCopyWithImpl; @useResult $Res call({ - bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn + bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode }); @@ -62,7 +62,7 @@ class _$PartyRoomUIStateCopyWithImpl<$Res> /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,Object? isGuestMode = null,}) { return _then(_self.copyWith( isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable @@ -81,6 +81,7 @@ as String,isReconnecting: null == isReconnecting ? _self.isReconnecting : isReco as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts : reconnectAttempts // ignore: cast_nullable_to_non_nullable as int,isMinimized: null == isMinimized ? _self.isMinimized : isMinimized // ignore: cast_nullable_to_non_nullable as bool,isLoggingIn: null == isLoggingIn ? _self.isLoggingIn : isLoggingIn // ignore: cast_nullable_to_non_nullable +as bool,isGuestMode: null == isGuestMode ? _self.isGuestMode : isGuestMode // ignore: cast_nullable_to_non_nullable as bool, )); } @@ -163,10 +164,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _PartyRoomUIState() when $default != null: -return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn);case _: +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn,_that.isGuestMode);case _: return orElse(); } @@ -184,10 +185,10 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that. /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode) $default,) {final _that = this; switch (_that) { case _PartyRoomUIState(): -return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn);} +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn,_that.isGuestMode);} } /// A variant of `when` that fallback to returning `null` /// @@ -201,10 +202,10 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that. /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode)? $default,) {final _that = this; switch (_that) { case _PartyRoomUIState() when $default != null: -return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn);case _: +return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that.currentPage,_that.pageSize,_that.totalRooms,_that.selectedMainTagId,_that.selectedSubTagId,_that.searchOwnerName,_that.isLoading,_that.errorMessage,_that.preRegisterCode,_that.registerGameUserId,_that.isReconnecting,_that.reconnectAttempts,_that.isMinimized,_that.isLoggingIn,_that.isGuestMode);case _: return null; } @@ -216,7 +217,7 @@ return $default(_that.isConnecting,_that.showRoomList,_that.roomListItems,_that. class _PartyRoomUIState implements PartyRoomUIState { - const _PartyRoomUIState({this.isConnecting = false, this.showRoomList = false, final List roomListItems = const [], this.currentPage = 1, this.pageSize = 20, this.totalRooms = 0, this.selectedMainTagId, this.selectedSubTagId, this.searchOwnerName = '', this.isLoading = false, this.errorMessage, this.preRegisterCode = '', this.registerGameUserId = '', this.isReconnecting = false, this.reconnectAttempts = 0, this.isMinimized = false, this.isLoggingIn = true}): _roomListItems = roomListItems; + const _PartyRoomUIState({this.isConnecting = false, this.showRoomList = false, final List roomListItems = const [], this.currentPage = 1, this.pageSize = 20, this.totalRooms = 0, this.selectedMainTagId, this.selectedSubTagId, this.searchOwnerName = '', this.isLoading = false, this.errorMessage, this.preRegisterCode = '', this.registerGameUserId = '', this.isReconnecting = false, this.reconnectAttempts = 0, this.isMinimized = false, this.isLoggingIn = true, this.isGuestMode = true}): _roomListItems = roomListItems; @override@JsonKey() final bool isConnecting; @@ -242,6 +243,7 @@ class _PartyRoomUIState implements PartyRoomUIState { @override@JsonKey() final int reconnectAttempts; @override@JsonKey() final bool isMinimized; @override@JsonKey() final bool isLoggingIn; +@override@JsonKey() final bool isGuestMode; /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. @@ -253,16 +255,16 @@ _$PartyRoomUIStateCopyWith<_PartyRoomUIState> get copyWith => __$PartyRoomUIStat @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other._roomListItems, _roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _PartyRoomUIState&&(identical(other.isConnecting, isConnecting) || other.isConnecting == isConnecting)&&(identical(other.showRoomList, showRoomList) || other.showRoomList == showRoomList)&&const DeepCollectionEquality().equals(other._roomListItems, _roomListItems)&&(identical(other.currentPage, currentPage) || other.currentPage == currentPage)&&(identical(other.pageSize, pageSize) || other.pageSize == pageSize)&&(identical(other.totalRooms, totalRooms) || other.totalRooms == totalRooms)&&(identical(other.selectedMainTagId, selectedMainTagId) || other.selectedMainTagId == selectedMainTagId)&&(identical(other.selectedSubTagId, selectedSubTagId) || other.selectedSubTagId == selectedSubTagId)&&(identical(other.searchOwnerName, searchOwnerName) || other.searchOwnerName == searchOwnerName)&&(identical(other.isLoading, isLoading) || other.isLoading == isLoading)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.preRegisterCode, preRegisterCode) || other.preRegisterCode == preRegisterCode)&&(identical(other.registerGameUserId, registerGameUserId) || other.registerGameUserId == registerGameUserId)&&(identical(other.isReconnecting, isReconnecting) || other.isReconnecting == isReconnecting)&&(identical(other.reconnectAttempts, reconnectAttempts) || other.reconnectAttempts == reconnectAttempts)&&(identical(other.isMinimized, isMinimized) || other.isMinimized == isMinimized)&&(identical(other.isLoggingIn, isLoggingIn) || other.isLoggingIn == isLoggingIn)&&(identical(other.isGuestMode, isGuestMode) || other.isGuestMode == isGuestMode)); } @override -int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(_roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn); +int get hashCode => Object.hash(runtimeType,isConnecting,showRoomList,const DeepCollectionEquality().hash(_roomListItems),currentPage,pageSize,totalRooms,selectedMainTagId,selectedSubTagId,searchOwnerName,isLoading,errorMessage,preRegisterCode,registerGameUserId,isReconnecting,reconnectAttempts,isMinimized,isLoggingIn,isGuestMode); @override String toString() { - return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn)'; + return 'PartyRoomUIState(isConnecting: $isConnecting, showRoomList: $showRoomList, roomListItems: $roomListItems, currentPage: $currentPage, pageSize: $pageSize, totalRooms: $totalRooms, selectedMainTagId: $selectedMainTagId, selectedSubTagId: $selectedSubTagId, searchOwnerName: $searchOwnerName, isLoading: $isLoading, errorMessage: $errorMessage, preRegisterCode: $preRegisterCode, registerGameUserId: $registerGameUserId, isReconnecting: $isReconnecting, reconnectAttempts: $reconnectAttempts, isMinimized: $isMinimized, isLoggingIn: $isLoggingIn, isGuestMode: $isGuestMode)'; } @@ -273,7 +275,7 @@ abstract mixin class _$PartyRoomUIStateCopyWith<$Res> implements $PartyRoomUISta factory _$PartyRoomUIStateCopyWith(_PartyRoomUIState value, $Res Function(_PartyRoomUIState) _then) = __$PartyRoomUIStateCopyWithImpl; @override @useResult $Res call({ - bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn + bool isConnecting, bool showRoomList, List roomListItems, int currentPage, int pageSize, int totalRooms, String? selectedMainTagId, String? selectedSubTagId, String searchOwnerName, bool isLoading, String? errorMessage, String preRegisterCode, String registerGameUserId, bool isReconnecting, int reconnectAttempts, bool isMinimized, bool isLoggingIn, bool isGuestMode }); @@ -290,7 +292,7 @@ class __$PartyRoomUIStateCopyWithImpl<$Res> /// Create a copy of PartyRoomUIState /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? isConnecting = null,Object? showRoomList = null,Object? roomListItems = null,Object? currentPage = null,Object? pageSize = null,Object? totalRooms = null,Object? selectedMainTagId = freezed,Object? selectedSubTagId = freezed,Object? searchOwnerName = null,Object? isLoading = null,Object? errorMessage = freezed,Object? preRegisterCode = null,Object? registerGameUserId = null,Object? isReconnecting = null,Object? reconnectAttempts = null,Object? isMinimized = null,Object? isLoggingIn = null,Object? isGuestMode = null,}) { return _then(_PartyRoomUIState( isConnecting: null == isConnecting ? _self.isConnecting : isConnecting // ignore: cast_nullable_to_non_nullable as bool,showRoomList: null == showRoomList ? _self.showRoomList : showRoomList // ignore: cast_nullable_to_non_nullable @@ -309,6 +311,7 @@ as String,isReconnecting: null == isReconnecting ? _self.isReconnecting : isReco as bool,reconnectAttempts: null == reconnectAttempts ? _self.reconnectAttempts : reconnectAttempts // ignore: cast_nullable_to_non_nullable as int,isMinimized: null == isMinimized ? _self.isMinimized : isMinimized // ignore: cast_nullable_to_non_nullable as bool,isLoggingIn: null == isLoggingIn ? _self.isLoggingIn : isLoggingIn // ignore: cast_nullable_to_non_nullable +as bool,isGuestMode: null == isGuestMode ? _self.isGuestMode : isGuestMode // ignore: cast_nullable_to_non_nullable as bool, )); } 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 6b26a6a..449df70 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'c5282725c1e078efefe0023dbedfba02091d4d8e'; +String _$partyRoomUIModelHash() => r'48291373cafc9005843478a90970152426b3a666'; abstract class _$PartyRoomUIModel extends $Notifier { PartyRoomUIState build(); diff --git a/lib/ui/party_room/widgets/party_room_list_page.dart b/lib/ui/party_room/widgets/party_room_list_page.dart index b687eed..781187a 100644 --- a/lib/ui/party_room/widgets/party_room_list_page.dart +++ b/lib/ui/party_room/widgets/party_room_list_page.dart @@ -55,6 +55,34 @@ class PartyRoomListPage extends HookConsumerWidget { padding: EdgeInsets.zero, content: Column( children: [ + // 游客模式提示 + if (uiState.isGuestMode) + Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), + color: const Color(0xFF2D2D2D), + child: Row( + children: [ + Icon(FluentIcons.info, size: 16, color: const Color(0xFF4A9EFF)), + const SizedBox(width: 8), + Expanded( + child: Text( + '您正在以游客身份浏览,登录后可创建或加入房间。', + style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: 0.8)), + ), + ), + const SizedBox(width: 8), + FilledButton( + style: ButtonStyle( + padding: WidgetStateProperty.all(const EdgeInsets.symmetric(horizontal: 12, vertical: 6)), + ), + onPressed: () { + ref.read(partyRoomUIModelProvider.notifier).exitGuestMode(); + }, + child: const Text('登录', style: TextStyle(fontSize: 12)), + ), + ], + ), + ), // 筛选栏 Container( padding: const EdgeInsets.all(16), @@ -401,11 +429,54 @@ class PartyRoomListPage extends HookConsumerWidget { } Future _showCreateRoomDialog(BuildContext context, WidgetRef ref) async { + final uiState = ref.read(partyRoomUIModelProvider); + + // 检查是否为游客模式 + if (uiState.isGuestMode) { + final shouldLogin = await showDialog( + 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 (shouldLogin == true) { + ref.read(partyRoomUIModelProvider.notifier).exitGuestMode(); + } + return; + } + await showDialog(context: context, builder: (context) => const CreateRoomDialog()); } Future _joinRoom(BuildContext context, WidgetRef ref, PartyRoom partyRoom, dynamic room) async { final partyRoomState = ref.read(partyRoomProvider); + final uiState = ref.read(partyRoomUIModelProvider); + + // 检查是否为游客模式 + if (uiState.isGuestMode) { + final shouldLogin = await showDialog( + 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 (shouldLogin == true) { + ref.read(partyRoomUIModelProvider.notifier).exitGuestMode(); + } + return; + } // 如果已经在房间中 if (partyRoomState.room.isInRoom) { 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 467043f..f51c330 100644 --- a/lib/ui/party_room/widgets/party_room_register_page.dart +++ b/lib/ui/party_room/widgets/party_room_register_page.dart @@ -28,6 +28,12 @@ class PartyRoomRegisterPage extends HookConsumerWidget { children: [ Row( children: [ + IconButton( + onPressed: () { + uiModel.enterGuestMode(); + }, + icon: Padding(padding: const EdgeInsets.all(8.0), child: Icon(FluentIcons.back, size: 24)), + ), const Expanded( child: Text( '注册账号', diff --git a/lib/ui/settings/settings_ui_model.g.dart b/lib/ui/settings/settings_ui_model.g.dart index 4fd2682..82f5ddf 100644 --- a/lib/ui/settings/settings_ui_model.g.dart +++ b/lib/ui/settings/settings_ui_model.g.dart @@ -41,7 +41,7 @@ final class SettingsUIModelProvider } } -String _$settingsUIModelHash() => r'd19104d924f018a9230548d0372692fc344adacd'; +String _$settingsUIModelHash() => r'5c08c56bf5464ef44bee8edb8c18c08d4217f135'; abstract class _$SettingsUIModel extends $Notifier { SettingsUIState build(); From b65187d4f08db02bceadf058a6d05f12711f603e Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Thu, 20 Nov 2025 00:27:20 +0800 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20=E5=A2=9E=E5=8A=A0=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=E7=9A=84=E6=B8=B8=E6=88=8F=E8=BF=9B=E7=A8=8B=E7=9B=91?= =?UTF-8?q?=E5=90=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/helper/game_log_analyzer.dart | 495 ++++++++++++++++++ lib/common/rust/api/win32_api.dart | 37 ++ lib/common/rust/frb_generated.dart | 130 ++++- lib/common/rust/frb_generated.io.dart | 126 +++++ lib/ui/party_room/party_room_ui.dart | 2 +- lib/ui/party_room/party_room_ui_model.dart | 56 +- lib/ui/party_room/party_room_ui_model.g.dart | 2 +- .../utils/game_log_tracker_provider.dart | 172 ++++++ .../game_log_tracker_provider.freezed.dart | 295 +++++++++++ .../utils/game_log_tracker_provider.g.dart | 128 +++++ .../widgets/detail/party_room_header.dart | 2 +- .../detail/party_room_member_list.dart | 32 +- .../detail/party_room_message_list.dart | 100 +++- .../log_analyze_ui/log_analyze_provider.dart | 388 +------------- .../log_analyze_provider.freezed.dart | 280 ---------- .../log_analyze_provider.g.dart | 2 +- rust/Cargo.toml | 7 +- rust/src/api/win32_api.rs | 125 +++++ rust/src/frb_generated.rs | 196 ++++++- 19 files changed, 1873 insertions(+), 702 deletions(-) create mode 100644 lib/common/helper/game_log_analyzer.dart create mode 100644 lib/ui/party_room/utils/game_log_tracker_provider.dart create mode 100644 lib/ui/party_room/utils/game_log_tracker_provider.freezed.dart create mode 100644 lib/ui/party_room/utils/game_log_tracker_provider.g.dart delete mode 100644 lib/ui/tools/log_analyze_ui/log_analyze_provider.freezed.dart diff --git a/lib/common/helper/game_log_analyzer.dart b/lib/common/helper/game_log_analyzer.dart new file mode 100644 index 0000000..881da31 --- /dev/null +++ b/lib/common/helper/game_log_analyzer.dart @@ -0,0 +1,495 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:intl/intl.dart'; +import 'package:starcitizen_doctor/common/helper/log_helper.dart'; +import 'package:starcitizen_doctor/generated/l10n.dart'; + +/// 日志分析结果数据类 +class LogAnalyzeLineData { + final String type; + final String title; + final String? data; + final String? dateTime; + final String? tag; // 统计标签,用于定位日志(如 "game_start"),不依赖本地化 + + // 格式化后的字段 + final String? victimId; // 受害者ID (actor_death) + final String? killerId; // 击杀者ID (actor_death) + final String? location; // 位置信息 (request_location_inventory) + final String? playerName; // 玩家名称 (player_login) + + const LogAnalyzeLineData({ + required this.type, + required this.title, + this.data, + this.dateTime, + this.tag, + this.victimId, + this.killerId, + this.location, + this.playerName, + }); + + @override + String toString() { + return 'LogAnalyzeLineData(type: $type, title: $title, data: $data, dateTime: $dateTime)'; + } +} + +/// 日志分析统计数据 +class LogAnalyzeStatistics { + final String playerName; + final int killCount; + final int deathCount; + final int selfKillCount; + final int vehicleDestructionCount; + final int vehicleDestructionCountHard; + final DateTime? gameStartTime; + final int gameCrashLineNumber; + final String? latestLocation; // 最新位置信息(全量查找) + + const LogAnalyzeStatistics({ + required this.playerName, + required this.killCount, + required this.deathCount, + required this.selfKillCount, + required this.vehicleDestructionCount, + required this.vehicleDestructionCountHard, + this.gameStartTime, + required this.gameCrashLineNumber, + this.latestLocation, + }); +} + +/// 游戏日志分析器 +class GameLogAnalyzer { + static const String unknownValue = ""; + + // 正则表达式定义 + static final _baseRegExp = RegExp(r'\[Notice\]\s+<([^>]+)>'); + static final _gameLoadingRegExp = RegExp( + r'<[^>]+>\s+Loading screen for\s+(\w+)\s+:\s+SC_Frontend closed after\s+(\d+\.\d+)\s+seconds', + ); + static final _logDateTimeRegExp = RegExp(r'<(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)>'); + static final DateFormat _dateTimeFormatter = DateFormat('yyyy-MM-dd HH:mm:ss:SSS'); + + // 致命碰撞解析 + static final _fatalCollisionPatterns = { + 'zone': RegExp(r'\[Part:[^\]]*?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.]+)'), + }; + + // 载具损毁解析 + static final _vehicleDestructionPattern = RegExp( + r"Vehicle\s+'([^']+)'.*?" // 载具型号 + r"in zone\s+'([^']+)'.*?" // Zone + r"destroy level \d+ to (\d+).*?" // 损毁等级 + r"caused by\s+'([^']+)'", // 责任方 + ); + + // 角色死亡解析 + static final _actorDeathPattern = RegExp( + r"CActor::Kill: '([^']+)'.*?" // 受害者ID + r"in zone '([^']+)'.*?" // 死亡位置区域 + r"killed by '([^']+)'.*?" // 击杀者ID + r"with damage type '([^']+)'", // 伤害类型 + ); + + // 角色名称解析 + static final _characterNamePattern = RegExp(r"name\s+([^-]+)"); + + // 本地库存请求解析 + static final _requestLocationInventoryPattern = RegExp(r"Player\[([^\]]+)\].*?Location\[([^\]]+)\]"); + + /// 分析整个日志文件 + /// + /// [logFile] 日志文件 + /// [startTime] 开始时间,如果提供则只统计此时间之后的数据 + /// 返回日志分析结果列表和统计数据 + static Future<(List, LogAnalyzeStatistics)> analyzeLogFile( + File logFile, { + DateTime? startTime, + }) async { + if (!(await logFile.exists())) { + return ( + [LogAnalyzeLineData(type: "error", title: S.current.log_analyzer_no_log_file)], + LogAnalyzeStatistics( + playerName: "", + killCount: 0, + deathCount: 0, + selfKillCount: 0, + vehicleDestructionCount: 0, + vehicleDestructionCountHard: 0, + gameCrashLineNumber: -1, + ), + ); + } + + final logLines = utf8.decode((await logFile.readAsBytes()), allowMalformed: true).split("\n"); + return _analyzeLogLines(logLines, startTime: startTime); + } + + /// 分析日志行列表 + /// + /// [logLines] 日志行列表 + /// [startTime] 开始时间,如果提供则只影响计数统计,不影响 gameStartTime 和位置的全量查找 + /// 返回日志分析结果列表和统计数据 + static (List, LogAnalyzeStatistics) _analyzeLogLines( + List logLines, { + DateTime? startTime, + }) { + final results = []; + String playerName = ""; + int killCount = 0; + int deathCount = 0; + int selfKillCount = 0; + int vehicleDestructionCount = 0; + int vehicleDestructionCountHard = 0; + DateTime? gameStartTime; // 全量查找,不受 startTime 影响 + String? latestLocation; // 全量查找最新位置 + int gameCrashLineNumber = -1; + bool shouldCount = startTime == null; // 只影响计数 + + for (var i = 0; i < logLines.length; i++) { + final line = logLines[i]; + if (line.isEmpty) continue; + + // 如果设置了 startTime,检查当前行时间 + if (startTime != null && !shouldCount) { + final lineTime = _getLogLineDateTime(line); + if (lineTime != null && lineTime.isAfter(startTime)) { + shouldCount = true; + } + } + + // 处理游戏开始(全量查找第一次出现) + if (gameStartTime == null) { + gameStartTime = _getLogLineDateTime(line); + if (gameStartTime != null) { + results.add( + LogAnalyzeLineData( + type: "info", + title: S.current.log_analyzer_game_start, + tag: "game_start", // 使用 tag 标识,不依赖本地化 + ), + ); + } + } + + // 游戏加载时间 + final gameLoading = _parseGameLoading(line); + if (gameLoading != null) { + results.add( + LogAnalyzeLineData( + type: "info", + title: S.current.log_analyzer_game_loading, + data: S.current.log_analyzer_mode_loading_time(gameLoading.$1, gameLoading.$2), + dateTime: _getLogLineDateTimeString(line), + ), + ); + continue; + } + + // 基础事件解析 + final baseEvent = _parseBaseEvent(line); + if (baseEvent != null) { + LogAnalyzeLineData? data; + switch (baseEvent) { + case "AccountLoginCharacterStatus_Character": + data = _parseCharacterName(line); + if (data != null && data.playerName != null) { + playerName = data.playerName!; // 全量更新玩家名称 + } + break; + case "FatalCollision": + data = _parseFatalCollision(line); + break; + case "Vehicle Destruction": + data = _parseVehicleDestruction(line, playerName, shouldCount, (isHard) { + if (isHard) { + vehicleDestructionCountHard++; + } else { + vehicleDestructionCount++; + } + }); + break; + case "Actor Death": + data = _parseActorDeath(line, playerName, shouldCount, (isKill, isDeath, isSelfKill) { + if (isSelfKill) { + selfKillCount++; + } else { + if (isKill) killCount++; + if (isDeath) deathCount++; + } + }); + break; + case "RequestLocationInventory": + data = _parseRequestLocationInventory(line); + if (data != null && data.location != null) { + latestLocation = data.location; // 全量更新最新位置 + } + break; + } + if (data != null) { + results.add(data); + continue; + } + } + + // 游戏关闭 + if (line.contains("[CIG] CCIGBroker::FastShutdown")) { + results.add( + LogAnalyzeLineData( + type: "info", + title: S.current.log_analyzer_game_close, + dateTime: _getLogLineDateTimeString(line), + ), + ); + continue; + } + + // 游戏崩溃 + if (line.contains("Cloud Imperium Games public crash handler")) { + gameCrashLineNumber = i; + } + } + + // 处理崩溃信息 + if (gameCrashLineNumber > 0) { + final lastLineDateTime = gameStartTime != null + ? _getLogLineDateTime(logLines.lastWhere((e) => e.startsWith("<20"))) + : null; + final crashInfo = logLines.sublist(gameCrashLineNumber); + final info = SCLoggerHelper.getGameRunningLogInfo(crashInfo); + crashInfo.add(S.current.log_analyzer_one_click_diagnosis_header); + if (info != null) { + crashInfo.add(info.key); + if (info.value.isNotEmpty) { + crashInfo.add(S.current.log_analyzer_details_info(info.value)); + } + } else { + crashInfo.add(S.current.log_analyzer_no_crash_detected); + } + results.add( + LogAnalyzeLineData( + type: "game_crash", + title: S.current.log_analyzer_game_crash, + data: crashInfo.join("\n"), + dateTime: lastLineDateTime != null ? _dateTimeFormatter.format(lastLineDateTime) : null, + ), + ); + } + + // 添加统计信息 + if (killCount > 0 || deathCount > 0) { + results.add( + LogAnalyzeLineData( + type: "statistics", + title: S.current.log_analyzer_kill_summary, + data: S.current.log_analyzer_kill_death_suicide_count( + killCount, + deathCount, + selfKillCount, + vehicleDestructionCount, + vehicleDestructionCountHard, + ), + ), + ); + } + + // 统计游戏时长 + if (gameStartTime != null) { + final lastLineDateTime = _getLogLineDateTime(logLines.lastWhere((e) => e.startsWith("<20"), orElse: () => "")); + if (lastLineDateTime != null) { + final duration = lastLineDateTime.difference(gameStartTime); + results.add( + LogAnalyzeLineData( + type: "statistics", + title: S.current.log_analyzer_play_time, + data: S.current.log_analyzer_play_time_format( + duration.inHours, + duration.inMinutes.remainder(60), + duration.inSeconds.remainder(60), + ), + ), + ); + } + } + + final statistics = LogAnalyzeStatistics( + playerName: playerName, + killCount: killCount, + deathCount: deathCount, + selfKillCount: selfKillCount, + vehicleDestructionCount: vehicleDestructionCount, + vehicleDestructionCountHard: vehicleDestructionCountHard, + gameStartTime: gameStartTime, + gameCrashLineNumber: gameCrashLineNumber, + latestLocation: latestLocation, + ); + + return (results, statistics); + } + + // ==================== 解析辅助方法 ==================== + + static String? _parseBaseEvent(String line) { + final match = _baseRegExp.firstMatch(line); + return match?.group(1); + } + + static (String, String)? _parseGameLoading(String line) { + final match = _gameLoadingRegExp.firstMatch(line); + if (match != null) { + return (match.group(1) ?? "-", match.group(2) ?? "-"); + } + return null; + } + + static DateTime? _getLogLineDateTime(String line) { + final match = _logDateTimeRegExp.firstMatch(line); + if (match != null) { + final dateTimeString = match.group(1); + if (dateTimeString != null) { + return DateTime.parse(dateTimeString).toLocal(); + } + } + return null; + } + + static String? _getLogLineDateTimeString(String line) { + final dateTime = _getLogLineDateTime(line); + if (dateTime != null) { + return _dateTimeFormatter.format(dateTime); + } + return null; + } + + static String? _safeExtract(RegExp pattern, String line) => pattern.firstMatch(line)?.group(1)?.trim(); + + static LogAnalyzeLineData? _parseFatalCollision(String line) { + 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( + type: "fatal_collision", + title: S.current.log_analyzer_filter_fatal_collision, + data: S.current.log_analyzer_collision_details( + zone, + playerPilot ? '✅' : '❌', + hitEntity, + hitEntityVehicle, + distance.toStringAsFixed(2), + ), + dateTime: _getLogLineDateTimeString(line), + ); + } + + static LogAnalyzeLineData? _parseVehicleDestruction( + String line, + String playerName, + bool shouldCount, + void Function(bool isHard) onDestruction, + ) { + final match = _vehicleDestructionPattern.firstMatch(line); + if (match != null) { + final vehicleModel = match.group(1) ?? unknownValue; + final zone = match.group(2) ?? unknownValue; + final destructionLevel = int.tryParse(match.group(3) ?? '') ?? 0; + final causedBy = match.group(4) ?? unknownValue; + + final destructionLevelMap = {1: S.current.log_analyzer_soft_death, 2: S.current.log_analyzer_disintegration}; + + if (shouldCount && causedBy.trim() == playerName) { + onDestruction(destructionLevel == 2); + } + + return LogAnalyzeLineData( + type: "vehicle_destruction", + title: S.current.log_analyzer_filter_vehicle_damaged, + data: S.current.log_analyzer_vehicle_damage_details( + vehicleModel, + zone, + destructionLevel.toString(), + destructionLevelMap[destructionLevel] ?? unknownValue, + causedBy, + ), + dateTime: _getLogLineDateTimeString(line), + ); + } + return null; + } + + static LogAnalyzeLineData? _parseActorDeath( + String line, + String playerName, + bool shouldCount, + void Function(bool isKill, bool isDeath, bool isSelfKill) onDeath, + ) { + 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; + + 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); + } + } + + return LogAnalyzeLineData( + type: "actor_death", + title: S.current.log_analyzer_filter_character_death, + data: S.current.log_analyzer_death_details(victimId, damageType, killerId, zone), + dateTime: _getLogLineDateTimeString(line), + victimId: victimId, // 格式化字段 + killerId: killerId, // 格式化字段 + ); + } + return null; + } + + static LogAnalyzeLineData? _parseCharacterName(String line) { + final match = _characterNamePattern.firstMatch(line); + if (match != null) { + final characterName = match.group(1)?.trim() ?? unknownValue; + return LogAnalyzeLineData( + type: "player_login", + title: S.current.log_analyzer_player_login(characterName), + dateTime: _getLogLineDateTimeString(line), + playerName: characterName, // 格式化字段 + ); + } + return null; + } + + static LogAnalyzeLineData? _parseRequestLocationInventory(String line) { + final match = _requestLocationInventoryPattern.firstMatch(line); + if (match != null) { + final playerId = match.group(1) ?? unknownValue; + final location = match.group(2) ?? unknownValue; + + return LogAnalyzeLineData( + type: "request_location_inventory", + title: S.current.log_analyzer_view_local_inventory, + data: S.current.log_analyzer_player_location(playerId, location), + dateTime: _getLogLineDateTimeString(line), + location: location, // 格式化字段 + ); + } + return null; + } +} diff --git a/lib/common/rust/api/win32_api.dart b/lib/common/rust/api/win32_api.dart index e0f4ccd..78d9de7 100644 --- a/lib/common/rust/api/win32_api.dart +++ b/lib/common/rust/api/win32_api.dart @@ -6,6 +6,9 @@ import '../frb_generated.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +// These functions are ignored because they are not marked as `pub`: `get_process_path` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `fmt` + Future sendNotify({ String? summary, String? body, @@ -22,3 +25,37 @@ Future setForegroundWindow({required String windowName}) => RustLib .instance .api .crateApiWin32ApiSetForegroundWindow(windowName: windowName); + +Future getProcessPidByName({required String processName}) => RustLib + .instance + .api + .crateApiWin32ApiGetProcessPidByName(processName: processName); + +Future> getProcessListByName({required String processName}) => + RustLib.instance.api.crateApiWin32ApiGetProcessListByName( + processName: processName, + ); + +class ProcessInfo { + final int pid; + final String name; + final String path; + + const ProcessInfo({ + required this.pid, + required this.name, + required this.path, + }); + + @override + int get hashCode => pid.hashCode ^ name.hashCode ^ path.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is ProcessInfo && + runtimeType == other.runtimeType && + pid == other.pid && + name == other.name && + path == other.path; +} diff --git a/lib/common/rust/frb_generated.dart b/lib/common/rust/frb_generated.dart index 5cbea7e..3bdc0d7 100644 --- a/lib/common/rust/frb_generated.dart +++ b/lib/common/rust/frb_generated.dart @@ -69,7 +69,7 @@ class RustLib extends BaseEntrypoint { String get codegenVersion => '2.11.1'; @override - int get rustContentHash => -706588047; + int get rustContentHash => 1227557070; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( @@ -95,6 +95,14 @@ abstract class RustLibApi extends BaseApi { bool? withCustomDns, }); + Future> crateApiWin32ApiGetProcessListByName({ + required String processName, + }); + + Future crateApiWin32ApiGetProcessPidByName({ + required String processName, + }); + Future crateApiAsarApiGetRsiLauncherAsarData({ required String asarPath, }); @@ -280,6 +288,66 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ], ); + @override + Future> crateApiWin32ApiGetProcessListByName({ + required String processName, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(processName); + return wire.wire__crate__api__win32_api__get_process_list_by_name( + port_, + arg0, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_list_process_info, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiGetProcessListByNameConstMeta, + argValues: [processName], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiGetProcessListByNameConstMeta => + const TaskConstMeta( + debugName: "get_process_list_by_name", + argNames: ["processName"], + ); + + @override + Future crateApiWin32ApiGetProcessPidByName({ + required String processName, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + var arg0 = cst_encode_String(processName); + return wire.wire__crate__api__win32_api__get_process_pid_by_name( + port_, + arg0, + ); + }, + codec: DcoCodec( + decodeSuccessData: dco_decode_i_32, + decodeErrorData: dco_decode_AnyhowException, + ), + constMeta: kCrateApiWin32ApiGetProcessPidByNameConstMeta, + argValues: [processName], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiWin32ApiGetProcessPidByNameConstMeta => + const TaskConstMeta( + debugName: "get_process_pid_by_name", + argNames: ["processName"], + ); + @override Future crateApiAsarApiGetRsiLauncherAsarData({ required String asarPath, @@ -722,6 +790,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw as Uint8List; } + @protected + List dco_decode_list_process_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_process_info).toList(); + } + @protected List<(String, String)> dco_decode_list_record_string_string(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -770,6 +844,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return raw == null ? null : dco_decode_list_prim_u_8_strict(raw); } + @protected + ProcessInfo dco_decode_process_info(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 3) + throw Exception('unexpected arr length: expect 3 but see ${arr.length}'); + return ProcessInfo( + pid: dco_decode_u_32(arr[0]), + name: dco_decode_String(arr[1]), + path: dco_decode_String(arr[2]), + ); + } + @protected (String, String) dco_decode_record_string_string(dynamic raw) { // Codec=Dco (DartCObject based), see doc to use other codecs @@ -949,6 +1036,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return deserializer.buffer.getUint8List(len_); } + @protected + List sse_decode_list_process_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = []; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_process_info(deserializer)); + } + return ans_; + } + @protected List<(String, String)> sse_decode_list_record_string_string( SseDeserializer deserializer, @@ -1034,6 +1133,15 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + ProcessInfo sse_decode_process_info(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_pid = sse_decode_u_32(deserializer); + var var_name = sse_decode_String(deserializer); + var var_path = sse_decode_String(deserializer); + return ProcessInfo(pid: var_pid, name: var_name, path: var_path); + } + @protected (String, String) sse_decode_record_string_string( SseDeserializer deserializer, @@ -1295,6 +1403,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { serializer.buffer.putUint8List(self); } + @protected + void sse_encode_list_process_info( + List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_process_info(item, serializer); + } + } + @protected void sse_encode_list_record_string_string( List<(String, String)> self, @@ -1378,6 +1498,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { } } + @protected + void sse_encode_process_info(ProcessInfo self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_u_32(self.pid, serializer); + sse_encode_String(self.name, serializer); + sse_encode_String(self.path, serializer); + } + @protected void sse_encode_record_string_string( (String, String) self, diff --git a/lib/common/rust/frb_generated.io.dart b/lib/common/rust/frb_generated.io.dart index c03e956..49e40fa 100644 --- a/lib/common/rust/frb_generated.io.dart +++ b/lib/common/rust/frb_generated.io.dart @@ -62,6 +62,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + @protected + List dco_decode_list_process_info(dynamic raw); + @protected List<(String, String)> dco_decode_list_record_string_string(dynamic raw); @@ -86,6 +89,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List? dco_decode_opt_list_prim_u_8_strict(dynamic raw); + @protected + ProcessInfo dco_decode_process_info(dynamic raw); + @protected (String, String) dco_decode_record_string_string(dynamic raw); @@ -159,6 +165,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + List sse_decode_list_process_info(SseDeserializer deserializer); + @protected List<(String, String)> sse_decode_list_record_string_string( SseDeserializer deserializer, @@ -187,6 +196,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { @protected Uint8List? sse_decode_opt_list_prim_u_8_strict(SseDeserializer deserializer); + @protected + ProcessInfo sse_decode_process_info(SseDeserializer deserializer); + @protected (String, String) sse_decode_record_string_string( SseDeserializer deserializer, @@ -315,6 +327,18 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { return ans; } + @protected + ffi.Pointer cst_encode_list_process_info( + List raw, + ) { + // Codec=Cst (C-struct based), see doc to use other codecs + final ans = wire.cst_new_list_process_info(raw.length); + for (var i = 0; i < raw.length; ++i) { + cst_api_fill_to_wire_process_info(raw[i], ans.ref.ptr[i]); + } + return ans; + } + @protected ffi.Pointer cst_encode_list_record_string_string(List<(String, String)> raw) { @@ -374,6 +398,16 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { cst_api_fill_to_wire_rsi_launcher_asar_data(apiObj, wireObj.ref); } + @protected + void cst_api_fill_to_wire_process_info( + ProcessInfo apiObj, + wire_cst_process_info wireObj, + ) { + wireObj.pid = cst_encode_u_32(apiObj.pid); + wireObj.name = cst_encode_String(apiObj.name); + wireObj.path = cst_encode_String(apiObj.path); + } + @protected void cst_api_fill_to_wire_record_string_string( (String, String) apiObj, @@ -499,6 +533,12 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_list_process_info( + List self, + SseSerializer serializer, + ); + @protected void sse_encode_list_record_string_string( List<(String, String)> self, @@ -532,6 +572,9 @@ abstract class RustLibApiImplPlatform extends BaseApiImpl { SseSerializer serializer, ); + @protected + void sse_encode_process_info(ProcessInfo self, SseSerializer serializer); + @protected void sse_encode_record_string_string( (String, String) self, @@ -719,6 +762,60 @@ class RustLibWire implements BaseWire { ) >(); + void wire__crate__api__win32_api__get_process_list_by_name( + int port_, + ffi.Pointer process_name, + ) { + return _wire__crate__api__win32_api__get_process_list_by_name( + port_, + process_name, + ); + } + + late final _wire__crate__api__win32_api__get_process_list_by_namePtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__win32_api__get_process_list_by_name', + ); + late final _wire__crate__api__win32_api__get_process_list_by_name = + _wire__crate__api__win32_api__get_process_list_by_namePtr + .asFunction< + void Function(int, ffi.Pointer) + >(); + + void wire__crate__api__win32_api__get_process_pid_by_name( + int port_, + ffi.Pointer process_name, + ) { + return _wire__crate__api__win32_api__get_process_pid_by_name( + port_, + process_name, + ); + } + + late final _wire__crate__api__win32_api__get_process_pid_by_namePtr = + _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Int64, + ffi.Pointer, + ) + > + >( + 'frbgen_starcitizen_doctor_wire__crate__api__win32_api__get_process_pid_by_name', + ); + late final _wire__crate__api__win32_api__get_process_pid_by_name = + _wire__crate__api__win32_api__get_process_pid_by_namePtr + .asFunction< + void Function(int, ffi.Pointer) + >(); + void wire__crate__api__asar_api__get_rsi_launcher_asar_data( int port_, ffi.Pointer asar_path, @@ -1144,6 +1241,19 @@ class RustLibWire implements BaseWire { late final _cst_new_list_prim_u_8_strict = _cst_new_list_prim_u_8_strictPtr .asFunction Function(int)>(); + ffi.Pointer cst_new_list_process_info(int len) { + return _cst_new_list_process_info(len); + } + + late final _cst_new_list_process_infoPtr = + _lookup< + ffi.NativeFunction< + ffi.Pointer Function(ffi.Int32) + > + >('frbgen_starcitizen_doctor_cst_new_list_process_info'); + late final _cst_new_list_process_info = _cst_new_list_process_infoPtr + .asFunction Function(int)>(); + ffi.Pointer cst_new_list_record_string_string(int len) { return _cst_new_list_record_string_string(len); @@ -1224,6 +1334,22 @@ final class wire_cst_list_String extends ffi.Struct { external int len; } +final class wire_cst_process_info extends ffi.Struct { + @ffi.Uint32() + external int pid; + + external ffi.Pointer name; + + external ffi.Pointer path; +} + +final class wire_cst_list_process_info extends ffi.Struct { + external ffi.Pointer ptr; + + @ffi.Int32() + external int len; +} + final class wire_cst_rs_process_stream_data extends ffi.Struct { @ffi.Int32() external int data_type; diff --git a/lib/ui/party_room/party_room_ui.dart b/lib/ui/party_room/party_room_ui.dart index bc19a47..bc63f08 100644 --- a/lib/ui/party_room/party_room_ui.dart +++ b/lib/ui/party_room/party_room_ui.dart @@ -7,7 +7,7 @@ import 'package:starcitizen_doctor/ui/party_room/widgets/party_room_list_page.da import 'package:starcitizen_doctor/ui/party_room/widgets/detail/party_room_detail_page.dart'; import 'package:starcitizen_doctor/ui/party_room/widgets/party_room_register_page.dart'; -class PartyRoomUI extends HookConsumerWidget { +class PartyRoomUI extends ConsumerWidget { const PartyRoomUI({super.key}); @override diff --git a/lib/ui/party_room/party_room_ui_model.dart b/lib/ui/party_room/party_room_ui_model.dart index f782256..0bb398f 100644 --- a/lib/ui/party_room/party_room_ui_model.dart +++ b/lib/ui/party_room/party_room_ui_model.dart @@ -1,10 +1,15 @@ import 'dart:async'; +import 'package:fixnum/fixnum.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:starcitizen_doctor/common/utils/log.dart'; import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.dart'; import 'package:starcitizen_doctor/provider/party_room.dart'; +import 'package:starcitizen_doctor/ui/party_room/utils/party_room_utils.dart' show PartyRoomUtils; + +import 'utils/game_log_tracker_provider.dart'; part 'party_room_ui_model.freezed.dart'; @@ -37,6 +42,7 @@ sealed class PartyRoomUIState with _$PartyRoomUIState { @riverpod class PartyRoomUIModel extends _$PartyRoomUIModel { Timer? _reconnectTimer; + ProviderSubscription? _gameLogSubscription; @override PartyRoomUIState build() { @@ -48,18 +54,59 @@ class PartyRoomUIModel extends _$PartyRoomUIModel { if (previous?.room.isInRoom == true && !next.room.isInRoom) { state = state.copyWith(isMinimized: false); } + + // 监听房间创建时间变化,设置游戏日志监听 + if (previous?.room.currentRoom?.createdAt != next.room.currentRoom?.createdAt) { + _setupGameLogListener(next.room.currentRoom?.createdAt); + } }); connectToServer(); - // 在 dispose 时清理定时器 + // 在 dispose 时清理定时器和订阅 ref.onDispose(() { _reconnectTimer?.cancel(); + _gameLogSubscription?.close(); }); - return state; } + /// 设置游戏日志监听 + void _setupGameLogListener(Int64? createdAt) { + // 清除之前的订阅 + _gameLogSubscription?.close(); + _gameLogSubscription = null; + + final dateTime = PartyRoomUtils.getDateTime(createdAt); + if (dateTime != null) { + _gameLogSubscription = ref.listen( + partyRoomGameLogTrackerProviderProvider(startTime: dateTime), + (previous, next) => _onUpdateGameStatus(previous, next), + ); + } + } + + /// 处理游戏状态更新 + void _onUpdateGameStatus(PartyRoomGameLogTrackerProviderState? previous, PartyRoomGameLogTrackerProviderState next) { + // 防抖 + final currentGameStartTime = previous?.gameStartTime?.millisecondsSinceEpoch; + final gameStartTime = next.gameStartTime?.microsecondsSinceEpoch; + if (next.kills != previous?.kills || + next.deaths != previous?.deaths || + next.location != previous?.location || + currentGameStartTime != gameStartTime) { + // 更新状态 + ref + .read(partyRoomProvider.notifier) + .setStatus( + kills: next.kills != previous?.kills ? next.kills : null, + deaths: next.deaths != previous?.deaths ? next.deaths : null, + currentLocation: next.location != previous?.location ? next.location : null, + playTime: currentGameStartTime != gameStartTime ? gameStartTime : null, + ); + } + } + /// 处理连接状态变化 void _handleConnectionStateChange(PartyRoomFullState? previous, PartyRoomFullState next) { // 检测断线:之前已连接但现在未连接 @@ -140,17 +187,16 @@ class PartyRoomUIModel extends _$PartyRoomUIModel { // 加载标签(游客和登录用户都需要) await partyRoom.loadTags(); - - // 非游客模式:尝试登录 try { state = state.copyWith(isLoggingIn: true); await partyRoom.login(); // 登录成功,加载房间列表 await loadRoomList(); - state = state.copyWith(showRoomList: true); + state = state.copyWith(showRoomList: true, isLoggingIn: false, isGuestMode: false); } catch (e) { // 未注册,保持在连接状态 dPrint('[PartyRoomUI] Login failed, need register: $e'); + state = state.copyWith(isGuestMode: true); } finally { state = state.copyWith(isLoggingIn: false); } 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 449df70..ff73367 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'48291373cafc9005843478a90970152426b3a666'; +String _$partyRoomUIModelHash() => r'a0b6c3632ff33f2d58882f9bc1ab58c69c2487f4'; 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 new file mode 100644 index 0000000..6b21537 --- /dev/null +++ b/lib/ui/party_room/utils/game_log_tracker_provider.dart @@ -0,0 +1,172 @@ +import 'dart:io'; + +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; + +import 'package:starcitizen_doctor/common/helper/game_log_analyzer.dart'; +import 'package:starcitizen_doctor/common/rust/api/win32_api.dart' as win32; +import 'package:starcitizen_doctor/common/utils/log.dart'; + +part 'game_log_tracker_provider.freezed.dart'; + +part 'game_log_tracker_provider.g.dart'; + +@freezed +sealed class PartyRoomGameLogTrackerProviderState with _$PartyRoomGameLogTrackerProviderState { + const factory PartyRoomGameLogTrackerProviderState({ + @Default('') String location, + @Default(0) int kills, + @Default(0) int deaths, + DateTime? gameStartTime, + @Default([]) List killedIds, // 本次迭代新增的击杀ID + @Default([]) List deathIds, // 本次迭代新增的死亡ID + }) = _PartyRoomGameLogTrackerProviderState; +} + +@riverpod +class PartyRoomGameLogTrackerProvider extends _$PartyRoomGameLogTrackerProvider { + var _disposed = false; + + // 记录上次查询的时间点,用于计算增量 + DateTime? _lastQueryTime; + + @override + PartyRoomGameLogTrackerProviderState build({required DateTime startTime}) { + dPrint("[PartyRoomGameLogTrackerProvider] init $startTime"); + ref.onDispose(() { + _disposed = true; + dPrint("[PartyRoomGameLogTrackerProvider] disposed $startTime"); + }); + _lastQueryTime = startTime; + _listenToGameLogs(startTime); + return const PartyRoomGameLogTrackerProviderState(); + } + + Future _listenToGameLogs(DateTime startTime) async { + await Future.delayed(const Duration(seconds: 1)); + while (!_disposed) { + try { + // 获取正在运行的游戏进程 + final l = await win32.getProcessListByName(processName: "StarCitizen.exe"); + final p = l + .where((e) => e.path.toLowerCase().contains("starcitizen") && e.path.toLowerCase().contains("bin64")) + .firstOrNull; + + if (p == null) throw Exception("process not found"); + + final logPath = _getLogPath(p); + final logFile = File(logPath); + + if (await logFile.exists()) { + // 分析日志文件 + await _analyzeLog(logFile, startTime); + } else { + state = state.copyWith(location: '', gameStartTime: null); + } + } catch (e) { + // 游戏未启动或发生错误 + state = state.copyWith( + location: '<游戏未启动>', + gameStartTime: null, + kills: 0, + deaths: 0, + killedIds: [], + deathIds: [], + ); + } + await Future.delayed(const Duration(seconds: 5)); + } + } + + Future _analyzeLog(File logFile, DateTime startTime) async { + try { + final now = DateTime.now(); + + // 使用 GameLogAnalyzer 分析日志 + // startTime 只影响计数统计 + final result = await GameLogAnalyzer.analyzeLogFile(logFile, startTime: startTime); + final (logData, statistics) = result; + + // 从统计数据中直接获取最新位置(全量查找的结果) + final location = statistics.latestLocation == null ? '<主菜单>' : '[${statistics.latestLocation}]'; + + // 计算基于 _lastQueryTime 之后的增量 ID + final newKilledIds = []; + final newDeathIds = []; + + if (_lastQueryTime != null) { + // 遍历所有 actor_death 事件 + for (final data in logData) { + if (data.type != "actor_death") continue; + + // 解析事件时间 + if (data.dateTime != null) { + try { + // 日志时间格式: "yyyy-MM-dd HH:mm:ss:SSS" + // 转换为 ISO 8601 格式再解析 + final parts = data.dateTime!.split(' '); + if (parts.length >= 2) { + final datePart = parts[0]; // yyyy-MM-dd + final timeParts = parts[1].split(':'); + if (timeParts.length >= 3) { + final hour = timeParts[0]; + final minute = timeParts[1]; + final secondMillis = timeParts[2]; // ss:SSS 或 ss.SSS + final timeStr = '$datePart $hour:$minute:${secondMillis.replaceAll(':', '.')}'; + final eventTime = DateTime.parse(timeStr); + + // 只处理在 _lastQueryTime 之后的事件 + if (eventTime.isBefore(_lastQueryTime!)) continue; + } + } + } catch (e) { + dPrint("[PartyRoomGameLogTrackerProvider] Failed to parse dateTime: ${data.dateTime}, error: $e"); + // 时间解析失败,继续处理该事件(保守策略) + } + } + + // 使用格式化字段,不再重新解析 + 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); + } + } + } + } + + // 更新状态,只存储本次迭代的增量数据 + state = state.copyWith( + location: location, + kills: statistics.killCount, + // 从 startTime 开始的总计数 + deaths: statistics.deathCount, + // 从 startTime 开始的总计数 + gameStartTime: statistics.gameStartTime, + // 全量查找的游戏开始时间 + killedIds: newKilledIds, + // 只存储本次迭代的增量 + deathIds: newDeathIds, // 只存储本次迭代的增量 + ); + + // 更新查询时间为本次查询的时刻 + _lastQueryTime = now; + } catch (e, stackTrace) { + dPrint("[PartyRoomGameLogTrackerProvider] Error analyzing log: $e"); + dPrint("[PartyRoomGameLogTrackerProvider] Stack trace: $stackTrace"); + } + } + + String _getLogPath(win32.ProcessInfo p) { + var path = p.path; + return path.replaceAll(r"Bin64\StarCitizen.exe", "Game.log"); + } +} 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 new file mode 100644 index 0000000..0b16234 --- /dev/null +++ b/lib/ui/party_room/utils/game_log_tracker_provider.freezed.dart @@ -0,0 +1,295 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'game_log_tracker_provider.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$PartyRoomGameLogTrackerProviderState { + + String get location; int get kills; int get deaths; DateTime? get gameStartTime; List get killedIds;// 本次迭代新增的击杀ID + List get deathIds; +/// Create a copy of PartyRoomGameLogTrackerProviderState +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$PartyRoomGameLogTrackerProviderStateCopyWith get copyWith => _$PartyRoomGameLogTrackerProviderStateCopyWithImpl(this as PartyRoomGameLogTrackerProviderState, _$identity); + + + +@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)); +} + + +@override +int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(killedIds),const DeepCollectionEquality().hash(deathIds)); + +@override +String toString() { + return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, killedIds: $killedIds, deathIds: $deathIds)'; +} + + +} + +/// @nodoc +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 +}); + + + + +} +/// @nodoc +class _$PartyRoomGameLogTrackerProviderStateCopyWithImpl<$Res> + implements $PartyRoomGameLogTrackerProviderStateCopyWith<$Res> { + _$PartyRoomGameLogTrackerProviderStateCopyWithImpl(this._self, this._then); + + final PartyRoomGameLogTrackerProviderState _self; + final $Res Function(PartyRoomGameLogTrackerProviderState) _then; + +/// 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,}) { + 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, + )); +} + +} + + +/// Adds pattern-matching-related methods to [PartyRoomGameLogTrackerProviderState]. +extension PartyRoomGameLogTrackerProviderStatePatterns on PartyRoomGameLogTrackerProviderState { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap(TResult Function( _PartyRoomGameLogTrackerProviderState value)? $default,{required TResult orElse(),}){ +final _that = this; +switch (_that) { +case _PartyRoomGameLogTrackerProviderState() when $default != null: +return $default(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map(TResult Function( _PartyRoomGameLogTrackerProviderState value) $default,){ +final _that = this; +switch (_that) { +case _PartyRoomGameLogTrackerProviderState(): +return $default(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull(TResult? Function( _PartyRoomGameLogTrackerProviderState value)? $default,){ +final _that = this; +switch (_that) { +case _PartyRoomGameLogTrackerProviderState() when $default != null: +return $default(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List killedIds, List deathIds)? $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 orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when(TResult Function( String location, int kills, int deaths, DateTime? gameStartTime, List killedIds, List deathIds) $default,) {final _that = this; +switch (_that) { +case _PartyRoomGameLogTrackerProviderState(): +return $default(_that.location,_that.kills,_that.deaths,_that.gameStartTime,_that.killedIds,_that.deathIds);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String location, int kills, int deaths, DateTime? gameStartTime, List killedIds, List deathIds)? $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 null; + +} +} + +} + +/// @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; + + +@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; + // 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); +} + + +/// Create a copy of PartyRoomGameLogTrackerProviderState +/// with the given fields replaced by the non-null parameter values. +@override @JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +_$PartyRoomGameLogTrackerProviderStateCopyWith<_PartyRoomGameLogTrackerProviderState> get copyWith => __$PartyRoomGameLogTrackerProviderStateCopyWithImpl<_PartyRoomGameLogTrackerProviderState>(this, _$identity); + + + +@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)); +} + + +@override +int get hashCode => Object.hash(runtimeType,location,kills,deaths,gameStartTime,const DeepCollectionEquality().hash(_killedIds),const DeepCollectionEquality().hash(_deathIds)); + +@override +String toString() { + return 'PartyRoomGameLogTrackerProviderState(location: $location, kills: $kills, deaths: $deaths, gameStartTime: $gameStartTime, killedIds: $killedIds, deathIds: $deathIds)'; +} + + +} + +/// @nodoc +abstract mixin class _$PartyRoomGameLogTrackerProviderStateCopyWith<$Res> implements $PartyRoomGameLogTrackerProviderStateCopyWith<$Res> { + 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 +}); + + + + +} +/// @nodoc +class __$PartyRoomGameLogTrackerProviderStateCopyWithImpl<$Res> + implements _$PartyRoomGameLogTrackerProviderStateCopyWith<$Res> { + __$PartyRoomGameLogTrackerProviderStateCopyWithImpl(this._self, this._then); + + final _PartyRoomGameLogTrackerProviderState _self; + final $Res Function(_PartyRoomGameLogTrackerProviderState) _then; + +/// 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,}) { + 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, + )); +} + + +} + +// dart format on 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 new file mode 100644 index 0000000..7547c9f --- /dev/null +++ b/lib/ui/party_room/utils/game_log_tracker_provider.g.dart @@ -0,0 +1,128 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'game_log_tracker_provider.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(PartyRoomGameLogTrackerProvider) +const partyRoomGameLogTrackerProviderProvider = + PartyRoomGameLogTrackerProviderFamily._(); + +final class PartyRoomGameLogTrackerProviderProvider + extends + $NotifierProvider< + PartyRoomGameLogTrackerProvider, + PartyRoomGameLogTrackerProviderState + > { + const PartyRoomGameLogTrackerProviderProvider._({ + required PartyRoomGameLogTrackerProviderFamily super.from, + required DateTime super.argument, + }) : super( + retry: null, + name: r'partyRoomGameLogTrackerProviderProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$partyRoomGameLogTrackerProviderHash(); + + @override + String toString() { + return r'partyRoomGameLogTrackerProviderProvider' + '' + '($argument)'; + } + + @$internal + @override + PartyRoomGameLogTrackerProvider create() => PartyRoomGameLogTrackerProvider(); + + /// {@macro riverpod.override_with_value} + Override overrideWithValue(PartyRoomGameLogTrackerProviderState value) { + return $ProviderOverride( + origin: this, + providerOverride: + $SyncValueProvider(value), + ); + } + + @override + bool operator ==(Object other) { + return other is PartyRoomGameLogTrackerProviderProvider && + other.argument == argument; + } + + @override + int get hashCode { + return argument.hashCode; + } +} + +String _$partyRoomGameLogTrackerProviderHash() => + r'ecb015eb46d25bfe11bbb153242fd5c4f20ef367'; + +final class PartyRoomGameLogTrackerProviderFamily extends $Family + with + $ClassFamilyOverride< + PartyRoomGameLogTrackerProvider, + PartyRoomGameLogTrackerProviderState, + PartyRoomGameLogTrackerProviderState, + PartyRoomGameLogTrackerProviderState, + DateTime + > { + const PartyRoomGameLogTrackerProviderFamily._() + : super( + retry: null, + name: r'partyRoomGameLogTrackerProviderProvider', + dependencies: null, + $allTransitiveDependencies: null, + isAutoDispose: true, + ); + + PartyRoomGameLogTrackerProviderProvider call({required DateTime startTime}) => + PartyRoomGameLogTrackerProviderProvider._( + argument: startTime, + from: this, + ); + + @override + String toString() => r'partyRoomGameLogTrackerProviderProvider'; +} + +abstract class _$PartyRoomGameLogTrackerProvider + extends $Notifier { + late final _$args = ref.$arg as DateTime; + DateTime get startTime => _$args; + + PartyRoomGameLogTrackerProviderState build({required DateTime startTime}); + @$mustCallSuper + @override + void runBuild() { + final created = build(startTime: _$args); + final ref = + this.ref + as $Ref< + PartyRoomGameLogTrackerProviderState, + PartyRoomGameLogTrackerProviderState + >; + final element = + ref.element + as $ClassProviderElement< + AnyNotifier< + PartyRoomGameLogTrackerProviderState, + PartyRoomGameLogTrackerProviderState + >, + PartyRoomGameLogTrackerProviderState, + Object?, + Object? + >; + element.handleValue(ref, created); + } +} diff --git a/lib/ui/party_room/widgets/detail/party_room_header.dart b/lib/ui/party_room/widgets/detail/party_room_header.dart index 04e2f3a..6f11aad 100644 --- a/lib/ui/party_room/widgets/detail/party_room_header.dart +++ b/lib/ui/party_room/widgets/detail/party_room_header.dart @@ -28,7 +28,7 @@ class PartyRoomHeader extends ConsumerWidget { Row( children: [ IconButton( - icon: const Icon(FluentIcons.back, size: 16, color: Color(0xFFB5BAC1)), + icon: const Icon(FluentIcons.back, size: 16, color: Colors.white), onPressed: () { ref.read(partyRoomUIModelProvider.notifier).setMinimized(true); }, 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 7c539b0..b339d1d 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 @@ -54,6 +54,7 @@ class PartyRoomMemberItem extends ConsumerWidget { child: Container( margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), child: GestureDetector( + onTapUp: (details) => _showMemberContextMenu(context, member, partyRoom, isOwner, isSelf, flyoutController), onSecondaryTapUp: (details) => _showMemberContextMenu(context, member, partyRoom, isOwner, isSelf, flyoutController), child: HoverButton( @@ -94,24 +95,23 @@ class PartyRoomMemberItem extends ConsumerWidget { ], ], ), - if (member.status.currentLocation.isNotEmpty) - Text( - member.status.currentLocation, - style: const TextStyle(fontSize: 10, color: Color(0xFF80848E)), - overflow: TextOverflow.ellipsis, - ), + 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)), + ), + ], + ), ], ), ), - // 状态指示器 - Container( - width: 8, - height: 8, - decoration: const BoxDecoration( - color: Color(0xFF23A559), // 在线绿色 - shape: BoxShape.circle, - ), - ), ], ), ); @@ -134,7 +134,7 @@ class PartyRoomMemberItem extends ConsumerWidget { // 复制ID - 所有用户可用 MenuFlyoutItem( leading: const Icon(FluentIcons.copy, size: 16), - text: const Text('复制用户ID'), + text: const Text('复制游戏ID'), onPressed: () async { await Clipboard.setData(ClipboardData(text: member.gameUserId)); }, 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 d275c95..83ac2ae 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 @@ -6,6 +6,7 @@ import 'package:starcitizen_doctor/generated/proto/partroom/partroom.pb.dart' as import 'package:starcitizen_doctor/provider/party_room.dart'; import 'package:starcitizen_doctor/widgets/src/cache_image.dart'; import 'package:url_launcher/url_launcher_string.dart'; +import 'package:flutter/services.dart'; /// 消息列表组件 class PartyRoomMessageList extends ConsumerWidget { @@ -20,19 +21,17 @@ class PartyRoomMessageList extends ConsumerWidget { final room = partyRoomState.room.currentRoom; final hasSocialLinks = room != null && room.socialLinks.isNotEmpty; - // 计算总项数:社交链接消息(如果有)+ 事件消息 - final totalItems = (hasSocialLinks ? 1 : 0) + events.length; + // 计算总项数:社交链接消息(如果有)+ 复制 ID 消息 + 事件消息 + final totalItems = (hasSocialLinks ? 1 : 0) + 1 + events.length; if (totalItems == 0) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(FluentIcons.chat, size: 64, color: Color(0xFF404249)), + Icon(FluentIcons.chat, size: 64, color: Colors.white.withValues(alpha: .6)), const SizedBox(height: 16), Text('暂无消息', style: TextStyle(color: Colors.white.withValues(alpha: 0.5), fontSize: 14)), - const SizedBox(height: 4), - Text('发送一条信号开始对话吧!', style: TextStyle(color: Colors.white.withValues(alpha: 0.3), fontSize: 12)), ], ), ); @@ -47,9 +46,13 @@ class PartyRoomMessageList extends ConsumerWidget { if (hasSocialLinks && index == 0) { return _buildSocialLinksMessage(room); } - + // 第二条消息显示复制 ID + final copyIdIndex = hasSocialLinks ? 1 : 0; + if (index == copyIdIndex) { + return _buildCopyIdMessage(room); + } // 其他消息显示事件 - final eventIndex = hasSocialLinks ? index - 1 : index; + final eventIndex = index - (hasSocialLinks ? 2 : 1); final event = events[eventIndex]; return _MessageItem(event: event); }, @@ -78,7 +81,7 @@ class PartyRoomMessageList extends ConsumerWidget { const SizedBox(width: 12), const Expanded( child: Text( - '该房间包含第三方社交连接,点击加入一起开黑吧~', + '该房间包含第三方社交连接,点击加入自由交流吧~', style: TextStyle(fontSize: 14, color: Color(0xFFDBDEE1), fontWeight: FontWeight.w500), ), ), @@ -117,6 +120,68 @@ class PartyRoomMessageList extends ConsumerWidget { ); } + Widget _buildCopyIdMessage(dynamic room) { + final ownerGameId = room?.ownerGameId ?? ''; + return Container( + margin: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration(color: const Color(0xFF2B2D31), borderRadius: BorderRadius.circular(8)), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration(color: Colors.purple, shape: BoxShape.circle), + child: const Icon(FluentIcons.copy, size: 14, color: Colors.white), + ), + const SizedBox(width: 12), + const Expanded( + child: Text( + '复制房主的游戏ID,可在游戏首页添加好友,快速组队', + style: TextStyle(fontSize: 14, color: Color(0xFFDBDEE1), fontWeight: FontWeight.w500), + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration(color: const Color(0xFF1E1F22), borderRadius: BorderRadius.circular(4)), + child: Text(ownerGameId, style: const TextStyle(fontSize: 13, color: Color(0xFFDBDEE1))), + ), + ), + const SizedBox(width: 8), + Button( + onPressed: ownerGameId.isNotEmpty + ? () async { + await Clipboard.setData(ClipboardData(text: ownerGameId)); + } + : null, + style: ButtonStyle( + padding: WidgetStateProperty.all(const EdgeInsets.symmetric(horizontal: 12, vertical: 8)), + backgroundColor: WidgetStateProperty.resolveWith((states) { + if (states.isPressed) return const Color(0xFF3A40A0); + if (states.isHovered) return const Color(0xFF4752C4); + return const Color(0xFF5865F2); + }), + ), + child: const Text( + '复制', + style: TextStyle(fontSize: 13, color: Colors.white, fontWeight: FontWeight.w500), + ), + ), + ], + ), + ], + ), + ); + } + IconData _getSocialIcon(String link) { if (link.contains('qq.com')) return FontAwesomeIcons.qq; if (link.contains('discord')) return FontAwesomeIcons.discord; @@ -145,6 +210,9 @@ class _MessageItem extends ConsumerWidget { final userName = _getEventUserName(roomEvent); final avatarUrl = _getEventAvatarUrl(roomEvent); + final text = _getEventText(roomEvent, ref); + if (text == null) return const SizedBox.shrink(); + return Container( margin: const EdgeInsets.only(bottom: 16), child: Row( @@ -176,7 +244,7 @@ class _MessageItem extends ConsumerWidget { ), const SizedBox(height: 4), Text( - _getEventText(roomEvent, ref), + text, style: TextStyle( fontSize: 14, color: isSignal ? const Color(0xFFDBDEE1) : const Color(0xFF949BA4), @@ -223,7 +291,7 @@ class _MessageItem extends ConsumerWidget { return null; } - String _getEventText(partroom.RoomEvent event, WidgetRef ref) { + String? _getEventText(partroom.RoomEvent event, WidgetRef ref) { final partyRoomState = ref.read(partyRoomProvider); final signalTypes = partyRoomState.room.signalTypes; switch (event.type) { @@ -245,21 +313,13 @@ class _MessageItem extends ConsumerWidget { case partroom.RoomEventType.ROOM_UPDATED: return '房间信息已更新'; case partroom.RoomEventType.MEMBER_STATUS_UPDATED: - if (event.hasMember()) { - final member = event.member; - final name = member.handleName.isNotEmpty ? member.handleName : member.gameUserId; - if (member.hasStatus() && member.status.currentLocation.isNotEmpty) { - return '$name 更新了状态: ${member.status.currentLocation}'; - } - return '$name 更新了状态'; - } - return '成员状态已更新'; + return null; case partroom.RoomEventType.ROOM_DISMISSED: return '房间已解散'; case partroom.RoomEventType.MEMBER_KICKED: return '被踢出房间'; default: - return '未知事件'; + return null; } } diff --git a/lib/ui/tools/log_analyze_ui/log_analyze_provider.dart b/lib/ui/tools/log_analyze_ui/log_analyze_provider.dart index 96d637e..42c97d8 100644 --- a/lib/ui/tools/log_analyze_ui/log_analyze_provider.dart +++ b/lib/ui/tools/log_analyze_ui/log_analyze_provider.dart @@ -1,19 +1,14 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:fluent_ui/fluent_ui.dart' show debugPrint; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:intl/intl.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; -import 'package:starcitizen_doctor/common/helper/log_helper.dart'; +import 'package:starcitizen_doctor/common/helper/game_log_analyzer.dart'; import 'package:starcitizen_doctor/generated/l10n.dart'; import 'package:watcher/watcher.dart'; part 'log_analyze_provider.g.dart'; -part 'log_analyze_provider.freezed.dart'; - final Map logAnalyzeSearchTypeMap = { null: S.current.log_analyzer_filter_all, "info": S.current.log_analyzer_filter_basic_info, @@ -26,132 +21,29 @@ final Map logAnalyzeSearchTypeMap = { "request_location_inventory": S.current.log_analyzer_filter_local_inventory, }; -@freezed -abstract class LogAnalyzeLineData with _$LogAnalyzeLineData { - const factory LogAnalyzeLineData({ - required String type, - required String title, - String? data, - String? dateTime, - }) = _LogAnalyzeLineData; -} - @riverpod class ToolsLogAnalyze extends _$ToolsLogAnalyze { - static const String unknownValue = ""; - @override Future> build(String gameInstallPath, bool listSortReverse) async { final logFile = File("$gameInstallPath/Game.log"); debugPrint("[ToolsLogAnalyze] logFile: ${logFile.absolute.path}"); if (gameInstallPath.isEmpty || !(await logFile.exists())) { - return [ - LogAnalyzeLineData( - type: "error", - title: S.current.log_analyzer_no_log_file, - ) - ]; + return [const LogAnalyzeLineData(type: "error", title: "未找到日志文件")]; } - state = AsyncData([]); + state = const AsyncData([]); _launchLogAnalyze(logFile); return state.value ?? []; } - String _playerName = ""; // 记录玩家名称 - int _killCount = 0; // 记录击杀其他实体次数 - int _deathCount = 0; // 记录被击杀次数 - int _selfKillCount = 0; // 记录自杀次数 - int _vehicleDestructionCount = 0; // 记录载具损毁次数 (软死亡) - int _vehicleDestructionCountHard = 0; // 记录载具损毁次数 (解体) - DateTime? _gameStartTime; // 记录游戏开始时间 - int _gameCrashLineNumber = -1; // 记录${S.current.log_analyzer_filter_game_crash}行号 - int _currentLineNumber = 0; // 当前行号 + void _launchLogAnalyze(File logFile) async { + // 使用新的 GameLogAnalyzer 工具类 + final result = await GameLogAnalyzer.analyzeLogFile(logFile); + final (results, _) = result; - void _launchLogAnalyze(File logFile, {int startLine = 0}) async { - final logLines = utf8.decode((await logFile.readAsBytes()), allowMalformed: true).split("\n"); - debugPrint("[ToolsLogAnalyze] logLines: ${logLines.length}"); - if (startLine == 0) { - _killCount = 0; - _deathCount = 0; - _selfKillCount = 0; - _vehicleDestructionCount = 0; - _vehicleDestructionCountHard = 0; - _gameStartTime = null; - _gameCrashLineNumber = -1; - } else if (startLine > logLines.length) { - // 考虑文件重新写入的情况 - ref.invalidateSelf(); - } - _currentLineNumber = logLines.length; - // for i in logLines - for (var i = 0; i < logLines.length; i++) { - // 支持追加模式 - if (i < startLine) continue; - final line = logLines[i]; - if (line.isEmpty) continue; - final data = _handleLogLine(line, i); - if (data != null) { - _appendResult(data); - // wait for ui update - await Future.delayed(Duration(seconds: 0)); - } - } - - final lastLineDateTime = - _gameStartTime != null ? _getLogLineDateTime(logLines.lastWhere((e) => e.startsWith("<20"))) : null; - - // 检查${S.current.log_analyzer_filter_game_crash}行号 - if (_gameCrashLineNumber > 0) { - // crashInfo 从 logLines _gameCrashLineNumber 开始到最后一行 - final crashInfo = logLines.sublist(_gameCrashLineNumber); - // 运行一键诊断 - final info = SCLoggerHelper.getGameRunningLogInfo(crashInfo); - crashInfo.add(S.current.log_analyzer_one_click_diagnosis_header); - if (info != null) { - crashInfo.add(info.key); - if (info.value.isNotEmpty) { - crashInfo.add(S.current.log_analyzer_details_info(info.value)); - } - } else { - crashInfo.add(S.current.log_analyzer_no_crash_detected); - } - _appendResult(LogAnalyzeLineData( - type: "game_crash", - title: S.current.log_analyzer_game_crash, - data: crashInfo.join("\n"), - dateTime: lastLineDateTime != null ? _dateTimeFormatter.format(lastLineDateTime) : null, - )); - } - - // ${S.current.log_analyzer_kill_summary} - if (_killCount > 0 || _deathCount > 0) { - _appendResult(LogAnalyzeLineData( - type: "statistics", - title: S.current.log_analyzer_kill_summary, - data: S.current.log_analyzer_kill_death_suicide_count( - _killCount, - _deathCount, - _selfKillCount, - _vehicleDestructionCount, - _vehicleDestructionCountHard, - ), - )); - } - - // 统计${S.current.log_analyzer_play_time},_gameStartTime 减去 最后一行的时间 - if (_gameStartTime != null) { - if (lastLineDateTime != null) { - final duration = lastLineDateTime.difference(_gameStartTime!); - _appendResult(LogAnalyzeLineData( - type: "statistics", - title: S.current.log_analyzer_play_time, - data: S.current.log_analyzer_play_time_format( - duration.inHours, - duration.inMinutes.remainder(60), - duration.inSeconds.remainder(60), - ), - )); - } + // 逐条添加结果以支持流式显示 + for (final data in results) { + _appendResult(data); + await Future.delayed(Duration.zero); // 让 UI 有机会更新 } _startListenFile(logFile); @@ -165,21 +57,21 @@ class ToolsLogAnalyze extends _$ToolsLogAnalyze { debugPrint("[ToolsLogAnalyze] startListenFile: ${logFile.absolute.path}"); // 监听文件 late final StreamSubscription sub; - sub = FileWatcher(logFile.absolute.path, pollingDelay: Duration(seconds: 1)).events.listen((change) { + sub = FileWatcher(logFile.absolute.path, pollingDelay: const Duration(seconds: 1)).events.listen((change) { sub.cancel(); if (!_isListenEnabled) return; _isListenEnabled = false; debugPrint("[ToolsLogAnalyze] logFile change: ${change.type}"); switch (change.type) { case ChangeType.MODIFY: - // 移除${S.current.log_analyzer_filter_statistics} + // 移除统计信息 final newList = state.value?.where((e) => e.type != "statistics").toList(); if (listSortReverse) { state = AsyncData(newList?.reversed.toList() ?? []); } else { state = AsyncData(newList ?? []); } - return _launchLogAnalyze(logFile, startLine: _currentLineNumber); + return _launchLogAnalyze(logFile); case ChangeType.ADD: case ChangeType.REMOVE: ref.invalidateSelf(); @@ -191,67 +83,6 @@ class ToolsLogAnalyze extends _$ToolsLogAnalyze { }); } - LogAnalyzeLineData? _handleLogLine(String line, int index) { - // 处理 log 行,检测可以提取的内容 - if (_gameStartTime == null) { - _gameStartTime = _getLogLineDateTime(line); - return LogAnalyzeLineData( - type: "info", - title: S.current.log_analyzer_game_start, - dateTime: _getLogLineDateTimeString(line), - ); - } - // 读取${S.current.log_analyzer_game_loading}时间 - final gameLoading = _logGetGameLoading(line); - if (gameLoading != null) { - return LogAnalyzeLineData( - type: "info", - title: S.current.log_analyzer_game_loading, - data: S.current.log_analyzer_mode_loading_time( - gameLoading.$1, - gameLoading.$2, - ), - dateTime: _getLogLineDateTimeString(line), - ); - } - - // 运行基础时间解析器 - final baseEvent = _baseEventDecoder(line); - if (baseEvent != null) { - switch (baseEvent) { - case "AccountLoginCharacterStatus_Character": - // 角色登录 - return _logGetCharacterName(line); - case "FatalCollision": - // 载具${S.current.log_analyzer_filter_fatal_collision} - return _logGetFatalCollision(line); - case "Vehicle Destruction": - // ${S.current.log_analyzer_filter_vehicle_damaged} - return _logGetVehicleDestruction(line); - case "Actor Death": - // ${S.current.log_analyzer_filter_character_death} - return _logGetActorDeath(line); - case "RequestLocationInventory": - // 请求${S.current.log_analyzer_filter_local_inventory} - return _logGetRequestLocationInventory(line); - } - } - - if (line.contains("[CIG] CCIGBroker::FastShutdown")) { - return LogAnalyzeLineData( - type: "info", - title: S.current.log_analyzer_game_close, - dateTime: _getLogLineDateTimeString(line), - ); - } - - if (line.contains("Cloud Imperium Games public crash handler")) { - _gameCrashLineNumber = index; - } - - return null; - } - void _appendResult(LogAnalyzeLineData data) { // 追加结果到 state final currentState = state.value; @@ -266,195 +97,4 @@ class ToolsLogAnalyze extends _$ToolsLogAnalyze { state = AsyncData([data]); } } - - final _baseRegExp = RegExp(r'\[Notice\]\s+<([^>]+)>'); - - String? _baseEventDecoder(String line) { - // 解析 log 行的基本信息 - final match = _baseRegExp.firstMatch(line); - if (match != null) { - final type = match.group(1); - return type; - } - return null; - } - - final _gameLoadingRegExp = - RegExp(r'<[^>]+>\s+Loading screen for\s+(\w+)\s+:\s+SC_Frontend closed after\s+(\d+\.\d+)\s+seconds'); - - (String, String)? _logGetGameLoading(String line) { - final match = _gameLoadingRegExp.firstMatch(line); - if (match != null) { - return (match.group(1) ?? "-", match.group(2) ?? "-"); - } - return null; - } - - final DateFormat _dateTimeFormatter = DateFormat('yyyy-MM-dd HH:mm:ss:SSS'); - final _logDateTimeRegExp = RegExp(r'<(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)>'); - - DateTime? _getLogLineDateTime(String line) { - // 提取 log 行的时间 - final match = _logDateTimeRegExp.firstMatch(line); - if (match != null) { - final dateTimeString = match.group(1); - if (dateTimeString != null) { - return DateTime.parse(dateTimeString).toLocal(); - } - } - return null; - } - - String? _getLogLineDateTimeString(String line) { - // 提取 log 行的时间 - final dateTime = _getLogLineDateTime(line); - if (dateTime != null) { - return _dateTimeFormatter.format(dateTime); - } - return null; - } - - // 安全提取函数 - String? safeExtract(RegExp pattern, String line) => pattern.firstMatch(line)?.group(1)?.trim(); - - LogAnalyzeLineData? _logGetFatalCollision(String line) { - final patterns = { - 'zone': RegExp(r'\[Part:[^\]]*?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.]+)') - }; - - final zone = safeExtract(patterns['zone']!, line) ?? unknownValue; - final playerPilot = (safeExtract(patterns['player_pilot']!, line) ?? '0') == '1'; - final hitEntity = safeExtract(patterns['hit_entity']!, line) ?? unknownValue; - final hitEntityVehicle = safeExtract(patterns['hit_entity_vehicle']!, line) ?? unknownValue; - final distance = double.tryParse(safeExtract(patterns['distance']!, line) ?? '') ?? 0.0; - return LogAnalyzeLineData( - type: "fatal_collision", - title: S.current.log_analyzer_filter_fatal_collision, - data: S.current.log_analyzer_collision_details( - zone, - playerPilot ? '✅' : '❌', - hitEntity, - hitEntityVehicle, - distance.toStringAsFixed(2), - ), - dateTime: _getLogLineDateTimeString(line), - ); - } - - LogAnalyzeLineData? _logGetVehicleDestruction(String line) { - final pattern = RegExp(r"Vehicle\s+'([^']+)'.*?" // 载具型号 - r"in zone\s+'([^']+)'.*?" // Zone - r"destroy level \d+ to (\d+).*?" // 损毁等级 - r"caused by\s+'([^']+)'" // 责任方 - ); - final match = pattern.firstMatch(line); - if (match != null) { - final vehicleModel = match.group(1) ?? unknownValue; - final zone = match.group(2) ?? unknownValue; - final destructionLevel = int.tryParse(match.group(3) ?? '') ?? 0; - final causedBy = match.group(4) ?? unknownValue; - - final destructionLevelMap = {1: S.current.log_analyzer_soft_death, 2: S.current.log_analyzer_disintegration}; - - if (causedBy.trim() == _playerName) { - if (destructionLevel == 1) { - _vehicleDestructionCount++; - } else if (destructionLevel == 2) { - _vehicleDestructionCountHard++; - } - } - - return LogAnalyzeLineData( - type: "vehicle_destruction", - title: S.current.log_analyzer_filter_vehicle_damaged, - data: S.current.log_analyzer_vehicle_damage_details( - vehicleModel, - zone, - destructionLevel.toString(), - destructionLevelMap[destructionLevel] ?? unknownValue, - causedBy, - ), - dateTime: _getLogLineDateTimeString(line), - ); - } - return null; - } - - LogAnalyzeLineData? _logGetActorDeath(String line) { - final pattern = RegExp(r"CActor::Kill: '([^']+)'.*?" // 受害者ID - r"in zone '([^']+)'.*?" // 死亡位置区域 - r"killed by '([^']+)'.*?" // 击杀者ID - r"with damage type '([^']+)'" // 伤害类型 - ); - - final match = pattern.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; - - if (victimId.trim() == killerId.trim()) { - // 自杀 - _selfKillCount++; - } else { - if (victimId.trim() == _playerName) { - _deathCount++; - } - if (killerId.trim() == _playerName) { - _killCount++; - } - } - - return LogAnalyzeLineData( - type: "actor_death", - title: S.current.log_analyzer_filter_character_death, - data: S.current.log_analyzer_death_details( - victimId, - damageType, - killerId, - zone, - ), - dateTime: _getLogLineDateTimeString(line), - ); - } - - return null; - } - - LogAnalyzeLineData? _logGetCharacterName(String line) { - final pattern = RegExp(r"name\s+([^-]+)"); - final match = pattern.firstMatch(line); - if (match != null) { - final characterName = match.group(1)?.trim() ?? unknownValue; - _playerName = characterName.trim(); // 更新玩家名称 - return LogAnalyzeLineData( - type: "player_login", - title: S.current.log_analyzer_player_login(characterName), - dateTime: _getLogLineDateTimeString(line), - ); - } - return null; - } - - LogAnalyzeLineData? _logGetRequestLocationInventory(String line) { - final pattern = RegExp(r"Player\[([^\]]+)\].*?Location\[([^\]]+)\]"); - final match = pattern.firstMatch(line); - if (match != null) { - final playerId = match.group(1) ?? unknownValue; - final location = match.group(2) ?? unknownValue; - - return LogAnalyzeLineData( - type: "request_location_inventory", - title: S.current.log_analyzer_view_local_inventory, - data: S.current.log_analyzer_player_location(playerId, location), - dateTime: _getLogLineDateTimeString(line), - ); - } - return null; - } } diff --git a/lib/ui/tools/log_analyze_ui/log_analyze_provider.freezed.dart b/lib/ui/tools/log_analyze_ui/log_analyze_provider.freezed.dart deleted file mode 100644 index 7815d63..0000000 --- a/lib/ui/tools/log_analyze_ui/log_analyze_provider.freezed.dart +++ /dev/null @@ -1,280 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND -// coverage:ignore-file -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'log_analyze_provider.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -// dart format off -T _$identity(T value) => value; -/// @nodoc -mixin _$LogAnalyzeLineData { - - String get type; String get title; String? get data; String? get dateTime; -/// Create a copy of LogAnalyzeLineData -/// with the given fields replaced by the non-null parameter values. -@JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -$LogAnalyzeLineDataCopyWith get copyWith => _$LogAnalyzeLineDataCopyWithImpl(this as LogAnalyzeLineData, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is LogAnalyzeLineData&&(identical(other.type, type) || other.type == type)&&(identical(other.title, title) || other.title == title)&&(identical(other.data, data) || other.data == data)&&(identical(other.dateTime, dateTime) || other.dateTime == dateTime)); -} - - -@override -int get hashCode => Object.hash(runtimeType,type,title,data,dateTime); - -@override -String toString() { - return 'LogAnalyzeLineData(type: $type, title: $title, data: $data, dateTime: $dateTime)'; -} - - -} - -/// @nodoc -abstract mixin class $LogAnalyzeLineDataCopyWith<$Res> { - factory $LogAnalyzeLineDataCopyWith(LogAnalyzeLineData value, $Res Function(LogAnalyzeLineData) _then) = _$LogAnalyzeLineDataCopyWithImpl; -@useResult -$Res call({ - String type, String title, String? data, String? dateTime -}); - - - - -} -/// @nodoc -class _$LogAnalyzeLineDataCopyWithImpl<$Res> - implements $LogAnalyzeLineDataCopyWith<$Res> { - _$LogAnalyzeLineDataCopyWithImpl(this._self, this._then); - - final LogAnalyzeLineData _self; - final $Res Function(LogAnalyzeLineData) _then; - -/// Create a copy of LogAnalyzeLineData -/// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? title = null,Object? data = freezed,Object? dateTime = freezed,}) { - return _then(_self.copyWith( -type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable -as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable -as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable -as String?,dateTime: freezed == dateTime ? _self.dateTime : dateTime // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - -} - - -/// Adds pattern-matching-related methods to [LogAnalyzeLineData]. -extension LogAnalyzeLineDataPatterns on LogAnalyzeLineData { -/// A variant of `map` that fallback to returning `orElse`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeMap(TResult Function( _LogAnalyzeLineData value)? $default,{required TResult orElse(),}){ -final _that = this; -switch (_that) { -case _LogAnalyzeLineData() when $default != null: -return $default(_that);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// Callbacks receives the raw object, upcasted. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case final Subclass2 value: -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult map(TResult Function( _LogAnalyzeLineData value) $default,){ -final _that = this; -switch (_that) { -case _LogAnalyzeLineData(): -return $default(_that);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `map` that fallback to returning `null`. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case final Subclass value: -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? mapOrNull(TResult? Function( _LogAnalyzeLineData value)? $default,){ -final _that = this; -switch (_that) { -case _LogAnalyzeLineData() when $default != null: -return $default(_that);case _: - return null; - -} -} -/// A variant of `when` that fallback to an `orElse` callback. -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return orElse(); -/// } -/// ``` - -@optionalTypeArgs TResult maybeWhen(TResult Function( String type, String title, String? data, String? dateTime)? $default,{required TResult orElse(),}) {final _that = this; -switch (_that) { -case _LogAnalyzeLineData() when $default != null: -return $default(_that.type,_that.title,_that.data,_that.dateTime);case _: - return orElse(); - -} -} -/// A `switch`-like method, using callbacks. -/// -/// As opposed to `map`, this offers destructuring. -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case Subclass2(:final field2): -/// return ...; -/// } -/// ``` - -@optionalTypeArgs TResult when(TResult Function( String type, String title, String? data, String? dateTime) $default,) {final _that = this; -switch (_that) { -case _LogAnalyzeLineData(): -return $default(_that.type,_that.title,_that.data,_that.dateTime);case _: - throw StateError('Unexpected subclass'); - -} -} -/// A variant of `when` that fallback to returning `null` -/// -/// It is equivalent to doing: -/// ```dart -/// switch (sealedClass) { -/// case Subclass(:final field): -/// return ...; -/// case _: -/// return null; -/// } -/// ``` - -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String type, String title, String? data, String? dateTime)? $default,) {final _that = this; -switch (_that) { -case _LogAnalyzeLineData() when $default != null: -return $default(_that.type,_that.title,_that.data,_that.dateTime);case _: - return null; - -} -} - -} - -/// @nodoc - - -class _LogAnalyzeLineData implements LogAnalyzeLineData { - const _LogAnalyzeLineData({required this.type, required this.title, this.data, this.dateTime}); - - -@override final String type; -@override final String title; -@override final String? data; -@override final String? dateTime; - -/// Create a copy of LogAnalyzeLineData -/// with the given fields replaced by the non-null parameter values. -@override @JsonKey(includeFromJson: false, includeToJson: false) -@pragma('vm:prefer-inline') -_$LogAnalyzeLineDataCopyWith<_LogAnalyzeLineData> get copyWith => __$LogAnalyzeLineDataCopyWithImpl<_LogAnalyzeLineData>(this, _$identity); - - - -@override -bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _LogAnalyzeLineData&&(identical(other.type, type) || other.type == type)&&(identical(other.title, title) || other.title == title)&&(identical(other.data, data) || other.data == data)&&(identical(other.dateTime, dateTime) || other.dateTime == dateTime)); -} - - -@override -int get hashCode => Object.hash(runtimeType,type,title,data,dateTime); - -@override -String toString() { - return 'LogAnalyzeLineData(type: $type, title: $title, data: $data, dateTime: $dateTime)'; -} - - -} - -/// @nodoc -abstract mixin class _$LogAnalyzeLineDataCopyWith<$Res> implements $LogAnalyzeLineDataCopyWith<$Res> { - factory _$LogAnalyzeLineDataCopyWith(_LogAnalyzeLineData value, $Res Function(_LogAnalyzeLineData) _then) = __$LogAnalyzeLineDataCopyWithImpl; -@override @useResult -$Res call({ - String type, String title, String? data, String? dateTime -}); - - - - -} -/// @nodoc -class __$LogAnalyzeLineDataCopyWithImpl<$Res> - implements _$LogAnalyzeLineDataCopyWith<$Res> { - __$LogAnalyzeLineDataCopyWithImpl(this._self, this._then); - - final _LogAnalyzeLineData _self; - final $Res Function(_LogAnalyzeLineData) _then; - -/// Create a copy of LogAnalyzeLineData -/// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? title = null,Object? data = freezed,Object? dateTime = freezed,}) { - return _then(_LogAnalyzeLineData( -type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable -as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable -as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable -as String?,dateTime: freezed == dateTime ? _self.dateTime : dateTime // ignore: cast_nullable_to_non_nullable -as String?, - )); -} - - -} - -// dart format on diff --git a/lib/ui/tools/log_analyze_ui/log_analyze_provider.g.dart b/lib/ui/tools/log_analyze_ui/log_analyze_provider.g.dart index 9ca1959..bcb5d08 100644 --- a/lib/ui/tools/log_analyze_ui/log_analyze_provider.g.dart +++ b/lib/ui/tools/log_analyze_ui/log_analyze_provider.g.dart @@ -50,7 +50,7 @@ final class ToolsLogAnalyzeProvider } } -String _$toolsLogAnalyzeHash() => r'5666c3f882e22e2192593629164bc53f8ce4aabe'; +String _$toolsLogAnalyzeHash() => r'f5079c7d35daf25b07f83bacb224484171e9c93f'; final class ToolsLogAnalyzeFamily extends $Family with diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 204095f..d784f6d 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -29,7 +29,12 @@ ndarray = "0.17" serde_json = "1.0" [target.'cfg(windows)'.dependencies] -windows = { version = "0.62.2", features = ["Win32_UI_WindowsAndMessaging"] } +windows = { version = "0.62.2", features = [ + "Win32_UI_WindowsAndMessaging", + "Win32_System_Diagnostics_ToolHelp", + "Win32_System_Threading", + "Win32_Foundation" +] } win32job = "2" [lints.rust] diff --git a/rust/src/api/win32_api.rs b/rust/src/api/win32_api.rs index aea7837..338be7b 100644 --- a/rust/src/api/win32_api.rs +++ b/rust/src/api/win32_api.rs @@ -49,4 +49,129 @@ pub fn set_foreground_window(window_name: &str) -> anyhow::Result { pub fn set_foreground_window(window_name: &str) -> anyhow::Result { println!("set_foreground_window (unix): {}", window_name); return Ok(false); +} + +#[derive(Debug, Clone)] +pub struct ProcessInfo { + pub pid: u32, + pub name: String, + pub path: String, +} + +#[cfg(target_os = "windows")] +pub fn get_process_pid_by_name(process_name: &str) -> anyhow::Result { + // 保持向后兼容:返回第一个匹配进程的 PID + let processes = get_process_list_by_name(process_name)?; + if let Some(first) = processes.first() { + Ok(first.pid as i32) + } else { + Ok(-1) + } +} + +#[cfg(not(target_os = "windows"))] +pub fn get_process_pid_by_name(process_name: &str) -> anyhow::Result { + println!("get_process_pid_by_name (unix): {}", process_name); + Ok(-1) +} + +#[cfg(target_os = "windows")] +pub fn get_process_list_by_name(process_name: &str) -> anyhow::Result> { + use std::mem; + use windows::Win32::Foundation::CloseHandle; + use windows::Win32::System::Diagnostics::ToolHelp::{ + CreateToolhelp32Snapshot, Process32FirstW, Process32NextW, PROCESSENTRY32W, + TH32CS_SNAPPROCESS, + }; + + let mut result = Vec::new(); + let search_lower = process_name.to_lowercase(); + + unsafe { + let snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)?; + if snapshot.is_invalid() { + return Ok(result); + } + + let mut process_entry: PROCESSENTRY32W = mem::zeroed(); + process_entry.dwSize = mem::size_of::() as u32; + + if Process32FirstW(snapshot, &mut process_entry).is_err() { + let _ = CloseHandle(snapshot); + return Ok(result); + } + + loop { + // 将 WCHAR 数组转换为 String + let exe_file = String::from_utf16_lossy( + &process_entry.szExeFile[..process_entry + .szExeFile + .iter() + .position(|&c| c == 0) + .unwrap_or(process_entry.szExeFile.len())], + ); + + // 支持部分匹配(不区分大小写) + if exe_file.to_lowercase().contains(&search_lower) { + let pid = process_entry.th32ProcessID; + + // 获取完整路径 + let full_path = get_process_path(pid).unwrap_or_default(); + + result.push(ProcessInfo { + pid, + name: exe_file, + path: full_path, + }); + } + + if Process32NextW(snapshot, &mut process_entry).is_err() { + break; + } + } + + let _ = CloseHandle(snapshot); + } + + Ok(result) +} + +#[cfg(target_os = "windows")] +fn get_process_path(pid: u32) -> Option { + use windows::core::PWSTR; + use windows::Win32::Foundation::{CloseHandle, MAX_PATH}; + use windows::Win32::System::Threading::{ + OpenProcess, QueryFullProcessImageNameW, PROCESS_NAME_WIN32, PROCESS_QUERY_LIMITED_INFORMATION, + }; + + unsafe { + if let Ok(h_process) = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid) { + if !h_process.is_invalid() { + let mut path_buffer = [0u16; MAX_PATH as usize]; + let mut path_len = path_buffer.len() as u32; + + let result = if QueryFullProcessImageNameW( + h_process, + PROCESS_NAME_WIN32, + PWSTR::from_raw(path_buffer.as_mut_ptr()), + &mut path_len, + ).is_ok() && path_len > 0 { + Some(String::from_utf16_lossy(&path_buffer[..path_len as usize])) + } else { + None + }; + + let _ = CloseHandle(h_process); + return result; + } + } + } + + None +} + +#[cfg(not(target_os = "windows"))] +pub fn get_process_list_by_name(process_name: &str) -> anyhow::Result> { + println!("get_process_list_by_name (unix): {}", process_name); + Ok(Vec::new()) } \ No newline at end of file diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 6e2f10b..3c763f0 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -37,7 +37,7 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_auto_opaque = RustAutoOpaqueNom, ); pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -706588047; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = 1227557070; // Section: executor @@ -156,6 +156,54 @@ fn wire__crate__api__http_api__fetch_impl( }, ) } +fn wire__crate__api__win32_api__get_process_list_by_name_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + process_name: impl CstDecode, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_process_list_by_name", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_process_name = process_name.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = + crate::api::win32_api::get_process_list_by_name(&api_process_name)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} +fn wire__crate__api__win32_api__get_process_pid_by_name_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + process_name: impl CstDecode, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "get_process_pid_by_name", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let api_process_name = process_name.cst_decode(); + move |context| { + transform_result_dco::<_, _, flutter_rust_bridge::for_generated::anyhow::Error>( + (move || { + let output_ok = + crate::api::win32_api::get_process_pid_by_name(&api_process_name)?; + Ok(output_ok) + })(), + ) + } + }, + ) +} fn wire__crate__api__asar_api__get_rsi_launcher_asar_data_impl( port_: flutter_rust_bridge::for_generated::MessagePort, asar_path: impl CstDecode, @@ -627,6 +675,20 @@ impl SseDecode for Vec { } } +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode( + deserializer, + )); + } + return ans_; + } +} + impl SseDecode for Vec<(String, String)> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -731,6 +793,20 @@ impl SseDecode for Option> { } } +impl SseDecode for crate::api::win32_api::ProcessInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_pid = ::sse_decode(deserializer); + let mut var_name = ::sse_decode(deserializer); + let mut var_path = ::sse_decode(deserializer); + return crate::api::win32_api::ProcessInfo { + pid: var_pid, + name: var_name, + path: var_path, + }; + } +} + impl SseDecode for (String, String) { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { @@ -918,6 +994,28 @@ impl flutter_rust_bridge::IntoIntoDart } } // Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::api::win32_api::ProcessInfo { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + [ + self.pid.into_into_dart().into_dart(), + self.name.into_into_dart().into_dart(), + self.path.into_into_dart().into_dart(), + ] + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::api::win32_api::ProcessInfo +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::api::win32_api::ProcessInfo +{ + fn into_into_dart(self) -> crate::api::win32_api::ProcessInfo { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs impl flutter_rust_bridge::IntoDart for crate::api::rs_process::RsProcessStreamData { fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { [ @@ -1077,6 +1175,16 @@ impl SseEncode for Vec { } } +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + impl SseEncode for Vec<(String, String)> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1180,6 +1288,15 @@ impl SseEncode for Option> { } } +impl SseEncode for crate::api::win32_api::ProcessInfo { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.pid, serializer); + ::sse_encode(self.name, serializer); + ::sse_encode(self.path, serializer); + } +} + impl SseEncode for (String, String) { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { @@ -1379,6 +1496,16 @@ mod io { } } } + impl CstDecode> for *mut wire_cst_list_process_info { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> Vec { + let vec = unsafe { + let wrap = flutter_rust_bridge::for_generated::box_from_leak_ptr(self); + flutter_rust_bridge::for_generated::vec_from_leak_ptr(wrap.ptr, wrap.len) + }; + vec.into_iter().map(CstDecode::cst_decode).collect() + } + } impl CstDecode> for *mut wire_cst_list_record_string_string { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> Vec<(String, String)> { @@ -1389,6 +1516,16 @@ mod io { vec.into_iter().map(CstDecode::cst_decode).collect() } } + impl CstDecode for wire_cst_process_info { + // Codec=Cst (C-struct based), see doc to use other codecs + fn cst_decode(self) -> crate::api::win32_api::ProcessInfo { + crate::api::win32_api::ProcessInfo { + pid: self.pid.cst_decode(), + name: self.name.cst_decode(), + path: self.path.cst_decode(), + } + } + } impl CstDecode<(String, String)> for wire_cst_record_string_string { // Codec=Cst (C-struct based), see doc to use other codecs fn cst_decode(self) -> (String, String) { @@ -1429,6 +1566,20 @@ mod io { } } } + impl NewWithNullPtr for wire_cst_process_info { + fn new_with_null_ptr() -> Self { + Self { + pid: Default::default(), + name: core::ptr::null_mut(), + path: core::ptr::null_mut(), + } + } + } + impl Default for wire_cst_process_info { + fn default() -> Self { + Self::new_with_null_ptr() + } + } impl NewWithNullPtr for wire_cst_record_string_string { fn new_with_null_ptr() -> Self { Self { @@ -1533,6 +1684,22 @@ mod io { ) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__get_process_list_by_name( + port_: i64, + process_name: *mut wire_cst_list_prim_u_8_strict, + ) { + wire__crate__api__win32_api__get_process_list_by_name_impl(port_, process_name) + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__win32_api__get_process_pid_by_name( + port_: i64, + process_name: *mut wire_cst_list_prim_u_8_strict, + ) { + wire__crate__api__win32_api__get_process_pid_by_name_impl(port_, process_name) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_wire__crate__api__asar_api__get_rsi_launcher_asar_data( port_: i64, @@ -1698,6 +1865,20 @@ mod io { flutter_rust_bridge::for_generated::new_leak_box_ptr(ans) } + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_starcitizen_doctor_cst_new_list_process_info( + len: i32, + ) -> *mut wire_cst_list_process_info { + let wrap = wire_cst_list_process_info { + ptr: flutter_rust_bridge::for_generated::new_leak_vec_ptr( + ::new_with_null_ptr(), + len, + ), + len, + }; + flutter_rust_bridge::for_generated::new_leak_box_ptr(wrap) + } + #[unsafe(no_mangle)] pub extern "C" fn frbgen_starcitizen_doctor_cst_new_list_record_string_string( len: i32, @@ -1732,12 +1913,25 @@ mod io { } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_list_process_info { + ptr: *mut wire_cst_process_info, + len: i32, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_list_record_string_string { ptr: *mut wire_cst_record_string_string, len: i32, } #[repr(C)] #[derive(Clone, Copy)] + pub struct wire_cst_process_info { + pid: u32, + name: *mut wire_cst_list_prim_u_8_strict, + path: *mut wire_cst_list_prim_u_8_strict, + } + #[repr(C)] + #[derive(Clone, Copy)] pub struct wire_cst_record_string_string { field0: *mut wire_cst_list_prim_u_8_strict, field1: *mut wire_cst_list_prim_u_8_strict, From d1f19bae4e37d3714edc51ad7ffa1891d6eb8bde Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Thu, 20 Nov 2025 09:28:46 +0800 Subject: [PATCH 08/11] fix: party room provider --- lib/provider/party_room.dart | 19 +++++++++++++++---- .../widgets/create_room_dialog.dart | 6 +++--- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/provider/party_room.dart b/lib/provider/party_room.dart index ddf6a3a..adabc90 100644 --- a/lib/provider/party_room.dart +++ b/lib/provider/party_room.dart @@ -781,9 +781,9 @@ class PartyRoom extends _$PartyRoom { void _handleRoomEvent(partroom.RoomEvent event) { dPrint('[PartyRoom] Event received: ${event.type}'); - // 添加到最近事件列表(保留最近 50 条) + // 添加到最近事件列表(保留最近 1000 条) final recentEvents = [...state.room.recentEvents, event]; - if (recentEvents.length > 50) { + if (recentEvents.length > 1000) { recentEvents.removeAt(0); } @@ -794,13 +794,24 @@ class PartyRoom extends _$PartyRoom { case partroom.RoomEventType.MEMBER_JOINED: case partroom.RoomEventType.MEMBER_LEFT: case partroom.RoomEventType.MEMBER_KICKED: - case partroom.RoomEventType.MEMBER_STATUS_UPDATED: // 刷新成员列表 if (state.room.roomUuid != null) { getRoomMembers(state.room.roomUuid!); } break; - + case partroom.RoomEventType.MEMBER_STATUS_UPDATED: + // 刷新成员状态 + state = state.copyWith( + room: state.room.copyWith( + members: state.room.members.map((member) { + if (member.gameUserId == event.member.gameUserId) { + return event.member; + } + return member; + }).toList(), + ), + ); + break; case partroom.RoomEventType.OWNER_CHANGED: // 检查是否自己成为房主 final isOwner = event.member.gameUserId == state.auth.userInfo?.gameUserId; diff --git a/lib/ui/party_room/widgets/create_room_dialog.dart b/lib/ui/party_room/widgets/create_room_dialog.dart index bc2bc68..f5338db 100644 --- a/lib/ui/party_room/widgets/create_room_dialog.dart +++ b/lib/ui/party_room/widgets/create_room_dialog.dart @@ -127,7 +127,7 @@ class CreateRoomDialog extends HookConsumerWidget { ), const SizedBox(height: 16), InfoLabel( - label: '目标人数 (2-600)', + label: '目标人数 (2-100)', child: TextBox( controller: targetMembersController, placeholder: '输入目标人数', @@ -194,12 +194,12 @@ class CreateRoomDialog extends HookConsumerWidget { } final targetMembers = int.tryParse(targetMembersController.text); - if (targetMembers == null || targetMembers < 2 || targetMembers > 600) { + if (targetMembers == null || targetMembers < 2 || targetMembers > 100) { await showDialog( context: context, builder: (context) => ContentDialog( title: const Text('提示'), - content: const Text('目标人数必须在 2-600 之间'), + content: const Text('目标人数必须在 2-100 之间'), actions: [FilledButton(child: const Text('确定'), onPressed: () => Navigator.pop(context))], ), ); From 7a38d0d38677f2e427f2f0b9d5630751c70bb48d Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Thu, 20 Nov 2025 09:54:06 +0800 Subject: [PATCH 09/11] fix: MEMBER_STATUS_UPDATED --- lib/provider/party_room.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/provider/party_room.dart b/lib/provider/party_room.dart index adabc90..6ef7446 100644 --- a/lib/provider/party_room.dart +++ b/lib/provider/party_room.dart @@ -800,12 +800,12 @@ class PartyRoom extends _$PartyRoom { } break; case partroom.RoomEventType.MEMBER_STATUS_UPDATED: - // 刷新成员状态 + // 刷新成员状态,只更新对应成员的 status state = state.copyWith( room: state.room.copyWith( members: state.room.members.map((member) { if (member.gameUserId == event.member.gameUserId) { - return event.member; + return member.deepCopy()..status = event.member.status; } return member; }).toList(), From a199ba745a0ea7eac92c41c88f5d09c8e3f542fc Mon Sep 17 00:00:00 2001 From: xkeyC <3334969096@qq.com> Date: Thu, 20 Nov 2025 11:32:14 +0800 Subject: [PATCH 10/11] feat: use CDN url --- lib/common/conf/url_conf.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/common/conf/url_conf.dart b/lib/common/conf/url_conf.dart index 9ae9191..2edad62 100644 --- a/lib/common/conf/url_conf.dart +++ b/lib/common/conf/url_conf.dart @@ -11,10 +11,9 @@ class URLConf { static const String analyticsApiHome = "https://scbox.org"; /// PartyRoom Server - static const String partyRoomServerAddress = "partyroom.grpc.scbox.xkeyc.cn"; + static const String partyRoomServerAddress = "ecdn.partyroom.grpc.scbox.xkeyc.cn"; static const int partyRoomServerPort = 443; - static bool isUrlCheckPass = false; /// URLS 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 11/11] 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, ), ),