feat: custom horizontal member page

Closes #51

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-12-25 21:22:44 +08:00
parent 513a3d2175
commit eee7eda1a2
11 changed files with 676 additions and 121 deletions

View File

@@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:math';
import 'package:PiliPalaX/common/constants.dart'; import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
@@ -42,9 +43,7 @@ class ListSheetContent extends StatefulWidget {
class _ListSheetContentState extends State<ListSheetContent> class _ListSheetContentState extends State<ListSheetContent>
with TickerProviderStateMixin { with TickerProviderStateMixin {
late List<ItemScrollController> itemScrollController = []; late List<ItemScrollController> itemScrollController = [];
late int currentIndex = int? currentIndex;
widget.episodes!.indexWhere((dynamic e) => e.cid == widget.currentCid) ??
0;
late List<bool> reverse; late List<bool> reverse;
int get _index => widget.index ?? 0; int get _index => widget.index ?? 0;
@@ -60,11 +59,12 @@ class _ListSheetContentState extends State<ListSheetContent>
@override @override
void didUpdateWidget(ListSheetContent oldWidget) { void didUpdateWidget(ListSheetContent oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
currentIndex = widget.episodes! currentIndex = _currentIndex;
.indexWhere((dynamic e) => e.cid == widget.currentCid) ??
0;
} }
int get _currentIndex =>
max(0, widget.episodes.indexWhere((e) => e.cid == widget.currentCid));
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -85,9 +85,7 @@ class _ListSheetContentState extends State<ListSheetContent>
reverse = _isList reverse = _isList
? List.generate(widget.season.sections.length, (_) => false) ? List.generate(widget.season.sections.length, (_) => false)
: [false]; : [false];
WidgetsBinding.instance.addPostFrameCallback((_) { currentIndex = _currentIndex;
itemScrollController[_index].jumpTo(index: currentIndex);
});
if (widget.bvid != null && widget.season != null) { if (widget.bvid != null && widget.season != null) {
_favStream ??= StreamController<int>(); _favStream ??= StreamController<int>();
() async { () async {
@@ -98,6 +96,11 @@ class _ListSheetContentState extends State<ListSheetContent>
} }
}(); }();
} }
WidgetsBinding.instance.addPostFrameCallback((_) {
if (currentIndex != null) {
itemScrollController[_index].jumpTo(index: currentIndex!);
}
});
} }
@override @override
@@ -312,10 +315,12 @@ class _ListSheetContentState extends State<ListSheetContent>
_ctr?.animateTo(_index); _ctr?.animateTo(_index);
await Future.delayed(const Duration(milliseconds: 225)); await Future.delayed(const Duration(milliseconds: 225));
} }
itemScrollController[_ctr?.index ?? 0].scrollTo( if (currentIndex != null) {
index: currentIndex, itemScrollController[_ctr?.index ?? 0].scrollTo(
duration: const Duration(milliseconds: 200), index: currentIndex!,
); duration: const Duration(milliseconds: 200),
);
}
}, },
), ),
const Spacer(), const Spacer(),

View File

