feat: Add user profile refresh

This commit is contained in:
xkeyC 2025-12-26 17:07:25 +08:00
parent 724f7d8242
commit 1d59acff2d
2 changed files with 147 additions and 83 deletions

View File

@ -277,6 +277,24 @@ class PartyRoom extends _$PartyRoom {
} }
} }
///
Future<void> refreshUserProfile() async {
try {
final client = state.client.authClient;
if (client == null) throw Exception('Not connected to server');
final response = await client.refreshUserProfile(auth.RefreshUserProfileRequest(), options: getAuthCallOptions());
if (response.success && response.hasUserInfo()) {
state = state.copyWith(auth: state.auth.copyWith(userInfo: response.userInfo));
dPrint('[PartyRoom] User profile refreshed: ${response.userInfo.gameUserId}');
}
} catch (e) {
dPrint('[PartyRoom] RefreshUserProfile error: $e');
rethrow;
}
}
// ========== ========== // ========== ==========
/// ///

View File

@ -5,6 +5,7 @@ import 'package:starcitizen_doctor/provider/party_room.dart';
import 'package:starcitizen_doctor/ui/party_room/party_room_ui_model.dart'; import 'package:starcitizen_doctor/ui/party_room/party_room_ui_model.dart';
import 'package:starcitizen_doctor/ui/party_room/utils/party_room_utils.dart'; import 'package:starcitizen_doctor/ui/party_room/utils/party_room_utils.dart';
import 'package:starcitizen_doctor/widgets/widgets.dart'; import 'package:starcitizen_doctor/widgets/widgets.dart';
import 'package:grpc/grpc.dart';
class UserAvatarWidget extends HookConsumerWidget { class UserAvatarWidget extends HookConsumerWidget {
final VoidCallback onTapNavigateToPartyRoom; final VoidCallback onTapNavigateToPartyRoom;
@ -52,14 +53,14 @@ class UserAvatarWidget extends HookConsumerWidget {
child: uiState.isLoggingIn child: uiState.isLoggingIn
? const Padding(padding: EdgeInsets.all(4), child: ProgressRing(strokeWidth: 2)) ? const Padding(padding: EdgeInsets.all(4), child: ProgressRing(strokeWidth: 2))
: (fullAvatarUrl != null : (fullAvatarUrl != null
? CacheNetImage(url: fullAvatarUrl, fit: BoxFit.cover) ? CacheNetImage(url: fullAvatarUrl, fit: BoxFit.cover)
: Center( : Center(
child: Icon( child: Icon(
isLoggedIn ? FluentIcons.contact : FluentIcons.unknown, isLoggedIn ? FluentIcons.contact : FluentIcons.unknown,
size: 16, size: 16,
color: Colors.white, color: Colors.white,
), ),
)), )),
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -85,91 +86,136 @@ class UserAvatarWidget extends HookConsumerWidget {
barrierDismissible: true, barrierDismissible: true,
barrierColor: Colors.transparent, barrierColor: Colors.transparent,
builder: (BuildContext dialogContext) { builder: (BuildContext dialogContext) {
return Stack( return Consumer(
children: [ builder: (context, ref, child) {
// final partyRoomState = ref.watch(partyRoomProvider);
GestureDetector( final userInfo = partyRoomState.auth.userInfo;
onTap: () => Navigator.of(dialogContext).pop(), final displayUserName = userInfo?.gameUserId ?? userName;
child: Container(color: Colors.transparent), final displayAvatarUrl = PartyRoomUtils.getAvatarUrl(userInfo?.avatarUrl);
), final displayEnlistedDate = userInfo?.enlistedDate ?? enlistedDate;
//
Positioned( return Stack(
left: offset.dx - 100, children: [
top: offset.dy + (box?.size.height ?? 0) + 8, //
child: Container( GestureDetector(
width: 360, onTap: () => Navigator.of(dialogContext).pop(),
decoration: BoxDecoration( child: Container(color: Colors.transparent),
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), Positioned(
child: Column( left: offset.dx - 100,
mainAxisSize: MainAxisSize.min, top: offset.dy + (box?.size.height ?? 0) + 8,
crossAxisAlignment: CrossAxisAlignment.start, child: Container(
children: [ width: 360,
// decoration: BoxDecoration(
Row( 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: [ children: [
Container( //
width: 48, Row(
height: 48, children: [
decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(24)), Container(
child: ClipRRect( width: 48,
borderRadius: BorderRadius.circular(24), height: 48,
child: avatarUrl != null decoration: BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(24)),
? CacheNetImage(url: avatarUrl, fit: BoxFit.cover) child: ClipRRect(
: const Center(child: Icon(FluentIcons.contact, size: 24, color: Colors.white)), borderRadius: BorderRadius.circular(24),
), child: displayAvatarUrl != null
? CacheNetImage(url: displayAvatarUrl, 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(
displayUserName,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
'注册时间:${PartyRoomUtils.formatDateTime(displayEnlistedDate)}',
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)),
),
],
),
),
],
), ),
const SizedBox(width: 12), const SizedBox(height: 16),
Expanded( const Divider(),
child: Column( const SizedBox(height: 8),
crossAxisAlignment: CrossAxisAlignment.start, //
children: [ Row(
Text( children: [
userName, //
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), Expanded(
maxLines: 1, child: Tooltip(
overflow: TextOverflow.ellipsis, message: '每小时仅可刷新一次',
child: FilledButton(
onPressed: () async {
try {
await ref.read(partyRoomProvider.notifier).refreshUserProfile();
if (context.mounted) {
showToast(context, '刷新成功');
}
} catch (e) {
if (context.mounted) {
if (e is GrpcError && e.code == StatusCode.resourceExhausted) {
showToast(context, '资料刷新过于频繁,请一小时后再试');
} else {
showToast(context, '刷新失败: $e');
}
}
}
},
child: const Text('刷新资料'),
),
), ),
const SizedBox(height: 4), ),
Text( const SizedBox(width: 8),
'注册时间:${PartyRoomUtils.formatDateTime(enlistedDate)}', //
style: TextStyle(fontSize: 12, color: Colors.white.withValues(alpha: .6)), Expanded(
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),
),
), ),
], ),
), ],
), ),
], ],
), ),
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)),
),
),
],
), ),
), ),
), ],
), );
], },
); );
}, },
); );