Merge branch 'main' into design

This commit is contained in:
guozhigq
2024-10-30 09:50:36 +08:00
28 changed files with 554 additions and 320 deletions

View File

@ -430,7 +430,7 @@ class EpisodeGridItem extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: isCurrentIndex color: isCurrentIndex
? colorScheme.primaryContainer.withOpacity(0.6) ? colorScheme.primaryContainer.withOpacity(0.6)
: colorScheme.secondaryContainer.withOpacity(0.4), : colorScheme.onInverseSurface.withOpacity(0.6),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all( border: Border.all(
color: isCurrentIndex color: isCurrentIndex

View File

@ -175,7 +175,7 @@ class Api {
static const String delHistory = '/x/v2/history/delete'; static const String delHistory = '/x/v2/history/delete';
// 搜索历史记录 // 搜索历史记录
static const String searchHistory = '/x/web-goblin/history/search'; static const String searchHistory = '/x/web-interface/history/search';
// 热搜 // 热搜
static const String hotSearchList = static const String hotSearchList =
@ -301,10 +301,6 @@ class Api {
static const String bangumiList = static const String bangumiList =
'/pgc/season/index/result?st=1&order=3&season_version=-1&spoken_language_type=-1&area=-1&is_finish=-1&copyright=-1&season_status=-1&season_month=-1&year=-1&style_id=-1&sort=0&season_type=1&pagesize=20&type=1'; '/pgc/season/index/result?st=1&order=3&season_version=-1&spoken_language_type=-1&area=-1&is_finish=-1&copyright=-1&season_status=-1&season_month=-1&year=-1&style_id=-1&sort=0&season_type=1&pagesize=20&type=1';
// 我的订阅
static const String bangumiFollow =
'/x/space/bangumi/follow/list?type=1&follow_status=0&pn=1&ps=15&ts=1691544359969';
// 黑名单 // 黑名单
static const String blackLst = '/x/relation/blacks'; static const String blackLst = '/x/relation/blacks';
@ -604,4 +600,10 @@ class Api {
/// 图片上传 /// 图片上传
static const String uploadImage = '/x/dynamic/feed/draw/upload_bfs'; static const String uploadImage = '/x/dynamic/feed/draw/upload_bfs';
/// 更新追番状态
static const String updateBangumiStatus = '/pgc/web/follow/status/update';
/// 番剧点赞投币收藏状态
static const String bangumiActionStatus = '/pgc/season/episode/community';
} }

View File

@ -1,5 +1,8 @@
import 'dart:convert';
import '../models/bangumi/list.dart'; import '../models/bangumi/list.dart';
import 'index.dart'; import 'index.dart';
import 'package:html/parser.dart' as html_parser;
import 'package:html/dom.dart' as html_dom;
class BangumiHttp { class BangumiHttp {
static Future bangumiList({int? page}) async { static Future bangumiList({int? page}) async {
@ -18,8 +21,19 @@ class BangumiHttp {
} }
} }
static Future bangumiFollow({int? mid}) async { static Future getRecentBangumi({
var res = await Request().get(Api.bangumiFollow, data: {'vmid': mid}); int? mid,
int type = 1,
int pn = 1,
int ps = 20,
}) async {
var res = await Request().get(Api.getRecentBangumiApi, data: {
'vmid': mid,
'type': type,
'follow_status': 0,
'pn': pn,
'ps': ps,
});
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return { return {
'status': true, 'status': true,
@ -33,4 +47,62 @@ class BangumiHttp {
}; };
} }
} }
// 获取追番状态
static Future bangumiStatus({required int seasonId}) async {
var res = await Request()
.get('https://www.bilibili.com/bangumi/play/ss$seasonId');
html_dom.Document document = html_parser.parse(res.data);
// 查找 id 为 __NEXT_DATA__ 的 script 元素
html_dom.Element? scriptElement =
document.querySelector('script#\\__NEXT_DATA__');
if (scriptElement != null) {
// 提取 script 元素的内容
String scriptContent = scriptElement.text;
final dynamic scriptContentJson = jsonDecode(scriptContent);
Map followState = scriptContentJson['props']['pageProps']['followState'];
return {
'status': true,
'data': {
'isFollowed': followState['isFollowed'],
'followStatus': followState['followStatus']
}
};
} else {
print('Script element with id "__NEXT_DATA__" not found.');
}
}
// 更新追番状态
static Future updateBangumiStatus({
required int seasonId,
required int status,
}) async {
var res = await Request().post(Api.updateBangumiStatus, data: {
'season_id': seasonId,
'status': status,
});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
// 获取番剧点赞投币收藏状态
static Future bangumiActionStatus({required int epId}) async {
var res = await Request().get(
Api.bangumiActionStatus,
data: {'ep_id': epId},
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
} }

View File

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart';
import 'package:pilipala/models/msg/like.dart'; import 'package:pilipala/models/msg/like.dart';
import 'package:pilipala/models/msg/reply.dart'; import 'package:pilipala/models/msg/reply.dart';
import 'package:pilipala/models/msg/system.dart'; import 'package:pilipala/models/msg/system.dart';
@ -63,7 +64,7 @@ class MsgHttp {
.toList(), .toList(),
}; };
} catch (err) { } catch (err) {
print('err🔟: $err'); debugPrint('err: $err');
} }
} else { } else {
return { return {

View File

@ -47,6 +47,7 @@ class BangumiListItemModel {
this.title, this.title,
this.titleIcon, this.titleIcon,
this.progress, this.progress,
this.progressIndex,
}); });
String? badge; String? badge;
@ -66,8 +67,8 @@ class BangumiListItemModel {
String? subTitle; String? subTitle;
String? title; String? title;
String? titleIcon; String? titleIcon;
String? progress; String? progress;
int? progressIndex;
BangumiListItemModel.fromJson(Map<String, dynamic> json) { BangumiListItemModel.fromJson(Map<String, dynamic> json) {
badge = json['badge'] == '' ? null : json['badge']; badge = json['badge'] == '' ? null : json['badge'];
@ -87,7 +88,9 @@ class BangumiListItemModel {
subTitle = json['sub_title']; subTitle = json['sub_title'];
title = json['title']; title = json['title'];
titleIcon = json['title_icon']; titleIcon = json['title_icon'];
progress = json['progress']; progress = json['progress'];
progressIndex = int.parse(
RegExp(r'第(\d+)话').firstMatch(json['progress'] ?? '第1话')?.group(1) ??
'0');
} }
} }