@@ -17,10 +17,14 @@ class VideoCardHMemberVideo extends StatelessWidget {
required this.videoItem, required this.videoItem,
this.longPress, this.longPress,
this.longPressEnd, this.longPressEnd,
this.onTap,
this.bvid,
}); });
final Item videoItem; final Item videoItem;
final Function()? longPress; final Function()? longPress;
final Function()? longPressEnd; final Function()? longPressEnd;
final VoidCallback? onTap;
final dynamic bvid;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -32,6 +36,10 @@ class VideoCardHMemberVideo extends StatelessWidget {
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
onLongPress: longPress, onLongPress: longPress,
onTap: () async { onTap: () async {
if (onTap != null) {
onTap!();
return;
}
try { try {
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}', Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}',
arguments: {'heroTag': heroTag}); arguments: {'heroTag': heroTag});
@@ -115,10 +123,15 @@ class VideoCardHMemberVideo extends StatelessWidget {
videoItem.title ?? '', videoItem.title ?? '',
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w400, fontWeight: videoItem.bvid == bvid
? FontWeight.bold
: FontWeight.w400,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize, fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42, height: 1.42,
letterSpacing: 0.3, letterSpacing: 0.3,
color: videoItem.bvid == bvid
? Theme.of(context).colorScheme.primary
: null,
), ),
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,

View File

@@ -1,7 +1,4 @@
import 'dart:convert'; import 'package:PiliPalaX/utils/utils.dart';
import 'package:PiliPalaX/http/constants.dart';
import 'package:PiliPalaX/http/init.dart';
import 'package:flutter/material.dart'; 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';
@@ -14,8 +11,6 @@ import 'package:PiliPalaX/models/member/coin.dart';
import 'package:PiliPalaX/models/member/info.dart'; import 'package:PiliPalaX/models/member/info.dart';
import 'package:PiliPalaX/utils/storage.dart'; import 'package:PiliPalaX/utils/storage.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as html_parser;
import '../video/detail/introduction/widgets/group_panel.dart'; import '../video/detail/introduction/widgets/group_panel.dart';
@@ -52,7 +47,7 @@ class MemberController extends GetxController {
// 获取用户信息 // 获取用户信息
Future<Map<String, dynamic>> getInfo() async { Future<Map<String, dynamic>> getInfo() async {
await getWwebid(); wwebid = await Utils.getWwebid(mid);
await getMemberStat(); await getMemberStat();
await getMemberView(); await getMemberView();
var res = await MemberHttp.memberInfo(mid: mid, wwebid: wwebid); var res = await MemberHttp.memberInfo(mid: mid, wwebid: wwebid);
@@ -63,20 +58,6 @@ class MemberController extends GetxController {
return res; return res;
} }
Future getWwebid() async {
try {
dynamic response =
await Request().get('${HttpString.spaceBaseUrl}/$mid/dynamic');
dom.Document document = html_parser.parse(response.data);
dom.Element? scriptElement =
document.querySelector('script#__RENDER_DATA__');
wwebid = jsonDecode(
Uri.decodeComponent(scriptElement?.text ?? ''))['access_id'];
} catch (e) {
debugPrint('failed to get wwebid: $e');
}
}
// 获取用户状态 // 获取用户状态
Future<Map<String, dynamic>> getMemberStat() async { Future<Map<String, dynamic>> getMemberStat() async {
var res = await MemberHttp.memberStat(mid: mid); var res = await MemberHttp.memberStat(mid: mid);

View File

@@ -1,14 +1,9 @@
import 'dart:convert';
import 'package:PiliPalaX/http/constants.dart';
import 'package:PiliPalaX/http/init.dart';
import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/utils/extension.dart'; import 'package:PiliPalaX/utils/extension.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPalaX/http/member.dart'; import 'package:PiliPalaX/http/member.dart';
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as html_parser;
class MemberSearchController extends GetxController class MemberSearchController extends GetxController
with GetSingleTickerProviderStateMixin { with GetSingleTickerProviderStateMixin {
@@ -39,7 +34,9 @@ class MemberSearchController extends GetxController
super.onInit(); super.onInit();
mid = int.parse(Get.parameters['mid']!); mid = int.parse(Get.parameters['mid']!);
uname.value = Get.parameters['uname']!; uname.value = Get.parameters['uname']!;
getWwebid(); Utils.getWwebid(mid).then((res) {
wwebid = res;
});
} }
// 清空搜索 // 清空搜索
@@ -105,20 +102,6 @@ class MemberSearchController extends GetxController
} }
} }
Future getWwebid() async {
try {
dynamic response =
await Request().get('${HttpString.spaceBaseUrl}/$mid/dynamic');
dom.Document document = html_parser.parse(response.data);
dom.Element? scriptElement =
document.querySelector('script#__RENDER_DATA__');
wwebid = jsonDecode(
Uri.decodeComponent(scriptElement?.text ?? ''))['access_id'];
} catch (e) {
debugPrint('failed to get wwebid: $e');
}
}
// 搜索视频 // 搜索视频
Future searchArchives([bool isRefresh = true]) async { Future searchArchives([bool isRefresh = true]) async {
if (isRefresh.not && isEndArchive) return; if (isRefresh.not && isEndArchive) return;

View File

@@ -274,6 +274,12 @@ class _ExtraSettingState extends State<ExtraSetting> {
setKey: SettingBoxKey.horizontalSeasonPanel, setKey: SettingBoxKey.horizontalSeasonPanel,
defaultVal: false, defaultVal: false,
), ),
SetSwitchItem(
title: '横屏播放页在侧栏打开UP主页',
leading: const Icon(Icons.account_circle_outlined),
setKey: SettingBoxKey.horizontalMemberPage,
defaultVal: false,
),
Obx( Obx(
() => ListTile( () => ListTile(
enableFeedback: true, enableFeedback: true,

View File

@@ -10,7 +10,6 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:PiliPalaX/common/constants.dart'; import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/pages/mine/controller.dart'; import 'package:PiliPalaX/pages/mine/controller.dart';
import 'package:PiliPalaX/pages/video/detail/index.dart'; import 'package:PiliPalaX/pages/video/detail/index.dart';
@@ -37,11 +36,13 @@ class VideoIntroPanel extends StatefulWidget {
required this.showAiBottomSheet, required this.showAiBottomSheet,
required this.showIntroDetail, required this.showIntroDetail,
required this.showEpisodes, required this.showEpisodes,
required this.onShowMemberPage,
}); });
final String heroTag; final String heroTag;
final Function showAiBottomSheet; final Function showAiBottomSheet;
final Function showIntroDetail; final Function showIntroDetail;
final Function showEpisodes; final Function showEpisodes;
final ValueChanged onShowMemberPage;
@override @override
State<VideoIntroPanel> createState() => _VideoIntroPanelState(); State<VideoIntroPanel> createState() => _VideoIntroPanelState();
@@ -95,6 +96,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
videoIntroController.videoTags, videoIntroController.videoTags,
), ),
showEpisodes: widget.showEpisodes, showEpisodes: widget.showEpisodes,
onShowMemberPage: widget.onShowMemberPage,
) )
: VideoInfo( : VideoInfo(
//key:herotag //key:herotag
@@ -108,6 +110,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
videoIntroController.videoTags, videoIntroController.videoTags,
), ),
showEpisodes: widget.showEpisodes, showEpisodes: widget.showEpisodes,
onShowMemberPage: widget.onShowMemberPage,
)); ));
} }
} }
@@ -115,19 +118,21 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
class VideoInfo extends StatefulWidget { class VideoInfo extends StatefulWidget {
final bool loadingStatus; final bool loadingStatus;
final VideoDetailData? videoDetail; final VideoDetailData? videoDetail;
final String? heroTag; final String heroTag;
final Function showAiBottomSheet; final Function showAiBottomSheet;
final Function showIntroDetail; final Function showIntroDetail;
final Function showEpisodes; final Function showEpisodes;
final ValueChanged onShowMemberPage;
const VideoInfo({ const VideoInfo({
super.key, super.key,
this.loadingStatus = false, this.loadingStatus = false,
this.videoDetail, this.videoDetail,
this.heroTag, required this.heroTag,
required this.showAiBottomSheet, required this.showAiBottomSheet,
required this.showIntroDetail, required this.showIntroDetail,
required this.showEpisodes, required this.showEpisodes,
required this.onShowMemberPage,
}); });
@override @override
@@ -135,19 +140,18 @@ class VideoInfo extends StatefulWidget {
} }
class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin { class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
// final String heroTag = Get.arguments['heroTag'];
late String heroTag;
late final VideoIntroController videoIntroController; late final VideoIntroController videoIntroController;
late final VideoDetailController videoDetailCtr; late final VideoDetailController videoDetailCtr;
late final Map<dynamic, dynamic> videoItem; late final Map<dynamic, dynamic> videoItem;
final Box<dynamic> setting = GStorage.setting; late final _coinKey = GlobalKey<ActionItemState>();
late final _favKey = GlobalKey<ActionItemState>();
late final bool loadingStatus; // 加载状态
late String memberHeroTag;
late bool enableAi; late bool enableAi;
bool isProcessing = false; bool isProcessing = false;
late final _horizontalMemberPage = GStorage.horizontalMemberPage;
void Function()? handleState(Future Function() action) { void Function()? handleState(Future Function() action) {
return isProcessing return isProcessing
? null ? null
@@ -158,19 +162,14 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
}; };
} }
late final _coinKey = GlobalKey<ActionItemState>();
late final _favKey = GlobalKey<ActionItemState>();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
heroTag = widget.heroTag!; videoIntroController = Get.put(VideoIntroController(), tag: widget.heroTag);
videoIntroController = Get.put(VideoIntroController(), tag: heroTag); videoDetailCtr = Get.find<VideoDetailController>(tag: widget.heroTag);
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
videoItem = videoIntroController.videoItem!; videoItem = videoIntroController.videoItem!;
loadingStatus = widget.loadingStatus; enableAi = GStorage.setting.get(SettingBoxKey.enableAi, defaultValue: true);
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
if (videoIntroController.expandableCtr == null) { if (videoIntroController.expandableCtr == null) {
bool alwaysExapndIntroPanel = GStorage.alwaysExapndIntroPanel; bool alwaysExapndIntroPanel = GStorage.alwaysExapndIntroPanel;
@@ -224,7 +223,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
return; return;
} }
final bool enableDragQuickFav = final bool enableDragQuickFav =
setting.get(SettingBoxKey.enableQuickFav, defaultValue: false); GStorage.setting.get(SettingBoxKey.enableQuickFav, defaultValue: false);
// 快速收藏 & // 快速收藏 &
// 点按 收藏至默认文件夹 // 点按 收藏至默认文件夹
// 长按选择文件夹 // 长按选择文件夹
@@ -241,7 +240,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
// 视频介绍 // 视频介绍
showIntroDetail() { showIntroDetail() {
if (loadingStatus) { if (widget.loadingStatus) {
return; return;
} }
feedBack(); feedBack();
@@ -252,16 +251,26 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
// 用户主页 // 用户主页
onPushMember() { onPushMember() {
feedBack(); feedBack();
int? mid = !loadingStatus int? mid = !widget.loadingStatus
? widget.videoDetail?.owner?.mid ? widget.videoDetail?.owner?.mid
: videoItem['owner']?.mid; : videoItem['owner']?.mid;
if (mid != null) { if (mid != null) {
memberHeroTag = Utils.makeHeroTag(mid); if (context.orientation == Orientation.landscape &&
String face = !loadingStatus _horizontalMemberPage) {
? widget.videoDetail!.owner!.face widget.onShowMemberPage(mid);
: videoItem['owner'].face; } else {
Get.toNamed('/member?mid=$mid', // memberHeroTag = Utils.makeHeroTag(mid);
arguments: {'face': face, 'heroTag': memberHeroTag}); // String face = !loadingStatus
// ? widget.videoDetail!.owner!.face
// : videoItem['owner'].face;
Get.toNamed(
'/member?mid=$mid',
// arguments: {
// 'face': face,
// 'heroTag': memberHeroTag,
// },
);
}
} }
} }
@@ -296,7 +305,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
children: [ children: [
NetworkImgLayer( NetworkImgLayer(
type: 'avatar', type: 'avatar',
src: loadingStatus src: widget.loadingStatus
? videoItem['owner']?.face ?? "" ? videoItem['owner']?.face ?? ""
: widget.videoDetail!.owner!.face, : widget.videoDetail!.owner!.face,
width: 30, width: 30,
@@ -311,7 +320,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
CrossAxisAlignment.start, CrossAxisAlignment.start,
children: [ children: [
Text( Text(
loadingStatus widget.loadingStatus
? videoItem['owner']?.name ?? "" ? videoItem['owner']?.name ?? ""
: widget.videoDetail!.owner!.name, : widget.videoDetail!.owner!.name,
maxLines: 1, maxLines: 1,
@@ -347,13 +356,28 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
width: 80, width: 80,
alignment: Alignment.center, alignment: Alignment.center,
child: GestureDetector( child: GestureDetector(
onTap: () => Get.toNamed( onTap: () {
'/member?mid=${videoItem['staff'][index].mid}', int? ownerMid = !widget.loadingStatus
arguments: { ? widget.videoDetail?.owner?.mid
'face': videoItem['staff'][index].face, : videoItem['owner']?.mid;
heroTag: Utils.makeHeroTag( if (videoItem['staff'][index].mid ==
videoItem['staff'][index].mid), ownerMid &&
}), context.orientation ==
Orientation.landscape &&
_horizontalMemberPage) {
widget.onShowMemberPage(ownerMid);
} else {
Get.toNamed(
'/member?mid=${videoItem['staff'][index].mid}',
// arguments: {
// 'face':
// videoItem['staff'][index].face,
// 'heroTag': Utils.makeHeroTag(
// videoItem['staff'][index].mid),
// },
);
}
},
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -460,7 +484,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
statView( statView(
context: context, context: context,
theme: 'gray', theme: 'gray',
view: !loadingStatus view: !widget.loadingStatus
? widget.videoDetail?.stat?.view ?? '-' ? widget.videoDetail?.stat?.view ?? '-'
: videoItem['stat']?.view ?? '-', : videoItem['stat']?.view ?? '-',
size: 'medium', size: 'medium',
@@ -469,7 +493,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
statDanMu( statDanMu(
context: context, context: context,
theme: 'gray', theme: 'gray',
danmu: !loadingStatus danmu: !widget.loadingStatus
? widget.videoDetail?.stat?.danmu ?? '-' ? widget.videoDetail?.stat?.danmu ?? '-'
: videoItem['stat']?.danmu ?? '-', : videoItem['stat']?.danmu ?? '-',
size: 'medium', size: 'medium',
@@ -477,7 +501,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
const SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
Utils.dateFormat( Utils.dateFormat(
!loadingStatus !widget.loadingStatus
? widget.videoDetail?.pubdate ? widget.videoDetail?.pubdate
: videoItem['pubdate'], : videoItem['pubdate'],
formatType: 'detail'), formatType: 'detail'),
@@ -635,14 +659,14 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
// 点赞收藏转发 布局样式2 // 点赞收藏转发 布局样式2
if (!isHorizontal) actionGrid(context, videoIntroController), if (!isHorizontal) actionGrid(context, videoIntroController),
// 合集 // 合集
if (!loadingStatus && if (!widget.loadingStatus &&
widget.videoDetail?.ugcSeason != null && widget.videoDetail?.ugcSeason != null &&
(context.orientation != Orientation.landscape || (context.orientation != Orientation.landscape ||
(context.orientation == Orientation.landscape && (context.orientation == Orientation.landscape &&
videoDetailCtr.horizontalSeasonPanel.not))) videoDetailCtr.horizontalSeasonPanel.not)))
Obx( Obx(
() => SeasonPanel( () => SeasonPanel(
heroTag: heroTag, heroTag: widget.heroTag,
ugcSeason: widget.videoDetail!.ugcSeason!, ugcSeason: widget.videoDetail!.ugcSeason!,
cid: videoIntroController.lastPlayCid.value != 0 cid: videoIntroController.lastPlayCid.value != 0
? (widget.videoDetail!.pages?.isNotEmpty == true ? (widget.videoDetail!.pages?.isNotEmpty == true
@@ -654,7 +678,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
pages: widget.videoDetail!.pages, pages: widget.videoDetail!.pages,
), ),
), ),
if (!loadingStatus && if (!widget.loadingStatus &&
widget.videoDetail?.pages != null && widget.videoDetail?.pages != null &&
widget.videoDetail!.pages!.length > 1 && widget.videoDetail!.pages!.length > 1 &&
(context.orientation != Orientation.landscape || (context.orientation != Orientation.landscape ||
@@ -662,7 +686,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
videoDetailCtr.horizontalSeasonPanel.not))) ...[ videoDetailCtr.horizontalSeasonPanel.not))) ...[
Obx( Obx(
() => PagesPanel( () => PagesPanel(
heroTag: heroTag, heroTag: widget.heroTag,
pages: widget.videoDetail!.pages!, pages: widget.videoDetail!.pages!,
cid: videoIntroController.lastPlayCid.value, cid: videoIntroController.lastPlayCid.value,
bvid: videoIntroController.bvid, bvid: videoIntroController.bvid,
@@ -722,9 +746,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
onTap: handleState(videoIntroController.actionLikeVideo), onTap: handleState(videoIntroController.actionLikeVideo),
onLongPress: handleState(videoIntroController.actionOneThree), onLongPress: handleState(videoIntroController.actionOneThree),
selectStatus: videoIntroController.hasLike.value, selectStatus: videoIntroController.hasLike.value,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '点赞', semanticsLabel: '点赞',
text: !loadingStatus text: !widget.loadingStatus
? Utils.numFormat(widget.videoDetail!.stat!.like!) ? Utils.numFormat(widget.videoDetail!.stat!.like!)
: '-', : '-',
needAnim: true, needAnim: true,
@@ -748,7 +772,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown), selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
onTap: handleState(videoIntroController.actionDislikeVideo), onTap: handleState(videoIntroController.actionDislikeVideo),
selectStatus: videoIntroController.hasDislike.value, selectStatus: videoIntroController.hasDislike.value,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '点踩', semanticsLabel: '点踩',
text: "点踩"), text: "点踩"),
), ),
@@ -765,9 +789,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
selectIcon: const Icon(FontAwesomeIcons.b), selectIcon: const Icon(FontAwesomeIcons.b),
onTap: handleState(videoIntroController.actionCoinVideo), onTap: handleState(videoIntroController.actionCoinVideo),
selectStatus: videoIntroController.hasCoin.value, selectStatus: videoIntroController.hasCoin.value,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '投币', semanticsLabel: '投币',
text: !loadingStatus text: !widget.loadingStatus
? Utils.numFormat(widget.videoDetail!.stat!.coin!) ? Utils.numFormat(widget.videoDetail!.stat!.coin!)
: '-', : '-',
needAnim: true, needAnim: true,
@@ -781,9 +805,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
onTap: () => showFavBottomSheet(), onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'), onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value, selectStatus: videoIntroController.hasFav.value,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '收藏', semanticsLabel: '收藏',
text: !loadingStatus text: !widget.loadingStatus
? Utils.numFormat(widget.videoDetail!.stat!.favorite!) ? Utils.numFormat(widget.videoDetail!.stat!.favorite!)
: '-', : '-',
needAnim: true, needAnim: true,
@@ -794,18 +818,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
onTap: () => videoDetailCtr.tabCtr onTap: () => videoDetailCtr.tabCtr
.animateTo(videoDetailCtr.tabCtr.index == 1 ? 0 : 1), .animateTo(videoDetailCtr.tabCtr.index == 1 ? 0 : 1),
selectStatus: false, selectStatus: false,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '评论', semanticsLabel: '评论',
text: !loadingStatus text: !widget.loadingStatus
? Utils.numFormat(widget.videoDetail!.stat!.reply!) ? Utils.numFormat(widget.videoDetail!.stat!.reply!)
: '评论'), : '评论'),
ActionItem( ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare), icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(), onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false, selectStatus: false,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
semanticsLabel: '分享', semanticsLabel: '分享',
text: !loadingStatus text: !widget.loadingStatus
? Utils.numFormat(widget.videoDetail!.stat!.share!) ? Utils.numFormat(widget.videoDetail!.stat!.share!)
: '分享'), : '分享'),
], ],
@@ -821,9 +845,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
icon: const Icon(FontAwesomeIcons.thumbsUp), icon: const Icon(FontAwesomeIcons.thumbsUp),
onTap: handleState(videoIntroController.actionLikeVideo), onTap: handleState(videoIntroController.actionLikeVideo),
selectStatus: videoIntroController.hasLike.value, selectStatus: videoIntroController.hasLike.value,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
text: text: !widget.loadingStatus
!loadingStatus ? widget.videoDetail!.stat!.like!.toString() : '-', ? widget.videoDetail!.stat!.like!.toString()
: '-',
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -832,9 +857,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
icon: const Icon(FontAwesomeIcons.b), icon: const Icon(FontAwesomeIcons.b),
onTap: handleState(videoIntroController.actionCoinVideo), onTap: handleState(videoIntroController.actionCoinVideo),
selectStatus: videoIntroController.hasCoin.value, selectStatus: videoIntroController.hasCoin.value,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
text: text: !widget.loadingStatus
!loadingStatus ? widget.videoDetail!.stat!.coin!.toString() : '-', ? widget.videoDetail!.stat!.coin!.toString()
: '-',
), ),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
@@ -844,8 +870,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
onTap: () => showFavBottomSheet(), onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'), onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value, selectStatus: videoIntroController.hasFav.value,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
text: !loadingStatus text: !widget.loadingStatus
? widget.videoDetail!.stat!.favorite!.toString() ? widget.videoDetail!.stat!.favorite!.toString()
: '-', : '-',
), ),
@@ -857,16 +883,17 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
videoDetailCtr.tabCtr.animateTo(1); videoDetailCtr.tabCtr.animateTo(1);
}, },
selectStatus: false, selectStatus: false,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
text: text: !widget.loadingStatus
!loadingStatus ? widget.videoDetail!.stat!.reply!.toString() : '-', ? widget.videoDetail!.stat!.reply!.toString()
: '-',
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
ActionRowItem( ActionRowItem(
icon: const Icon(FontAwesomeIcons.share), icon: const Icon(FontAwesomeIcons.share),
onTap: () => videoIntroController.actionShareVideo(), onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false, selectStatus: false,
loadingStatus: loadingStatus, loadingStatus: widget.loadingStatus,
// text: !loadingStatus // text: !loadingStatus
// ? widget.videoDetail!.stat!.share!.toString() // ? widget.videoDetail!.stat!.share!.toString()
// : '-', // : '-',

View File

@@ -0,0 +1,103 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/http/member.dart';
import 'package:PiliPalaX/models/space_archive/data.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
import 'package:PiliPalaX/pages/member/new/content/member_contribute/member_contribute.dart'
show ContributeType;
import 'package:PiliPalaX/utils/utils.dart';
import 'package:get/get.dart';
class HorizontalMemberPageController extends CommonController {
HorizontalMemberPageController({this.mid});
dynamic mid;
dynamic name;
dynamic wwebid;
Rx<LoadingState> userState = LoadingState.loading().obs;
RxMap userStat = {}.obs;
@override
void onInit() {
super.onInit();
currentPage = 0;
getUserInfo();
}
Future getUserInfo() async {
wwebid ??= await Utils.getWwebid(mid);
dynamic res = await MemberHttp.memberInfo(mid: mid, wwebid: wwebid);
if (res['status']) {
name = res['data'].name;
userState.value = LoadingState.success(res['data']);
getMemberStat();
queryData();
} else {
userState.value = LoadingState.error(res['msg']);
}
}
Future getMemberStat() async {
var res = await MemberHttp.memberStat(mid: mid);
if (res['status']) {
userStat.value = res['data'];
getMemberView();
}
}
Future getMemberView() async {
var res = await MemberHttp.memberView(mid: mid);
if (res['status']) {
userStat.addAll(res['data']);
}
}
@override
bool customHandleResponse(Success response) {
Data data = response.response;
next = data.next;
aid = data.item?.lastOrNull?.param;
isEnd = data.hasNext == false;
if (currentPage == 0) {
count.value = data.count ?? -1;
} else if (loadingState.value is Success) {
data.item?.insertAll(0, (loadingState.value as Success).response);
}
loadingState.value = LoadingState.success(data.item);
return true;
}
String? aid;
RxString order = 'pubdate'.obs;
RxString sort = 'desc'.obs;
RxInt count = (-1).obs;
int? next;
@override
Future<LoadingState> customGetData() => MemberHttp.spaceArchive(
type: ContributeType.video,
mid: mid,
aid: aid,
order: order.value,
sort: sort.value,
pn: null,
next: next,
seasonId: null,
seriesId: null,
);
@override
Future onRefresh() async {
aid = null;
next = null;
currentPage = 0;
isEnd = false;
await queryData();
}
queryBySort() {
order.value = order.value == 'pubdate' ? 'click' : 'pubdate';
loadingState.value = LoadingState.loading();
onRefresh();
}
}

View File

@@ -0,0 +1,401 @@
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/icon_button.dart';
import 'package:PiliPalaX/common/widgets/loading_widget.dart';
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
import 'package:PiliPalaX/common/widgets/video_card_h_member_video.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/member/info.dart';
import 'package:PiliPalaX/pages/video/detail/controller.dart';
import 'package:PiliPalaX/pages/video/detail/introduction/controller.dart';
import 'package:PiliPalaX/pages/video/detail/member/controller.dart';
import 'package:PiliPalaX/pages/video/detail/reply/view.dart'
show MySliverPersistentHeaderDelegate;
import 'package:PiliPalaX/utils/extension.dart';
import 'package:PiliPalaX/utils/grid.dart';
import 'package:PiliPalaX/utils/id_utils.dart';
import 'package:PiliPalaX/utils/storage.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import '../../../../models/space_archive/item.dart';
class HorizontalMemberPage extends StatefulWidget {
const HorizontalMemberPage({
super.key,
required this.mid,
required this.videoDetailController,
required this.videoIntroController,
});
final dynamic mid;
final VideoDetailController videoDetailController;
final VideoIntroController videoIntroController;
@override
State<HorizontalMemberPage> createState() => _HorizontalMemberPageState();
}
class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
late final HorizontalMemberPageController _controller;
int? _ownerMid;
dynamic _bvid;
late final String _tag;
@override
void initState() {
super.initState();
_tag = Utils.makeHeroTag(widget.mid);
_controller = Get.put(
HorizontalMemberPageController(mid: widget.mid),
tag: _tag,
);
_bvid = widget.videoDetailController.bvid;
_ownerMid = GStorage.userInfo.get('userInfoCache')?.mid;
}
@override
void dispose() {
Get.delete<HorizontalMemberPageController>(tag: _tag);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
toolbarHeight: 36,
actions: [
iconButton(
context: context,
onPressed: Get.back,
tooltip: '关闭',
icon: Icons.clear,
size: 28,
),
const SizedBox(width: 16),
],
),
body: Obx(
() => _buildUserPage(_controller.userState.value),
),
);
}
Widget _buildUserPage(LoadingState userState) {
return switch (userState) {
Loading() => loadingWidget,
Success() => Column(
children: [
_buildUserInfo(userState.response),
const SizedBox(height: 5),
Expanded(
child: Obx(() => _buildVideoList(_controller.loadingState.value)),
)
],
),
Error() => errorWidget(
errMsg: userState.errMsg,
callback: () {
_controller.userState.value = LoadingState.loading();
_controller.getUserInfo();
},
),
LoadingState() => throw UnimplementedError(),
};
}
Widget get _buildSliverHeader {
return SliverPersistentHeader(
pinned: false,
floating: true,
delegate: MySliverPersistentHeaderDelegate(
child: Container(
height: 40,
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
color: Theme.of(context).colorScheme.surface,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Obx(
() => Text(
_controller.count.value != -1
? '${_controller.count.value}视频'
: '',
style: const TextStyle(fontSize: 13),
),
),
SizedBox(
height: 35,
child: TextButton.icon(
onPressed: _controller.queryBySort,
icon: Icon(
Icons.sort,
size: 16,
color: Theme.of(context).colorScheme.secondary,
),
label: Obx(
() => Text(
_controller.order.value == 'pubdate' ? '最新发布' : '最多播放',
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.secondary,
),
),
),
),
)
],
),
),
),
);
}
Widget _buildVideoList(LoadingState loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => CustomScrollView(
slivers: [
_buildSliverHeader,
SliverPadding(
// 单列布局 EdgeInsets.zero
padding: EdgeInsets.fromLTRB(
StyleString.safeSpace,
StyleString.safeSpace - 5,
StyleString.safeSpace,
MediaQuery.of(context).padding.bottom + 10,
),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: StyleString.safeSpace,
crossAxisSpacing: StyleString.safeSpace,
maxCrossAxisExtent: Grid.maxRowWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.4,
mainAxisExtent: 0,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
_controller.onLoadMore();
}
return VideoCardHMemberVideo(
videoItem: loadingState.response[index],
bvid: _bvid,
onTap: () {
final Item videoItem = loadingState.response[index];
widget.videoIntroController.changeSeasonOrbangu(
null,
videoItem.bvid,
videoItem.firstCid,
IdUtils.bv2av(videoItem.bvid!),
videoItem.cover,
);
_bvid = videoItem.bvid;
setState(() {});
},
);
},
childCount: loadingState.response.length,
),
),
),
],
),
Error() => errorWidget(
errMsg: loadingState.errMsg,
callback: _controller.onReload,
),
LoadingState() => throw UnimplementedError(),
};
}
Widget _buildUserInfo(MemberInfoModel memberInfoModel) {
return Row(
children: [
const SizedBox(width: 16),
_buildAvatar(memberInfoModel.face),
const SizedBox(width: 10),
Expanded(child: _buildInfo(memberInfoModel)),
const SizedBox(width: 16),
],
);
}
_buildInfo(MemberInfoModel memberInfoModel) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
GestureDetector(
onTap: () {
Utils.copyText(memberInfoModel.name ?? '');
},
child: Text(
memberInfoModel.name ?? '',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: (memberInfoModel.vip?.status ?? -1) > 0 &&
memberInfoModel.vip?.type == 2
? context.vipColor
: null,
),
),
),
const SizedBox(width: 8),
Image.asset(
'assets/images/lv/lv${memberInfoModel.level}.png',
height: 11,
),
],
),
const SizedBox(height: 2),
Obx(
() => Row(
children: List.generate(5, (index) {
if (index % 2 == 0) {
return _buildChildInfo(
title: const ['粉丝', '关注', '获赞'][index ~/ 2],
num: index == 0
? _controller.userStat['follower'] != null
? Utils.numFormat(_controller.userStat['follower'])
: ''
: index == 2
? _controller.userStat['following'] ?? ''
: _controller.userStat['likes'] != null
? Utils.numFormat(_controller.userStat['likes'])
: '',
onTap: () {
if (index == 0) {
Get.toNamed(
'/fan?mid=${widget.mid}&name=${_controller.name}');
} else if (index == 2) {
Get.toNamed(
'/follow?mid=${widget.mid}&name=${_controller.name}');
}
},
);
} else {
return SizedBox(
height: 10,
width: 20,
child: VerticalDivider(
width: 1,
color: Theme.of(context).colorScheme.outline,
),
);
}
}),
),
),
const SizedBox(height: 2),
Row(
children: [
Expanded(
child: FilledButton.tonal(
style: FilledButton.styleFrom(
backgroundColor: memberInfoModel.isFollowed == true
? Theme.of(context).colorScheme.onInverseSurface
: null,
foregroundColor: memberInfoModel.isFollowed == true
? Theme.of(context).colorScheme.outline
: null,
padding: const EdgeInsets.all(0),
visualDensity: const VisualDensity(
vertical: -2,
),
),
onPressed: () {
if (widget.mid == _ownerMid) {
Get.toNamed('/editProfile');
} else {
if (_ownerMid == null) {
SmartDialog.showToast('账号未登录');
return;
}
Utils.actionRelationMod(
context: context,
mid: widget.mid,
isFollow: memberInfoModel.isFollowed ?? false,
callback: (attribute) {
_controller.userState.value = LoadingState.success(
memberInfoModel..isFollowed = attribute != 0);
},
);
}
},
child: Text(
widget.mid == _ownerMid
? '编辑资料'
: memberInfoModel.isFollowed == true
? '已关注'
: '关注',
maxLines: 1,
style: TextStyle(fontSize: 14),
),
),
),
const SizedBox(width: 8),
Expanded(
child: OutlinedButton(
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.all(0),
visualDensity: const VisualDensity(
vertical: -2,
),
),
onPressed: () {
Get.toNamed('/member?mid=${widget.mid}');
},
child: Text(
'查看主页',
maxLines: 1,
style: TextStyle(fontSize: 14),
),
),
),
],
),
],
);
Widget _buildChildInfo({
required String title,
required dynamic num,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Text(
'$num$title',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.outline,
),
),
);
}
_buildAvatar(face) => Hero(
tag: face,
child: GestureDetector(
onTap: () {
widget.videoDetailController.onViewImage();
context.imageView(
imgList: [face],
onDismissed: widget.videoDetailController.onDismissed,
);
},
child: NetworkImgLayer(
src: face,
type: 'avatar',
width: 70,
height: 70,
),
),
);
}

