217 lines
5.7 KiB
Dart
217 lines
5.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../../../app/i18n/app_localizations.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: Text(context.t('message.title')),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () {},
|
|
child: Text(context.t('message.markAllRead'), style: AppTypography.labelSmall.copyWith(
|
|
color: AppColors.primary,
|
|
)),
|
|
),
|
|
],
|
|
bottom: TabBar(
|
|
controller: _tabController,
|
|
tabs: [
|
|
Tab(text: context.t('common.all')),
|
|
Tab(text: context.t('message.tabTrade')),
|
|
Tab(text: context.t('message.tabExpiry')),
|
|
Tab(text: context.t('message.tabAnnouncement')),
|
|
],
|
|
),
|
|
),
|
|
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(context: context);
|
|
}
|
|
|
|
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(
|
|
'Purchase Successful',
|
|
'You have successfully purchased Starbucks \$25 Gift Card for \$21.25',
|
|
'14:32',
|
|
MessageType.transaction,
|
|
false,
|
|
),
|
|
_MockMessage(
|
|
'Coupon Expiring Soon',
|
|
'Your Target \$30 Voucher will expire in 3 days',
|
|
'10:15',
|
|
MessageType.expiry,
|
|
false,
|
|
),
|
|
_MockMessage(
|
|
'Price Alert',
|
|
'Amazon \$100 Voucher price dropped to \$82, below your alert price',
|
|
'Yesterday',
|
|
MessageType.price,
|
|
true,
|
|
),
|
|
_MockMessage(
|
|
'Sale Completed',
|
|
'Your listed Nike \$80 Voucher has been sold for \$68.00',
|
|
'02/07',
|
|
MessageType.transaction,
|
|
true,
|
|
),
|
|
_MockMessage(
|
|
'Redeem Successful',
|
|
'Walmart \$50 Voucher redeemed at store',
|
|
'02/06',
|
|
MessageType.transaction,
|
|
true,
|
|
),
|
|
];
|