View File

@ -39,7 +39,7 @@ class Total {
List<MessageLikeItem>? items; List<MessageLikeItem>? items;
factory Total.fromJson(Map<String, dynamic> json) => Total( factory Total.fromJson(Map<String, dynamic> json) => Total(
cursor: Cursor.fromJson(json['cursor']), cursor: json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null,
items: json["items"] == null items: json["items"] == null
? [] ? []
: json["items"].map<MessageLikeItem>((e) { : json["items"].map<MessageLikeItem>((e) {

View File

@ -14,7 +14,8 @@ class SessionDataModel {
SessionDataModel.fromJson(Map<String, dynamic> json) { SessionDataModel.fromJson(Map<String, dynamic> json) {
sessionList = json['session_list'] sessionList = json['session_list']
?.map<SessionList>((e) => SessionList.fromJson(e)) ?.map<SessionList>((e) => SessionList.fromJson(e))
.toList(); .toList() ??
[];
hasMore = json['has_more']; hasMore = json['has_more'];
} }
} }

View File

@ -73,7 +73,7 @@ class ReplyCursor {
isEnd = json['is_end']; isEnd = json['is_end'];
mode = json['mode']; mode = json['mode'];
modeText = json['mode_text']; modeText = json['mode_text'];
allCount = json['all_count']; allCount = json['all_count'] ?? 0;
supportMode = json['support_mode'].cast<int>(); supportMode = json['support_mode'].cast<int>();
name = json['name']; name = json['name'];
paginationReply = json['pagination_reply'] != null paginationReply = json['pagination_reply'] != null

View File

@ -11,6 +11,7 @@ class ReplyItemModel {
this.parent, this.parent,
this.dialog, this.dialog,
this.count, this.count,
this.rcount,
this.floor, this.floor,
this.state, this.state,
this.fansgrade, this.fansgrade,
@ -41,6 +42,7 @@ class ReplyItemModel {
int? parent; int? parent;
int? dialog; int? dialog;
int? count; int? count;
int? rcount;
int? floor; int? floor;
int? state; int? state;
int? fansgrade; int? fansgrade;
@ -72,6 +74,7 @@ class ReplyItemModel {
parent = json['parent']; parent = json['parent'];
dialog = json['dialog']; dialog = json['dialog'];
count = json['count']; count = json['count'];
rcount = json['rcount'] ?? 0;
floor = json['floor']; floor = json['floor'];
state = json['state']; state = json['state'];
fansgrade = json['fansgrade']; fansgrade = json['fansgrade'];

View File

@ -9,6 +9,7 @@ class BangumiController extends GetxController {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
RxList<BangumiListItemModel> bangumiList = <BangumiListItemModel>[].obs; RxList<BangumiListItemModel> bangumiList = <BangumiListItemModel>[].obs;
RxList<BangumiListItemModel> bangumiFollowList = <BangumiListItemModel>[].obs; RxList<BangumiListItemModel> bangumiFollowList = <BangumiListItemModel>[].obs;
RxInt total = 0.obs;
int _currentPage = 1; int _currentPage = 1;
bool isLoadingMore = true; bool isLoadingMore = true;
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
@ -54,9 +55,10 @@ class BangumiController extends GetxController {
if (userInfo == null) { if (userInfo == null) {
return; return;
} }
var result = await BangumiHttp.bangumiFollow(mid: userInfo.mid); var result = await BangumiHttp.getRecentBangumi(mid: userInfo.mid);
if (result['status']) { if (result['status']) {
bangumiFollowList.value = result['data'].list; bangumiFollowList.value = result['data'].list;
total.value = result['data'].total;
} else {} } else {}
return result; return result;
} }

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/http/bangumi.dart';
import 'package:pilipala/http/constants.dart'; import 'package:pilipala/http/constants.dart';
import 'package:pilipala/http/search.dart'; import 'package:pilipala/http/search.dart';
import 'package:pilipala/http/video.dart'; import 'package:pilipala/http/video.dart';
@ -52,28 +53,34 @@ class BangumiIntroController extends GetxController {
Rx<FavFolderData> favFolderData = FavFolderData().obs; Rx<FavFolderData> favFolderData = FavFolderData().obs;
List addMediaIdsNew = []; List addMediaIdsNew = [];
List delMediaIdsNew = []; List delMediaIdsNew = [];
// 关注状态 默认未关注 // 追番状态 1想看 2在看 3已看
RxMap followStatus = {}.obs; RxBool isFollowed = false.obs;
RxInt followStatus = 1.obs;
int _tempThemeValue = -1; int _tempThemeValue = -1;
var userInfo; var userInfo;
PersistentBottomSheetController? bottomSheetController; PersistentBottomSheetController? bottomSheetController;
List<Map<String, dynamic>> followStatusList = [
{'title': '标记为 「想看」', 'status': 1},
{'title': '标记为 「在看」', 'status': 2},
{'title': '标记为 「已看」', 'status': 3},
{'title': '取消追番', 'status': -1},
];
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
userLogin = userInfo != null; userLogin = userInfo != null;
if (userLogin && seasonId != null) {
bangumiStatus();
}
} }
// 获取番剧简介&选集 // 获取番剧简介&选集
Future queryBangumiIntro() async { Future queryBangumiIntro() async {
if (userLogin) { if (userLogin) {
// 获取点赞状态 // 获取点赞投币收藏状态
queryHasLikeVideo(); bangumiActionStatus();
// 获取投币状态
queryHasCoinVideo();
// 获取收藏状态
queryHasFavVideo();
} }
var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId); var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId);
if (result['status']) { if (result['status']) {
@ -83,26 +90,15 @@ class BangumiIntroController extends GetxController {
return result; return result;
} }
// 获取点赞状态 // 获取番剧点赞投币收藏状态
Future queryHasLikeVideo() async { Future bangumiActionStatus() async {
var result = await VideoHttp.hasLikeVideo(bvid: bvid); var result = await BangumiHttp.bangumiActionStatus(epId: epId!);
// data num 被点赞标志 0未点赞 1已点赞
hasLike.value = result["data"] == 1 ? true : false;
}
// 获取投币状态
Future queryHasCoinVideo() async {
var result = await VideoHttp.hasCoinVideo(bvid: bvid);
hasCoin.value = result["data"]['multiply'] == 0 ? false : true;
}
// 获取收藏状态
Future queryHasFavVideo() async {
var result = await VideoHttp.hasFavVideo(aid: IdUtils.bv2av(bvid));
if (result['status']) { if (result['status']) {
hasFav.value = result["data"]['favoured']; hasLike.value = result['data']['like'] == 1;
hasCoin.value = result['data']['coin_number'] != 0;
hasFav.value = result['data']['favorite'] == 1;
} else { } else {
hasFav.value = false; SmartDialog.showToast(result['msg']);
} }
} }
@ -110,7 +106,7 @@ class BangumiIntroController extends GetxController {
Future actionLikeVideo() async { Future actionLikeVideo() async {
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value); var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
if (result['status']) { if (result['status']) {
SmartDialog.showToast(!hasLike.value ? '点赞成功 👍' : '取消赞'); SmartDialog.showToast(!hasLike.value ? '点赞成功' : '取消赞');
hasLike.value = !hasLike.value; hasLike.value = !hasLike.value;
bangumiDetail.value.stat!['likes'] = bangumiDetail.value.stat!['likes'] =
bangumiDetail.value.stat!['likes'] + (!hasLike.value ? 1 : -1); bangumiDetail.value.stat!['likes'] + (!hasLike.value ? 1 : -1);
@ -147,7 +143,7 @@ class BangumiIntroController extends GetxController {
var res = await VideoHttp.coinVideo( var res = await VideoHttp.coinVideo(
bvid: bvid, multiply: _tempThemeValue); bvid: bvid, multiply: _tempThemeValue);
if (res['status']) { if (res['status']) {
SmartDialog.showToast('投币成功 👏'); SmartDialog.showToast('投币成功');
hasCoin.value = true; hasCoin.value = true;
bangumiDetail.value.stat!['coins'] = bangumiDetail.value.stat!['coins'] =
bangumiDetail.value.stat!['coins'] + bangumiDetail.value.stat!['coins'] +
@ -185,9 +181,11 @@ class BangumiIntroController extends GetxController {
addMediaIdsNew = []; addMediaIdsNew = [];
delMediaIdsNew = []; delMediaIdsNew = [];
// 重新获取收藏状态 // 重新获取收藏状态
queryHasFavVideo(); bangumiActionStatus();
SmartDialog.showToast('操作成功'); SmartDialog.showToast('操作成功');
Get.back(); Get.back();
} else {
SmartDialog.showToast(result['msg']);
} }
} }
@ -239,15 +237,22 @@ class BangumiIntroController extends GetxController {
// 追番 // 追番
Future bangumiAdd() async { Future bangumiAdd() async {
var result = var result = await VideoHttp.bangumiAdd(
await VideoHttp.bangumiAdd(seasonId: bangumiDetail.value.seasonId); seasonId: seasonId ?? bangumiDetail.value.seasonId);
if (result['status']) {
followStatus.value = 2;
isFollowed.value = true;
}
SmartDialog.showToast(result['msg']); SmartDialog.showToast(result['msg']);
} }
// 取消追番 // 取消追番
Future bangumiDel() async { Future bangumiDel() async {
var result = var result = await VideoHttp.bangumiDel(
await VideoHttp.bangumiDel(seasonId: bangumiDetail.value.seasonId); seasonId: seasonId ?? bangumiDetail.value.seasonId);
if (result['status']) {
isFollowed.value = false;
}
SmartDialog.showToast(result['msg']); SmartDialog.showToast(result['msg']);
} }
@ -315,4 +320,35 @@ class BangumiIntroController extends GetxController {
hiddenEpisodeBottomSheet() { hiddenEpisodeBottomSheet() {
bottomSheetController?.close(); bottomSheetController?.close();
} }
// 获取追番状态
Future bangumiStatus() async {
var result = await BangumiHttp.bangumiStatus(seasonId: seasonId!);
if (result['status']) {
followStatus.value = result['data']['followStatus'];
isFollowed.value = result['data']['isFollowed'];
}
return result;
}
// 更新追番状态
Future updateBangumiStatus(int status) async {
Get.back();
if (status == -1) {
bangumiDel();
} else {
var result = await BangumiHttp.bangumiStatus(seasonId: seasonId!);
if (result['status']) {
followStatus.value = status;
final title = followStatusList.firstWhere(
(e) => e['status'] == status,
orElse: () => {'title': '未知状态'},
)['title'];
SmartDialog.showToast('追番状态$title');
} else {
SmartDialog.showToast(result['msg']);
}
return result;
}
}
} }

View File

@ -239,51 +239,40 @@ class _BangumiInfoState extends State<BangumiInfo> {
Expanded( Expanded(
child: InkWell( child: InkWell(
onTap: () => showIntroDetail(), onTap: () => showIntroDetail(),
borderRadius: BorderRadius.circular(8),
child: SizedBox( child: SizedBox(
height: 115 / 0.75, height: 115 / 0.75,
child: Padding(
padding: const EdgeInsets.fromLTRB(6, 4, 6, 6),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
widget.bangumiDetail!.title!, widget.bangumiDetail!.title!,
style: const TextStyle( style: const TextStyle(
fontSize: 16, fontSize: 18,
fontWeight: FontWeight.w500, fontWeight: FontWeight.bold,
), ),
maxLines: 1, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
const SizedBox(width: 20), const SizedBox(width: 20),
SizedBox( Obx(
width: 34, () => BangumiStatusWidget(
height: 34, ctr: bangumiIntroController,
child: IconButton( isFollowed:
style: ButtonStyle( bangumiIntroController.isFollowed.value,
padding: MaterialStateProperty.all(
EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith(
(Set<MaterialState> states) {
return t.colorScheme.primaryContainer
.withOpacity(0.7);
}),
),
onPressed: () =>
bangumiIntroController.bangumiAdd(),
icon: Icon(
Icons.favorite_border_rounded,
color: t.colorScheme.primary,
size: 22,
),
), ),
), ),
], ],
), ),
const SizedBox(height: 4),
Row( Row(
children: [ children: [
StatView( StatView(
@ -297,42 +286,13 @@ class _BangumiInfoState extends State<BangumiInfo> {
), ),
], ],
), ),
const SizedBox(height: 6), const SizedBox(height: 10),
Row(
children: [
Text(
(widget.bangumiDetail!.areas!.isNotEmpty
? widget.bangumiDetail!.areas!.first['name']
: ''),
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
const SizedBox(width: 6),
Text(
widget.bangumiDetail!.publish!['pub_time_show'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
],
),
Text(
widget.bangumiDetail!.newEp!['desc'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
const Spacer(),
Text( Text(
'简介:${widget.bangumiDetail!.evaluate!}', '简介:${widget.bangumiDetail!.evaluate!}',
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 14,
color: t.colorScheme.outline, color: t.colorScheme.outline,
), ),
), ),
@ -341,6 +301,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
), ),
), ),
), ),
),
], ],
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
@ -426,3 +387,97 @@ class _BangumiInfoState extends State<BangumiInfo> {
}); });
} }
} }
// 追番状态
class BangumiStatusWidget extends StatelessWidget {
final BangumiIntroController ctr;
final bool isFollowed;
const BangumiStatusWidget({
Key? key,
required this.ctr,
required this.isFollowed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
ColorScheme colorScheme = Theme.of(context).colorScheme;
void updateFollowStatus() {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return morePanel(context, ctr);
},
);
}
return Obx(
() => SizedBox(
width: 34,
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith((Set<MaterialState> states) {
return ctr.isFollowed.value
? colorScheme.primaryContainer.withOpacity(0.7)
: colorScheme.outlineVariant.withOpacity(0.7);
}),
),
onPressed:
isFollowed ? () => updateFollowStatus() : () => ctr.bangumiAdd(),
icon: Icon(
ctr.isFollowed.value
? Icons.favorite
: Icons.favorite_border_rounded,
color: ctr.isFollowed.value
? colorScheme.primary
: colorScheme.outline,
size: 22,
),
),
),
);
}
Widget morePanel(BuildContext context, BangumiIntroController ctr) {
return Container(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () => Get.back(),
child: Container(
height: 35,
padding: const EdgeInsets.only(bottom: 2),
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline,
borderRadius: const BorderRadius.all(Radius.circular(3))),
),
),
),
),
...ctr.followStatusList
.map(
(e) => ListTile(
onTap: () => ctr.updateBangumiStatus(e['status']),
selected: ctr.followStatus == e['status'],
title: Text(e['title']),
),
)
.toList(),
const SizedBox(height: 20),
],
),
);
}
}