View File

@@ -15,6 +15,7 @@ import 'package:PiliPalaX/pages/video/detail/introduction/widgets/intro_detail.d
as video; as video;
import 'package:PiliPalaX/pages/video/detail/introduction/widgets/page.dart'; import 'package:PiliPalaX/pages/video/detail/introduction/widgets/page.dart';
import 'package:PiliPalaX/pages/video/detail/introduction/widgets/season.dart'; import 'package:PiliPalaX/pages/video/detail/introduction/widgets/season.dart';
import 'package:PiliPalaX/pages/video/detail/member/horizontal_member_page.dart';
import 'package:PiliPalaX/pages/video/detail/reply_reply/view.dart'; import 'package:PiliPalaX/pages/video/detail/reply_reply/view.dart';
import 'package:PiliPalaX/pages/video/detail/widgets/ai_detail.dart'; import 'package:PiliPalaX/pages/video/detail/widgets/ai_detail.dart';
import 'package:PiliPalaX/utils/extension.dart'; import 'package:PiliPalaX/utils/extension.dart';
@@ -1365,6 +1366,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
showAiBottomSheet: showAiBottomSheet, showAiBottomSheet: showAiBottomSheet,
showIntroDetail: showIntroDetail, showIntroDetail: showIntroDetail,
showEpisodes: showEpisodes, showEpisodes: showEpisodes,
onShowMemberPage: onShowMemberPage,
), ),
if (needRelated && videoDetailController.showRelatedVideo) ...[ if (needRelated && videoDetailController.showRelatedVideo) ...[
SliverToBoxAdapter( SliverToBoxAdapter(
@@ -1796,4 +1798,17 @@ class _VideoDetailPageState extends State<VideoDetailPage>
verticalScreenForTwoSeconds(); verticalScreenForTwoSeconds();
} }
} }
void onShowMemberPage(mid) {
videoDetailController.childKey.currentState?.showBottomSheet(
(context) {
return HorizontalMemberPage(
mid: mid,
videoDetailController: videoDetailController,
videoIntroController: videoIntroController,
);
},
enableDrag: true,
);
}
} }

