mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-05-14 13:13:58 +08:00
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:
@@ -47,7 +47,7 @@ class _ContactPageState extends State<ContactPage>
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
UserModel? userModel = await Navigator.of(context).push(
|
||||
final UserModel? userModel = await Navigator.of(context).push(
|
||||
GetPageRoute(
|
||||
page: () => FollowSearchPage(
|
||||
mid: mid,
|
||||
|
||||
@@ -617,7 +617,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
||||
RichTextItem? voteItem = editController.items.firstWhereOrNull(
|
||||
(e) => e.type == RichTextType.vote,
|
||||
);
|
||||
VoteInfo? voteInfo = await Navigator.of(context).push(
|
||||
final VoteInfo? voteInfo = await Navigator.of(context).push(
|
||||
GetPageRoute(
|
||||
page: () => CreateVotePage(
|
||||
voteId: voteItem?.id == null ? null : int.parse(voteItem!.id!),
|
||||
@@ -818,7 +818,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState<CreateDynPanel> {
|
||||
|
||||
Future<void> _onReserve() async {
|
||||
controller.keepChatPanel();
|
||||
ReserveInfoData? reserveInfo = await Navigator.of(context).push(
|
||||
final ReserveInfoData? reserveInfo = await Navigator.of(context).push(
|
||||
GetPageRoute(
|
||||
page: () => CreateReservePage(sid: _reserveCard.value?.id),
|
||||
),
|
||||
|
||||
@@ -74,6 +74,7 @@ class _FansPageState extends FollowTypePageState<FansPage> {
|
||||
mid: item.mid,
|
||||
name: item.uname!,
|
||||
avatar: item.face!,
|
||||
selected: true,
|
||||
),
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -63,7 +63,7 @@ class _FavVideoPageState extends State<FavVideoPage>
|
||||
heroTag: heroTag,
|
||||
item: item,
|
||||
onTap: () async {
|
||||
var res = await Get.toNamed(
|
||||
final res = await Get.toNamed(
|
||||
'/favDetail',
|
||||
arguments: item,
|
||||
parameters: {
|
||||
|
||||
@@ -33,6 +33,7 @@ class FollowItem extends StatelessWidget {
|
||||
mid: item.mid,
|
||||
name: item.uname!,
|
||||
avatar: item.face!,
|
||||
selected: true,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -172,8 +172,8 @@ class _MemberFavoriteState extends State<MemberFavorite>
|
||||
height: 98,
|
||||
child: MemberFavItem(
|
||||
item: item,
|
||||
callback: (res) {
|
||||
if (res == true) {
|
||||
onDelete: (isDeleted) {
|
||||
if (isDeleted ?? false) {
|
||||
_controller.favState
|
||||
..value.mediaListResponse?.list?.remove(item)
|
||||
..refresh();
|
||||
|
||||
@@ -12,10 +12,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberFavItem extends StatelessWidget {
|
||||
const MemberFavItem({super.key, required this.item, this.callback});
|
||||
const MemberFavItem({super.key, required this.item, this.onDelete});
|
||||
|
||||
final SpaceFavItemModel item;
|
||||
final ValueChanged<bool?>? callback;
|
||||
final ValueChanged<bool?>? onDelete;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -34,14 +34,14 @@ class MemberFavItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
if (item.type == 0 || item.type == 11) {
|
||||
var res = await Get.toNamed(
|
||||
final bool? isDeleted = await Get.toNamed(
|
||||
'/favDetail',
|
||||
parameters: {
|
||||
'mediaId': item.id.toString(),
|
||||
'heroTag': Utils.makeHeroTag(item.id),
|
||||
},
|
||||
);
|
||||
callback?.call(res);
|
||||
onDelete?.call(isDeleted);
|
||||
} else {
|
||||
SubDetailPage.toSubDetailPage(
|
||||
item.id!,
|
||||
|
||||
@@ -669,7 +669,7 @@ List<SettingsModel> get styleSettings => [
|
||||
),
|
||||
NormalModel(
|
||||
onTap: (context, setState) async {
|
||||
var result = await Get.toNamed('/fontSizeSetting');
|
||||
final double? result = await Get.toNamed('/fontSizeSetting');
|
||||
if (result != null) {
|
||||
Get.put(ColorSelectController()).currentTextScale.value = result;
|
||||
}
|
||||
|
||||
@@ -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('部分分享失败');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,7 +320,7 @@ class _ReplyPageState extends CommonRichTextPubPageState<ReplyPage> {
|
||||
item(
|
||||
onTap: () async {
|
||||
controller.keepChatPanel();
|
||||
({String title, String url})? res = await Get.to(
|
||||
final ({String title, String url})? res = await Get.to(
|
||||
ReplySearchPage(type: widget.replyType, oid: widget.oid),
|
||||
);
|
||||
if (res != null) {
|
||||
|
||||
@@ -82,7 +82,7 @@ class WhisperDetailController extends CommonListController<RspSessionMsg, Msg> {
|
||||
SmartDialog.showToast('请先登录');
|
||||
return;
|
||||
}
|
||||
var result = await ImGrpc.sendMsg(
|
||||
final result = await ImGrpc.sendMsg(
|
||||
senderUid: account.mid,
|
||||
receiverId: mid!,
|
||||
content: msgType == 5
|
||||
|
||||
Reference in New Issue
Block a user