View File

@ -76,9 +76,14 @@ class _BangumiPageState extends State<BangumiPage>
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Obx(
'最近追番', () => 0 != _bangumidController.total.value
style: Theme.of(context).textTheme.titleMedium, ? Text(
'我的追番(${_bangumidController.total.value})',
style:
Theme.of(context).textTheme.titleMedium,
)
: const SizedBox(),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {

View File

@ -175,11 +175,13 @@ class _BangumiPanelState extends State<BangumiPanel> {
return Container( return Container(
width: 150, width: 150,
margin: const EdgeInsets.only(right: 10), margin: const EdgeInsets.only(right: 10),
child: Material( clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface, color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: BorderRadius.circular(6), borderRadius: BorderRadius.circular(8),
clipBehavior: Clip.hardEdge, ),
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () => changeFucCall(page, i), onTap: () => changeFucCall(page, i),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@ -230,7 +232,6 @@ class _BangumiPanelState extends State<BangumiPanel> {
), ),
), ),
), ),
),
); );
}, },
), ),

View File

@ -25,6 +25,7 @@ class BangumiCardV extends StatelessWidget {
RoutePush.bangumiPush( RoutePush.bangumiPush(
bangumiItem.seasonId, bangumiItem.seasonId,
null, null,
progressIndex: bangumiItem.progressIndex,
heroTag: heroTag, heroTag: heroTag,
); );
}, },

