mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-11 05:11:34 +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:
@@ -375,7 +375,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
|
||||
// 发送私信
|
||||
static Future sendMsg({
|
||||
static Future<LoadingState<Null>> sendMsg({
|
||||
int? senderUid,
|
||||
int? receiverId,
|
||||
int? msgType,
|
||||
@@ -417,12 +417,9 @@ abstract final class MsgHttp {
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
return const Success(null);
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
return Error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -62,7 +62,6 @@ abstract class PageUtils {
|
||||
}) async {
|
||||
// if (kDebugMode) debugPrint(content.toString());
|
||||
|
||||
int? selectedIndex;
|
||||
List<UserModel> userList = <UserModel>[];
|
||||
|
||||
final shareListRes = await ImGrpc.shareList(size: 3);
|
||||
@@ -77,11 +76,10 @@ abstract class PageUtils {
|
||||
),
|
||||
);
|
||||
} else if (context.mounted) {
|
||||
UserModel? userModel = await Navigator.of(context).push(
|
||||
final UserModel? userModel = await Navigator.of(context).push(
|
||||
GetPageRoute(page: () => const ContactPage()),
|
||||
);
|
||||
if (userModel != null) {
|
||||
selectedIndex = 0;
|
||||
userList.add(userModel);
|
||||
}
|
||||
}
|
||||
@@ -92,7 +90,6 @@ abstract class PageUtils {
|
||||
builder: (context) => SharePanel(
|
||||
content: content,
|
||||
userList: userList,
|
||||
selectedIndex: selectedIndex,
|
||||
),
|
||||
useSafeArea: true,
|
||||
enableDrag: false,
|
||||
|
||||
@@ -11,7 +11,6 @@ import 'package:PiliPlus/http/dynamics.dart';
|
||||
import 'package:PiliPlus/http/fav.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/member.dart';
|
||||
import 'package:PiliPlus/http/msg.dart';
|
||||
import 'package:PiliPlus/http/user.dart';
|
||||
import 'package:PiliPlus/http/validate.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
@@ -64,13 +63,11 @@ abstract final class RequestUtils {
|
||||
// 16:番剧(id 为 epid)
|
||||
// 17:番剧
|
||||
// https://github.com/SocialSisterYi/bilibili-API-collect/tree/master/docs/message/private_msg_content.md
|
||||
static Future<void> pmShare({
|
||||
static Future<bool> pmShare({
|
||||
required int receiverId,
|
||||
required Map content,
|
||||
String? message,
|
||||
}) async {
|
||||
SmartDialog.showLoading();
|
||||
|
||||
final ownerMid = Accounts.main.mid;
|
||||
final contentRes = await ImGrpc.sendMsg(
|
||||
senderUid: ownerMid,
|
||||
@@ -83,26 +80,19 @@ abstract final class RequestUtils {
|
||||
|
||||
if (contentRes.isSuccess) {
|
||||
if (message?.isNotEmpty == true) {
|
||||
var msgRes = await MsgHttp.sendMsg(
|
||||
final msgRes = await ImGrpc.sendMsg(
|
||||
senderUid: ownerMid,
|
||||
receiverId: receiverId,
|
||||
content: jsonEncode({"content": message}),
|
||||
msgType: 1,
|
||||
msgType: MsgType.EN_MSG_TYPE_TEXT,
|
||||
);
|
||||
Get.back();
|
||||
if (msgRes['status']) {
|
||||
SmartDialog.showToast('分享成功');
|
||||
} else {
|
||||
SmartDialog.showToast('内容分享成功,但消息分享失败: ${msgRes['msg']}');
|
||||
}
|
||||
return msgRes.isSuccess;
|
||||
} else {
|
||||
Get.back();
|
||||
SmartDialog.showToast('分享成功');
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast('分享失败: ${(contentRes as Error).errMsg}');
|
||||
return false;
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
}
|
||||
|
||||
static Future<void> actionRelationMod({
|
||||
|
||||
Reference in New Issue
Block a user