View File

@@ -133,6 +133,9 @@ class GStorage {
static bool get horizontalSeasonPanel => static bool get horizontalSeasonPanel =>
setting.get(SettingBoxKey.horizontalSeasonPanel, defaultValue: false); setting.get(SettingBoxKey.horizontalSeasonPanel, defaultValue: false);
static bool get horizontalMemberPage =>
setting.get(SettingBoxKey.horizontalMemberPage, defaultValue: false);
static List<double> get dynamicDetailRatio => static List<double> get dynamicDetailRatio =>
setting.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]); setting.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]);
@@ -332,6 +335,7 @@ class SettingBoxKey {
alwaysExapndIntroPanel = 'alwaysExapndIntroPanel', alwaysExapndIntroPanel = 'alwaysExapndIntroPanel',
exapndIntroPanelH = 'exapndIntroPanelH', exapndIntroPanelH = 'exapndIntroPanelH',
horizontalSeasonPanel = 'horizontalSeasonPanel', horizontalSeasonPanel = 'horizontalSeasonPanel',
horizontalMemberPage = 'horizontalMemberPage',
// Sponsor Block // Sponsor Block
enableSponsorBlock = 'enableSponsorBlock', enableSponsorBlock = 'enableSponsorBlock',

View File

@@ -27,10 +27,27 @@ import 'package:path_provider/path_provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:webview_cookie_manager/webview_cookie_manager.dart'; import 'package:webview_cookie_manager/webview_cookie_manager.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web; import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
import 'package:html/dom.dart' as dom;
import 'package:html/parser.dart' as html_parser;
class Utils { class Utils {
static final Random random = Random(); static final Random random = Random();
static Future<dynamic> getWwebid(mid) async {
try {
dynamic response =
await Request().get('${HttpString.spaceBaseUrl}/$mid/dynamic');
dom.Document document = html_parser.parse(response.data);
dom.Element? scriptElement =
document.querySelector('script#__RENDER_DATA__');
return jsonDecode(
Uri.decodeComponent(scriptElement?.text ?? ''))['access_id'];
} catch (e) {
debugPrint('failed to get wwebid: $e');
return null;
}
}
static Future afterLoginByApp( static Future afterLoginByApp(
Map<String, dynamic> token_info, cookie_info) async { Map<String, dynamic> token_info, cookie_info) async {
try { try {