View File

@ -48,8 +48,17 @@ class DynamicDetailController extends GetxController {
} }
Future queryReplyList({reqType = 'init'}) async { Future queryReplyList({reqType = 'init'}) async {
if (isLoadingMore) {
return;
}
isLoadingMore = true;
if (reqType == 'init') { if (reqType == 'init') {
nextOffset = ""; nextOffset = '';
noMore.value = '';
}
if (noMore.value == '没有更多了') {
isLoadingMore = false;
return;
} }
var res = await ReplyHttp.replyList( var res = await ReplyHttp.replyList(
oid: oid!, oid: oid!,

View File

@ -31,7 +31,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
with TickerProviderStateMixin { with TickerProviderStateMixin {
late DynamicDetailController _dynamicDetailController; late DynamicDetailController _dynamicDetailController;
late AnimationController fabAnimationCtr; late AnimationController fabAnimationCtr;
Future? _futureBuilderFuture; late Future _futureBuilderFuture;
late StreamController<bool> titleStreamC = late StreamController<bool> titleStreamC =
StreamController<bool>.broadcast(); // appBar title StreamController<bool>.broadcast(); // appBar title
late ScrollController scrollController; late ScrollController scrollController;
@ -278,8 +278,8 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map; Map? data = snapshot.data;
if (snapshot.data['status']) { if (data != null && snapshot.data['status']) {
RxList<ReplyItemModel> replyList = RxList<ReplyItemModel> replyList =
_dynamicDetailController.replyList; _dynamicDetailController.replyList;
// 请求成功 // 请求成功
@ -345,8 +345,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
} else { } else {
// 请求错误 // 请求错误
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data?['msg'] ?? '请求异常',
fn: () => setState(() {}), fn: () => setState(() {
_futureBuilderFuture =
_dynamicDetailController.queryReplyList();
}),
); );
} }
} else { } else {

View File

@ -395,7 +395,8 @@ class SearchBar extends StatelessWidget {
color: colorScheme.onSecondaryContainer, color: colorScheme.onSecondaryContainer,
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Obx( Expanded(
child: Obx(
() => Text( () => Text(
ctr!.defaultSearch.value, ctr!.defaultSearch.value,
maxLines: 1, maxLines: 1,
@ -403,6 +404,8 @@ class SearchBar extends StatelessWidget {
style: TextStyle(color: colorScheme.outline), style: TextStyle(color: colorScheme.outline),
), ),
), ),
),
const SizedBox(width: 10),
], ],
), ),
), ),

