Merge branch 'main' into feature-minePage

This commit is contained in:
guozhigq
2024-10-01 11:47:47 +08:00
22 changed files with 233 additions and 75 deletions

View File

@ -1,11 +1,9 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:get/get.dart';
import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart';
import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart';
import 'package:pilipala/utils/highlight.dart';
import 'network_img_layer.dart';
// ignore: must_be_immutable
class HtmlRender extends StatelessWidget {
@ -85,11 +83,11 @@ class HtmlRender extends StatelessWidget {
},
child: Center(
child: Hero(
tag: imgUrl,
tag: imgList?[index] ?? imgUrl,
child: CachedNetworkImage(
fadeInDuration:
const Duration(milliseconds: 0),
imageUrl: imgUrl,
imageUrl: imgList?[index] ?? imgUrl,
fit: BoxFit.contain,
),
),

View File

@ -555,6 +555,10 @@ class Api {
static const String messageSystemAPi =
'${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify';
/// 系统通知 个人
static const String userMessageSystemAPi =
'${HttpString.messageBaseUrl}/x/sys-msg/query_user_notify';
/// 系统通知标记已读
static const String systemMarkRead =
'${HttpString.messageBaseUrl}/x/sys-msg/update_cursor';
@ -584,4 +588,8 @@ class Api {
///
static const String getViewInfo = '/x/article/viewinfo';
/// 直播间记录
static const String liveRoomEntry =
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction';
}

View File

@ -142,4 +142,15 @@ class LiveHttp {
};
}
}
// 直播历史记录
static Future liveRoomEntry({required int roomId}) async {
await Request().post(Api.liveRoomEntry, queryParameters: {
'room_id': roomId,
'platform': 'pc',
'csrf_token': await Request.getCsrf(),
'csrf': await Request.getCsrf(),
'visit_id': '',
});
}
}

View File

@ -330,4 +330,27 @@ class MsgHttp {
};
}
}
static Future messageSystemAccount() async {
var res = await Request().get(Api.userMessageSystemAPi, data: {
'csrf': await Request.getCsrf(),
'page_size': 20,
'build': 0,
'mobi_app': 'web',
});
if (res.data['code'] == 0) {
try {
return {
'status': true,
'data': res.data['data']['system_notify_list']
.map<MessageSystemModel>((e) => MessageSystemModel.fromJson(e))
.toList(),
};
} catch (err) {
return {'status': false, 'date': [], 'msg': err.toString()};
}
} else {
return {'status': false, 'date': [], 'msg': res.data['message']};
}
}
}

View File

