mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
feat: custom horizontal member page
Closes #51 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
@@ -42,9 +43,7 @@ class ListSheetContent extends StatefulWidget {
|
||||
class _ListSheetContentState extends State<ListSheetContent>
|
||||
with TickerProviderStateMixin {
|
||||
late List<ItemScrollController> itemScrollController = [];
|
||||
late int currentIndex =
|
||||
widget.episodes!.indexWhere((dynamic e) => e.cid == widget.currentCid) ??
|
||||
0;
|
||||
int? currentIndex;
|
||||
late List<bool> reverse;
|
||||
|
||||
int get _index => widget.index ?? 0;
|
||||
@@ -60,11 +59,12 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
@override
|
||||
void didUpdateWidget(ListSheetContent oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
currentIndex = widget.episodes!
|
||||
.indexWhere((dynamic e) => e.cid == widget.currentCid) ??
|
||||
0;
|
||||
currentIndex = _currentIndex;
|
||||
}
|
||||
|
||||
int get _currentIndex =>
|
||||
max(0, widget.episodes.indexWhere((e) => e.cid == widget.currentCid));
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -85,9 +85,7 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
reverse = _isList
|
||||
? List.generate(widget.season.sections.length, (_) => false)
|
||||
: [false];
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
itemScrollController[_index].jumpTo(index: currentIndex);
|
||||
});
|
||||
currentIndex = _currentIndex;
|
||||
if (widget.bvid != null && widget.season != null) {
|
||||
_favStream ??= StreamController<int>();
|
||||
() async {
|
||||
@@ -98,6 +96,11 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
}
|
||||
}();
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (currentIndex != null) {
|
||||
itemScrollController[_index].jumpTo(index: currentIndex!);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -312,10 +315,12 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
_ctr?.animateTo(_index);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
if (currentIndex != null) {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex!,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
|
||||
@@ -17,10 +17,14 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
required this.videoItem,
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
this.onTap,
|
||||
this.bvid,
|
||||
});
|
||||
final Item videoItem;
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
final VoidCallback? onTap;
|
||||
final dynamic bvid;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -32,6 +36,10 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
onLongPress: longPress,
|
||||
onTap: () async {
|
||||
if (onTap != null) {
|
||||
onTap!();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.firstCid}',
|
||||
arguments: {'heroTag': heroTag});
|
||||
@@ -115,10 +123,15 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
videoItem.title ?? '',
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w400,
|
||||
fontWeight: videoItem.bvid == bvid
|
||||
? FontWeight.bold
|
||||
: FontWeight.w400,
|
||||
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
|
||||
height: 1.42,
|
||||
letterSpacing: 0.3,
|
||||
color: videoItem.bvid == bvid
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPalaX/http/constants.dart';
|
||||
import 'package:PiliPalaX/http/init.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';
|
||||
@@ -14,8 +11,6 @@ import 'package:PiliPalaX/models/member/coin.dart';
|
||||
import 'package:PiliPalaX/models/member/info.dart';
|
||||
import 'package:PiliPalaX/utils/storage.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';
|
||||
|
||||
@@ -52,7 +47,7 @@ class MemberController extends GetxController {
|
||||
|
||||
// 获取用户信息
|
||||
Future<Map<String, dynamic>> getInfo() async {
|
||||
await getWwebid();
|
||||
wwebid = await Utils.getWwebid(mid);
|
||||
await getMemberStat();
|
||||
await getMemberView();
|
||||
var res = await MemberHttp.memberInfo(mid: mid, wwebid: wwebid);
|
||||
@@ -63,20 +58,6 @@ class MemberController extends GetxController {
|
||||
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 {
|
||||
var res = await MemberHttp.memberStat(mid: mid);
|
||||
|
||||
@@ -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/utils/extension.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.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
|
||||
with GetSingleTickerProviderStateMixin {
|
||||
@@ -39,7 +34,9 @@ class MemberSearchController extends GetxController
|
||||
super.onInit();
|
||||
mid = int.parse(Get.parameters['mid']!);
|
||||
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 {
|
||||
if (isRefresh.not && isEndArchive) return;
|
||||
|
||||
@@ -274,6 +274,12 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
||||
setKey: SettingBoxKey.horizontalSeasonPanel,
|
||||
defaultVal: false,
|
||||
),
|
||||
SetSwitchItem(
|
||||
title: '横屏播放页在侧栏打开UP主页',
|
||||
leading: const Icon(Icons.account_circle_outlined),
|
||||
setKey: SettingBoxKey.horizontalMemberPage,
|
||||
defaultVal: false,
|
||||
),
|
||||
Obx(
|
||||
() => ListTile(
|
||||
enableFeedback: true,
|
||||
|
||||
@@ -10,7 +10,6 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/pages/mine/controller.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/index.dart';
|
||||
@@ -37,11 +36,13 @@ class VideoIntroPanel extends StatefulWidget {
|
||||
required this.showAiBottomSheet,
|
||||
required this.showIntroDetail,
|
||||
required this.showEpisodes,
|
||||
required this.onShowMemberPage,
|
||||
});
|
||||
final String heroTag;
|
||||
final Function showAiBottomSheet;
|
||||
final Function showIntroDetail;
|
||||
final Function showEpisodes;
|
||||
final ValueChanged onShowMemberPage;
|
||||
|
||||
@override
|
||||
State<VideoIntroPanel> createState() => _VideoIntroPanelState();
|
||||
@@ -95,6 +96,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
videoIntroController.videoTags,
|
||||
),
|
||||
showEpisodes: widget.showEpisodes,
|
||||
onShowMemberPage: widget.onShowMemberPage,
|
||||
)
|
||||
: VideoInfo(
|
||||
//key:herotag
|
||||
@@ -108,6 +110,7 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
videoIntroController.videoTags,
|
||||
),
|
||||
showEpisodes: widget.showEpisodes,
|
||||
onShowMemberPage: widget.onShowMemberPage,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -115,19 +118,21 @@ class _VideoIntroPanelState extends State<VideoIntroPanel>
|
||||
class VideoInfo extends StatefulWidget {
|
||||
final bool loadingStatus;
|
||||
final VideoDetailData? videoDetail;
|
||||
final String? heroTag;
|
||||
final String heroTag;
|
||||
final Function showAiBottomSheet;
|
||||
final Function showIntroDetail;
|
||||
final Function showEpisodes;
|
||||
final ValueChanged onShowMemberPage;
|
||||
|
||||
const VideoInfo({
|
||||
super.key,
|
||||
this.loadingStatus = false,
|
||||
this.videoDetail,
|
||||
this.heroTag,
|
||||
required this.heroTag,
|
||||
required this.showAiBottomSheet,
|
||||
required this.showIntroDetail,
|
||||
required this.showEpisodes,
|
||||
required this.onShowMemberPage,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -135,19 +140,18 @@ class VideoInfo extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// final String heroTag = Get.arguments['heroTag'];
|
||||
late String heroTag;
|
||||
late final VideoIntroController videoIntroController;
|
||||
late final VideoDetailController videoDetailCtr;
|
||||
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;
|
||||
bool isProcessing = false;
|
||||
|
||||
late final _horizontalMemberPage = GStorage.horizontalMemberPage;
|
||||
|
||||
void Function()? handleState(Future Function() action) {
|
||||
return isProcessing
|
||||
? null
|
||||
@@ -158,19 +162,14 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
};
|
||||
}
|
||||
|
||||
late final _coinKey = GlobalKey<ActionItemState>();
|
||||
late final _favKey = GlobalKey<ActionItemState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
heroTag = widget.heroTag!;
|
||||
videoIntroController = Get.put(VideoIntroController(), tag: heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
|
||||
videoIntroController = Get.put(VideoIntroController(), tag: widget.heroTag);
|
||||
videoDetailCtr = Get.find<VideoDetailController>(tag: widget.heroTag);
|
||||
videoItem = videoIntroController.videoItem!;
|
||||
|
||||
loadingStatus = widget.loadingStatus;
|
||||
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
||||
enableAi = GStorage.setting.get(SettingBoxKey.enableAi, defaultValue: true);
|
||||
|
||||
if (videoIntroController.expandableCtr == null) {
|
||||
bool alwaysExapndIntroPanel = GStorage.alwaysExapndIntroPanel;
|
||||
@@ -224,7 +223,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
return;
|
||||
}
|
||||
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() {
|
||||
if (loadingStatus) {
|
||||
if (widget.loadingStatus) {
|
||||
return;
|
||||
}
|
||||
feedBack();
|
||||
@@ -252,16 +251,26 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// 用户主页
|
||||
onPushMember() {
|
||||
feedBack();
|
||||
int? mid = !loadingStatus
|
||||
int? mid = !widget.loadingStatus
|
||||
? widget.videoDetail?.owner?.mid
|
||||
: videoItem['owner']?.mid;
|
||||
if (mid != null) {
|
||||
memberHeroTag = Utils.makeHeroTag(mid);
|
||||
String face = !loadingStatus
|
||||
? widget.videoDetail!.owner!.face
|
||||
: videoItem['owner'].face;
|
||||
Get.toNamed('/member?mid=$mid',
|
||||
arguments: {'face': face, 'heroTag': memberHeroTag});
|
||||
if (context.orientation == Orientation.landscape &&
|
||||
_horizontalMemberPage) {
|
||||
widget.onShowMemberPage(mid);
|
||||
} else {
|
||||
// memberHeroTag = Utils.makeHeroTag(mid);
|
||||
// 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: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: loadingStatus
|
||||
src: widget.loadingStatus
|
||||
? videoItem['owner']?.face ?? ""
|
||||
: widget.videoDetail!.owner!.face,
|
||||
width: 30,
|
||||
@@ -311,7 +320,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
loadingStatus
|
||||
widget.loadingStatus
|
||||
? videoItem['owner']?.name ?? ""
|
||||
: widget.videoDetail!.owner!.name,
|
||||
maxLines: 1,
|
||||
@@ -347,13 +356,28 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
width: 80,
|
||||
alignment: Alignment.center,
|
||||
child: GestureDetector(
|
||||
onTap: () => Get.toNamed(
|
||||
'/member?mid=${videoItem['staff'][index].mid}',
|
||||
arguments: {
|
||||
'face': videoItem['staff'][index].face,
|
||||
heroTag: Utils.makeHeroTag(
|
||||
videoItem['staff'][index].mid),
|
||||
}),
|
||||
onTap: () {
|
||||
int? ownerMid = !widget.loadingStatus
|
||||
? widget.videoDetail?.owner?.mid
|
||||
: videoItem['owner']?.mid;
|
||||
if (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(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -460,7 +484,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
statView(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
view: !loadingStatus
|
||||
view: !widget.loadingStatus
|
||||
? widget.videoDetail?.stat?.view ?? '-'
|
||||
: videoItem['stat']?.view ?? '-',
|
||||
size: 'medium',
|
||||
@@ -469,7 +493,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
statDanMu(
|
||||
context: context,
|
||||
theme: 'gray',
|
||||
danmu: !loadingStatus
|
||||
danmu: !widget.loadingStatus
|
||||
? widget.videoDetail?.stat?.danmu ?? '-'
|
||||
: videoItem['stat']?.danmu ?? '-',
|
||||
size: 'medium',
|
||||
@@ -477,7 +501,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
Utils.dateFormat(
|
||||
!loadingStatus
|
||||
!widget.loadingStatus
|
||||
? widget.videoDetail?.pubdate
|
||||
: videoItem['pubdate'],
|
||||
formatType: 'detail'),
|
||||
@@ -635,14 +659,14 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// 点赞收藏转发 布局样式2
|
||||
if (!isHorizontal) actionGrid(context, videoIntroController),
|
||||
// 合集
|
||||
if (!loadingStatus &&
|
||||
if (!widget.loadingStatus &&
|
||||
widget.videoDetail?.ugcSeason != null &&
|
||||
(context.orientation != Orientation.landscape ||
|
||||
(context.orientation == Orientation.landscape &&
|
||||
videoDetailCtr.horizontalSeasonPanel.not)))
|
||||
Obx(
|
||||
() => SeasonPanel(
|
||||
heroTag: heroTag,
|
||||
heroTag: widget.heroTag,
|
||||
ugcSeason: widget.videoDetail!.ugcSeason!,
|
||||
cid: videoIntroController.lastPlayCid.value != 0
|
||||
? (widget.videoDetail!.pages?.isNotEmpty == true
|
||||
@@ -654,7 +678,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
pages: widget.videoDetail!.pages,
|
||||
),
|
||||
),
|
||||
if (!loadingStatus &&
|
||||
if (!widget.loadingStatus &&
|
||||
widget.videoDetail?.pages != null &&
|
||||
widget.videoDetail!.pages!.length > 1 &&
|
||||
(context.orientation != Orientation.landscape ||
|
||||
@@ -662,7 +686,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
videoDetailCtr.horizontalSeasonPanel.not))) ...[
|
||||
Obx(
|
||||
() => PagesPanel(
|
||||
heroTag: heroTag,
|
||||
heroTag: widget.heroTag,
|
||||
pages: widget.videoDetail!.pages!,
|
||||
cid: videoIntroController.lastPlayCid.value,
|
||||
bvid: videoIntroController.bvid,
|
||||
@@ -722,9 +746,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||
onLongPress: handleState(videoIntroController.actionOneThree),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: loadingStatus,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
semanticsLabel: '点赞',
|
||||
text: !loadingStatus
|
||||
text: !widget.loadingStatus
|
||||
? Utils.numFormat(widget.videoDetail!.stat!.like!)
|
||||
: '-',
|
||||
needAnim: true,
|
||||
@@ -748,7 +772,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
|
||||
onTap: handleState(videoIntroController.actionDislikeVideo),
|
||||
selectStatus: videoIntroController.hasDislike.value,
|
||||
loadingStatus: loadingStatus,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
semanticsLabel: '点踩',
|
||||
text: "点踩"),
|
||||
),
|
||||
@@ -765,9 +789,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: loadingStatus,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
semanticsLabel: '投币',
|
||||
text: !loadingStatus
|
||||
text: !widget.loadingStatus
|
||||
? Utils.numFormat(widget.videoDetail!.stat!.coin!)
|
||||
: '-',
|
||||
needAnim: true,
|
||||
@@ -781,9 +805,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
onTap: () => showFavBottomSheet(),
|
||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: loadingStatus,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
semanticsLabel: '收藏',
|
||||
text: !loadingStatus
|
||||
text: !widget.loadingStatus
|
||||
? Utils.numFormat(widget.videoDetail!.stat!.favorite!)
|
||||
: '-',
|
||||
needAnim: true,
|
||||
@@ -794,18 +818,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
onTap: () => videoDetailCtr.tabCtr
|
||||
.animateTo(videoDetailCtr.tabCtr.index == 1 ? 0 : 1),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
semanticsLabel: '评论',
|
||||
text: !loadingStatus
|
||||
text: !widget.loadingStatus
|
||||
? Utils.numFormat(widget.videoDetail!.stat!.reply!)
|
||||
: '评论'),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
semanticsLabel: '分享',
|
||||
text: !loadingStatus
|
||||
text: !widget.loadingStatus
|
||||
? Utils.numFormat(widget.videoDetail!.stat!.share!)
|
||||
: '分享'),
|
||||
],
|
||||
@@ -821,9 +845,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||
onTap: handleState(videoIntroController.actionLikeVideo),
|
||||
selectStatus: videoIntroController.hasLike.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.like!.toString() : '-',
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.like!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -832,9 +857,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
icon: const Icon(FontAwesomeIcons.b),
|
||||
onTap: handleState(videoIntroController.actionCoinVideo),
|
||||
selectStatus: videoIntroController.hasCoin.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.coin!.toString() : '-',
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.coin!.toString()
|
||||
: '-',
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@@ -844,8 +870,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
onTap: () => showFavBottomSheet(),
|
||||
onLongPress: () => showFavBottomSheet(type: 'longPress'),
|
||||
selectStatus: videoIntroController.hasFav.value,
|
||||
loadingStatus: loadingStatus,
|
||||
text: !loadingStatus
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.favorite!.toString()
|
||||
: '-',
|
||||
),
|
||||
@@ -857,16 +883,17 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
videoDetailCtr.tabCtr.animateTo(1);
|
||||
},
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
text:
|
||||
!loadingStatus ? widget.videoDetail!.stat!.reply!.toString() : '-',
|
||||
loadingStatus: widget.loadingStatus,
|
||||
text: !widget.loadingStatus
|
||||
? widget.videoDetail!.stat!.reply!.toString()
|
||||
: '-',
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionRowItem(
|
||||
icon: const Icon(FontAwesomeIcons.share),
|
||||
onTap: () => videoIntroController.actionShareVideo(),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
loadingStatus: widget.loadingStatus,
|
||||
// text: !loadingStatus
|
||||
// ? widget.videoDetail!.stat!.share!.toString()
|
||||
// : '-',
|
||||
|
||||
103
lib/pages/video/detail/member/controller.dart
Normal file
103
lib/pages/video/detail/member/controller.dart
Normal 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();
|
||||
}
|
||||
}
|
||||
401
lib/pages/video/detail/member/horizontal_member_page.dart
Normal file
401
lib/pages/video/detail/member/horizontal_member_page.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import 'package:PiliPalaX/pages/video/detail/introduction/widgets/intro_detail.d
|
||||
as video;
|
||||
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/member/horizontal_member_page.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/reply_reply/view.dart';
|
||||
import 'package:PiliPalaX/pages/video/detail/widgets/ai_detail.dart';
|
||||
import 'package:PiliPalaX/utils/extension.dart';
|
||||
@@ -1365,6 +1366,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
showAiBottomSheet: showAiBottomSheet,
|
||||
showIntroDetail: showIntroDetail,
|
||||
showEpisodes: showEpisodes,
|
||||
onShowMemberPage: onShowMemberPage,
|
||||
),
|
||||
if (needRelated && videoDetailController.showRelatedVideo) ...[
|
||||
SliverToBoxAdapter(
|
||||
@@ -1796,4 +1798,17 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
verticalScreenForTwoSeconds();
|
||||
}
|
||||
}
|
||||
|
||||
void onShowMemberPage(mid) {
|
||||
videoDetailController.childKey.currentState?.showBottomSheet(
|
||||
(context) {
|
||||
return HorizontalMemberPage(
|
||||
mid: mid,
|
||||
videoDetailController: videoDetailController,
|
||||
videoIntroController: videoIntroController,
|
||||
);
|
||||
},
|
||||
enableDrag: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,9 @@ class GStorage {
|
||||
static bool get horizontalSeasonPanel =>
|
||||
setting.get(SettingBoxKey.horizontalSeasonPanel, defaultValue: false);
|
||||
|
||||
static bool get horizontalMemberPage =>
|
||||
setting.get(SettingBoxKey.horizontalMemberPage, defaultValue: false);
|
||||
|
||||
static List<double> get dynamicDetailRatio =>
|
||||
setting.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]);
|
||||
|
||||
@@ -332,6 +335,7 @@ class SettingBoxKey {
|
||||
alwaysExapndIntroPanel = 'alwaysExapndIntroPanel',
|
||||
exapndIntroPanelH = 'exapndIntroPanelH',
|
||||
horizontalSeasonPanel = 'horizontalSeasonPanel',
|
||||
horizontalMemberPage = 'horizontalMemberPage',
|
||||
|
||||
// Sponsor Block
|
||||
enableSponsorBlock = 'enableSponsorBlock',
|
||||
|
||||
@@ -27,10 +27,27 @@ import 'package:path_provider/path_provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:webview_cookie_manager/webview_cookie_manager.dart';
|
||||
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 {
|
||||
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(
|
||||
Map<String, dynamic> token_info, cookie_info) async {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user