Add multi-select support to pmshare panel (#1779)

* Add multi-select support to share panel

- Replace single selection index with per-user selected flag
- Allow sending to multiple selected users
- Add sending state to prevent multiple clicks
- Update default selection logic to mark first user as selected

* 简化代码逻辑

* update

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>

---------

Signed-off-by: lesetong <oscarlbw@qq.com>
Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
lesetong
2025-12-17 12:46:36 +08:00
committed by GitHub
parent 348a9e014e
commit ab1e5cb62a
14 changed files with 113 additions and 110 deletions

View File

@@ -11,15 +11,17 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class UserModel {
const UserModel({
UserModel({
required this.mid,
required this.name,
required this.avatar,
this.selected = false,
});
final int mid;
final String name;
final String avatar;
bool selected;
@override
bool operator ==(Object other) {
@@ -41,11 +43,9 @@ class SharePanel extends StatefulWidget {
super.key,
required this.content,
this.userList,
this.selectedIndex,
});
final Map content;
final int? selectedIndex;
final List<UserModel>? userList;
@override
@@ -53,7 +53,6 @@ class SharePanel extends StatefulWidget {
}
class _SharePanelState extends State<SharePanel> {
int _selectedIndex = -1;
final List<UserModel> _userList = <UserModel>[];
final ScrollController _scrollController = ScrollController();
final FocusNode _focusNode = FocusNode();
@@ -72,9 +71,6 @@ class _SharePanelState extends State<SharePanel> {
super.initState();
if (widget.userList?.isNotEmpty == true) {
_userList.addAll(widget.userList!);
if (widget.selectedIndex != null) {
_selectedIndex = widget.selectedIndex!;
}
}
}
@@ -114,61 +110,66 @@ class _SharePanelState extends State<SharePanel> {
padding: EdgeInsets.zero,
childBuilder: (index) {
final item = _userList[index];
return GestureDetector(
onTap: () {
_selectedIndex = index;
setState(() {});
},
behavior: HitTestBehavior.opaque,
child: SizedBox(
width: 65,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.topCenter,
children: [
Column(
return Builder(
builder: (context) {
return GestureDetector(
onTap: () {
item.selected = !item.selected;
(context as Element).markNeedsBuild();
},
behavior: HitTestBehavior.opaque,
child: SizedBox(
width: 65,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.topCenter,
children: [
Padding(
padding: const EdgeInsets.all(5),
child: NetworkImgLayer(
width: 40,
height: 40,
src: item.avatar,
type: ImageType.avatar,
Column(
children: [
Padding(
padding: const EdgeInsets.all(5),
child: NetworkImgLayer(
width: 40,
height: 40,
src: item.avatar,
type: ImageType.avatar,
),
),
const SizedBox(height: 2),
Text(
item.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
),
],
),
if (item.selected)
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: theme.colorScheme.primary
.withValues(
alpha: 0.3,
),
shape: BoxShape.circle,
border: Border.all(
width: 1.5,
color: theme.colorScheme.primary,
),
),
child: const Icon(
Icons.check,
size: 20,
color: Colors.white,
),
),
),
const SizedBox(height: 2),
Text(
item.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(fontSize: 12),
),
],
),
if (index == _selectedIndex)
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: theme.colorScheme.primary.withValues(
alpha: 0.3,
),
shape: BoxShape.circle,
border: Border.all(
width: 1.5,
color: theme.colorScheme.primary,
),
),
child: const Icon(
Icons.check,
size: 20,
color: Colors.white,
),
),
],
),
),
),
);
},
);
},
),
@@ -176,14 +177,13 @@ class _SharePanelState extends State<SharePanel> {
GestureDetector(
onTap: () async {
_focusNode.unfocus();
UserModel? userModel = await Navigator.of(context).push(
final UserModel? userModel = await Navigator.of(context).push(
GetPageRoute(page: () => const ContactPage()),
);
if (userModel != null) {
_userList
..remove(userModel)
..insert(0, userModel);
_selectedIndex = 0;
_scrollController.jumpToTop();
setState(() {});
}
@@ -248,17 +248,7 @@ class _SharePanelState extends State<SharePanel> {
),
const SizedBox(width: 12),
FilledButton.tonal(
onPressed: () {
if (_selectedIndex == -1) {
SmartDialog.showToast('请选择分享的用户');
return;
}
RequestUtils.pmShare(
receiverId: _userList[_selectedIndex].mid,
content: widget.content,
message: _controller.text,
);
},
onPressed: _onSend,
style: FilledButton.styleFrom(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: const VisualDensity(
@@ -274,4 +264,31 @@ class _SharePanelState extends State<SharePanel> {
),
);
}
Future<void> _onSend() async {
final list = _userList.where((user) => user.selected);
if (list.isEmpty) {
SmartDialog.showToast('请选择分享的用户');
return;
}
SmartDialog.showLoading();
final res = await Future.wait(
list.map(
(user) => RequestUtils.pmShare(
receiverId: user.mid,
content: widget.content,
message: _controller.text,
),
),
);
SmartDialog.dismiss();
if (res.every((e) => e)) {
Get.back();
SmartDialog.showToast('分享成功');
} else if (res.every((e) => !e)) {
SmartDialog.showToast('分享失败');
} else {
SmartDialog.showToast('部分分享失败');
}
}
}