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: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(),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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()
|
||||||
// : '-',
|
// : '-',
|
||||||
|
|||||||
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;
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user