完成选手信息页
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 509 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 5.1 KiB |
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
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/config/app_config.dart';
|
import 'package:recording_tool/app/config/app_config.dart';
|
||||||
@@ -67,42 +68,41 @@ class _EventInfoPageState extends ConsumerState<EventInfoPage> {
|
|||||||
final state = ref.watch(eventInfoProvider);
|
final state = ref.watch(eventInfoProvider);
|
||||||
final profile = state.profile;
|
final profile = state.profile;
|
||||||
|
|
||||||
return Scaffold(
|
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
backgroundColor: Colors.white,
|
value: SystemUiOverlayStyle.light.copyWith(
|
||||||
body: SafeArea(
|
statusBarColor: Colors.transparent,
|
||||||
child: Column(
|
systemNavigationBarColor: const Color(0xFFF5F6F8),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: const Color(0xFFF5F6F8),
|
||||||
|
appBar: myAppBar(context: context),
|
||||||
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
_Header(onBack: () => AppNavigator.pop(context: context)),
|
const _TopGradientHeader(),
|
||||||
Expanded(
|
SafeArea(
|
||||||
child: SingleChildScrollView(
|
bottom: false,
|
||||||
padding: EdgeInsets.fromLTRB(62.w, 8.h, 62.w, 40.h),
|
child: Column(
|
||||||
child: Column(
|
children: [
|
||||||
children: [
|
// _Header(onBack: () => AppNavigator.pop(context: context)),
|
||||||
_ProfileSection(profile: profile),
|
SizedBox(height: 10.h),
|
||||||
SizedBox(height: 20.h),
|
Padding(
|
||||||
Text(
|
padding: EdgeInsets.symmetric(horizontal: 10.w),
|
||||||
state.eventTitle,
|
child: _ProfileSection(
|
||||||
style: TextStyle(
|
profile: profile,
|
||||||
fontSize: 20.sp,
|
eventTitle: state.eventTitle,
|
||||||
height: 1.2,
|
|
||||||
color: const Color(0xFF2F2F2F),
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
SizedBox(height: 24.h),
|
),
|
||||||
SizedBox(
|
SizedBox(height: 8.h),
|
||||||
height: 400.h,
|
Expanded(
|
||||||
child: _ScheduleList(
|
child: _ScheduleList(
|
||||||
state: state,
|
state: state,
|
||||||
onRetry: () => ref
|
onRetry: () => ref
|
||||||
.read(eventInfoProvider.notifier)
|
.read(eventInfoProvider.notifier)
|
||||||
.loadRegistrationList(),
|
.loadRegistrationList(),
|
||||||
onItemTap: onItemTap,
|
onItemTap: onItemTap,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -112,6 +112,31 @@ class _EventInfoPageState extends ConsumerState<EventInfoPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TopGradientHeader extends StatelessWidget {
|
||||||
|
const _TopGradientHeader();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SizedBox(
|
||||||
|
height: 219.h,
|
||||||
|
width: double.infinity,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
bottomLeft: Radius.circular(36.r),
|
||||||
|
bottomRight: Radius.circular(36.r),
|
||||||
|
),
|
||||||
|
gradient: const LinearGradient(
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
colors: [Color(0xFF5EC9FF), Color(0xFF178BFF)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildEventRegistrationDestination({
|
Widget buildEventRegistrationDestination({
|
||||||
required EventRegistrationItem item,
|
required EventRegistrationItem item,
|
||||||
required String playerId,
|
required String playerId,
|
||||||
@@ -135,16 +160,16 @@ class _Header extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 52.h,
|
height: 44.h,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
onPressed: onBack,
|
onPressed: onBack,
|
||||||
icon: Icon(Icons.arrow_back_ios_new, size: 32.r),
|
icon: Icon(Icons.chevron_left_rounded, size: 28.r),
|
||||||
color: Colors.black,
|
color: Colors.white,
|
||||||
tooltip: '返回',
|
tooltip: '返回',
|
||||||
padding: EdgeInsets.only(left: 16.w),
|
padding: EdgeInsets.only(left: 10.w),
|
||||||
constraints: BoxConstraints(minWidth: 56.w, minHeight: 52.h),
|
constraints: BoxConstraints(minWidth: 44.w, minHeight: 44.h),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -152,52 +177,143 @@ class _Header extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ProfileSection extends StatelessWidget {
|
class _ProfileSection extends StatelessWidget {
|
||||||
const _ProfileSection({required this.profile});
|
const _ProfileSection({required this.profile, required this.eventTitle});
|
||||||
|
|
||||||
final EventProfile profile;
|
final EventProfile profile;
|
||||||
|
final String eventTitle;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
final title = eventTitle.trim().isEmpty ? '赛事信息' : eventTitle.trim();
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
return Container(
|
||||||
children: [
|
clipBehavior: Clip.antiAlias,
|
||||||
profile.avatarUrl.isEmpty
|
padding: EdgeInsets.fromLTRB(12.w, 10.h, 12.w, 12.h),
|
||||||
? AppAvatar(size: 50.r)
|
decoration: BoxDecoration(
|
||||||
: AppAvatar(size: 50.r, imageUrl: profile.avatarUrl),
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(6.r),
|
||||||
SizedBox(width: 28.w),
|
),
|
||||||
Expanded(
|
child: Stack(
|
||||||
child: Padding(
|
children: [
|
||||||
padding: EdgeInsets.only(top: 30.h),
|
// Positioned(
|
||||||
child: Column(
|
// top: -28.h,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
// right: -14.w,
|
||||||
children: [
|
// child: Transform.rotate(
|
||||||
Text(
|
// angle: 0.68,
|
||||||
profile.name,
|
// child: Container(
|
||||||
maxLines: 1,
|
// width: 110.w,
|
||||||
overflow: TextOverflow.ellipsis,
|
// height: 138.h,
|
||||||
style: TextStyle(
|
// decoration: BoxDecoration(
|
||||||
fontSize: 22.sp,
|
// gradient: LinearGradient(
|
||||||
height: 1.2,
|
// begin: Alignment.topCenter,
|
||||||
color: const Color(0xFF2F2F2F),
|
// end: Alignment.bottomCenter,
|
||||||
|
// colors: [
|
||||||
|
// const Color(0xFF53D7FF).withValues(alpha: 0.12),
|
||||||
|
// const Color(0xFFFF85C8).withValues(alpha: 0.13),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
profile.avatarUrl.isEmpty
|
||||||
|
? AppAvatar(
|
||||||
|
size: 52.r,
|
||||||
|
initials: profile.name.isEmpty ? 'S' : profile.name,
|
||||||
|
)
|
||||||
|
: AppAvatar(size: 52.r, imageUrl: profile.avatarUrl),
|
||||||
|
SizedBox(width: 12.w),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
profile.name.isEmpty ? '参赛选手' : profile.name,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF33363B),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 5.h),
|
||||||
|
Text(
|
||||||
|
profile.phone,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF969CA6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
SizedBox(height: 36.h),
|
),
|
||||||
Text(
|
SizedBox(height: 10.h),
|
||||||
profile.phone,
|
Divider(
|
||||||
maxLines: 1,
|
height: 1.h,
|
||||||
overflow: TextOverflow.ellipsis,
|
thickness: 1.h,
|
||||||
style: TextStyle(
|
color: const Color(0xFFE8EAED),
|
||||||
fontSize: 22.sp,
|
),
|
||||||
height: 1.2,
|
SizedBox(height: 10.h),
|
||||||
color: const Color(0xFF2F2F2F),
|
_EventTitleHighlight(title: title),
|
||||||
),
|
],
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EventTitleHighlight extends StatelessWidget {
|
||||||
|
const _EventTitleHighlight({required this.title});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: IntrinsicWidth(
|
||||||
|
child: Stack(
|
||||||
|
alignment: Alignment.bottomLeft,
|
||||||
|
children: [
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 2.h,
|
||||||
|
child: Container(
|
||||||
|
height: 8.h,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFB8F5E8),
|
||||||
|
borderRadius: BorderRadius.circular(4.r),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16.sp,
|
||||||
|
height: 1.25,
|
||||||
|
color: const Color(0xFF33363B),
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,97 +339,68 @@ class _ScheduleList extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
'暂无赛事报名信息',
|
'暂无赛事报名信息',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(fontSize: 18.sp, color: const Color(0xFF2F2F2F)),
|
style: TextStyle(fontSize: 16.sp, color: const Color(0xFF3A3D42)),
|
||||||
),
|
),
|
||||||
SizedBox(height: 18.h),
|
SizedBox(height: 14.h),
|
||||||
TextButton(onPressed: onRetry, child: const Text('重新加载')),
|
TextButton(onPressed: onRetry, child: const Text('重新加载')),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListView.builder(
|
return ListView.separated(
|
||||||
|
padding: EdgeInsets.fromLTRB(10.w, 0, 10.w, 24.h),
|
||||||
itemCount: state.items.length,
|
itemCount: state.items.length,
|
||||||
|
separatorBuilder: (_, _) => SizedBox(height: 8.h),
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final item = state.items[index];
|
final item = state.items[index];
|
||||||
return _ScheduleCard(item: item, onTap: () => onItemTap(item));
|
return _ScheduleCard(
|
||||||
|
item: item,
|
||||||
|
scheduleIndex: index + 1,
|
||||||
|
onTap: () => onItemTap(item),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ScheduleCard extends StatelessWidget {
|
class _ScheduleCard extends StatelessWidget {
|
||||||
const _ScheduleCard({required this.item, required this.onTap});
|
const _ScheduleCard({
|
||||||
|
required this.item,
|
||||||
|
required this.scheduleIndex,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
final EventRegistrationItem item;
|
final EventRegistrationItem item;
|
||||||
|
final int scheduleIndex;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(6.r),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
borderRadius: BorderRadius.circular(6.r),
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(minHeight: 138.h),
|
constraints: BoxConstraints(minHeight: 86.h),
|
||||||
padding: EdgeInsets.fromLTRB(10.w, 14.h, 14.w, 0),
|
padding: EdgeInsets.fromLTRB(12.w, 10.h, 14.w, 10.h),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: const Color(0xFF7A7A7A)),
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(6.r),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: _ScheduleDetails(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
item: item,
|
||||||
children: [
|
scheduleIndex: scheduleIndex,
|
||||||
Text(
|
|
||||||
item.groupName.isEmpty
|
|
||||||
? item.itemName
|
|
||||||
: '${item.itemName} (${item.groupName})',
|
|
||||||
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.matchPlace,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20.sp,
|
|
||||||
height: 1.2,
|
|
||||||
color: const Color(0xFF2F2F2F),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(height: 26.h),
|
|
||||||
Text(
|
|
||||||
item.scheduleTime,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20.sp,
|
|
||||||
height: 1.2,
|
|
||||||
color: const Color(0xFF2F2F2F),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(width: 12.w),
|
SizedBox(width: 12.w),
|
||||||
SizedBox(
|
_ScheduleStatus(item: item),
|
||||||
width: 148.w,
|
|
||||||
height: 120.h,
|
|
||||||
child: Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: _ScheduleBadge(item: item),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -322,72 +409,148 @@ class _ScheduleCard extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ScheduleBadge extends StatelessWidget {
|
class _ScheduleDetails extends StatelessWidget {
|
||||||
const _ScheduleBadge({required this.item});
|
const _ScheduleDetails({required this.item, required this.scheduleIndex});
|
||||||
|
|
||||||
|
final EventRegistrationItem item;
|
||||||
|
final int scheduleIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final memberLine = _formatMemberLine(item);
|
||||||
|
final opponentLine = _formatOpponentLine(item);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
_formatTitle(item),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF30343A),
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(height: 6.h),
|
||||||
|
_MetaText(_formatVenue(item, scheduleIndex)),
|
||||||
|
SizedBox(height: 3.h),
|
||||||
|
_MetaText(_formatScheduleTime(item)),
|
||||||
|
if (memberLine.isNotEmpty) ...[
|
||||||
|
SizedBox(height: 3.h),
|
||||||
|
_MetaText(memberLine),
|
||||||
|
],
|
||||||
|
if (opponentLine.isNotEmpty) ...[
|
||||||
|
SizedBox(height: 3.h),
|
||||||
|
_MetaText(opponentLine),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MetaText extends StatelessWidget {
|
||||||
|
const _MetaText(this.text);
|
||||||
|
|
||||||
|
final String text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Text(
|
||||||
|
text,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10.sp,
|
||||||
|
height: 1.2,
|
||||||
|
color: const Color(0xFF6E747D),
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ScheduleStatus extends StatelessWidget {
|
||||||
|
const _ScheduleStatus({required this.item});
|
||||||
|
|
||||||
final EventRegistrationItem item;
|
final EventRegistrationItem item;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (item.statusLabel != null) {
|
final status = item.statusLabel;
|
||||||
return CustomPaint(
|
if (status != null) {
|
||||||
painter: _CutCornerBorderPainter(color: const Color(0xFF7A7A7A)),
|
return Text(
|
||||||
child: SizedBox(
|
status,
|
||||||
width: 148.w,
|
style: TextStyle(
|
||||||
height: 90.h,
|
fontSize: 10.sp,
|
||||||
child: Center(
|
height: 1.2,
|
||||||
child: Text(
|
color: const Color(0xFF1DBF73),
|
||||||
item.statusLabel!,
|
fontWeight: FontWeight.w500,
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: TextStyle(fontSize: 20.sp, color: const Color(0xFF2F2F2F)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Text(
|
return Text(
|
||||||
item.matchPlace,
|
_formatNumberBadge(item),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 34.sp,
|
fontSize: 20.sp,
|
||||||
height: 1,
|
height: 1,
|
||||||
color: const Color(0xFF2F2F2F),
|
color: const Color(0xFFF27D69),
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CutCornerBorderPainter extends CustomPainter {
|
String _formatTitle(EventRegistrationItem item) {
|
||||||
const _CutCornerBorderPainter({required this.color});
|
if (item.groupName.isEmpty) return item.itemName;
|
||||||
|
return '${item.itemName} (${item.groupName})';
|
||||||
|
}
|
||||||
|
|
||||||
final Color color;
|
String _formatVenue(EventRegistrationItem item, int scheduleIndex) {
|
||||||
|
final place = item.matchPlace.trim();
|
||||||
|
if (place.isEmpty) return '$scheduleIndex号场馆$scheduleIndex区';
|
||||||
|
return '$place号场馆$scheduleIndex区';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
String _formatScheduleTime(EventRegistrationItem item) {
|
||||||
void paint(Canvas canvas, Size size) {
|
final start = DateTime.tryParse(item.matchStartTime);
|
||||||
final side = 12.r;
|
final end = DateTime.tryParse(item.matchEndTime);
|
||||||
final path = Path()
|
if (start == null && end == null) return item.scheduleTime;
|
||||||
..moveTo(side, 0)
|
if (start != null && end != null) {
|
||||||
..lineTo(size.width - side, 0)
|
return '${start.month}月${start.day}日 ${_formatClock(start)}-${_formatClock(end)}';
|
||||||
..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);
|
|
||||||
}
|
}
|
||||||
|
final value = start ?? end!;
|
||||||
|
return '${value.month}月${value.day}日 ${_formatClock(value)}';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
String _formatClock(DateTime value) {
|
||||||
bool shouldRepaint(covariant _CutCornerBorderPainter oldDelegate) {
|
return '${value.hour}:${value.minute.toString().padLeft(2, '0')}';
|
||||||
return oldDelegate.color != color;
|
}
|
||||||
}
|
|
||||||
|
String _formatMemberLine(EventRegistrationItem item) {
|
||||||
|
final names = item.teamMembers
|
||||||
|
.map((member) => member.name.trim())
|
||||||
|
.where((name) => name.isNotEmpty)
|
||||||
|
.toList(growable: false);
|
||||||
|
return names.join('、');
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatOpponentLine(EventRegistrationItem item) {
|
||||||
|
final opponent = item.opponentName.trim();
|
||||||
|
if (opponent.isEmpty) return '';
|
||||||
|
return '对手:$opponent';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _formatNumberBadge(EventRegistrationItem item) {
|
||||||
|
final place = item.matchPlace.trim();
|
||||||
|
if (place.isEmpty) return '';
|
||||||
|
final match = RegExp(r'\d+').firstMatch(place);
|
||||||
|
if (match != null) return '${match.group(0)}号';
|
||||||
|
return place;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ class $AssetsHtmlGen {
|
|||||||
class $AssetsImagesGen {
|
class $AssetsImagesGen {
|
||||||
const $AssetsImagesGen();
|
const $AssetsImagesGen();
|
||||||
|
|
||||||
|
/// File path: assets/images/image_app_bar_bg.png
|
||||||
|
AssetGenImage get imageAppBarBg =>
|
||||||
|
const AssetGenImage('assets/images/image_app_bar_bg.png');
|
||||||
|
|
||||||
/// File path: assets/images/image_app_icon.png
|
/// File path: assets/images/image_app_icon.png
|
||||||
AssetGenImage get imageAppIcon =>
|
AssetGenImage get imageAppIcon =>
|
||||||
const AssetGenImage('assets/images/image_app_icon.png');
|
const AssetGenImage('assets/images/image_app_icon.png');
|
||||||
@@ -40,10 +44,18 @@ class $AssetsImagesGen {
|
|||||||
AssetGenImage get imageLogo =>
|
AssetGenImage get imageLogo =>
|
||||||
const AssetGenImage('assets/images/image_logo.png');
|
const AssetGenImage('assets/images/image_logo.png');
|
||||||
|
|
||||||
|
/// File path: assets/images/image_ok.png
|
||||||
|
AssetGenImage get imageOk =>
|
||||||
|
const AssetGenImage('assets/images/image_ok.png');
|
||||||
|
|
||||||
/// File path: assets/images/image_page_bg.png
|
/// File path: assets/images/image_page_bg.png
|
||||||
AssetGenImage get imagePageBg =>
|
AssetGenImage get imagePageBg =>
|
||||||
const AssetGenImage('assets/images/image_page_bg.png');
|
const AssetGenImage('assets/images/image_page_bg.png');
|
||||||
|
|
||||||
|
/// File path: assets/images/image_scan.png
|
||||||
|
AssetGenImage get imageScan =>
|
||||||
|
const AssetGenImage('assets/images/image_scan.png');
|
||||||
|
|
||||||
/// File path: assets/images/image_scan_qrcode.png
|
/// File path: assets/images/image_scan_qrcode.png
|
||||||
AssetGenImage get imageScanQrcode =>
|
AssetGenImage get imageScanQrcode =>
|
||||||
const AssetGenImage('assets/images/image_scan_qrcode.png');
|
const AssetGenImage('assets/images/image_scan_qrcode.png');
|
||||||
@@ -54,11 +66,14 @@ class $AssetsImagesGen {
|
|||||||
|
|
||||||
/// List of all assets
|
/// List of all assets
|
||||||
List<AssetGenImage> get values => [
|
List<AssetGenImage> get values => [
|
||||||
|
imageAppBarBg,
|
||||||
imageAppIcon,
|
imageAppIcon,
|
||||||
imageAppNameText,
|
imageAppNameText,
|
||||||
imageDialogBg,
|
imageDialogBg,
|
||||||
imageLogo,
|
imageLogo,
|
||||||
|
imageOk,
|
||||||
imagePageBg,
|
imagePageBg,
|
||||||
|
imageScan,
|
||||||
imageScanQrcode,
|
imageScanQrcode,
|
||||||
imageVs,
|
imageVs,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,31 +1,77 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.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/gen/assets.gen.dart';
|
||||||
|
|
||||||
AppBar myAppBar({
|
/// 全局页面标题栏:背景图撑满 + 居中标题 + 返回按钮。
|
||||||
|
/// 可直接赋给 [Scaffold.appBar]。
|
||||||
|
class AppPageBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
|
const AppPageBar({
|
||||||
|
super.key,
|
||||||
|
this.title = '',
|
||||||
|
this.titleWidget,
|
||||||
|
this.backgroundImage,
|
||||||
|
this.onBack,
|
||||||
|
this.actions,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final Widget? titleWidget;
|
||||||
|
final String? backgroundImage;
|
||||||
|
final VoidCallback? onBack;
|
||||||
|
final List<Widget>? actions;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final imagePath = backgroundImage ?? Assets.images.imageAppBarBg.path;
|
||||||
|
|
||||||
|
return AppBar(
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
centerTitle: true,
|
||||||
|
systemOverlayStyle: SystemUiOverlayStyle.light,
|
||||||
|
flexibleSpace: SizedBox.expand(
|
||||||
|
child: Image.asset(
|
||||||
|
imagePath,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.chevron_left_rounded,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 28.sp,
|
||||||
|
),
|
||||||
|
onPressed: onBack ?? () => AppNavigator.pop(context: context),
|
||||||
|
),
|
||||||
|
title:
|
||||||
|
titleWidget ??
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontSize: 20.sp,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
fontFamily: 'PingFang SC',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: actions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 兼容旧调用;内部委托 [AppPageBar]。
|
||||||
|
PreferredSizeWidget myAppBar({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
String title = '',
|
String title = '',
|
||||||
Widget? titleWidget,
|
Widget? titleWidget,
|
||||||
}) => AppBar(
|
List<Widget>? actions,
|
||||||
leadingWidth: 56.w,
|
}) {
|
||||||
leading: IconButton(
|
return AppPageBar(title: title, titleWidget: titleWidget, actions: actions);
|
||||||
icon: const Icon(Icons.arrow_back),
|
}
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).maybePop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
title:
|
|
||||||
titleWidget ??
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 20.sp,
|
|
||||||
fontFamily: 'PingFang SC',
|
|
||||||
fontWeight: FontWeight.w400,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
centerTitle: true,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
elevation: 0,
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
export 'app_avatar.dart';
|
export 'app_avatar.dart';
|
||||||
|
export 'app_bar.dart';
|
||||||
export 'app_button.dart';
|
export 'app_button.dart';
|
||||||
export 'app_card.dart';
|
export 'app_card.dart';
|
||||||
export 'app_dialog.dart';
|
export 'app_dialog.dart';
|
||||||
|
|||||||
Reference in New Issue
Block a user