feat: UI update

This commit is contained in:
xkeyC 2025-11-19 21:14:36 +08:00
parent 2ffb02e62d
commit 6fda5628ff
10 changed files with 126 additions and 65 deletions

View File

@ -28,9 +28,10 @@ class S {
static const AppLocalizationDelegate delegate = AppLocalizationDelegate();
static Future<S> 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;

View File

@ -889,4 +889,8 @@ class PartyRoom extends _$PartyRoom {
_stopEventStream();
_confBox?.close();
}
common.Tag? getMainTagById(String mainTagId) {
return state.room.tags[mainTagId];
}
}

View File

@ -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)),
),
],

View File

@ -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<double> animation) {
final offsetAnimation = Tween<Offset>(
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,
);
}
}

View File

@ -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);
}
}

View File

@ -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,
),
),
],
),

View File

@ -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;
}
}

View File

@ -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<void> _showCreateRoomDialog(BuildContext context, WidgetRef ref) async {
await showDialog(context: context, builder: (context) => const CreateRoomDialog());
}

View File

@ -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:

View File

@ -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