You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
760 lines
26 KiB
760 lines
26 KiB
import 'package:flutter/material.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
|
import 'dart:math';
|
|
import '../controllers/chat_controller.dart';
|
|
import '../models/message_model.dart';
|
|
import '../../../core/theme/app_colors.dart';
|
|
import 'chat_music_card.dart';
|
|
import 'chat_navigation_card.dart';
|
|
import 'weather_day_card.dart';
|
|
|
|
class ChatView extends GetView<ChatController> {
|
|
const ChatView({Key? key}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
|
|
|
return Scaffold(
|
|
backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
|
|
appBar: _buildAppBar(isDarkMode),
|
|
body: _buildBody(isDarkMode),
|
|
);
|
|
}
|
|
|
|
// 构建AppBar
|
|
PreferredSizeWidget _buildAppBar(bool isDarkMode) {
|
|
return AppBar(
|
|
elevation: 1,
|
|
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white,
|
|
leading: IconButton(
|
|
icon: Icon(Icons.arrow_back_ios,
|
|
color: isDarkMode ? Colors.white : Colors.black87, size: 20.r),
|
|
onPressed: () => Get.back(),
|
|
),
|
|
titleSpacing: 0,
|
|
title: Row(
|
|
children: [
|
|
// AI头像
|
|
Container(
|
|
width: 40.r,
|
|
height: 40.r,
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primary,
|
|
borderRadius: BorderRadius.circular(20.r),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 3,
|
|
offset: Offset(0, 1),
|
|
),
|
|
],
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(20.r),
|
|
child: controller.agent.avatarUrl.isNotEmpty
|
|
? Image.asset(
|
|
controller.agent.avatarUrl,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Icon(
|
|
Icons.smart_toy_rounded,
|
|
color: Colors.white,
|
|
size: 22.r,
|
|
);
|
|
},
|
|
)
|
|
: Icon(
|
|
Icons.smart_toy_rounded,
|
|
color: Colors.white,
|
|
size: 22.r,
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 12.w),
|
|
// AI名称和状态
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
'${controller.agent.name}'.tr,
|
|
style: TextStyle(
|
|
fontSize: 16.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: isDarkMode ? Colors.white : Colors.black87,
|
|
),
|
|
),
|
|
Row(
|
|
children: [
|
|
Container(
|
|
width: 6.r,
|
|
height: 6.r,
|
|
decoration: BoxDecoration(
|
|
color: Colors.green,
|
|
shape: BoxShape.circle,
|
|
),
|
|
),
|
|
SizedBox(width: 4.w),
|
|
Text(
|
|
'online'.tr, // 在线
|
|
style: TextStyle(
|
|
fontSize: 12.sp,
|
|
color: Colors.green,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
// TTS开关
|
|
Obx(() => IconButton(
|
|
icon: Icon(
|
|
controller.isTtsEnabled.value
|
|
? Icons.volume_up
|
|
: Icons.volume_off,
|
|
color: controller.isTtsEnabled.value
|
|
? AppColors.primary
|
|
: isDarkMode
|
|
? Colors.grey[400]
|
|
: Colors.grey,
|
|
),
|
|
onPressed: controller.toggleTts,
|
|
)),
|
|
// 更多选项
|
|
Container(
|
|
width: 34.r,
|
|
height: 34.r,
|
|
margin: EdgeInsets.only(right: 8.w),
|
|
decoration: BoxDecoration(
|
|
color:
|
|
isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
if (!isDarkMode)
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 2,
|
|
spreadRadius: 0.5,
|
|
),
|
|
],
|
|
),
|
|
child: PopupMenuButton(
|
|
icon: Icon(Icons.more_vert,
|
|
color: isDarkMode ? Colors.white : Colors.black87, size: 20.r),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(12.r),
|
|
),
|
|
color: isDarkMode ? AppColors.cardBackground : Colors.white,
|
|
itemBuilder: (context) => [
|
|
PopupMenuItem(
|
|
value: 'clear',
|
|
child: Row(
|
|
children: [
|
|
Icon(Icons.delete_outline, color: Colors.red),
|
|
SizedBox(width: 8.w),
|
|
Text(
|
|
'clearChatHistory'.tr, // 清空聊天记录
|
|
style: TextStyle(
|
|
color: isDarkMode ? Colors.white : Colors.black87,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
onTap: () {
|
|
// 延迟执行,避免在弹出菜单关闭前执行
|
|
Future.delayed(
|
|
const Duration(milliseconds: 100),
|
|
() => controller.clearChat(),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
// 构建主体内容
|
|
Widget _buildBody(bool isDarkMode) {
|
|
return Column(
|
|
children: [
|
|
// 聊天内容区域
|
|
Expanded(
|
|
child: Obx(() {
|
|
if (controller.messages.isEmpty) {
|
|
return _buildEmptyState(isDarkMode);
|
|
} else {
|
|
return _buildChatList(isDarkMode);
|
|
}
|
|
}),
|
|
),
|
|
|
|
// 底部输入区域
|
|
_buildInputArea(isDarkMode),
|
|
],
|
|
);
|
|
}
|
|
|
|
// 构建空状态
|
|
Widget _buildEmptyState(bool isDarkMode) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
width: 80.w,
|
|
height: 80.w,
|
|
decoration: BoxDecoration(
|
|
color:
|
|
isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Icon(
|
|
Icons.chat_bubble_outline,
|
|
size: 40.w,
|
|
color:
|
|
isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
|
|
),
|
|
),
|
|
SizedBox(height: 24.h),
|
|
Text(
|
|
'你好!我是${controller.agent.name}助手',
|
|
style: TextStyle(
|
|
fontSize: 18.sp,
|
|
fontWeight: FontWeight.bold,
|
|
color: isDarkMode ? Colors.white : Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 12.h),
|
|
Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 40.w),
|
|
child: Text(
|
|
'我能帮你回答问题、实时语音翻译、推荐音乐和播客等。有什么我可以帮你的吗?',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
|
|
height: 1.5,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// 构建聊天列表
|
|
Widget _buildChatList(bool isDarkMode) {
|
|
return ListView.builder(
|
|
controller: controller.scrollController,
|
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
|
|
itemCount: controller.messages.length,
|
|
reverse: false,
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
cacheExtent: 1000, // 增加缓存范围,减少重建
|
|
itemBuilder: (context, index) {
|
|
final message = controller.messages[index];
|
|
return _buildMessageItem(message, isDarkMode);
|
|
},
|
|
);
|
|
}
|
|
|
|
// 构建消息项
|
|
Widget _buildMessageItem(Message message, bool isDarkMode) {
|
|
final isUser = message.role == 'user';
|
|
|
|
return Padding(
|
|
padding: EdgeInsets.only(bottom: 16.h),
|
|
child: Row(
|
|
mainAxisAlignment:
|
|
isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
if (!isUser) _buildAvatar(false, isDarkMode),
|
|
SizedBox(width: 8.w),
|
|
Flexible(
|
|
child: Column(
|
|
crossAxisAlignment:
|
|
isUser ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
|
children: [
|
|
// 消息气泡
|
|
Container(
|
|
padding: EdgeInsets.all(12.r),
|
|
decoration: BoxDecoration(
|
|
color: isUser
|
|
? AppColors.primary
|
|
: isDarkMode
|
|
? AppColors.cardBackground
|
|
: Colors.grey[200],
|
|
borderRadius: BorderRadius.circular(16.r),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// 消息内容
|
|
_buildMessageContent(message, isDarkMode, isUser),
|
|
|
|
// 加载中动画
|
|
if (message.isIntermediate)
|
|
Padding(
|
|
padding: EdgeInsets.only(top: 8.h),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: List.generate(
|
|
3,
|
|
(index) => _buildLoadingDot(index, isDarkMode),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// 时间戳
|
|
Padding(
|
|
padding: EdgeInsets.only(top: 4.h, left: 4.w, right: 4.w),
|
|
child: Text(
|
|
_formatTime(message.timestamp),
|
|
style: TextStyle(
|
|
fontSize: 10.sp,
|
|
color: isDarkMode ? Colors.grey[500] : Colors.grey[500],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
SizedBox(width: 8.w),
|
|
if (isUser) _buildAvatar(true, isDarkMode),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMessageContent(Message message, bool isDarkMode, bool isUser) {
|
|
Widget box = const SizedBox();
|
|
if (message.content.isNotEmpty) {
|
|
box = SelectableText(
|
|
message.content.tr,
|
|
style: TextStyle(
|
|
color: isUser
|
|
? Colors.white
|
|
: isDarkMode
|
|
? Colors.white
|
|
: Colors.black,
|
|
fontSize: 16.sp,
|
|
height: 1.5,
|
|
),
|
|
);
|
|
}
|
|
if (message.data != null) {
|
|
if (message.data?['card_weather'] != null) {
|
|
box = WeatherDayCard(message.data, isDarkMode);
|
|
}
|
|
if (message.data?['card_spotify'] != null) {
|
|
box = ChatMusicCard(message.data?['card_spotify'], isDarkMode);
|
|
}
|
|
if (message.data?['navigationcard'] != null) {
|
|
box = ChatNavigationCard(message.data, isDarkMode);
|
|
}
|
|
}
|
|
return box;
|
|
}
|
|
|
|
// 构建输入区域
|
|
Widget _buildInputArea(bool isDarkMode) {
|
|
final textController =
|
|
TextEditingController(text: controller.inputText.value);
|
|
|
|
// 检查是否处于语音识别模式
|
|
return Obx(() {
|
|
if (controller.isVoiceRecognitionActive.value) {
|
|
return _buildVoiceInputPanel(isDarkMode);
|
|
} else {
|
|
return _buildTextInputPanel(isDarkMode, textController);
|
|
}
|
|
});
|
|
}
|
|
|
|
// 构建文本输入面板
|
|
Widget _buildTextInputPanel(
|
|
bool isDarkMode, TextEditingController textController) {
|
|
return Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
|
|
decoration: BoxDecoration(
|
|
color: isDarkMode ? AppColors.cardBackground : Colors.white,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 3,
|
|
offset: Offset(0, -1),
|
|
),
|
|
],
|
|
),
|
|
child: SafeArea(
|
|
child: Row(
|
|
children: [
|
|
// 语音识别按钮
|
|
Container(
|
|
width: 40.w,
|
|
height: 40.w,
|
|
decoration: BoxDecoration(
|
|
color: isDarkMode
|
|
? Colors.white.withOpacity(0.1)
|
|
: Colors.grey[100],
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: IconButton(
|
|
icon: Icon(
|
|
Icons.mic_none,
|
|
color: isDarkMode ? Colors.white : Colors.grey[700],
|
|
size: 20.r,
|
|
),
|
|
padding: EdgeInsets.zero,
|
|
onPressed: controller.startVoiceRecognition,
|
|
),
|
|
),
|
|
|
|
SizedBox(width: 8.w),
|
|
|
|
// 输入框
|
|
Expanded(
|
|
child: TextField(
|
|
controller: textController,
|
|
decoration: InputDecoration(
|
|
hintText: 'inputMessage'.tr, // 输入消息...
|
|
hintStyle: TextStyle(
|
|
color: isDarkMode ? Colors.grey[500] : Colors.grey[500]),
|
|
filled: true,
|
|
fillColor: isDarkMode
|
|
? Colors.white.withOpacity(0.1)
|
|
: Colors.grey[100],
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(20.r),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 16.w,
|
|
vertical: 10.h,
|
|
),
|
|
),
|
|
maxLines: null,
|
|
textInputAction: TextInputAction.newline,
|
|
style: TextStyle(
|
|
fontSize: 15.sp,
|
|
color: isDarkMode ? Colors.white : Colors.black87,
|
|
),
|
|
onChanged: (value) => controller.inputText.value = value,
|
|
onSubmitted: (value) {
|
|
if (value.trim().isNotEmpty) {
|
|
controller.sendMessage(value);
|
|
textController.clear();
|
|
controller.inputText.value = ''; // 清空输入框
|
|
}
|
|
},
|
|
),
|
|
),
|
|
|
|
SizedBox(width: 8.w),
|
|
|
|
// 发送按钮
|
|
Obx(() {
|
|
final hasText = controller.inputText.value.isNotEmpty;
|
|
return Container(
|
|
width: 40.w,
|
|
height: 40.w,
|
|
decoration: BoxDecoration(
|
|
color: hasText
|
|
? AppColors.primary
|
|
: isDarkMode
|
|
? Colors.white.withOpacity(0.1)
|
|
: Colors.grey[200],
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: IconButton(
|
|
icon: Icon(
|
|
Icons.send,
|
|
color: hasText
|
|
? Colors.white
|
|
: isDarkMode
|
|
? Colors.white.withOpacity(0.5)
|
|
: Colors.grey[400],
|
|
size: 18.r,
|
|
),
|
|
padding: EdgeInsets.zero,
|
|
onPressed: hasText
|
|
? () {
|
|
controller.sendMessage(controller.inputText.value);
|
|
textController.clear();
|
|
controller.inputText.value = ''; // 清空输入框
|
|
}
|
|
: null,
|
|
),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 构建语音输入面板
|
|
Widget _buildVoiceInputPanel(bool isDarkMode) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: isDarkMode ? AppColors.cardBackground : Colors.white,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 3,
|
|
offset: Offset(0, -1),
|
|
),
|
|
],
|
|
),
|
|
child: SafeArea(
|
|
child: SizedBox(
|
|
height: 80.h,
|
|
child: Padding(
|
|
padding: EdgeInsets.symmetric(horizontal: 20.w),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
// 语音录制按钮
|
|
Container(
|
|
width: 60.r,
|
|
height: 60.r,
|
|
decoration: BoxDecoration(
|
|
color: isDarkMode
|
|
? Colors.white.withOpacity(0.1)
|
|
: Colors.white,
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 8,
|
|
spreadRadius: 1,
|
|
),
|
|
],
|
|
),
|
|
child: Center(
|
|
child: Obx(() => Icon(
|
|
controller.isListening.value
|
|
? Icons.mic
|
|
: Icons.mic_none,
|
|
color: controller.isListening.value
|
|
? AppColors.primary
|
|
: Colors.grey.shade400,
|
|
size: 28.r,
|
|
)),
|
|
),
|
|
),
|
|
|
|
// 中间音频波形和状态文本
|
|
Expanded(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
// 音频波形区域
|
|
Container(
|
|
height: 50.h,
|
|
child: Obx(() {
|
|
final isSpeaking = controller.isSpeechDetected.value;
|
|
return Center(
|
|
child: Container(
|
|
width: 220.w,
|
|
height: 30.h,
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: List.generate(
|
|
23,
|
|
(index) {
|
|
// 随机高度模拟波形
|
|
double height = isSpeaking
|
|
? (Random().nextDouble() * 20.0 + 5.0).h
|
|
: 2.h;
|
|
|
|
return SizedBox(
|
|
width: 6.w,
|
|
child: Center(
|
|
child: AnimatedContainer(
|
|
duration: Duration(milliseconds: 500),
|
|
width: 2.5.w,
|
|
height: height,
|
|
margin: EdgeInsets.symmetric(
|
|
horizontal: 1.5.w),
|
|
decoration: BoxDecoration(
|
|
color: isSpeaking
|
|
? AppColors.primary
|
|
: isDarkMode
|
|
? Colors.grey.shade600
|
|
: Colors.grey.shade400,
|
|
borderRadius:
|
|
BorderRadius.circular(2.r),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}),
|
|
),
|
|
SizedBox(height: 8.h),
|
|
|
|
// 语音状态文本
|
|
Obx(() => Text(
|
|
controller.isSpeechDetected.value
|
|
? 'imListening'.tr // 正在聆听...
|
|
: 'canStartTalking'.tr, // 你可以开始说话
|
|
style: TextStyle(
|
|
fontSize: 14.sp,
|
|
color: isDarkMode
|
|
? Colors.grey[400]
|
|
: Colors.grey[700],
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
)),
|
|
],
|
|
),
|
|
),
|
|
|
|
// 关闭按钮
|
|
Container(
|
|
width: 60.r,
|
|
height: 60.r,
|
|
decoration: BoxDecoration(
|
|
color: isDarkMode
|
|
? Colors.white.withOpacity(0.1)
|
|
: Colors.white,
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 8,
|
|
spreadRadius: 1,
|
|
),
|
|
],
|
|
),
|
|
child: Center(
|
|
child: IconButton(
|
|
icon: Icon(
|
|
Icons.close,
|
|
color: isDarkMode ? Colors.white : Colors.grey.shade600,
|
|
size: 28.r,
|
|
),
|
|
padding: EdgeInsets.zero,
|
|
onPressed: controller.stopVoiceRecognition,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 构建头像
|
|
Widget _buildAvatar(bool isUser, bool isDarkMode) {
|
|
return Container(
|
|
width: 36.r,
|
|
height: 36.r,
|
|
decoration: BoxDecoration(
|
|
color: isUser
|
|
? (isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[200])
|
|
: AppColors.primary,
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.05),
|
|
blurRadius: 2,
|
|
offset: Offset(0, 1),
|
|
),
|
|
],
|
|
),
|
|
child: isUser
|
|
? Center(
|
|
child: Icon(
|
|
Icons.person,
|
|
color: isDarkMode ? Colors.white : Colors.grey[700],
|
|
size: 20.r,
|
|
),
|
|
)
|
|
: ClipRRect(
|
|
borderRadius: BorderRadius.circular(18.r),
|
|
child: controller.agent.avatarUrl.isNotEmpty
|
|
? Image.asset(
|
|
controller.agent.avatarUrl,
|
|
fit: BoxFit.cover,
|
|
errorBuilder: (context, error, stackTrace) {
|
|
return Icon(
|
|
Icons.smart_toy_rounded,
|
|
color: Colors.white,
|
|
size: 20.r,
|
|
);
|
|
},
|
|
)
|
|
: Icon(
|
|
Icons.smart_toy_rounded,
|
|
color: Colors.white,
|
|
size: 20.r,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 构建加载动画点
|
|
Widget _buildLoadingDot(int index, bool isDarkMode) {
|
|
return AnimatedBuilder(
|
|
animation: AlwaysStoppedAnimation(0),
|
|
builder: (context, child) {
|
|
return Container(
|
|
margin: EdgeInsets.symmetric(horizontal: 2.w),
|
|
width: 8.r,
|
|
height: 8.r,
|
|
decoration: BoxDecoration(
|
|
color:
|
|
isDarkMode ? Colors.white.withOpacity(0.5) : Colors.grey[400],
|
|
shape: BoxShape.circle,
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
// 格式化时间
|
|
String _formatTime(DateTime time) {
|
|
final now = DateTime.now();
|
|
final today = DateTime(now.year, now.month, now.day);
|
|
//final yesterday = today.subtract(const Duration(days: 1));
|
|
final messageDate = DateTime(time.year, time.month, time.day);
|
|
|
|
String timeStr =
|
|
'${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
|
|
|
|
if (messageDate == today) {
|
|
return timeStr; // 只显示时间,如 "14:30"
|
|
}
|
|
// else if (messageDate == yesterday) {
|
|
// return 'Yesterday $timeStr'; // 如 "Yesterday 14:30"
|
|
// }
|
|
else {
|
|
// 国际通用日期格式 "MM-dd HH:mm"
|
|
return '${time.month.toString().padLeft(2, '0')}-${time.day.toString().padLeft(2, '0')} $timeStr';
|
|
}
|
|
}
|
|
}
|
|
|