@ -5,7 +5,7 @@ class MessageSystemModel {
int? cursor;
int? type;
String? title;
Map? content;
dynamic content;
Source? source;
String? timeAt;
int? cardType;
@ -45,7 +45,9 @@ class MessageSystemModel {
cursor: jsons["cursor"],
type: jsons["type"],
title: jsons["title"],
content: json.decode(jsons["content"]),
content: isValidJson(jsons["content"])
? json.decode(jsons["content"])
: jsons["content"],
source: Source.fromJson(jsons["source"]),
timeAt: jsons["time_at"],
cardType: jsons["card_type"],
@ -75,3 +77,12 @@ class Source {
logo: json["logo"],
);
}
bool isValidJson(String str) {
try {
json.decode(str);
} catch (e) {
return false;
}
return true;
}

View File

@ -39,11 +39,11 @@ class ModelResult {
ModelResult.fromJson(Map<String, dynamic> json) {
resultType = json['result_type'];
summary = json['summary'];
outline = json['result_type'] == 2
? json['outline']
outline = json['result_type'] == 0
? <OutlineItem>[]
: json['outline']
.map<OutlineItem>((e) => OutlineItem.fromJson(e))
.toList()
: <OutlineItem>[];
.toList();
}
}

View File

@ -61,7 +61,7 @@ class _BlackListPageState extends State<BlackListPage> {
centerTitle: false,
title: Obx(
() => Text(
'黑名单管理 - ${_blackListController.total.value}',
'黑名单管理 ${_blackListController.total.value == 0 ? '' : '- ${_blackListController.total.value}'}',
style: Theme.of(context).textTheme.titleMedium,
),
),
@ -76,8 +76,12 @@ class _BlackListPageState extends State<BlackListPage> {
if (data['status']) {
List<BlackListItem> list = _blackListController.blackList;
return Obx(
() => list.length == 1
? const SizedBox()
() => list.isEmpty
? CustomScrollView(
slivers: [
HttpError(errMsg: '你没有拉黑任何人哦_', fn: () => {})
],
)
: ListView.builder(
controller: scrollController,
itemCount: list.length,

View File

@ -49,7 +49,6 @@ class FansController extends GetxController {
} else if (type == 'onLoad') {
fansList.addAll(res['data'].list);
}
print(total);
if ((pn == 1 && total < ps) || res['data'].list.isEmpty) {
loadingText.value = '没有更多了';
}

View File

@ -103,9 +103,17 @@ class _FansPageState extends State<FansPage> {
),
);
} else {
return HttpError(
errMsg: data['msg'],
fn: () => _fansController.queryFans('init'),
return CustomScrollView(
physics: const NeverScrollableScrollPhysics(),
slivers: [
HttpError(
errMsg: data['msg'],
fn: () {
_futureBuilderFuture =
_fansController.queryFans('init');
},
)
],
);
}
} else {

View File

@ -97,6 +97,7 @@ class LiveRoomController extends GetxController {
autoplay: true,
);
plPlayerController.isOpenDanmu.value = danmakuSwitch.value;
heartBeat();
}
Future queryLiveInfo() async {
@ -281,8 +282,20 @@ class LiveRoomController extends GetxController {
}
}
// 历史记录
void heartBeat() {
LiveHttp.liveRoomEntry(roomId: roomId);
}
String encodeToBase64(String input) {
List<int> bytes = utf8.encode(input);
String base64Str = base64.encode(bytes);
return base64Str;
}
@override
void onClose() {
heartBeat();
plSocket?.onClose();
super.onClose();
}

View File

@ -1,3 +1,4 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/msg.dart';
import 'package:pilipala/models/msg/system.dart';
@ -5,18 +6,44 @@ import 'package:pilipala/models/msg/system.dart';
class MessageSystemController extends GetxController {
RxList<MessageSystemModel> systemItems = <MessageSystemModel>[].obs;
Future queryMessageSystem({String type = 'init'}) async {
var res = await MsgHttp.messageSystem();
if (res['status']) {
if (type == 'init') {
systemItems.value = res['data'];
} else {
systemItems.addAll(res['data']);
}
Future<void> queryAndProcessMessages({String type = 'init'}) async {
// 并行调用两个接口
var results = await Future.wait([
queryMessageSystem(type: type),
queryMessageSystemAccount(type: type),
]);
// 对返回的数据进行处理
var systemRes = results[0];
var accountRes = results[1];
if (systemRes['status'] || accountRes['status']) {
// 处理返回的数据
List<MessageSystemModel> combinedData = [
...systemRes['data'],
...accountRes['data']
];
combinedData.sort((a, b) => b.cursor!.compareTo(a.cursor!));
systemItems.addAll(combinedData);
systemItems.refresh();
if (systemItems.isNotEmpty) {
systemMarkRead(systemItems.first.cursor!);
}
} else {
SmartDialog.showToast(systemRes['msg'] ?? accountRes['msg']);
}
return systemRes;
}
// 获取系统消息
Future queryMessageSystem({String type = 'init'}) async {
var res = await MsgHttp.messageSystem();
return res;
}
// 获取系统消息 个人
Future queryMessageSystemAccount({String type = 'init'}) async {
var res = await MsgHttp.messageSystemAccount();
return res;
}

View File

@ -20,7 +20,7 @@ class _MessageSystemPageState extends State<MessageSystemPage> {
@override
void initState() {
super.initState();
_futureBuilderFuture = _messageSystemCtr.queryMessageSystem();
_futureBuilderFuture = _messageSystemCtr.queryAndProcessMessages();
}
@override
@ -31,7 +31,7 @@ class _MessageSystemPageState extends State<MessageSystemPage> {
),
body: RefreshIndicator(
onRefresh: () async {
await _messageSystemCtr.queryMessageSystem();
await _messageSystemCtr.queryAndProcessMessages();
},
child: FutureBuilder(
future: _futureBuilderFuture,
@ -42,7 +42,6 @@ class _MessageSystemPageState extends State<MessageSystemPage> {
}
if (snapshot.data['status']) {
final systemItems = _messageSystemCtr.systemItems;
print(systemItems.length);
return Obx(
() => ListView.separated(
controller: scrollController,
@ -115,7 +114,7 @@ class SystemItem extends StatelessWidget {
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
const SizedBox(height: 6),
Text(item.content!['web']),
Text(item.content is String ? item.content : item.content!['web']),
],
),
);

View File

@ -103,7 +103,7 @@ class _StyleSettingState extends State<StyleSetting> {
needReboot: true,
),
const SetSwitchItem(
title: '首页底栏背景渐变',
title: '首页顶部背景渐变',
setKey: SettingBoxKey.enableGradientBg,
defaultVal: true,
needReboot: true,

View File

@ -115,7 +115,7 @@ class VideoDetailController extends GetxController
].obs;
RxDouble sheetHeight = 0.0.obs;
RxString archiveSourceType = 'dash'.obs;
ScrollController? replyScrillController;
ScrollController? replyScrollController;
List<MediaVideoItemModel> mediaList = <MediaVideoItemModel>[];
RxBool isWatchLaterVisible = false.obs;
RxString watchLaterTitle = ''.obs;
@ -574,12 +574,12 @@ class VideoDetailController extends GetxController
}
void onControllerCreated(ScrollController controller) {
replyScrillController = controller;
replyScrollController = controller;
}
void onTapTabbar(int index) {
if (index == 1 && tabCtr.index == 1) {
replyScrillController?.animateTo(0,
if (tabCtr.animation!.isCompleted && index == 1 && tabCtr.index == 1) {
replyScrollController?.animateTo(0,
duration: const Duration(milliseconds: 300), curve: Curves.ease);
}
}

View File

@ -541,7 +541,7 @@ class VideoIntroController extends GetxController {
// ai总结
Future aiConclusion() async {
SmartDialog.showLoading(msg: '正在生ai总结');
SmartDialog.showLoading(msg: '正在生ai总结');
final res = await VideoHttp.aiConclusion(
bvid: bvid,
cid: lastPlayCid.value,
@ -551,7 +551,7 @@ class VideoIntroController extends GetxController {
if (res['status']) {
modelResult = res['data'].modelResult;
} else {
SmartDialog.showToast("当前视频可能暂不支持AI视频总结");
SmartDialog.showToast("当前视频暂不支持AI视频总结");
}
return res;
}

View File

@ -322,7 +322,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
expanded: Text(
widget.videoDetail!.title!,
softWrap: true,
maxLines: 4,
maxLines: 10,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,

View File

@ -49,15 +49,18 @@ class AiDetail extends StatelessWidget {
child: SingleChildScrollView(
child: Column(
children: [
SelectableText(
modelResult!.summary!,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
height: 1.5,
if (modelResult!.resultType != 0 &&
modelResult!.summary != '') ...[
SelectableText(
modelResult!.summary!,
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
height: 1.5,
),
),
),
const SizedBox(height: 20),
const SizedBox(height: 20),
],
ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),

View File

@ -94,6 +94,6 @@ class WebviewController extends GetxController {
},
),
)
..loadRequest(Uri.parse(url));
..loadRequest(Uri.parse(url.startsWith('http') ? url : 'https://$url'));
}
}

View File

@ -217,6 +217,7 @@ class SessionItem extends StatelessWidget {
final String heroTag = Utils.makeHeroTag(sessionItem.accountInfo?.mid ?? 0);
final content = sessionItem.lastMsg.content;
final msgStatus = sessionItem.lastMsg.msgStatus;
final int msgType = sessionItem.lastMsg.msgType;
return ListTile(
onTap: () {
@ -251,13 +252,15 @@ class SessionItem extends StatelessWidget {
subtitle: Text(
msgStatus == 1
? '你撤回了一条消息'
: content != null && content != ''
? (content['text'] ??
content['content'] ??
content['title'] ??
content['reply_content'] ??
'不支持的消息类型')
: '不支持的消息类型',
: msgType == 2
? '[图片]'
: content != null && content != ''
? (content['text'] ??
content['content'] ??
content['title'] ??
content['reply_content'] ??
'不支持的消息类型')
: '不支持的消息类型',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)

View File

@ -22,6 +22,7 @@ class WhisperDetailController extends GetxController {
final TextEditingController replyContentController = TextEditingController();
Box userInfoCache = GStrorage.userInfo;
List emoteList = [];
List<String> picList = [];
@override
void onInit() {
@ -41,6 +42,18 @@ class WhisperDetailController extends GetxController {
var res = await MsgHttp.sessionMsg(talkerId: talkerId);
if (res['status']) {
messageList.value = res['data'].messages;
// 找出图片
try {
for (var item in messageList) {
if (item.msgType == 2) {
picList.add(item.content['url']);
}
}
picList = picList.reversed.toList();
} catch (e) {
print('e: $e');
}
if (messageList.isNotEmpty) {
ackSessionMsg();
if (res['data'].eInfos != null) {

View File

@ -193,27 +193,21 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
? const SizedBox()
: Align(
alignment: Alignment.topCenter,
child: ListView.builder(
child: ListView.separated(
itemCount: messageList.length,
shrinkWrap: true,
reverse: true,
itemBuilder: (_, int i) {
if (i == 0) {
return Column(
children: [
ChatItem(
item: messageList[i],
e_infos: _whisperDetailController
.eInfos),
const SizedBox(height: 20),
],
);
} else {
return ChatItem(
item: messageList[i],
e_infos:
_whisperDetailController.eInfos);
}
return ChatItem(
item: messageList[i],
e_infos: _whisperDetailController.eInfos,
ctr: _whisperDetailController,
);
},
separatorBuilder: (_, int i) {
return i == 0
? const SizedBox(height: 20)
: const SizedBox.shrink();
},
),
),

View File

@ -2,14 +2,18 @@
// ignore_for_file: constant_identifier_names
import 'dart:convert';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart';
import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart';
import 'package:pilipala/utils/route_push.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/utils/storage.dart';
import '../../../http/search.dart';
import '../controller.dart';
enum MsgType {
invalid(value: 0, label: "空空的~"),
@ -42,10 +46,12 @@ enum MsgType {
class ChatItem extends StatelessWidget {
dynamic item;
List? e_infos;
WhisperDetailController ctr;
ChatItem({
super.key,
this.item,
required this.item,
required this.ctr,
this.e_infos,
});
@ -157,10 +163,48 @@ class ChatItem extends StatelessWidget {
case MsgType.text:
return richTextMessage(context);
case MsgType.pic:
return NetworkImgLayer(
width: 220,
height: 220 * content['height'] / content['width'],
src: content['url'],
return InkWell(
onTap: () {
Navigator.of(context).push(
HeroDialogRoute<void>(
builder: (BuildContext context) => InteractiveviewerGallery(
sources: ctr.picList,
initIndex: ctr.picList.indexOf(content['url']),
itemBuilder: (
BuildContext context,
int index,
bool isFocus,
bool enablePageView,
) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (enablePageView) {
Navigator.of(context).pop();
}
},
child: Center(
child: Hero(
tag: ctr.picList[index],
child: CachedNetworkImage(
fadeInDuration: const Duration(milliseconds: 0),
imageUrl: ctr.picList[index],
fit: BoxFit.contain,
),
),
),
);
},
onPageChanged: (int pageIndex) {},
),
),
);
},
child: NetworkImgLayer(
width: 220,
height: 220 * content['height'] / content['width'],
src: content['url'],
),
);
case MsgType.share_v2:
return Column(