新增赛事信息页
This commit is contained in:
@@ -3,7 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:recording_tool/app/router/app_navigator.dart';
|
import 'package:recording_tool/app/router/app_navigator.dart';
|
||||||
import 'package:recording_tool/features/auth/view_model_auth/view_model_auth.dart';
|
import 'package:recording_tool/features/auth/view_model_auth/view_model_auth.dart';
|
||||||
import 'package:recording_tool/features/scan_qrcode/pages/page_scan_qrcode.dart';
|
import 'package:recording_tool/features/events/pages/page_event_info.dart';
|
||||||
import 'package:recording_tool/shared/widgets/widgets.dart';
|
import 'package:recording_tool/shared/widgets/widgets.dart';
|
||||||
|
|
||||||
class AuthPageWidget extends ConsumerStatefulWidget {
|
class AuthPageWidget extends ConsumerStatefulWidget {
|
||||||
@@ -62,7 +62,7 @@ class _AuthPageWidgetState extends ConsumerState<AuthPageWidget> {
|
|||||||
.auth(code ?? '');
|
.auth(code ?? '');
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
if (success) {
|
if (success) {
|
||||||
AppNavigator.push(const ScanQrCodePage());
|
AppNavigator.push(const EventInfoPage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final message = ref.read(authProvider).errorMessage;
|
final message = ref.read(authProvider).errorMessage;
|
||||||
|
|||||||
@@ -0,0 +1,359 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:recording_tool/app/router/app_navigator.dart';
|
||||||
|
import 'package:recording_tool/features/recording/pages/page_record.dart';
|
||||||
|
|
||||||
|
class EventInfoPage extends StatelessWidget {
|
||||||
|
const EventInfoPage({super.key});
|
||||||
|
|
||||||
|
static const _profile = EventProfile(
|
||||||
|
avatarLabel: '头像',
|
||||||
|
name: '王东方',
|
||||||
|
phone: '199966666527',
|
||||||
|
eventTitle: '全国青少年无人机大赛',
|
||||||
|
);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
debugPrint('item tapped: ${item.name}');
|
||||||
|
AppNavigator.push(const RecordingPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
_Header(onBack: () => AppNavigator.pop(context: context)),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.fromLTRB(62.w, 8.h, 62.w, 40.h),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const _ProfileSection(profile: _profile),
|
||||||
|
SizedBox(height: 82.h),
|
||||||
|
Text(
|
||||||
|
_profile.eventTitle,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF2F2F2F),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Header extends StatelessWidget {
|
||||||
|
const _Header({required this.onBack});
|
||||||
|
|
||||||
|
final VoidCallback onBack;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 52.h,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: IconButton(
|
||||||
|
onPressed: onBack,
|
||||||
|
icon: Icon(Icons.arrow_back_ios_new, size: 32.r),
|
||||||
|
color: Colors.black,
|
||||||
|
tooltip: '返回',
|
||||||
|
padding: EdgeInsets.only(left: 16.w),
|
||||||
|
constraints: BoxConstraints(minWidth: 56.w, minHeight: 52.h),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ProfileSection extends StatelessWidget {
|
||||||
|
const _ProfileSection({required this.profile});
|
||||||
|
|
||||||
|
final EventProfile profile;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
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)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 28.w),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 30.h),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
profile.name,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF2F2F2F),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 36.h),
|
||||||
|
Text(
|
||||||
|
profile.phone,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 22.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF2F2F2F),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScheduleCard extends StatelessWidget {
|
||||||
|
const _ScheduleCard({required this.item, required this.onTap});
|
||||||
|
|
||||||
|
final EventScheduleItem item;
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: Colors.white,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(minHeight: 138.h),
|
||||||
|
padding: EdgeInsets.fromLTRB(10.w, 14.h, 14.w, 0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: const Color(0xFF7A7A7A)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${item.name} (${item.group})',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF2F2F2F),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 26.h),
|
||||||
|
Text(
|
||||||
|
item.venue,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF2F2F2F),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 26.h),
|
||||||
|
Text(
|
||||||
|
item.time,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF2F2F2F),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(width: 12.w),
|
||||||
|
SizedBox(
|
||||||
|
width: 148.w,
|
||||||
|
height: 120.h,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: _ScheduleBadge(item: item),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScheduleBadge extends StatelessWidget {
|
||||||
|
const _ScheduleBadge({required this.item});
|
||||||
|
|
||||||
|
final EventScheduleItem item;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (item.status != null) {
|
||||||
|
return CustomPaint(
|
||||||
|
painter: _CutCornerBorderPainter(color: const Color(0xFF7A7A7A)),
|
||||||
|
child: SizedBox(
|
||||||
|
width: 148.w,
|
||||||
|
height: 90.h,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
item.status!,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(fontSize: 20.sp, color: const Color(0xFF2F2F2F)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Text(
|
||||||
|
item.laneNo ?? '',
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 34.sp,
|
||||||
|
height: 1,
|
||||||
|
color: const Color(0xFF2F2F2F),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CutCornerBorderPainter extends CustomPainter {
|
||||||
|
const _CutCornerBorderPainter({required this.color});
|
||||||
|
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final side = 12.r;
|
||||||
|
final path = Path()
|
||||||
|
..moveTo(side, 0)
|
||||||
|
..lineTo(size.width - side, 0)
|
||||||
|
..lineTo(size.width, side)
|
||||||
|
..lineTo(size.width, size.height - side)
|
||||||
|
..lineTo(size.width - side, size.height)
|
||||||
|
..lineTo(side, size.height)
|
||||||
|
..lineTo(0, size.height - side)
|
||||||
|
..lineTo(0, side)
|
||||||
|
..close();
|
||||||
|
final paint = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1
|
||||||
|
..color = color;
|
||||||
|
canvas.drawPath(path, paint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant _CutCornerBorderPainter oldDelegate) {
|
||||||
|
return oldDelegate.color != color;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:recording_tool/app/router/app_navigator.dart';
|
import 'package:recording_tool/app/router/app_navigator.dart';
|
||||||
import 'package:recording_tool/features/recording/pages/page_record.dart';
|
import 'package:recording_tool/features/recording/pages/page_record.dart';
|
||||||
import 'package:recording_tool/features/scan_qrcode/pages/page_push_test.dart';
|
|
||||||
import 'package:recording_tool/shared/widgets/app_bar.dart';
|
import 'package:recording_tool/shared/widgets/app_bar.dart';
|
||||||
import 'package:recording_tool/shared/widgets/app_button.dart';
|
import 'package:recording_tool/shared/widgets/app_button.dart';
|
||||||
import 'package:recording_tool/shared/widgets/app_qr_scanner_dialog.dart';
|
import 'package:recording_tool/shared/widgets/app_qr_scanner_dialog.dart';
|
||||||
|
|||||||
@@ -250,16 +250,16 @@ class WebviewPageState extends State<WebviewPage> {
|
|||||||
final ok = await showDialog<bool>(
|
final ok = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: const Text("提示"),
|
title: const Text('提示'),
|
||||||
content: const Text("是否返回上一页?"),
|
content: const Text('是否返回上一页?'),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(false),
|
onPressed: () => Navigator.of(context).pop(false),
|
||||||
child: const Text("取消"),
|
child: const Text('取消'),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.of(context).pop(true),
|
onPressed: () => Navigator.of(context).pop(true),
|
||||||
child: const Text("确定"),
|
child: const Text('确定'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user