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.

1182 lines
42 KiB

import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:haptic_feedback/haptic_feedback.dart';
import '../../../core/theme/app_colors.dart';
import '../../../routes/app_routes.dart';
import '../controllers/meeting_controller.dart';
import '../model/meeting_model.dart';
class MeetingView extends GetView<MeetingController> {
const MeetingView({super.key});
static OverlayEntry? _recordingOverlayEntry;
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.grey[50],
appBar: _buildAppBar(context, isDarkMode),
body: _buildBody(context, isDarkMode),
floatingActionButton: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
FloatingActionButton(
heroTag: "addAudio",
onPressed: () {
_showAddAudioMenu(context, isDarkMode);
},
child: const Icon(Icons.add),
),
],
),
);
}
// 底部+号菜单
void _showAddAudioMenu(BuildContext context, bool isDarkMode) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom + 30.w,
left: 16.w,
right: 16.w,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 录音按钮
Container(
margin: EdgeInsets.only(bottom: 12.w),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop();
// TODO: 实现开始录音逻辑
_showStartsRecordSheet(context, isDarkMode);
},
child: Container(
height: 56.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.grey[100],
borderRadius: BorderRadius.circular(28.w),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 36.w,
height: 36.w,
decoration: BoxDecoration(
color: Colors.red.withOpacity(0.15),
shape: BoxShape.circle,
),
child: Icon(Icons.mic, color: Colors.red, size: 24.w),
),
SizedBox(width: 12.w),
Text(
'开始录音',
style: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black87,
fontSize: 16.sp,
fontWeight: FontWeight.w500,
),
),
Spacer(),
Icon(Icons.arrow_forward_ios,
color: Colors.grey, size: 18.w),
SizedBox(width: 16.w),
],
),
),
),
),
// 导入音频按钮
Container(
margin: EdgeInsets.only(bottom: 24.w),
child: GestureDetector(
onTap: () {
Navigator.of(context).pop();
_showImportAudioSheet(context, isDarkMode);
},
child: Container(
height: 56.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.10)
: Colors.white,
borderRadius: BorderRadius.circular(28.w),
border: Border.all(
color: Colors.blue.withOpacity(0.15),
width: 2,
),
boxShadow: [
BoxShadow(
color: Colors.blue.withOpacity(0.08),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 36.w,
height: 36.w,
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.15),
shape: BoxShape.circle,
),
child: Icon(Icons.file_upload_outlined,
color: Colors.blue, size: 24.w),
),
SizedBox(width: 12.w),
Text(
'导入音频',
style: TextStyle(
color: Colors.blue,
fontSize: 16.sp,
fontWeight: FontWeight.w500,
),
),
Spacer(),
Icon(Icons.arrow_forward_ios,
color: Colors.grey, size: 18.w),
SizedBox(width: 16.w),
],
),
),
),
),
// 关闭按钮
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
width: 56.w,
height: 56.w,
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.08)
: Colors.black.withOpacity(0.08),
shape: BoxShape.circle,
),
child: Icon(Icons.close,
color: isDarkMode ? Colors.white : Colors.black54,
size: 32.w),
),
),
SizedBox(height: 10.w),
],
),
);
},
);
}
// 新增:从opus导出选择弹窗
void _showLocalOpusPicker(BuildContext context, bool isDarkMode) async {
// 这里以用户目录下的 Downloads 为例,可根据实际需求修改路径
controller.loadTranslatopus();
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
),
padding: EdgeInsets.only(
left: 0,
right: 0,
top: 12.w,
bottom: MediaQuery.of(context).viewInsets.bottom + 12.w,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'选择音视频文件',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
Divider(
height: 1,
color: isDarkMode ? Colors.white12 : Colors.grey[200]),
SizedBox(height: 8.w),
SizedBox(
height: 300.w,
child: ListView.builder(
itemCount: controller.files.length,
itemBuilder: (context, idx) {
final file = controller.files[idx];
final fileName = file.path.split('/').last;
final isChecked =
controller.selected.contains(file.path);
return CheckboxListTile(
value: isChecked,
onChanged: (checked) {
setState(() {
if (checked == true) {
controller.selected.add(file.path);
} else {
controller.selected.remove(file.path);
}
});
},
title: Text(
fileName,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 15.sp,
),
),
controlAffinity: ListTileControlAffinity.leading,
activeColor: Colors.blue,
);
},
),
),
SizedBox(height: 12.w),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey,
),
onPressed: () => Navigator.of(context).pop(),
child:
Text('取消', style: TextStyle(color: Colors.white)),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
),
onPressed: controller.selected.isEmpty
? null
: () {
Navigator.of(context).pop();
// TODO: 调用导入逻辑
controller.shareAllTranslatAudio();
},
child:
Text('导入选中', style: TextStyle(color: Colors.white)),
),
],
),
SizedBox(height: 8.w),
],
),
);
},
);
},
);
}
// 新增:本地音视频文件选择弹窗
void _showLocalAudioVideoPicker(BuildContext context, bool isDarkMode) async {
// 这里以用户目录下的 Downloads 为例,可根据实际需求修改路径
controller.loadTranslatAudio();
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return StatefulBuilder(
builder: (context, setState) {
return Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
),
padding: EdgeInsets.only(
left: 0,
right: 0,
top: 12.w,
bottom: MediaQuery.of(context).viewInsets.bottom + 12.w,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'选择音视频文件',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
Divider(
height: 1,
color: isDarkMode ? Colors.white12 : Colors.grey[200]),
SizedBox(height: 8.w),
SizedBox(
height: 300.w,
child: ListView.builder(
itemCount: controller.files.length,
itemBuilder: (context, idx) {
final file = controller.files[idx];
final fileName = file.path.split('/').last;
final isChecked =
controller.selected.contains(file.path);
return CheckboxListTile(
value: isChecked,
onChanged: (checked) {
setState(() {
if (checked == true) {
controller.selected.add(file.path);
} else {
controller.selected.remove(file.path);
}
});
},
title: Text(
fileName,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 15.sp,
),
),
controlAffinity: ListTileControlAffinity.leading,
activeColor: Colors.blue,
);
},
),
),
SizedBox(height: 12.w),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey,
),
onPressed: () => Navigator.of(context).pop(),
child:
Text('取消', style: TextStyle(color: Colors.white)),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
),
onPressed: controller.selected.isEmpty
? null
: () {
Navigator.of(context).pop();
// TODO: 调用导入逻辑
controller.addTranslatAudio();
},
child:
Text('导入选中', style: TextStyle(color: Colors.white)),
),
],
),
SizedBox(height: 8.w),
],
),
);
},
);
},
);
}
// 导入音频弹窗
void _showImportAudioSheet(BuildContext context, bool isDarkMode) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
),
padding: EdgeInsets.only(
left: 0,
right: 0,
top: 12.w,
bottom: MediaQuery.of(context).viewInsets.bottom + 12.w,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 8.w),
child: Text(
'导入音频',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
),
Divider(
height: 1,
color: isDarkMode ? Colors.white12 : Colors.grey[200]),
// 选项列表
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.w),
child: Column(
children: [
_importAudioItem(
context,
isDarkMode,
icon: Image.asset('assets/images/explore.png',
width: 32.w, height: 32.w),
title: '从外部导入',
onTap: () {
Navigator.of(context).pop();
controller.addAudio();
// TODO: 实现从文件导入逻辑
},
),
SizedBox(height: 8.w),
_importAudioItem(
context,
isDarkMode,
icon: Image.asset('assets/images/explore.png',
width: 32.w, height: 32.w),
title: '从翻译里导入',
onTap: () {
Navigator.of(context).pop();
_showLocalAudioVideoPicker(context, isDarkMode);
// TODO: 实现从相册导入逻辑
},
),
SizedBox(height: 8.w),
_importAudioItem(
context,
isDarkMode,
icon: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('assets/images/explore.png',
width: 20.w, height: 20.w),
SizedBox(width: 4.w),
Image.asset('assets/images/explore.png',
width: 20.w, height: 20.w),
SizedBox(width: 4.w),
Image.asset('assets/images/explore.png',
width: 20.w, height: 20.w),
],
),
title: '从opus导出',
onTap: () {
Navigator.of(context).pop();
// TODO: 实现从其他APP导入逻辑
_showLocalOpusPicker(context, isDarkMode);
},
),
],
),
),
SizedBox(height: 8.w),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Text(
'支持导入单文件时长不超过5小时的音频',
style: TextStyle(
fontSize: 13.sp,
color: isDarkMode ? Colors.white54 : Colors.grey[600],
),
),
),
SizedBox(height: 16.w),
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Icon(Icons.close, size: 32.w, color: Colors.grey),
),
SizedBox(height: 8.w),
],
),
);
},
);
}
// 开始录音弹窗
void _showStartsRecordSheet(BuildContext context, bool isDarkMode) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
backgroundColor: Colors.transparent,
builder: (context) {
return Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(18.w)),
),
padding: EdgeInsets.only(
left: 0,
right: 0,
top: 12.w,
bottom: MediaQuery.of(context).viewInsets.bottom + 12.w,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
alignment: Alignment.centerLeft,
padding: EdgeInsets.symmetric(horizontal: 24.w, vertical: 8.w),
child: Text(
'开始录音',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
),
Divider(
height: 1,
color: isDarkMode ? Colors.white12 : Colors.grey[200]),
// 选项列表
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.w),
child: Column(
children: [
_importAudioItem(
context,
isDarkMode,
icon: Image.asset('assets/images/explore.png',
width: 32.w, height: 32.w),
title: '现场录音',
onTap: () {
Navigator.of(context).pop();
Get.toNamed(Routes.meetingRecord, arguments: {
'audioTypes': 0,
});
// TODO: 实现从文件导入逻辑
},
),
SizedBox(height: 8.w),
_importAudioItem(
context,
isDarkMode,
icon: Image.asset('assets/images/explore.png',
width: 32.w, height: 32.w),
title: '音、视频录音',
onTap: () {
Navigator.of(context).pop();
Get.toNamed(Routes.meetingRecord, arguments: {
'audioTypes': 1,
});
// TODO: 实现从相册导入逻辑
},
),
SizedBox(height: 8.w),
_importAudioItem(
context,
isDarkMode,
icon: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset('assets/images/explore.png',
width: 20.w, height: 20.w),
SizedBox(width: 4.w),
Image.asset('assets/images/explore.png',
width: 20.w, height: 20.w),
SizedBox(width: 4.w),
Image.asset('assets/images/explore.png',
width: 20.w, height: 20.w),
],
),
title: '通话录音',
onTap: () {
Navigator.of(context).pop();
Get.toNamed(Routes.meetingRecord, arguments: {
'audioTypes': 2,
});
// TODO: 实现从其他APP导入逻辑
},
),
],
),
),
SizedBox(height: 8.w),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Text(
'支持导入单文件时长不超过5小时的音频',
style: TextStyle(
fontSize: 13.sp,
color: isDarkMode ? Colors.white54 : Colors.grey[600],
),
),
),
SizedBox(height: 16.w),
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Icon(Icons.close, size: 32.w, color: Colors.grey),
),
SizedBox(height: 8.w),
],
),
);
},
);
}
// 选项框
Widget _importAudioItem(
BuildContext context,
bool isDarkMode, {
required Widget icon,
required String title,
required VoidCallback onTap,
}) {
return Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(12.w),
onTap: onTap,
child: Container(
height: 52.w,
padding: EdgeInsets.symmetric(horizontal: 12.w),
decoration: BoxDecoration(
color:
isDarkMode ? Colors.white.withOpacity(0.03) : Colors.grey[50],
borderRadius: BorderRadius.circular(12.w),
),
child: Row(
children: [
icon,
SizedBox(width: 14.w),
Expanded(
child: Text(
title,
style: TextStyle(
fontSize: 16.sp,
color: isDarkMode ? Colors.white : Colors.black87,
),
),
),
Icon(Icons.arrow_forward_ios, size: 18.w, color: Colors.grey),
],
),
),
),
);
}
// 新增右侧弹窗
void _showRightDrawerDialog(BuildContext context, bool isDarkMode) {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: "rightDrawer",
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, anim1, anim2) {
return Align(
alignment: Alignment.centerRight,
child: FractionallySizedBox(
widthFactor: 0.7,
child: Material(
color: Colors.transparent,
child: Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: isDarkMode ? Colors.white : Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(18.w),
bottomLeft: Radius.circular(18.w),
),
),
child: _buildDrawerContent(context, isDarkMode),
),
),
),
);
},
transitionBuilder: (context, anim1, anim2, child) {
final offsetAnimation = Tween<Offset>(
begin: const Offset(1, 0),
end: Offset.zero,
).animate(CurvedAnimation(parent: anim1, curve: Curves.easeOutCubic));
return SlideTransition(
position: offsetAnimation,
child: child,
);
},
);
}
// 侧边栏内容
Widget _buildDrawerContent(BuildContext context, bool isDarkMode) {
return SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.all(20.w),
child: TextField(
decoration: InputDecoration(
hintText: '在所有文件中搜索',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12.w),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.grey[100],
contentPadding: EdgeInsets.zero,
),
),
),
ListTile(
leading: Icon(Icons.folder, color: Colors.red),
title:
Text('全部文件(4)', style: TextStyle(fontWeight: FontWeight.bold)),
onTap: () {},
),
ListTile(
leading: Icon(Icons.category, color: Colors.blue),
title: Text('未分类(4)'),
onTap: () {},
),
ListTile(
leading: Icon(Icons.delete, color: Colors.teal),
title: Text('回收站'),
onTap: () {},
),
Divider(),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.w),
child: Text('文件夹', style: TextStyle(color: Colors.grey)),
),
...['NOTE', 'CALL', 'NotePin', 'Import', 'Desktop']
.map((name) => ListTile(
title: Text(name),
onTap: () {},
)),
Divider(),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.w),
child: Text('排序方式', style: TextStyle(color: Colors.grey)),
),
ListTile(
title: Text('创建时间'),
trailing: Icon(Icons.arrow_upward),
onTap: () {},
),
ListTile(
title: Text('修改时间'),
trailing: Icon(Icons.arrow_upward),
onTap: () {},
),
],
),
);
}
// 构建应用栏
PreferredSizeWidget _buildAppBar(BuildContext context, bool isDarkMode) {
return AppBar(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
elevation: 0.5,
leading: IconButton(
icon: Icon(
Icons.arrow_back_ios,
color: isDarkMode ? Colors.white : Colors.black,
size: 20.sp,
),
onPressed: () => Get.back(),
),
title: Text(
'conferenceAssistant'.tr, // 商务会议助手
style: TextStyle(
fontSize: 17.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
),
centerTitle: true,
actions: [
IconButton(
icon: Icon(Icons.folder_open,
color: isDarkMode ? Colors.white : Colors.black),
onPressed: () {
_showRightDrawerDialog(context, isDarkMode);
},
),
],
);
}
// 构建主体内容
Widget _buildBody(BuildContext context, bool isDarkMode) {
return Obx(
() => ListView.builder(
controller: controller.listController,
itemCount: controller.dataList.length,
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w),
itemBuilder: (BuildContext context, int index) {
MeetingModel data = MeetingModel.fromJson(controller.dataList[index]);
return _item(context, isDarkMode, data);
},
),
);
}
Widget _item(BuildContext context, bool isDarkMode, MeetingModel data) {
return GestureDetector(
onTap: () {
controller.isDisposed = true;
Get.toNamed(Routes.meetingDetails, arguments: {
'id': data.id,
});
},
onLongPressStart: (LongPressStartDetails details) {
_onLongPressItem(context, isDarkMode, details, data);
},
child: Container(
padding: EdgeInsets.symmetric(horizontal: 15.w, vertical: 10.w),
margin: EdgeInsets.symmetric(vertical: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
data.title,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 18.sp,
),
overflow: TextOverflow.ellipsis,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 5.w),
child: Row(
children: [
Icon(
Icons.calendar_month,
size: 14.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
),
2.horizontalSpace,
Text(
DateUtil.formatDateMs(
data.creationtime,
format: DateFormats.y_mo_d_h_m,
),
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
20.horizontalSpace,
Icon(
Icons.access_time,
size: 14.w,
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
),
2.horizontalSpace,
Text(
'${data.seconds}s',
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[500],
fontSize: 12.sp,
),
),
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 2.w),
margin: EdgeInsets.only(top: 5.w),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
borderRadius: BorderRadius.circular(4.w),
),
child: Text(
data.type,
style: TextStyle(
color: isDarkMode ? Colors.grey[400] : Colors.grey[600],
fontSize: 10.sp,
),
),
),
if (data.tasktype == 1)
Padding(
padding: EdgeInsets.only(top: 5.w),
child: Text(
'正在生成...',
style: TextStyle(
color: Colors.blue,
fontSize: 10.sp,
),
),
),
if (data.tasktype == 2)
Padding(
padding: EdgeInsets.only(top: 5.w),
child: Text(
'已生成',
style: TextStyle(
color: Colors.blue,
fontSize: 10.sp,
),
),
),
],
),
],
),
),
);
}
void _onLongPressItem(BuildContext context, bool isDarkMode,
LongPressStartDetails details, MeetingModel data) {
Haptics.vibrate(HapticsType.success);
RenderBox? renderBox =
Overlay.of(context).context.findRenderObject() as RenderBox;
final RelativeRect position = RelativeRect.fromRect(
Rect.fromLTRB(
details.globalPosition.dx,
details.globalPosition.dy,
details.globalPosition.dx + 110, // 菜单显示位置X轴坐标
details.globalPosition.dy - 40, // 菜单显示位置Y轴坐标
),
Offset.zero & renderBox.size,
);
showMenu(
context: context,
position: position,
color: isDarkMode ? AppColors.cardBackground : Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
items: [
_popupMenuEntry(
Icon(
Icons.edit_calendar,
size: 20.w,
color: isDarkMode ? Colors.white : Colors.black87,
),
'edit'.tr, // 编辑
isDarkMode ? Colors.white : Colors.black87,
() {
controller.titleController.text = data.title;
_bottomSheet(context, isDarkMode, data.id);
},
),
_popupMenuEntry(
Icon(
Icons.delete_outline,
size: 20.w,
color: isDarkMode ? Colors.red[700] : Colors.red,
),
'delete'.tr, // 删除
isDarkMode ? Colors.red[700]! : Colors.red,
() {
controller.deleteMeeting(data.id, data.filepath);
},
),
],
);
}
PopupMenuEntry _popupMenuEntry(
Icon icon,
String title,
Color color,
Function() onTap,
) {
return PopupMenuItem(
onTap: onTap,
child: SizedBox(
width: 140.w,
child: Row(
children: [
icon,
8.horizontalSpace,
Text(
title,
style: TextStyle(
color: color,
fontSize: 14.sp,
),
),
],
),
),
);
}
void _bottomSheet(
BuildContext context,
bool isDarkMode,
int id,
) {
Get.bottomSheet(
Padding(
padding: MediaQuery.of(context).viewInsets,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 20.w),
color: isDarkMode ? AppColors.darkBackground : Colors.grey[50],
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'editMeetingTitle'.tr, // 编辑会议标题
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp,
),
),
Container(
height: 48.w,
margin: EdgeInsets.only(top: 10.w, bottom: 20.w),
alignment: Alignment.center,
decoration: BoxDecoration(
color: isDarkMode
? Colors.white.withOpacity(0.05)
: Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: isDarkMode
? Colors.black.withOpacity(0.2)
: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: TextField(
controller: controller.titleController,
style: TextStyle(
color: isDarkMode ? Colors.white : Colors.black87,
fontSize: 16.sp,
),
decoration: InputDecoration(
hintText: 'maxCharacters'.tr,
hintStyle: TextStyle(
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 16.sp,
),
fillColor: Colors.transparent,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 10.w),
),
),
),
Padding(
padding: EdgeInsets.only(top: 30.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
onTap: () {
Get.back();
},
child: Container(
width: 120.w,
height: 40.w,
alignment: Alignment.center,
decoration: BoxDecoration(
border: Border.all(
width: 2.w,
color: isDarkMode ? AppColors.primary : Colors.blue,
),
borderRadius: BorderRadius.circular(12.w),
),
child: Text(
'cancel'.tr, // 取消
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? AppColors.primary : Colors.blue,
),
),
),
),
GestureDetector(
onTap: () {
String title = controller.titleController.text;
controller.editMeetingTitle(id, title);
},
child: Container(
width: 120.w,
height: 40.w,
alignment: Alignment.center,
decoration: BoxDecoration(
color: isDarkMode ? AppColors.primary : Colors.blue,
borderRadius: BorderRadius.circular(12.w),
),
child: Text(
'confirm'.tr, // 确认
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
),
),
),
),
],
),
),
],
),
),
),
isScrollControlled: true,
clipBehavior: Clip.hardEdge,
);
}
// ...existing code...
// void _showRecordingSheet(BuildContext context, bool isDarkMode) {
// if (_recordingOverlayEntry != null) return; // 防止重复添加
// _recordingOverlayEntry = OverlayEntry(
// builder: (ctx) => RecordingFloatingPanel(
// onStop: () {
// _recordingOverlayEntry?.remove();
// _recordingOverlayEntry = null;
// // 录音完成后的逻辑
// },
// ),
// );
// Overlay.of(context).insert(_recordingOverlayEntry!);
// }
}