feat: session secondary

Closes #837

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-05-09 21:32:16 +08:00
parent dea29054e6
commit a282baf5a2
25 changed files with 814 additions and 543 deletions

27
lib/grpc/dm.dart Normal file
View File

@@ -0,0 +1,27 @@
import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:fixnum/fixnum.dart';
class DmGrpc {
static Future<LoadingState<DmSegMobileReply>> dmSegMobile({
required int cid,
required int segmentIndex,
int type = 1,
}) async {
final res = await GrpcRepo.request(
GrpcUrl.dmSegMobile,
DmSegMobileReq(
oid: Int64(cid),
segmentIndex: Int64(segmentIndex),
type: type,
),
DmSegMobileReply.fromBuffer,
);
if (res['status']) {
return LoadingState.success(res['data']);
} else {
return const Error('');
}
}
}

29
lib/grpc/dyn.dart Normal file
View File

@@ -0,0 +1,29 @@
import 'package:PiliPlus/grpc/bilibili/app/dynamic/v1.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
class DynGrpc {
// static Future dynSpace({
// required int uid,
// required int page,
// }) {
// return _request(
// GrpcUrl.dynSpace,
// DynSpaceReq(
// hostUid: Int64(uid),
// localTime: 8,
// page: Int64(page),
// from: 'space',
// ),
// DynSpaceRsp.fromBuffer,
// );
// }
static Future dynRed() {
return GrpcRepo.request(
GrpcUrl.dynRed,
DynRedReq(tabOffset: [TabOffset(tab: 1)]),
DynRedReply.fromBuffer,
onSuccess: (response) => response.dynRedItem.count.toInt(),
);
}
}

View File

