216 lines
5.6 KiB
Dart
216 lines
5.6 KiB
Dart
import 'package:flutter/material.dart';
|
||
import '../../../../app/theme/app_colors.dart';
|
||
import '../../../../app/theme/app_typography.dart';
|
||
import '../../../../app/theme/app_spacing.dart';
|
||
import '../../../../shared/widgets/empty_state.dart';
|
||
|
||
/// A8. 消息模块
|
||
///
|
||
/// 交易通知、系统公告、券到期提醒、价格提醒
|
||
/// 分类Tab + 消息详情
|
||
class MessagePage extends StatefulWidget {
|
||
const MessagePage({super.key});
|
||
|
||
@override
|
||
State<MessagePage> createState() => _MessagePageState();
|
||
}
|
||
|
||
class _MessagePageState extends State<MessagePage>
|
||
with SingleTickerProviderStateMixin {
|
||
late TabController _tabController;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
_tabController = TabController(length: 4, vsync: this);
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_tabController.dispose();
|
||
super.dispose();
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Scaffold(
|
||
appBar: AppBar(
|
||
title: const Text('消息'),
|
||
actions: [
|
||
TextButton(
|
||
onPressed: () {},
|
||
child: Text('全部已读', style: AppTypography.labelSmall.copyWith(
|
||
color: AppColors.primary,
|
||
)),
|
||
),
|
||
],
|
||
bottom: TabBar(
|
||
controller: _tabController,
|
||
tabs: const [
|
||
Tab(text: '全部'),
|
||
Tab(text: '交易'),
|
||
Tab(text: '到期'),
|
||
Tab(text: '公告'),
|
||
],
|
||
),
|
||
),
|
||
body: TabBarView(
|
||
controller: _tabController,
|
||
children: [
|
||
_buildMessageList(all: true),
|
||
_buildMessageList(type: MessageType.transaction),
|
||
_buildMessageList(type: MessageType.expiry),
|
||
_buildMessageList(type: MessageType.announcement),
|
||
],
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _buildMessageList({bool all = false, MessageType? type}) {
|
||
if (type == MessageType.announcement) {
|
||
return EmptyState.noMessages();
|
||
}
|
||
|
||
final messages = _mockMessages
|
||
.where((m) => all || m.type == type)
|
||
.toList();
|
||
|
||
return ListView.separated(
|
||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||
itemCount: messages.length,
|
||
separatorBuilder: (_, __) => const Divider(indent: 76),
|
||
itemBuilder: (context, index) {
|
||
final msg = messages[index];
|
||
return _buildMessageItem(msg);
|
||
},
|
||
);
|
||
}
|
||
|
||
Widget _buildMessageItem(_MockMessage msg) {
|
||
return ListTile(
|
||
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4),
|
||
leading: Container(
|
||
width: 44,
|
||
height: 44,
|
||
decoration: BoxDecoration(
|
||
color: _iconBgColor(msg.type),
|
||
shape: BoxShape.circle,
|
||
),
|
||
child: Icon(_iconData(msg.type), size: 22, color: _iconColor(msg.type)),
|
||
),
|
||
title: Row(
|
||
children: [
|
||
Expanded(
|
||
child: Text(msg.title, style: AppTypography.labelMedium.copyWith(
|
||
fontWeight: msg.isRead ? FontWeight.w400 : FontWeight.w600,
|
||
)),
|
||
),
|
||
Text(msg.time, style: AppTypography.caption),
|
||
],
|
||
),
|
||
subtitle: Padding(
|
||
padding: const EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
msg.body,
|
||
style: AppTypography.bodySmall,
|
||
maxLines: 2,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
trailing: !msg.isRead
|
||
? Container(
|
||
width: 8,
|
||
height: 8,
|
||
decoration: const BoxDecoration(
|
||
color: AppColors.primary,
|
||
shape: BoxShape.circle,
|
||
),
|
||
)
|
||
: null,
|
||
onTap: () {
|
||
Navigator.pushNamed(context, '/message/detail');
|
||
},
|
||
);
|
||
}
|
||
|
||
IconData _iconData(MessageType type) {
|
||
switch (type) {
|
||
case MessageType.transaction:
|
||
return Icons.swap_horiz_rounded;
|
||
case MessageType.expiry:
|
||
return Icons.access_time_rounded;
|
||
case MessageType.price:
|
||
return Icons.trending_up_rounded;
|
||
case MessageType.announcement:
|
||
return Icons.campaign_rounded;
|
||
}
|
||
}
|
||
|
||
Color _iconColor(MessageType type) {
|
||
switch (type) {
|
||
case MessageType.transaction:
|
||
return AppColors.primary;
|
||
case MessageType.expiry:
|
||
return AppColors.warning;
|
||
case MessageType.price:
|
||
return AppColors.success;
|
||
case MessageType.announcement:
|
||
return AppColors.info;
|
||
}
|
||
}
|
||
|
||
Color _iconBgColor(MessageType type) {
|
||
return _iconColor(type).withValues(alpha: 0.1);
|
||
}
|
||
}
|
||
|
||
enum MessageType { transaction, expiry, price, announcement }
|
||
|
||
class _MockMessage {
|
||
final String title;
|
||
final String body;
|
||
final String time;
|
||
final MessageType type;
|
||
final bool isRead;
|
||
|
||
const _MockMessage(this.title, this.body, this.time, this.type, this.isRead);
|
||
}
|
||
|
||
const _mockMessages = [
|
||
_MockMessage(
|
||
'购买成功',
|
||
'您已成功购买 星巴克 \$25 礼品卡,共花费 \$21.25',
|
||
'14:32',
|
||
MessageType.transaction,
|
||
false,
|
||
),
|
||
_MockMessage(
|
||
'券即将到期',
|
||
'您持有的 Target \$30 折扣券 将于3天后到期,请及时使用',
|
||
'10:15',
|
||
MessageType.expiry,
|
||
false,
|
||
),
|
||
_MockMessage(
|
||
'价格提醒',
|
||
'您关注的 Amazon \$100 购物券 当前价格已降至 \$82,低于您设定的提醒价格',
|
||
'昨天',
|
||
MessageType.price,
|
||
true,
|
||
),
|
||
_MockMessage(
|
||
'出售成交',
|
||
'您挂单出售的 Nike \$80 运动券 已成功售出,收入 \$68.00',
|
||
'02/07',
|
||
MessageType.transaction,
|
||
true,
|
||
),
|
||
_MockMessage(
|
||
'核销成功',
|
||
'Walmart \$50 生活券 已在门店核销成功',
|
||
'02/06',
|
||
MessageType.transaction,
|
||
true,
|
||
),
|
||
];
|