更新 devtools_options.yaml 以启用 riverpod 和 shared_preferences 扩展;更新 AuthApi 枚举以添加 playerRegistrationList API;重构 Auth 页面以在用户已登录时直接导航到扫码页面;重构 EventInfoPage 以使用新的状态管理和加载赛事报名信息;更新扫码页面以导航到 EventInfoPage。

This commit is contained in:
2026-07-09 17:38:56 +08:00
parent 6138eddc16
commit 1c909277ff
11 changed files with 646 additions and 137 deletions
+92 -113
View File
@@ -1,64 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:recording_tool/app/router/app_navigator.dart';
import 'package:recording_tool/features/recording/model/model_recording_context.dart';
import 'package:recording_tool/features/events/model/model_event_info.dart';
import 'package:recording_tool/features/events/state/state_event_info.dart';
import 'package:recording_tool/features/events/view_model/view_model_event_info.dart';
import 'package:recording_tool/features/recording/pages/page_record.dart';
import 'package:recording_tool/shared/widgets/widgets.dart';
class EventInfoPage extends StatelessWidget {
const EventInfoPage({super.key});
class EventInfoPage extends ConsumerStatefulWidget {
const EventInfoPage({super.key, required this.playerId});
final String playerId;
static const mockRtmpUrl =
'rtmp://192.168.1.245:19090/蔡依婷vs夏志豪_空中格斗赛_高中组/蔡依婷vs夏志豪_空中格斗赛_高中组';
static const _profile = EventProfile(
avatarLabel: '头像',
name: '王东方',
phone: '199966666527',
eventTitle: '全国青少年无人机大赛',
);
@override
ConsumerState<EventInfoPage> createState() => _EventInfoPageState();
}
static const _items = [
EventScheduleItem(
name: '个人飞行赛',
group: '小学组',
venue: '场地 1',
time: '7月1日 12:00-15:00',
laneNo: '10号',
),
EventScheduleItem(
name: '空中足球赛',
group: '小学组',
venue: '场地 1',
time: '7月2日 12:00-15:00',
status: '已完成',
),
EventScheduleItem(
name: '穿越竞速赛',
group: '小学组',
venue: '场地 2',
time: '7月3日 09:00-11:30',
laneNo: '06号',
),
EventScheduleItem(
name: '编队飞行赛',
group: '小学组',
venue: '场地 3',
time: '7月3日 14:00-16:00',
status: '待开始',
),
];
void onItemTap(EventScheduleItem item) {
class _EventInfoPageState extends ConsumerState<EventInfoPage> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted) return;
ref.read(eventInfoProvider.notifier).loadRegistrationList();
});
}
Future<void> onItemTap(EventRegistrationItem item) async {
debugPrint('item tapped: ${item.name}');
await ref.read(eventInfoProvider.notifier).requestStreamKey(item);
if (!mounted) return;
final profile = ref.read(eventInfoProvider).profile;
AppNavigator.push(
RecordingPage(
recordingContext: item.toRecordingContext(profile: _profile),
streamUrl: mockRtmpUrl,
recordingContext: item.toRecordingContext(profile: profile),
streamUrl: EventInfoPage.mockRtmpUrl,
),
);
}
@override
Widget build(BuildContext context) {
final state = ref.watch(eventInfoProvider);
final profile = state.profile;
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
@@ -71,10 +58,10 @@ class EventInfoPage extends StatelessWidget {
padding: EdgeInsets.fromLTRB(62.w, 8.h, 62.w, 40.h),
child: Column(
children: [
const _ProfileSection(profile: _profile),
SizedBox(height: 82.h),
_ProfileSection(profile: profile),
SizedBox(height: 20.h),
Text(
_profile.eventTitle,
state.eventTitle,
style: TextStyle(
fontSize: 20.sp,
height: 1.2,
@@ -85,12 +72,12 @@ class EventInfoPage extends StatelessWidget {
SizedBox(height: 24.h),
SizedBox(
height: 400.h,
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => _ScheduleCard(
item: _items[index],
onTap: () => onItemTap(_items[index]),
),
child: _ScheduleList(
state: state,
onRetry: () => ref
.read(eventInfoProvider.notifier)
.loadRegistrationList(),
onItemTap: onItemTap,
),
),
],
@@ -104,52 +91,6 @@ class EventInfoPage extends StatelessWidget {
}
}
class EventProfile {
const EventProfile({
required this.avatarLabel,
required this.name,
required this.phone,
required this.eventTitle,
});
final String avatarLabel;
final String name;
final String phone;
final String eventTitle;
}
class EventScheduleItem {
const EventScheduleItem({
required this.name,
required this.group,
required this.venue,
required this.time,
this.laneNo,
this.status,
});
final String name;
final String group;
final String venue;
final String time;
final String? laneNo;
final String? status;
RecordingContext toRecordingContext({required EventProfile profile}) {
return RecordingContext(
eventTitle: profile.eventTitle,
matchName: name,
group: group,
venue: venue,
time: time,
playerName: profile.name,
playerPhone: profile.phone,
laneNo: laneNo,
status: status,
);
}
}
class _Header extends StatelessWidget {
const _Header({required this.onBack});
@@ -184,18 +125,10 @@ class _ProfileSection extends StatelessWidget {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
// width: 225.w,
// height: 184.h,
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(color: const Color(0xFF7A7A7A)),
),
child: Text(
profile.avatarLabel,
style: TextStyle(fontSize: 20.sp, color: const Color(0xFF2F2F2F)),
),
),
profile.avatarUrl.isEmpty
? AppAvatar(size: 50.r)
: AppAvatar(size: 50.r, imageUrl: profile.avatarUrl),
SizedBox(width: 28.w),
Expanded(
child: Padding(
@@ -233,10 +166,54 @@ class _ProfileSection extends StatelessWidget {
}
}
class _ScheduleList extends StatelessWidget {
const _ScheduleList({
required this.state,
required this.onRetry,
required this.onItemTap,
});
final EventInfoState state;
final VoidCallback onRetry;
final ValueChanged<EventRegistrationItem> onItemTap;
@override
Widget build(BuildContext context) {
if (state.isLoading) {
return const Center(child: CircularProgressIndicator());
}
if (state.items.isEmpty) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
state.errorMessage ?? '暂无赛事报名信息',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.sp, color: const Color(0xFF2F2F2F)),
),
SizedBox(height: 18.h),
TextButton(onPressed: onRetry, child: const Text('重新加载')),
],
),
);
}
return ListView.builder(
itemCount: state.items.length,
itemBuilder: (context, index) {
final item = state.items[index];
return _ScheduleCard(item: item, onTap: () => onItemTap(item));
},
);
}
}
class _ScheduleCard extends StatelessWidget {
const _ScheduleCard({required this.item, required this.onTap});
final EventScheduleItem item;
final EventRegistrationItem item;
final VoidCallback onTap;
@override
@@ -259,7 +236,9 @@ class _ScheduleCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${item.name} ${item.group}',
item.group.isEmpty
? item.name
: '${item.name} ${item.group}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
@@ -314,7 +293,7 @@ class _ScheduleCard extends StatelessWidget {
class _ScheduleBadge extends StatelessWidget {
const _ScheduleBadge({required this.item});
final EventScheduleItem item;
final EventRegistrationItem item;
@override
Widget build(BuildContext context) {