@@ -2,20 +2,12 @@ import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/grpc/bilibili/app/dynamic/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/app/dynamic/v2.pb.dart';
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/im/interfaces/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/im/type.pb.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/metadata.pb.dart'; import 'package:PiliPlus/grpc/bilibili/metadata.pb.dart';
import 'package:PiliPlus/grpc/bilibili/metadata/device.pb.dart'; import 'package:PiliPlus/grpc/bilibili/metadata/device.pb.dart';
import 'package:PiliPlus/grpc/bilibili/metadata/fawkes.pb.dart'; import 'package:PiliPlus/grpc/bilibili/metadata/fawkes.pb.dart';
import 'package:PiliPlus/grpc/bilibili/metadata/locale.pb.dart'; import 'package:PiliPlus/grpc/bilibili/metadata/locale.pb.dart';
import 'package:PiliPlus/grpc/bilibili/metadata/network.pb.dart' as network; import 'package:PiliPlus/grpc/bilibili/metadata/network.pb.dart' as network;
import 'package:PiliPlus/grpc/bilibili/metadata/restriction.pb.dart'; import 'package:PiliPlus/grpc/bilibili/metadata/restriction.pb.dart';
import 'package:PiliPlus/grpc/bilibili/pagination.pb.dart';
import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/utils/login_utils.dart'; import 'package:PiliPlus/utils/login_utils.dart';
@@ -24,8 +16,7 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:archive/archive.dart'; import 'package:archive/archive.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:protobuf/protobuf.dart' show GeneratedMessage, PbMap; import 'package:protobuf/protobuf.dart' show GeneratedMessage;
import 'package:uuid/uuid.dart';
class GrpcUrl { class GrpcUrl {
// static const playerOnline = // static const playerOnline =
@@ -56,7 +47,12 @@ class GrpcUrl {
static const sendMsg = '$im/SendMsg'; static const sendMsg = '$im/SendMsg';
static const shareList = '$im/ShareList'; static const shareList = '$im/ShareList';
static const sessionMain = '$im2/SessionMain'; static const sessionMain = '$im2/SessionMain';
static const sessionSecondary = '$im2/SessionSecondary';
static const clearUnread = '$im2/ClearUnread'; static const clearUnread = '$im2/ClearUnread';
static const sessionUpdate = '$im2/SessionUpdate';
static const pinSession = '$im2/PinSession';
static const unpinSession = '$im2/UnpinSession';
static const deleteSessionList = '$im2/DeleteSessionList';
} }
class GrpcRepo { class GrpcRepo {
@@ -172,7 +168,7 @@ class GrpcRepo {
} }
} }
static Future<Map<String, dynamic>> _request( static Future<Map<String, dynamic>> request(
url, GeneratedMessage request, Function grpcParser, url, GeneratedMessage request, Function grpcParser,
{Function? onSuccess}) async { {Function? onSuccess}) async {
final response = await Request().post(HttpString.appBaseUrl + url, final response = await Request().post(HttpString.appBaseUrl + url,
@@ -231,194 +227,4 @@ class GrpcRepo {
// return {'status': true, 'data': response.items}; // return {'status': true, 'data': response.items};
// }); // });
// } // }
// static Future replyInfo({required int rpid}) {
// return _request(
// GrpcUrl.replyInfo,
// ReplyInfoReq(rpid: Int64(rpid)),
// ReplyInfoReply.fromBuffer,
// onSuccess: (response) => response.reply,
// );
// }
// static Future dynSpace({
// required int uid,
// required int page,
// }) {
// return _request(
// GrpcUrl.dynSpace,
// DynSpaceReq(
// hostUid: Int64(uid),
// localTime: 8,
// page: Int64(page),
// from: 'space',
// ),
// DynSpaceRsp.fromBuffer,
// );
// }
static Future mainList({
int type = 1,
required int oid,
required Mode mode,
required String? offset,
required Int64? cursorNext,
}) {
return _request(
GrpcUrl.mainList,
MainListReq(
oid: Int64(oid),
type: Int64(type),
rpid: Int64(0),
cursor: CursorReq(
mode: mode,
next: cursorNext,
),
// pagination: FeedPagination(offset: offset ?? ''),
),
MainListReply.fromBuffer,
);
}
static Future detailList({
int type = 1,
required int oid,
required int root,
required int rpid,
required Mode mode,
required String? offset,
}) {
return _request(
GrpcUrl.detailList,
DetailListReq(
oid: Int64(oid),
type: Int64(type),
root: Int64(root),
rpid: Int64(rpid),
scene: DetailListScene.REPLY,
mode: mode,
pagination: FeedPagination(offset: offset ?? ''),
),
DetailListReply.fromBuffer,
);
}
static Future dialogList({
int type = 1,
required int oid,
required int root,
required int dialog,
required String? offset,
}) {
return _request(
GrpcUrl.dialogList,
DialogListReq(
oid: Int64(oid),
type: Int64(type),
root: Int64(root),
dialog: Int64(dialog),
pagination: FeedPagination(offset: offset ?? ''),
),
DialogListReply.fromBuffer,
);
}
static Future dynRed() {
return _request(
GrpcUrl.dynRed,
DynRedReq(tabOffset: [TabOffset(tab: 1)]),
DynRedReply.fromBuffer,
onSuccess: (response) => response.dynRedItem.count.toInt(),
);
}
static Future dmSegMobile(
{required int cid, required int segmentIndex, int type = 1}) {
return _request(
GrpcUrl.dmSegMobile,
DmSegMobileReq(
oid: Int64(cid),
segmentIndex: Int64(segmentIndex),
type: type,
),
DmSegMobileReply.fromBuffer,
);
}
static Future sendMsg({
required int senderUid,
required int receiverId,
required String content,
MsgType msgType = MsgType.EN_MSG_TYPE_TEXT,
}) {
final devId = const Uuid().v4();
return _request(
GrpcUrl.sendMsg,
ReqSendMsg(
msg: Msg(
senderUid: Int64(senderUid),
receiverType: 1,
receiverId: Int64(receiverId),
msgType: msgType.value,
content: content,
timestamp: Int64(DateTime.now().millisecondsSinceEpoch ~/ 1000),
msgStatus: 0,
newFaceVersion: 1,
),
devId: devId,
),
RspSendMsg.fromBuffer,
);
}
static Future shareList({int size = 10}) {
return _request(
GrpcUrl.shareList,
ReqShareList(size: size),
RspShareList.fromBuffer,
);
}
static Future sessionMain({PbMap<int, Offset>? offset}) {
return _request(
GrpcUrl.sessionMain,
SessionMainReq(
paginationParams: PaginationParams(offsets: offset),
),
SessionMainReply.fromBuffer,
);
}
static Future clearUnread({
SessionPageType? pageType,
SessionId? sessionId,
}) {
return _request(
GrpcUrl.clearUnread,
ClearUnreadReq(
pageType: pageType,
sessionId: sessionId,
),
ClearUnreadReply.fromBuffer,
);
}
static Future opusSpaceFlow({
required int hostMid,
String? next,
required String filterType,
}) {
return _request(
GrpcUrl.opusSpaceFlow,
OpusSpaceFlowReq(
hostMid: Int64(hostMid),
pagination: Pagination(
pageSize: 20,
next: next,
),
filterType: filterType,
),
OpusSpaceFlowResp.fromBuffer,
);
}
} }

146
lib/grpc/im.dart Normal file
View File

@@ -0,0 +1,146 @@
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/im/interfaces/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/im/type.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:fixnum/fixnum.dart';
import 'package:protobuf/protobuf.dart' show PbMap;
import 'package:uuid/uuid.dart';
class ImGrpc {
static Future sendMsg({
required int senderUid,
required int receiverId,
required String content,
MsgType msgType = MsgType.EN_MSG_TYPE_TEXT,
}) {
final devId = const Uuid().v4();
return GrpcRepo.request(
GrpcUrl.sendMsg,
ReqSendMsg(
msg: Msg(
senderUid: Int64(senderUid),
receiverType: 1,
receiverId: Int64(receiverId),
msgType: msgType.value,
content: content,
timestamp: Int64(DateTime.now().millisecondsSinceEpoch ~/ 1000),
msgStatus: 0,
newFaceVersion: 1,
),
devId: devId,
),
RspSendMsg.fromBuffer,
);
}
static Future shareList({int size = 10}) {
return GrpcRepo.request(
GrpcUrl.shareList,
ReqShareList(size: size),
RspShareList.fromBuffer,
);
}
static Future<LoadingState<SessionMainReply>> sessionMain({
PbMap<int, Offset>? offset,
}) async {
final res = await GrpcRepo.request(
GrpcUrl.sessionMain,
SessionMainReq(
paginationParams: PaginationParams(offsets: offset),
),
SessionMainReply.fromBuffer,
);
if (res['status']) {
return LoadingState.success(res['data']);
} else {
return LoadingState.error(res['msg']);
}
}
static Future<LoadingState<SessionSecondaryReply>> sessionSecondary({
PbMap<int, Offset>? offset,
SessionPageType? pageType,
}) async {
final res = await GrpcRepo.request(
GrpcUrl.sessionSecondary,
SessionSecondaryReq(
paginationParams: PaginationParams(offsets: offset),
pageType: pageType,
),
SessionSecondaryReply.fromBuffer,
);
if (res['status']) {
return LoadingState.success(res['data']);
} else {
return LoadingState.error(res['msg']);
}
}
static Future clearUnread({
SessionPageType? pageType,
SessionId? sessionId,
}) {
return GrpcRepo.request(
GrpcUrl.clearUnread,
ClearUnreadReq(
pageType: pageType,
sessionId: sessionId,
),
ClearUnreadReply.fromBuffer,
);
}
static Future sessionUpdate({
SessionPageType? pageType,
SessionId? sessionId,
}) {
return GrpcRepo.request(
GrpcUrl.sessionUpdate,
SessionUpdateReq(
pageType: pageType,
sessionId: sessionId,
),
SessionUpdateReply.fromBuffer,
);
}
static Future pinSession({
SessionId? sessionId,
Int64? topTimeMicros,
}) {
return GrpcRepo.request(
GrpcUrl.pinSession,
PinSessionReq(
sessionId: sessionId,
topTimeMicros: topTimeMicros,
),
PinSessionReply.fromBuffer,
);
}
static Future unpinSession({
SessionId? sessionId,
}) {
return GrpcRepo.request(
GrpcUrl.unpinSession,
UnPinSessionReq(
sessionId: sessionId,
),
UnPinSessionReply.fromBuffer,
);
}
static Future deleteSessionList({
SessionPageType? pageType,
}) {
return GrpcRepo.request(
GrpcUrl.deleteSessionList,
DeleteSessionListReq(
pageType: pageType,
),
DeleteSessionListReply.fromBuffer,
);
}
}

189
lib/grpc/reply.dart Normal file
View File

@@ -0,0 +1,189 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart';
import 'package:PiliPlus/grpc/bilibili/pagination.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:fixnum/fixnum.dart';
class ReplyGrpc {
// static Future replyInfo({required int rpid}) {
// return _request(
// GrpcUrl.replyInfo,
// ReplyInfoReq(rpid: Int64(rpid)),
// ReplyInfoReply.fromBuffer,
// onSuccess: (response) => response.reply,
// );
// }
// ref BiliRoamingX
static bool needRemoveGrpc(ReplyInfo reply) {
if ((reply.content.urls.isNotEmpty &&
reply.content.urls.values.any((url) {
return url.hasExtra() &&
(url.extra.goodsCmControl == 1 ||
url.extra.goodsItemId != 0 ||
url.extra.goodsPrefetchedCache.isNotEmpty);
})) ||
reply.content.message.contains(Constants.goodsUrlPrefix)) {
return true;
}
return false;
}
static Future<LoadingState<MainListReply>> mainList({
int type = 1,
required int oid,
required Mode mode,
required String? offset,
required Int64? cursorNext,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.request(
GrpcUrl.mainList,
MainListReq(
oid: Int64(oid),
type: Int64(type),
rpid: Int64(0),
cursor: CursorReq(
mode: mode,
next: cursorNext,
),
// pagination: FeedPagination(offset: offset ?? ''),
),
MainListReply.fromBuffer,
);
if (res['status']) {
MainListReply mainListReply = res['data'];
// keyword filter
if (ReplyHttp.replyRegExp.pattern.isNotEmpty) {
// upTop
if (mainListReply.hasUpTop() &&
ReplyHttp.replyRegExp
.hasMatch(mainListReply.upTop.content.message)) {
mainListReply.clearUpTop();
}
// replies
if (mainListReply.replies.isNotEmpty) {
mainListReply.replies.removeWhere((item) {
bool hasMatch =
ReplyHttp.replyRegExp.hasMatch(item.content.message);
// remove subreplies
if (!hasMatch) {
if (item.replies.isNotEmpty) {
item.replies.removeWhere((item) =>
ReplyHttp.replyRegExp.hasMatch(item.content.message));
}
}
return hasMatch;
});
}
}
// antiGoodsReply
if (antiGoodsReply) {
// upTop
if (mainListReply.hasUpTop() && needRemoveGrpc(mainListReply.upTop)) {
mainListReply.clearUpTop();
}
// replies
if (mainListReply.replies.isNotEmpty) {
mainListReply.replies.removeWhere((item) {
bool hasMatch = needRemoveGrpc(item);
// remove subreplies
if (!hasMatch) {
if (item.replies.isNotEmpty) {
item.replies.removeWhere(needRemoveGrpc);
}
}
return hasMatch;
});
}
}
return LoadingState.success(mainListReply);
} else {
return LoadingState.error(res['msg']);
}
}
static Future<LoadingState<DetailListReply>> detailList({
int type = 1,
required int oid,
required int root,
required int rpid,
required Mode mode,
required String? offset,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.request(
GrpcUrl.detailList,
DetailListReq(
oid: Int64(oid),
type: Int64(type),
root: Int64(root),
rpid: Int64(rpid),
scene: DetailListScene.REPLY,
mode: mode,
pagination: FeedPagination(offset: offset ?? ''),
),
DetailListReply.fromBuffer,
);
if (res['status']) {
DetailListReply detailListReply = res['data'];
if (ReplyHttp.replyRegExp.pattern.isNotEmpty) {
if (detailListReply.root.replies.isNotEmpty) {
detailListReply.root.replies.removeWhere(
(item) => ReplyHttp.replyRegExp.hasMatch(item.content.message));
}
}
if (antiGoodsReply) {
if (detailListReply.root.replies.isNotEmpty) {
detailListReply.root.replies.removeWhere(needRemoveGrpc);
}
}
return LoadingState.success(detailListReply);
} else {
return LoadingState.error(res['msg']);
}
}
static Future<LoadingState<DialogListReply>> dialogList({
int type = 1,
required int oid,
required int root,
required int dialog,
required String? offset,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.request(
GrpcUrl.dialogList,
DialogListReq(
oid: Int64(oid),
type: Int64(type),
root: Int64(root),
dialog: Int64(dialog),
pagination: FeedPagination(offset: offset ?? ''),
),
DialogListReply.fromBuffer,
);
if (res['status']) {
DialogListReply dialogListReply = res['data'];
if (ReplyHttp.replyRegExp.pattern.isNotEmpty) {
if (dialogListReply.replies.isNotEmpty) {
dialogListReply.replies.removeWhere(
(item) => ReplyHttp.replyRegExp.hasMatch(item.content.message));
}
}
if (antiGoodsReply) {
if (dialogListReply.replies.isNotEmpty) {
dialogListReply.replies.removeWhere(needRemoveGrpc);
}
}
return LoadingState.success(dialogListReply);
} else {
return LoadingState.error(res['msg']);
}
}
}

31
lib/grpc/space.dart Normal file
View File

@@ -0,0 +1,31 @@
import 'package:PiliPlus/grpc/bilibili/app/dynamic/v2.pb.dart';
import 'package:PiliPlus/grpc/bilibili/pagination.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:fixnum/fixnum.dart';
class SpaceGrpc {
static Future<LoadingState<OpusSpaceFlowResp>> opusSpaceFlow({
required int hostMid,
String? next,
required String filterType,
}) async {
final res = await GrpcRepo.request(
GrpcUrl.opusSpaceFlow,
OpusSpaceFlowReq(
hostMid: Int64(hostMid),
pagination: Pagination(
pageSize: 20,
next: next,
),
filterType: filterType,
),
OpusSpaceFlowResp.fromBuffer,
);
if (res['status']) {
return LoadingState.success(res['data']);
} else {
return LoadingState.error(res['msg']);
}
}
}

View File

@@ -1,37 +1,9 @@
import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
class DanmakuHttp { class DanmakuHttp {
// 获取视频弹幕
static Future<LoadingState<DmSegMobileReply>> queryDanmaku({
required int cid,
required int segmentIndex,
int queryCount = 1,
}) async {
// 构建参数对象
final response =
await GrpcRepo.dmSegMobile(cid: cid, segmentIndex: segmentIndex);
if (!response['status']) {
if (queryCount >= 3) {
return const Error('');
} else {
await Future.delayed(const Duration(seconds: 1));
return await queryDanmaku(
cid: cid,
segmentIndex: segmentIndex,
queryCount: ++queryCount,
);
}
}
DmSegMobileReply data = response['data'];
return LoadingState.success(data);
}
static Future shootDanmaku({ static Future shootDanmaku({
int type = 1, //弹幕类选择(1视频弹幕 2漫画弹幕) int type = 1, //弹幕类选择(1视频弹幕 2漫画弹幕)
required int oid, // 视频cid required int oid, // 视频cid

View File

@@ -2,9 +2,6 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/grpc/bilibili/app/dynamic/v2.pb.dart'
show OpusSpaceFlowResp;
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/init.dart';
@@ -52,18 +49,6 @@ class MemberHttp {
}; };
} }
// static Future<LoadingState> spaceDynamic({
// required int mid,
// required int page,
// }) async {
// dynamic result = await GrpcRepo.dynSpace(uid: mid, page: page);
// if (result['status']) {
// return LoadingState.success(result['data']);
// } else {
// return LoadingState.error(result['msg']);
// }
// }
static Future<LoadingState<SpaceArticleData>> spaceArticle({ static Future<LoadingState<SpaceArticleData>> spaceArticle({
required int mid, required int mid,
required int page, required int page,
@@ -848,23 +833,6 @@ class MemberHttp {
} }
} }
static Future<LoadingState<OpusSpaceFlowResp>> opusSpaceFlow({
required int hostMid,
String? next,
required String filterType,
}) async {
var res = await GrpcRepo.opusSpaceFlow(
hostMid: hostMid,
next: next,
filterType: filterType,
);
if (res['status']) {
return LoadingState.success(res['data']);
} else {
return LoadingState.error(res['msg']);
}
}
static Future<LoadingState<SpaceOpusData>> spaceOpus({ static Future<LoadingState<SpaceOpusData>> spaceOpus({
required int hostMid, required int hostMid,
required int page, required int page,

View File

@@ -1,7 +1,5 @@
import 'dart:math'; import 'dart:math';
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/constants.dart'; import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/init.dart';
@@ -17,7 +15,6 @@ import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/wbi_sign.dart'; import 'package:PiliPlus/utils/wbi_sign.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:protobuf/protobuf.dart' show PbMap;
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class MsgHttp { class MsgHttp {
@@ -433,16 +430,6 @@ class MsgHttp {
} }
} }
static Future<LoadingState<SessionMainReply>> sessionMain(
{PbMap<int, Offset>? offset}) async {
final res = await GrpcRepo.sessionMain(offset: offset);
if (res['status']) {
return LoadingState.success(res['data']);
} else {
return LoadingState.error(res['msg']);
}
}
static Future accountList(uids) async { static Future accountList(uids) async {
var res = await Request().get(Api.sessionAccountList, queryParameters: { var res = await Request().get(Api.sessionAccountList, queryParameters: {
'uids': uids, 'uids': uids,

View File

@@ -1,6 +1,4 @@
import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -11,7 +9,6 @@ import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:fixnum/fixnum.dart';
class ReplyHttp { class ReplyHttp {
static Options get _options => static Options get _options =>
@@ -124,89 +121,6 @@ class ReplyHttp {
} }
} }
static Future<LoadingState<MainListReply>> mainList({
int type = 1,
required int oid,
required Mode mode,
required String? offset,
required Int64? cursorNext,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.mainList(
type: type,
oid: oid,
mode: mode,
offset: offset,
cursorNext: cursorNext,
);
if (res['status']) {
MainListReply mainListReply = res['data'];
// keyword filter
if (replyRegExp.pattern.isNotEmpty) {
// upTop
if (mainListReply.hasUpTop() &&
replyRegExp.hasMatch(mainListReply.upTop.content.message)) {
mainListReply.clearUpTop();
}
// replies
if (mainListReply.replies.isNotEmpty) {
mainListReply.replies.removeWhere((item) {
bool hasMatch = replyRegExp.hasMatch(item.content.message);
// remove subreplies
if (hasMatch.not) {
if (item.replies.isNotEmpty) {
item.replies.removeWhere(
(item) => replyRegExp.hasMatch(item.content.message));
}
}
return hasMatch;
});
}
}
// antiGoodsReply
if (antiGoodsReply) {
// upTop
if (mainListReply.hasUpTop() && needRemoveGrpc(mainListReply.upTop)) {
mainListReply.clearUpTop();
}
// replies
if (mainListReply.replies.isNotEmpty) {
mainListReply.replies.removeWhere((item) {
bool hasMatch = needRemoveGrpc(item);
// remove subreplies
if (hasMatch.not) {
if (item.replies.isNotEmpty) {
item.replies.removeWhere(needRemoveGrpc);
}
}
return hasMatch;
});
}
}
return LoadingState.success(mainListReply);
} else {
return LoadingState.error(res['msg']);
}
}
// ref BiliRoamingX
static bool needRemoveGrpc(ReplyInfo reply) {
if ((reply.content.urls.isNotEmpty &&
reply.content.urls.values.any((url) {
return url.hasExtra() &&
(url.extra.goodsCmControl == 1 ||
url.extra.goodsItemId != 0 ||
url.extra.goodsPrefetchedCache.isNotEmpty);
})) ||
reply.content.message.contains(Constants.goodsUrlPrefix)) {
return true;
}
return false;
}
static bool needRemove(ReplyItemModel reply) { static bool needRemove(ReplyItemModel reply) {
try { try {
if ((reply.content?.jumpUrl?.isNotEmpty == true && if ((reply.content?.jumpUrl?.isNotEmpty == true &&
@@ -269,76 +183,6 @@ class ReplyHttp {
} }
} }
static Future<LoadingState> detailList({
int type = 1,
required int oid,
required int root,
required int rpid,
required Mode mode,
required String? offset,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.detailList(
type: type,
oid: oid,
root: root,
rpid: rpid,
mode: mode,
offset: offset,
);
if (res['status']) {
DetailListReply detailListReply = res['data'];
if (replyRegExp.pattern.isNotEmpty) {
if (detailListReply.root.replies.isNotEmpty) {
detailListReply.root.replies.removeWhere(
(item) => replyRegExp.hasMatch(item.content.message));
}
}
if (antiGoodsReply) {
if (detailListReply.root.replies.isNotEmpty) {
detailListReply.root.replies.removeWhere(needRemoveGrpc);
}
}
return LoadingState.success(detailListReply);
} else {
return LoadingState.error(res['msg']);
}
}
static Future<LoadingState> dialogList({
int type = 1,
required int oid,
required int root,
required int dialog,
required String? offset,
required bool antiGoodsReply,
}) async {
dynamic res = await GrpcRepo.dialogList(
type: type,
oid: oid,
root: root,
dialog: dialog,
offset: offset,
);
if (res['status']) {
DialogListReply dialogListReply = res['data'];
if (replyRegExp.pattern.isNotEmpty) {
if (dialogListReply.replies.isNotEmpty) {
dialogListReply.replies.removeWhere(
(item) => replyRegExp.hasMatch(item.content.message));
}
}
if (antiGoodsReply) {
if (dialogListReply.replies.isNotEmpty) {
dialogListReply.replies.removeWhere(needRemoveGrpc);
}
}
return LoadingState.success(dialogListReply);
} else {
return LoadingState.error(res['msg']);
}
}
static Future hateReply({ static Future hateReply({
required int type, required int type,
required int action, required int action,

View File

@@ -172,22 +172,6 @@ class VideoHttp {
} }
} }
// static Future<LoadingState> hotVideoListGrpc({required int idx}) async {
// dynamic res = await GrpcRepo.popular(idx);
// if (res['status']) {
// List<card.Card> list = <card.Card>[];
// Set<int> blackMids = GStorage.blackMids;
// for (card.Card item in res['data']) {
// if (!blackMids.contains(item.smallCoverV5.up.id.toInt())) {
// list.add(item);
// }
// }
// return LoadingState.success(list);
// } else {
// return LoadingState.error(res['msg']);
// }
// }
// 视频流 // 视频流
static Future videoUrl({ static Future videoUrl({
int? avid, int? avid,

View File

@@ -1,8 +1,8 @@
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
show MainListReply, ReplyInfo; show MainListReply, ReplyInfo;
import 'package:PiliPlus/grpc/reply.dart';
import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/dynamics.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/dynamics/article_content_model.dart' import 'package:PiliPlus/models/dynamics/article_content_model.dart'
@@ -172,7 +172,7 @@ class ArticleController extends ReplyController<MainListReply> {
@override @override
Future<LoadingState<MainListReply>> customGetData() { Future<LoadingState<MainListReply>> customGetData() {
return ReplyHttp.mainList( return ReplyGrpc.mainList(
type: commentType, type: commentType,
oid: commentId, oid: commentId,
mode: mode.value, mode: mode.value,

View File

@@ -1,5 +1,5 @@
import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart'; import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart';
import 'package:PiliPlus/http/danmaku.dart'; import 'package:PiliPlus/grpc/dm.dart';
import 'package:PiliPlus/plugin/pl_player/controller.dart'; import 'package:PiliPlus/plugin/pl_player/controller.dart';
class PlDanmakuController { class PlDanmakuController {
@@ -31,7 +31,7 @@ class PlDanmakuController {
return; return;
} }
requestedSeg.add(segmentIndex); requestedSeg.add(segmentIndex);
final result = await DanmakuHttp.queryDanmaku( final result = await DmGrpc.dmSegMobile(
cid: cid, cid: cid,
segmentIndex: segmentIndex + 1, segmentIndex: segmentIndex + 1,
); );

View File

@@ -1,8 +1,8 @@
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
show MainListReply, ReplyInfo; show MainListReply, ReplyInfo;
import 'package:PiliPlus/grpc/reply.dart';
import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/dynamics.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:PiliPlus/models/common/reply/reply_type.dart'; import 'package:PiliPlus/models/common/reply/reply_type.dart';
import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart';
@@ -54,7 +54,7 @@ class DynamicDetailController extends ReplyController<MainListReply> {
} }
@override @override
Future<LoadingState<MainListReply>> customGetData() => ReplyHttp.mainList( Future<LoadingState<MainListReply>> customGetData() => ReplyGrpc.mainList(
type: type, type: type,
oid: oid, oid: oid,
mode: mode.value, mode: mode.value,

View File

@@ -35,23 +35,20 @@ class _DynTopicPageState extends State<DynTopicPage> {
Obx(() { Obx(() {
if (_controller.topicSortByConf.value?.allSortBy?.isNotEmpty == if (_controller.topicSortByConf.value?.allSortBy?.isNotEmpty ==
true) { true) {
return Padding( return PopupMenuButton(
padding: const EdgeInsets.only(right: 16), initialValue: _controller.sortBy,
child: PopupMenuButton( itemBuilder: (context) {
initialValue: _controller.sortBy, return _controller.topicSortByConf.value!.allSortBy!
itemBuilder: (context) { .map<PopupMenuItem>((e) {
return _controller.topicSortByConf.value!.allSortBy! return PopupMenuItem(
.map<PopupMenuItem>((e) { value: e.sortBy,
return PopupMenuItem( child: Text(e.sortName!),
value: e.sortBy, onTap: () {
child: Text(e.sortName!), _controller.onSort(e.sortBy!);
onTap: () { },
_controller.onSort(e.sortBy!); );
}, }).toList();
); },
}).toList();
},
),
); );
} }
return const SizedBox.shrink(); return const SizedBox.shrink();

View File

@@ -1,6 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/grpc/dyn.dart';
import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart'; import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
@@ -175,7 +175,7 @@ class MainController extends GetxController {
if (!isLogin.value || dynIndex == -1) { if (!isLogin.value || dynIndex == -1) {
return; return;
} }
GrpcRepo.dynRed().then((res) { DynGrpc.dynRed().then((res) {
if (res['status']) { if (res['status']) {
setCount(res['data']); setCount(res['data']);
} }

View File

@@ -1,7 +1,7 @@
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
show MainListReply, ReplyInfo; show MainListReply, ReplyInfo;
import 'package:PiliPlus/grpc/reply.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -41,7 +41,7 @@ class VideoReplyController extends ReplyController<MainListReply>
} }
@override @override
Future<LoadingState<MainListReply>> customGetData() => ReplyHttp.mainList( Future<LoadingState<MainListReply>> customGetData() => ReplyGrpc.mainList(
oid: aid, oid: aid,
mode: mode.value, mode: mode.value,
cursorNext: cursorNext, cursorNext: cursorNext,

View File

@@ -1,7 +1,7 @@
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
show ReplyInfo, DetailListReply, Mode; show ReplyInfo, DetailListReply, Mode;
import 'package:PiliPlus/grpc/reply.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:PiliPlus/models/common/reply/reply_type.dart'; import 'package:PiliPlus/models/common/reply/reply_type.dart';
import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart';
import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/id_utils.dart';
@@ -107,7 +107,7 @@ class VideoReplyReplyController extends ReplyController
@override @override
Future<LoadingState> customGetData() => isDialogue Future<LoadingState> customGetData() => isDialogue
? ReplyHttp.dialogList( ? ReplyGrpc.dialogList(
type: replyType.index, type: replyType.index,
oid: oid, oid: oid,
root: rpid, root: rpid,
@@ -115,7 +115,7 @@ class VideoReplyReplyController extends ReplyController
offset: paginationReply?.nextOffset, offset: paginationReply?.nextOffset,
antiGoodsReply: antiGoodsReply, antiGoodsReply: antiGoodsReply,
) )
: ReplyHttp.detailList( : ReplyGrpc.detailList(
type: replyType.index, type: replyType.index,
oid: oid, oid: oid,
root: rpid, root: rpid,

View File

@@ -1,6 +1,6 @@
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
show SessionMainReply, Session, Offset, SessionPageType; show Offset, Session, SessionId, SessionMainReply, SessionPageType;
import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/grpc/im.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/models/msg/msgfeed_unread.dart'; import 'package:PiliPlus/models/msg/msgfeed_unread.dart';
@@ -74,7 +74,7 @@ class WhisperController
@override @override
Future<LoadingState<SessionMainReply>> customGetData() => Future<LoadingState<SessionMainReply>> customGetData() =>
MsgHttp.sessionMain(offset: offset); ImGrpc.sessionMain(offset: offset);
@override @override
Future<void> onRefresh() { Future<void> onRefresh() {
@@ -94,13 +94,13 @@ class WhisperController
} }
} }
Future<void> onSetTop(int index, bool isTop, int? talkerId) async { Future<void> onSetTop(int index, bool isTop, SessionId sessionId) async {
var res = await MsgHttp.setTop( var res = isTop
talkerId: talkerId, ? await ImGrpc.unpinSession(sessionId: sessionId)
opType: isTop ? 1 : 0, : await ImGrpc.pinSession(sessionId: sessionId);
);
if (res['status']) { if (res['status']) {
List<Session> list = (loadingState.value as Success).response; List<Session> list = loadingState.value.data!;
list[index].isPinned = isTop ? false : true; list[index].isPinned = isTop ? false : true;
if (!isTop) { if (!isTop) {
list.insert(0, list.removeAt(index)); list.insert(0, list.removeAt(index));
@@ -121,7 +121,7 @@ class WhisperController
} }
Future<void> onClearUnread() async { Future<void> onClearUnread() async {
final res = await GrpcRepo.clearUnread( final res = await ImGrpc.clearUnread(
pageType: SessionPageType.SESSION_PAGE_TYPE_HOME); pageType: SessionPageType.SESSION_PAGE_TYPE_HOME);
if (res['status']) { if (res['status']) {
if (loadingState.value is Success) { if (loadingState.value is Success) {

View File

@@ -50,7 +50,13 @@ class _WhisperPageState extends State<WhisperPage> {
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
slivers: [ slivers: [
_buildTopItems, _buildTopItems,
Obx(() => _buildBody(_whisperController.loadingState.value)), SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 100,
),
sliver:
Obx(() => _buildBody(_whisperController.loadingState.value)),
),
], ],
), ),
), ),
@@ -66,31 +72,26 @@ class _WhisperPageState extends State<WhisperPage> {
}, },
), ),
Success() => loadingState.response?.isNotEmpty == true Success() => loadingState.response?.isNotEmpty == true
? SliverPadding( ? SliverList.separated(
padding: EdgeInsets.only( itemCount: loadingState.response!.length,
bottom: MediaQuery.paddingOf(context).bottom + 100, itemBuilder: (context, index) {
), if (index == loadingState.response!.length - 1) {
sliver: SliverList.separated( _whisperController.onLoadMore();
itemCount: loadingState.response!.length, }
itemBuilder: (context, index) { return WhisperSessionItem(
if (index == loadingState.response!.length - 1) { item: loadingState.response![index],
_whisperController.onLoadMore(); onSetTop: (isTop, id) =>
} _whisperController.onSetTop(index, isTop, id),
return WhisperSessionItem( onRemove: (talkerId) =>
item: loadingState.response![index], _whisperController.onRemove(index, talkerId),
onSetTop: (isTop, talkerId) => onTap: () => _whisperController.onTap(index),
_whisperController.onSetTop(index, isTop, talkerId), );
onRemove: (talkerId) => },
_whisperController.onRemove(index, talkerId), separatorBuilder: (context, index) => Divider(
onTap: () => _whisperController.onTap(index), indent: 72,
); endIndent: 20,
}, height: 1,
separatorBuilder: (context, index) => Divider( color: Colors.grey.withOpacity(0.1),
indent: 72,
endIndent: 20,
height: 1,
color: Colors.grey.withOpacity(0.1),
),
), ),
) )
: HttpError( : HttpError(

View File

@@ -3,11 +3,13 @@ import 'dart:convert';
import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
show Session, UnreadStyle; show Session, SessionId, SessionPageType, SessionType, UnreadStyle;
import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/pages/whisper_secondary/view.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class WhisperSessionItem extends StatelessWidget { class WhisperSessionItem extends StatelessWidget {
@@ -20,7 +22,7 @@ class WhisperSessionItem extends StatelessWidget {
}); });
final Session item; final Session item;
final Function(bool isTop, int? talkerId) onSetTop; final Function(bool isTop, SessionId id) onSetTop;
final ValueChanged<int?> onRemove; final ValueChanged<int?> onRemove;
final VoidCallback onTap; final VoidCallback onTap;
@@ -49,27 +51,25 @@ class WhisperSessionItem extends StatelessWidget {
dense: true, dense: true,
onTap: () { onTap: () {
Get.back(); Get.back();
onSetTop( onSetTop(item.isPinned, item.id);
item.isPinned,
item.id.privateId.talkerUid.toInt(),
);
}, },
title: Text( title: Text(
item.isPinned ? '移除置顶' : '置顶', item.isPinned ? '移除置顶' : '置顶',
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
), ),
), ),
ListTile( if (item.id.privateId.hasTalkerUid())
dense: true, ListTile(
onTap: () { dense: true,
Get.back(); onTap: () {
onRemove(item.id.privateId.talkerUid.toInt()); Get.back();
}, onRemove(item.id.privateId.talkerUid.toInt());
title: const Text( },
'删除', title: const Text(
style: TextStyle(fontSize: 14), '删除',
style: TextStyle(fontSize: 14),
),
), ),
),
], ],
), ),
); );
@@ -78,17 +78,54 @@ class WhisperSessionItem extends StatelessWidget {
}, },
onTap: () { onTap: () {
onTap(); onTap();
Get.toNamed( if (item.id.privateId.hasTalkerUid()) {
'/whisperDetail', Get.toNamed(
parameters: { '/whisperDetail',
'talkerId': item.id.privateId.talkerUid.toString(), parameters: {
'name': item.sessionInfo.sessionName, 'talkerId': item.id.privateId.talkerUid.toString(),
'face': item.sessionInfo.avatar.fallbackLayers.layers.first.resource 'name': item.sessionInfo.sessionName,
.resImage.imageSrc.remote.url, 'face': item.sessionInfo.avatar.fallbackLayers.layers.first
if (item.sessionInfo.avatar.hasMid()) .resource.resImage.imageSrc.remote.url,
'mid': item.sessionInfo.avatar.mid.toString(), if (item.sessionInfo.avatar.hasMid())
}, 'mid': item.sessionInfo.avatar.mid.toString(),
); },
);
return;
}
if (item.id.foldId.hasType()) {
SessionPageType? sessionPageType = switch (item.id.foldId.type) {
SessionType.SESSION_TYPE_UNKNOWN =>
SessionPageType.SESSION_PAGE_TYPE_UNKNOWN,
SessionType.SESSION_TYPE_GROUP =>
SessionPageType.SESSION_PAGE_TYPE_GROUP,
SessionType.SESSION_TYPE_GROUP_FOLD =>
SessionPageType.SESSION_PAGE_TYPE_GROUP,
SessionType.SESSION_TYPE_UNFOLLOWED =>
SessionPageType.SESSION_PAGE_TYPE_UNFOLLOWED,
SessionType.SESSION_TYPE_STRANGER =>
SessionPageType.SESSION_PAGE_TYPE_STRANGER,
SessionType.SESSION_TYPE_DUSTBIN =>
SessionPageType.SESSION_PAGE_TYPE_DUSTBIN,
SessionType.SESSION_TYPE_CUSTOMER_FOLD =>
SessionPageType.SESSION_PAGE_TYPE_CUSTOMER,
SessionType.SESSION_TYPE_AI_FOLD =>
SessionPageType.SESSION_PAGE_TYPE_AI,
SessionType.SESSION_TYPE_CUSTOMER_ACCOUNT =>
SessionPageType.SESSION_PAGE_TYPE_CUSTOMER,
_ => null,
};
if (sessionPageType != null) {
Get.to(
WhisperSecPage(
name: item.sessionInfo.sessionName,
sessionPageType: sessionPageType,
),
);
} else {
SmartDialog.showToast(item.id.foldId.type.name);
}
}
}, },
leading: Builder( leading: Builder(
builder: (context) { builder: (context) {

View File

@@ -0,0 +1,114 @@
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart'
show Offset, Session, SessionId, SessionPageType, SessionSecondaryReply;
import 'package:PiliPlus/grpc/im.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:protobuf/protobuf.dart' show PbMap;
class WhisperSecController
extends CommonListController<SessionSecondaryReply, Session> {
WhisperSecController({
required this.sessionPageType,
});
PbMap<int, Offset>? offset;
final SessionPageType sessionPageType;
@override
void onInit() {
super.onInit();
queryData();
}
@override
Future<void> onRefresh() {
offset = null;
return super.onRefresh();
}
@override
List<Session>? getDataList(SessionSecondaryReply response) {
if (response.paginationParams.hasMore == false) {
isEnd = true;
}
return response.sessions;
}
@override
Future<LoadingState<SessionSecondaryReply>> customGetData() =>
ImGrpc.sessionSecondary(
offset: offset,
pageType: sessionPageType,
);
Future<void> onRemove(int index, int? talkerId) async {
var res = await MsgHttp.removeMsg(talkerId);
if (res['status']) {
loadingState
..value.data!.removeAt(index)
..refresh();
SmartDialog.showToast('删除成功');
} else {
SmartDialog.showToast(res['msg']);
}
}
Future<void> onSetTop(int index, bool isTop, SessionId sessionId) async {
var res = isTop
? await ImGrpc.unpinSession(sessionId: sessionId)
: await ImGrpc.pinSession(sessionId: sessionId);
if (res['status']) {
List<Session> list = loadingState.value.data!;
list[index].isPinned = isTop ? false : true;
if (!isTop) {
list.insert(0, list.removeAt(index));
}
loadingState.refresh();
SmartDialog.showToast('${isTop ? '移除' : ''}置顶成功');
} else {
SmartDialog.showToast(res['msg']);
}
}
void onTap(int index) {
Session item = loadingState.value.data![index];
if (item.hasUnread()) {
item.clearUnread();
loadingState.refresh();
}
}
Future<void> onClearUnread() async {
final res = await ImGrpc.clearUnread(
pageType: SessionPageType.SESSION_PAGE_TYPE_UNFOLLOWED);
if (res['status']) {
if (loadingState.value is Success) {
List<Session>? list = loadingState.value.data;
if (list?.isNotEmpty == true) {
for (var item in list!) {
if (item.hasUnread()) {
item.clearUnread();
}
}
loadingState.refresh();
}
}
SmartDialog.showToast('已标记为已读');
} else {
SmartDialog.showToast(res['msg']);
}
}
Future<void> onDeleteList() async {
var res = await ImGrpc.deleteSessionList(
pageType: SessionPageType.SESSION_PAGE_TYPE_UNFOLLOWED);
if (res['status']) {
loadingState.value = LoadingState.success(null);
} else {
SmartDialog.showToast(res['msg']);
}
}
}

View File

@@ -0,0 +1,139 @@
import 'package:PiliPlus/common/skeleton/whisper_item.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/whisper/widgets/item.dart';
import 'package:PiliPlus/pages/whisper_secondary/controller.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class WhisperSecPage extends StatefulWidget {
const WhisperSecPage({
super.key,
required this.name,
required this.sessionPageType,
});
final String name;
final SessionPageType sessionPageType;
@override
State<WhisperSecPage> createState() => _WhisperSecPageState();
}
class _WhisperSecPageState extends State<WhisperSecPage> {
late final WhisperSecController _controller = Get.put(
WhisperSecController(sessionPageType: widget.sessionPageType),
tag: widget.sessionPageType.name,
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
actions: [
PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
onTap: () {
showConfirmDialog(
context: context,
title: '一键已读',
content: '是否清除全部新消息提醒?',
onConfirm: _controller.onClearUnread,
);
},
child: const Row(
children: [
Icon(
size: 17,
Icons.cleaning_services,
),
Text(' 一键已读'),
],
),
),
PopupMenuItem(
onTap: () {
showConfirmDialog(
context: context,
title: '清空列表',
content: '清空后所有消息将被删除,无法恢复',
onConfirm: _controller.onDeleteList,
);
},
child: const Row(
children: [
Icon(
size: 19,
Icons.delete_forever_outlined,
),
Text(' 清空列表'),
],
),
),
];
},
),
],
),
body: refreshIndicator(
onRefresh: _controller.onRefresh,
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80),
sliver: Obx(() => _buildBody(_controller.loadingState.value)),
)
],
),
),
);
}
Widget _buildBody(LoadingState<List<Session>?> loadingState) {
return switch (loadingState) {
Loading() => SliverList.builder(
itemCount: 12,
itemBuilder: (context, index) {
return const WhisperItemSkeleton();
},
),
Success() => loadingState.response?.isNotEmpty == true
? SliverList.separated(
itemCount: loadingState.response!.length,
itemBuilder: (context, index) {
if (index == loadingState.response!.length - 1) {
_controller.onLoadMore();
}
return WhisperSessionItem(
item: loadingState.response![index],
onSetTop: (isTop, talkerId) =>
_controller.onSetTop(index, isTop, talkerId),
onRemove: (talkerId) => _controller.onRemove(index, talkerId),
onTap: () => _controller.onTap(index),
);
},
separatorBuilder: (context, index) => Divider(
indent: 72,
endIndent: 20,
height: 1,
color: Colors.grey.withOpacity(0.1),
),
)
: HttpError(
onReload: _controller.onReload,
),
Error() => HttpError(
errMsg: loadingState.errMsg,
onReload: _controller.onReload,
),
};
}
}

View File

@@ -1,7 +1,7 @@
import 'dart:math'; import 'dart:math';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'; import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/grpc/im.dart';
import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/dynamics.dart';
import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/models/bangumi/info.dart'; import 'package:PiliPlus/models/bangumi/info.dart';
@@ -36,7 +36,7 @@ class PageUtils {
int? selectedIndex; int? selectedIndex;
List<UserModel> userList = <UserModel>[]; List<UserModel> userList = <UserModel>[];
final shareListRes = await GrpcRepo.shareList(size: 3); final shareListRes = await ImGrpc.shareList(size: 3);
if (shareListRes['status'] && shareListRes['data'].sessionList.isNotEmpty) { if (shareListRes['status'] && shareListRes['data'].sessionList.isNotEmpty) {
userList.addAll(shareListRes['data'] userList.addAll(shareListRes['data']
.sessionList .sessionList

View File

@@ -7,7 +7,7 @@ import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/grpc/bilibili/im/type.pbenum.dart'; import 'package:PiliPlus/grpc/bilibili/im/type.pbenum.dart';
import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart'
show ReplyInfo; show ReplyInfo;
import 'package:PiliPlus/grpc/grpc_repo.dart'; import 'package:PiliPlus/grpc/im.dart';
import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/http/dynamics.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/http/member.dart';
@@ -53,7 +53,7 @@ class RequestUtils {
SmartDialog.showLoading(); SmartDialog.showLoading();
final ownerMid = Accounts.main.mid; final ownerMid = Accounts.main.mid;
final contentRes = await GrpcRepo.sendMsg( final contentRes = await ImGrpc.sendMsg(
senderUid: ownerMid, senderUid: ownerMid,
receiverId: receiverId, receiverId: receiverId,
content: jsonEncode(content), content: jsonEncode(content),