View File

@ -20,7 +20,7 @@ class MemberArticleController extends GetxController {
} }
Future getMemberArticle(type) async { Future getMemberArticle(type) async {
if (isLoading.value) { if (isLoading.value || !hasMore) {
return; return;
} }
isLoading.value = true; isLoading.value = true;

View File

@ -113,12 +113,14 @@ class _MemberArticlePageState extends State<MemberArticlePage> {
'articleType': 'opus', 'articleType': 'opus',
}); });
}, },
leading: NetworkImgLayer( leading: item?.cover != null
? NetworkImgLayer(
width: 50, width: 50,
height: 50, height: 50,
type: 'emote', type: 'emote',
src: item.cover['url'], src: item?.cover?['url'] ?? '',
), )
: const SizedBox(),
title: Text( title: Text(
item.content, item.content,
maxLines: 2, maxLines: 2,

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/no_data.dart';
import 'package:pilipala/models/msg/like.dart'; import 'package:pilipala/models/msg/like.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import '../utils/index.dart'; import '../utils/index.dart';
@ -57,13 +58,13 @@ class _MessageLikePageState extends State<MessageLikePage> {
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) { Map? data = snapshot.data;
return const SizedBox(); if (data != null && data['status']) {
}
if (snapshot.data['status']) {
final likeItems = _messageLikeCtr.likeItems; final likeItems = _messageLikeCtr.likeItems;
return Obx( return Obx(
() => ListView.separated( () => likeItems.isEmpty
? const CustomScrollView(slivers: [NoData()])
: ListView.separated(
controller: scrollController, controller: scrollController,
itemBuilder: (context, index) => LikeItem( itemBuilder: (context, index) => LikeItem(
item: likeItems[index], item: likeItems[index],
@ -84,7 +85,7 @@ class _MessageLikePageState extends State<MessageLikePage> {
} else { } else {
// 请求错误 // 请求错误
return HttpError( return HttpError(
errMsg: snapshot.data['msg'], errMsg: data?['msg'] ?? '请求异常',
fn: () { fn: () {
setState(() { setState(() {
_futureBuilderFuture = _messageLikeCtr.queryMessageLike(); _futureBuilderFuture = _messageLikeCtr.queryMessageLike();

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/no_data.dart';
import 'package:pilipala/models/msg/reply.dart'; import 'package:pilipala/models/msg/reply.dart';
import 'package:pilipala/pages/message/utils/index.dart'; import 'package:pilipala/pages/message/utils/index.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -58,13 +59,13 @@ class _MessageReplyPageState extends State<MessageReplyPage> {
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) { Map? data = snapshot.data;
return const SizedBox(); if (data != null && data['status']) {
}
if (snapshot.data['status']) {
final replyItems = _messageReplyCtr.replyItems; final replyItems = _messageReplyCtr.replyItems;
return Obx( return Obx(
() => ListView.separated( () => replyItems.isEmpty
? const CustomScrollView(slivers: [NoData()])
: ListView.separated(
controller: scrollController, controller: scrollController,
itemBuilder: (context, index) => itemBuilder: (context, index) =>
ReplyItem(item: replyItems[index]), ReplyItem(item: replyItems[index]),
@ -82,7 +83,7 @@ class _MessageReplyPageState extends State<MessageReplyPage> {
} else { } else {
// 请求错误 // 请求错误
return HttpError( return HttpError(
errMsg: snapshot.data['msg'], errMsg: data?['msg'] ?? '请求异常',
fn: () { fn: () {
setState(() { setState(() {
_futureBuilderFuture = _futureBuilderFuture =

View File

@ -75,7 +75,6 @@ class _MinePageState extends State<MinePage>
parent: BouncingScrollPhysics()), parent: BouncingScrollPhysics()),
child: Padding( child: Padding(
padding: const EdgeInsets.only(bottom: 110), padding: const EdgeInsets.only(bottom: 110),
child: Expanded(
child: Column( child: Column(
children: [ children: [
Obx(() => _buildProfileSection(context, ctr.userInfo.value)), Obx(() => _buildProfileSection(context, ctr.userInfo.value)),
@ -132,7 +131,6 @@ class _MinePageState extends State<MinePage>
), ),
), ),
), ),
),
); );
} }

View File

@ -80,16 +80,21 @@ class _SubDetailPageState extends State<SubDetailPage> {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( SizedBox(
width: Get.size.width - 100,
child: Text(
_subDetailController.item.title!, _subDetailController.item.title!,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
),
Text( Text(
'${_subDetailController.item.mediaCount!}条视频', '${_subDetailController.item.mediaCount!}条视频',
style: Theme.of(context).textTheme.labelMedium, style: Theme.of(context).textTheme.labelMedium,
) )
], ],
) ),
], ],
), ),
); );

View File

@ -59,9 +59,7 @@ class ReplyItem extends StatelessWidget {
return; return;
} }
feedBack(); feedBack();
if (replyReply != null) { replyReply?.call(replyItem, null, replyItem!.rcount! > 0);
replyReply!(replyItem, null, replyItem!.replies!.isNotEmpty);
}
}, },
onLongPress: () { onLongPress: () {
if (replySave) { if (replySave) {
@ -267,9 +265,7 @@ class ReplyItem extends StatelessWidget {
// 操作区域 // 操作区域
bottonAction(context, replyItem!.replyControl, replySave), bottonAction(context, replyItem!.replyControl, replySave),
// 一楼的评论 // 一楼的评论
if ((replyItem!.replyControl!.isShow! || if ((replyItem!.rcount! > 0) && showReplyRow!) ...[
replyItem!.replies!.isNotEmpty) &&
showReplyRow!) ...[
Padding( Padding(
padding: const EdgeInsets.only(top: 5, bottom: 12), padding: const EdgeInsets.only(top: 5, bottom: 12),
child: ReplyItemRow( child: ReplyItemRow(
@ -416,8 +412,7 @@ class ReplyItemRow extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final bool isShow = replyControl!.isShow!; final int extraRow = replyItem!.rcount! > 0 ? 1 : 0;
final int extraRow = replyControl != null && isShow ? 1 : 0;
ColorScheme colorScheme = Theme.of(context).colorScheme; ColorScheme colorScheme = Theme.of(context).colorScheme;
TextTheme textTheme = Theme.of(context).textTheme; TextTheme textTheme = Theme.of(context).textTheme;

View File

@ -45,7 +45,9 @@ class WhisperController extends GetxController {
if (isLoading) return; if (isLoading) return;
var res = await MsgHttp.sessionList( var res = await MsgHttp.sessionList(
endTs: type == 'onLoad' ? sessionList.last.sessionTs : null); endTs: type == 'onLoad' ? sessionList.last.sessionTs : null);
if (res['data'].sessionList != null && res['data'].sessionList.isNotEmpty) { if (res['status'] &&
res['data'].sessionList != null &&
res['data'].sessionList.isNotEmpty) {
await queryAccountList(res['data'].sessionList); await queryAccountList(res['data'].sessionList);
// 将 accountList 转换为 Map 结构 // 将 accountList 转换为 Map 结构
Map<int, dynamic> accountMap = {}; Map<int, dynamic> accountMap = {};
@ -67,8 +69,6 @@ class WhisperController extends GetxController {
); );
} }
} }
}
if (res['status'] && res['data'].sessionList != null) {
if (type == 'onLoad') { if (type == 'onLoad') {
sessionList.addAll(res['data'].sessionList); sessionList.addAll(res['data'].sessionList);
} else { } else {
@ -81,10 +81,33 @@ class WhisperController extends GetxController {
Future queryAccountList(sessionList) async { Future queryAccountList(sessionList) async {
List midsList = sessionList.map((e) => e.talkerId!).toList(); List midsList = sessionList.map((e) => e.talkerId!).toList();
var index = midsList.indexOf(0);
AccountListModel? accountInfo;
if (index != -1) {
accountInfo = AccountListModel(
mid: 0,
name: '客服消息',
face:
'https://i0.hdslb.com/bfs/activity-plat/static/20230809/f87fc7ea98282a4dd48ec7743044b0bf/OWdoP9ZXAX.png',
);
}
if (midsList.length == 1 && index != -1) {
accountList.add(accountInfo!);
return;
}
var res = await MsgHttp.accountList(midsList.join(',')); var res = await MsgHttp.accountList(midsList.join(','));
if (res['status']) { if (res['status']) {
accountList.value = res['data']; accountList.value = res['data'];
if (accountInfo != null) {
if (accountList.isNotEmpty) {
accountList.insert(index, accountInfo);
} else {
accountList.add(accountInfo);
} }
}
}
return res; return res;
} }

View File

@ -4,7 +4,9 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/skeleton/skeleton.dart'; import 'package:pilipala/common/skeleton/skeleton.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/no_data.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'controller.dart'; import 'controller.dart';
@ -126,7 +128,7 @@ class _WhisperPageState extends State<WhisperPage> {
RxList sessionList = _whisperController.sessionList; RxList sessionList = _whisperController.sessionList;
return Obx( return Obx(
() => sessionList.isEmpty () => sessionList.isEmpty
? const SizedBox() ? const CustomScrollView(slivers: [NoData()])
: ListView.separated( : ListView.separated(
itemCount: sessionList.length, itemCount: sessionList.length,
shrinkWrap: true, shrinkWrap: true,
@ -150,8 +152,15 @@ class _WhisperPageState extends State<WhisperPage> {
); );
} else { } else {
// 请求错误 // 请求错误
return Center( return HttpError(
child: Text(data?['msg'] ?? '请求异常'), errMsg: data?['msg'] ?? '请求异常',
fn: () {
setState(() {
_futureBuilderFuture =
_whisperController.querySessionList('init');
});
},
isInSliver: false,
); );
} }
} else { } else {
@ -227,8 +236,8 @@ class SessionItem extends StatelessWidget {
'/whisperDetail', '/whisperDetail',
parameters: { parameters: {
'talkerId': sessionItem.talkerId.toString(), 'talkerId': sessionItem.talkerId.toString(),
'name': sessionItem.accountInfo.name, 'name': sessionItem.accountInfo?.name ?? '',
'face': sessionItem.accountInfo.face ?? '', 'face': sessionItem.accountInfo?.face ?? '',
'mid': (sessionItem.accountInfo?.mid ?? 0).toString(), 'mid': (sessionItem.accountInfo?.mid ?? 0).toString(),
'heroTag': heroTag, 'heroTag': heroTag,
}, },
@ -244,11 +253,11 @@ class SessionItem extends StatelessWidget {
width: 45, width: 45,
height: 45, height: 45,
type: 'avatar', type: 'avatar',
src: sessionItem.accountInfo.face ?? '', src: sessionItem.accountInfo?.face ?? '',
), ),
), ),
), ),
title: Text(sessionItem.accountInfo.name), title: Text(sessionItem.accountInfo?.name ?? ''),
subtitle: Text( subtitle: Text(
msgStatus == 1 msgStatus == 1
? '你撤回了一条消息' ? '你撤回了一条消息'

View File

@ -8,7 +8,7 @@ import 'package:pilipala/utils/utils.dart';
class RoutePush { class RoutePush {
// 番剧跳转 // 番剧跳转
static Future<void> bangumiPush(int? seasonId, int? epId, static Future<void> bangumiPush(int? seasonId, int? epId,
{String? heroTag}) async { {String? heroTag, int? progressIndex}) async {
SmartDialog.showLoading<dynamic>(msg: '获取中...'); SmartDialog.showLoading<dynamic>(msg: '获取中...');
try { try {
var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId); var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId);
@ -19,7 +19,10 @@ class RoutePush {
return; return;
} }
final BangumiInfoModel bangumiDetail = result['data']; final BangumiInfoModel bangumiDetail = result['data'];
final EpisodeItem episode = bangumiDetail.episodes!.first; EpisodeItem episode = bangumiDetail.episodes!.first;
if (progressIndex != null && progressIndex >= 1) {
episode = bangumiDetail.episodes![progressIndex - 1];
}
final int epId = episode.id!; final int epId = episode.id!;
final int cid = episode.cid!; final int cid = episode.cid!;
final String bvid = episode.bvid!; final String bvid = episode.bvid!;
@ -31,7 +34,7 @@ class RoutePush {
}; };
arguments['heroTag'] = heroTag ?? Utils.makeHeroTag(cid); arguments['heroTag'] = heroTag ?? Utils.makeHeroTag(cid);
Get.toNamed( Get.toNamed(
'/video?bvid=$bvid&cid=$cid&epId=$epId', '/video?bvid=$bvid&cid=$cid&epId=$epId&seasonId=$seasonId',
arguments: arguments, arguments: arguments,
); );
} else { } else {