feat(mobile-next): 会话页接全局搜索、精简加号菜单并改为长按操作

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
编码工程师
2026-08-20 03:51:27 +08:00
co-authored by Cursor multica-agent
parent ce2a3ae059
commit 7f131457f7
@@ -1,10 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart'; import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart'; import 'package:openim_common/openim_common.dart';
import 'package:rotated_corner_decoration/rotated_corner_decoration.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:sprintf/sprintf.dart'; import 'package:sprintf/sprintf.dart';
@@ -21,51 +19,75 @@ class ConversationPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() => Scaffold( return Obx(() => Scaffold(
backgroundColor: AppColors.pageBg, backgroundColor: AppColors.pageBg,
appBar: TitleBar.conversation( appBar: TitleBar(
statusStr: logic.imSdkStatus, height: 56,
isFailed: logic.isFailedSdkStatus, backgroundColor: AppColors.pageBg,
popCtrl: logic.popCtrl, left: const Expanded(
onScan: logic.scan, child: Align(
onAddFriend: logic.addFriend, alignment: Alignment.centerLeft,
onAddGroup: logic.addGroup, child: Text(
onCreateGroup: logic.createGroup, '消息',
left: Expanded( style: TextStyle(
flex: 2, fontSize: AppFont.title,
child: Align( fontWeight: FontWeight.w700,
alignment: Alignment.centerLeft, color: AppColors.textPrimary,
child: const Text(
'消息',
style: TextStyle(
fontSize: AppFont.title,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
),
), ),
), ),
)), ),
),
right: PopButton(
popCtrl: logic.popCtrl,
bgRadius: AppRadius.modal,
menus: [
PopMenuInfo(
text: '发起群聊',
iconWidget: const Icon(Icons.group_outlined,
size: AppIconSize.sm, color: AppColors.textPrimary),
onTap: logic.createGroup,
),
PopMenuInfo(
text: '添加同事',
iconWidget: const Icon(Icons.person_add_alt,
size: AppIconSize.sm, color: AppColors.textPrimary),
onTap: logic.addFriend,
),
],
child: const Padding(
padding: EdgeInsets.all(AppGap.x1),
child: Icon(Icons.add,
size: AppIconSize.md, color: AppColors.textPrimary),
),
),
),
body: Column( body: Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.fromLTRB(
AppGap.x4, 0, AppGap.x4, AppGap.x2), AppGap.x4, 0, AppGap.x4, AppGap.x2),
child: Container( child: GestureDetector(
height: 36, onTap: logic.globalSearch,
decoration: BoxDecoration( behavior: HitTestBehavior.opaque,
color: AppColors.searchBg, child: Container(
borderRadius: BorderRadius.circular(AppRadius.control), height: 36,
), decoration: BoxDecoration(
padding: const EdgeInsets.symmetric(horizontal: AppGap.x3), color: AppColors.searchBg,
alignment: Alignment.centerLeft, borderRadius: BorderRadius.circular(AppRadius.control),
child: const Row( ),
children: [ padding:
Icon(Icons.search, const EdgeInsets.symmetric(horizontal: AppGap.x3),
size: AppIconSize.sm, color: AppColors.textSecondary), alignment: Alignment.centerLeft,
SizedBox(width: AppGap.x2), child: const Row(
Text('搜索', children: [
style: TextStyle( Icon(Icons.search,
fontSize: AppFont.sub, size: AppIconSize.sm,
color: AppColors.textSecondary)), color: AppColors.textSecondary),
], SizedBox(width: AppGap.x2),
Text('搜索',
style: TextStyle(
fontSize: AppFont.sub,
color: AppColors.textSecondary)),
],
),
), ),
), ),
), ),
@@ -89,155 +111,162 @@ class ConversationPage extends StatelessWidget {
subtitle: '去通讯录找个同事聊聊吧', subtitle: '去通讯录找个同事聊聊吧',
); );
} }
return SlidableAutoCloseBehavior( return ScrollablePositionedList.builder(
child: ScrollablePositionedList.builder( itemScrollController: logic.itemScrollController,
itemScrollController: logic.itemScrollController, itemBuilder: (_, index) =>
itemBuilder: (_, index) => _buildConversationItemView( _buildItemView(logic.list.elementAt(index)),
logic.list.elementAt(index), itemCount: logic.list.length,
), itemPositionsListener: logic.itemPositionsListener,
itemCount: logic.list.length,
itemPositionsListener: logic.itemPositionsListener,
),
); );
} }
Widget _buildConversationItemView(ConversationInfo info) => Slidable( void _showConversationMenu(BuildContext context, ConversationInfo info) {
endActionPane: ActionPane( showModalBottomSheet<void>(
motion: const ScrollMotion(), context: context,
extentRatio: logic.existUnreadMsg(info) backgroundColor: Colors.transparent,
? 0.7 builder: (ctx) {
: (logic.isPinned(info) ? 0.5 : 0.4), return Padding(
children: [ padding: const EdgeInsets.fromLTRB(AppGap.x4, 0, AppGap.x4, AppGap.x6),
CustomSlidableAction( child: Material(
onPressed: (_) => logic.pinConversation(info), color: AppColors.pageBg,
flex: logic.isPinned(info) ? 3 : 2, elevation: 0,
backgroundColor: Styles.c_0089FF, shadowColor: Colors.transparent,
padding: const EdgeInsets.all(1), shape: RoundedRectangleBorder(
child: borderRadius: BorderRadius.circular(AppRadius.modal),
(logic.isPinned(info) ? StrRes.cancelTop : StrRes.top).toText
..style = Styles.ts_FFFFFF_14sp,
), ),
if (logic.existUnreadMsg(info)) clipBehavior: Clip.antiAlias,
CustomSlidableAction( child: Column(
onPressed: (_) => logic.markMessageHasRead(info), mainAxisSize: MainAxisSize.min,
flex: 3, children: [
backgroundColor: Styles.c_8E9AB0, ListTile(
padding: const EdgeInsets.all(1), title: Text(
child: StrRes.markHasRead.toText logic.isPinned(info) ? '取消置顶' : '置顶',
..style = Styles.ts_FFFFFF_14sp style: const TextStyle(
..maxLines = 1, fontSize: AppFont.body, color: AppColors.textPrimary),
), ),
CustomSlidableAction( onTap: () {
onPressed: (_) => logic.deleteConversation(info), Navigator.pop(ctx);
flex: 2, logic.pinConversation(info);
backgroundColor: Styles.c_FF381F, },
padding: const EdgeInsets.all(1), ),
child: StrRes.delete.toText..style = Styles.ts_FFFFFF_14sp, const Divider(height: 0.5, color: AppColors.divider),
ListTile(
title: const Text(
'删除',
style: TextStyle(
fontSize: AppFont.body, color: AppColors.danger),
),
onTap: () {
Navigator.pop(ctx);
logic.deleteConversation(info);
},
),
],
), ),
], ),
), );
child: _buildItemView(info), },
); );
}
Widget _buildItemView(ConversationInfo info) => Ink( Widget _buildItemView(ConversationInfo info) => Builder(
child: InkWell( builder: (context) {
onTap: () => logic.toChat(conversationInfo: info), return InkWell(
child: Stack( onTap: () => logic.toChat(conversationInfo: info),
children: [ onLongPress: () => _showConversationMenu(context, info),
Container( splashColor: AppColors.chatBg,
height: 72, highlightColor: AppColors.chatBg,
padding: EdgeInsets.symmetric(horizontal: 16.w), child: Container(
child: Row( height: 72,
children: [ padding: const EdgeInsets.symmetric(horizontal: AppGap.x4),
Stack( child: Row(
children: [
AvatarView(
width: 48.w,
height: 48.h,
text: logic.getShowName(info),
url: info.faceURL,
isGroup: logic.isGroupChat(info),
textStyle: Styles.ts_FFFFFF_14sp_medium,
),
const SizedBox(width: AppGap.x3),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
AvatarView( Row(
width: 48.w, children: [
height: 48.h, ConstrainedBox(
text: logic.getShowName(info), constraints: BoxConstraints(maxWidth: 180.w),
url: info.faceURL, child: Text(
isGroup: logic.isGroupChat(info), logic.getShowName(info),
textStyle: Styles.ts_FFFFFF_14sp_medium, maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: AppFont.body,
fontWeight: FontWeight.w700,
color: AppColors.textPrimary,
),
),
),
const Spacer(),
Text(
logic.getTime(info),
style: const TextStyle(
fontSize: AppFont.small,
color: AppColors.textSecondary),
),
],
),
const SizedBox(height: AppGap.x1),
Row(
children: [
MatchTextView(
text: logic.getContent(info),
textStyle: const TextStyle(
fontSize: AppFont.sub,
color: AppColors.textSecondary),
prefixSpan: TextSpan(
text: '',
children: [
if (logic.isNotDisturb(info) &&
logic.getUnreadCount(info) > 0)
TextSpan(
text: '[${sprintf(StrRes.nPieces, [
logic.getUnreadCount(info)
])}] ',
style: const TextStyle(
fontSize: AppFont.sub,
color: AppColors.textSecondary),
),
TextSpan(
text: logic.getPrefixTag(info),
style: const TextStyle(
fontSize: AppFont.sub,
color: AppColors.primary),
),
],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
if (logic.isNotDisturb(info))
const Icon(Icons.notifications_off_outlined,
size: AppIconSize.sm,
color: AppColors.textSecondary)
else
UnreadCountView(
count: logic.getUnreadCount(info)),
],
), ),
], ],
), ),
12.horizontalSpace,
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
ConstrainedBox(
constraints: BoxConstraints(maxWidth: 180.w),
child: Text(
logic.getShowName(info),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: AppFont.body,
color: AppColors.textPrimary,
),
),
),
const Spacer(),
logic.getTime(info).toText
..style = Styles.ts_8E9AB0_12sp,
],
),
3.verticalSpace,
Row(
children: [
MatchTextView(
text: logic.getContent(info),
textStyle: Styles.ts_8E9AB0_14sp,
prefixSpan: TextSpan(
text: '',
children: [
if (logic.isNotDisturb(info) &&
logic.getUnreadCount(info) > 0)
TextSpan(
text: '[${sprintf(StrRes.nPieces, [
logic.getUnreadCount(info)
])}] ',
style: Styles.ts_8E9AB0_14sp,
),
TextSpan(
text: logic.getPrefixTag(info),
style: Styles.ts_0089FF_14sp,
),
],
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
if (logic.isNotDisturb(info))
ImageRes.notDisturb.toImage
..width = 13.63.w
..height = 14.07.h
else
UnreadCountView(
count: logic.getUnreadCount(info)),
],
),
],
),
),
],
),
),
if (logic.isPinned(info))
Container(
height: 72,
margin: EdgeInsets.only(right: 6.w),
foregroundDecoration: RotatedCornerDecoration.withColor(
color: Styles.c_0089FF,
badgeSize: Size(8.29.w, 8.29.h),
), ),
) ],
], ),
), ),
), );
},
); );
} }