Merge branch 'main' into design
This commit is contained in:
@ -1,6 +1,4 @@
|
|||||||
PODS:
|
PODS:
|
||||||
- auto_orientation (0.0.1):
|
|
||||||
- Flutter
|
|
||||||
- connectivity_plus (0.0.1):
|
- connectivity_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
@ -43,7 +41,6 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
|
|
||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
@ -68,8 +65,6 @@ SPEC REPOS:
|
|||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
auto_orientation:
|
|
||||||
:path: ".symlinks/plugins/auto_orientation/ios"
|
|
||||||
connectivity_plus:
|
connectivity_plus:
|
||||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
@ -106,7 +101,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
|
||||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||||
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
|
|||||||
@ -95,7 +95,7 @@ class BangumiInfoModel {
|
|||||||
jpTitle = json['jp_title'];
|
jpTitle = json['jp_title'];
|
||||||
link = json['link'];
|
link = json['link'];
|
||||||
mediaId = json['media_id'];
|
mediaId = json['media_id'];
|
||||||
newEp = json['newEp'];
|
newEp = json['new_ep'];
|
||||||
playStrategy = json['play_strategy'];
|
playStrategy = json['play_strategy'];
|
||||||
positive = json['positive'];
|
positive = json['positive'];
|
||||||
publish = json['publish'];
|
publish = json['publish'];
|
||||||
@ -184,7 +184,7 @@ class EpisodeItem {
|
|||||||
|
|
||||||
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
||||||
aid = json['aid'];
|
aid = json['aid'];
|
||||||
badge = json['badge'];
|
badge = json['badge'] != '' ? json['badge'] : null;
|
||||||
badgeInfo = json['badge_info'];
|
badgeInfo = json['badge_info'];
|
||||||
badgeType = json['badge_type'];
|
badgeType = json['badge_type'];
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
|
|||||||
@ -277,10 +277,12 @@ class SearchMBangumiModel {
|
|||||||
SearchMBangumiModel({this.list});
|
SearchMBangumiModel({this.list});
|
||||||
List<SearchMBangumiItemModel>? list;
|
List<SearchMBangumiItemModel>? list;
|
||||||
SearchMBangumiModel.fromJson(Map<String, dynamic> json) {
|
SearchMBangumiModel.fromJson(Map<String, dynamic> json) {
|
||||||
list = json['result']
|
list = json['result'] != null
|
||||||
.map<SearchMBangumiItemModel>(
|
? json['result']
|
||||||
(e) => SearchMBangumiItemModel.fromJson(e))
|
.map<SearchMBangumiItemModel>(
|
||||||
.toList();
|
(e) => SearchMBangumiItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
: [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ class VideoDetailData {
|
|||||||
bool? isChargeableSeason;
|
bool? isChargeableSeason;
|
||||||
bool? isStory;
|
bool? isStory;
|
||||||
bool? noCache;
|
bool? noCache;
|
||||||
List<Page>? pages;
|
List<Part>? pages;
|
||||||
Subtitle? subtitle;
|
Subtitle? subtitle;
|
||||||
// Label? label;
|
// Label? label;
|
||||||
UgcSeason? ugcSeason;
|
UgcSeason? ugcSeason;
|
||||||
@ -66,6 +66,7 @@ class VideoDetailData {
|
|||||||
HonorReply? honorReply;
|
HonorReply? honorReply;
|
||||||
String? likeIcon;
|
String? likeIcon;
|
||||||
bool? needJumpBv;
|
bool? needJumpBv;
|
||||||
|
String? epId;
|
||||||
|
|
||||||
VideoDetailData({
|
VideoDetailData({
|
||||||
this.bvid,
|
this.bvid,
|
||||||
@ -101,6 +102,7 @@ class VideoDetailData {
|
|||||||
this.honorReply,
|
this.honorReply,
|
||||||
this.likeIcon,
|
this.likeIcon,
|
||||||
this.needJumpBv,
|
this.needJumpBv,
|
||||||
|
this.epId,
|
||||||
});
|
});
|
||||||
|
|
||||||
VideoDetailData.fromJson(Map<String, dynamic> json) {
|
VideoDetailData.fromJson(Map<String, dynamic> json) {
|
||||||
@ -136,7 +138,7 @@ class VideoDetailData {
|
|||||||
noCache = json["no_cache"];
|
noCache = json["no_cache"];
|
||||||
pages = json["pages"] == null
|
pages = json["pages"] == null
|
||||||
? []
|
? []
|
||||||
: List<Page>.from(json["pages"]!.map((e) => Page.fromJson(e)));
|
: List<Part>.from(json["pages"]!.map((e) => Part.fromJson(e)));
|
||||||
subtitle =
|
subtitle =
|
||||||
json["subtitle"] == null ? null : Subtitle.fromJson(json["subtitle"]);
|
json["subtitle"] == null ? null : Subtitle.fromJson(json["subtitle"]);
|
||||||
ugcSeason = json["ugc_season"] != null
|
ugcSeason = json["ugc_season"] != null
|
||||||
@ -150,6 +152,15 @@ class VideoDetailData {
|
|||||||
: HonorReply.fromJson(json["honor_reply"]);
|
: HonorReply.fromJson(json["honor_reply"]);
|
||||||
likeIcon = json["like_icon"];
|
likeIcon = json["like_icon"];
|
||||||
needJumpBv = json["need_jump_bv"];
|
needJumpBv = json["need_jump_bv"];
|
||||||
|
if (json['redirect_url'] != null) {
|
||||||
|
RegExp regex = RegExp(r'\d+');
|
||||||
|
Iterable<Match> matches = regex.allMatches(json['redirect_url']);
|
||||||
|
List<String> numbers = [];
|
||||||
|
for (Match match in matches) {
|
||||||
|
numbers.add(match.group(0)!);
|
||||||
|
}
|
||||||
|
epId = numbers[0];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
@ -352,7 +363,7 @@ class Owner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Page {
|
class Part {
|
||||||
int? cid;
|
int? cid;
|
||||||
int? page;
|
int? page;
|
||||||
String? from;
|
String? from;
|
||||||
@ -363,7 +374,7 @@ class Page {
|
|||||||
Dimension? dimension;
|
Dimension? dimension;
|
||||||
String? firstFrame;
|
String? firstFrame;
|
||||||
|
|
||||||
Page({
|
Part({
|
||||||
this.cid,
|
this.cid,
|
||||||
this.page,
|
this.page,
|
||||||
this.from,
|
this.from,
|
||||||
@ -375,11 +386,11 @@ class Page {
|
|||||||
this.firstFrame,
|
this.firstFrame,
|
||||||
});
|
});
|
||||||
|
|
||||||
fromRawJson(String str) => Page.fromJson(json.decode(str));
|
fromRawJson(String str) => Part.fromJson(json.decode(str));
|
||||||
|
|
||||||
String toRawJson() => json.encode(toJson());
|
String toRawJson() => json.encode(toJson());
|
||||||
|
|
||||||
Page.fromJson(Map<String, dynamic> json) {
|
Part.fromJson(Map<String, dynamic> json) {
|
||||||
cid = json["cid"];
|
cid = json["cid"];
|
||||||
page = json["page"];
|
page = json["page"];
|
||||||
from = json["from"];
|
from = json["from"];
|
||||||
@ -620,7 +631,7 @@ class EpisodeItem {
|
|||||||
int? cid;
|
int? cid;
|
||||||
String? title;
|
String? title;
|
||||||
int? attribute;
|
int? attribute;
|
||||||
Page? page;
|
Part? page;
|
||||||
String? bvid;
|
String? bvid;
|
||||||
|
|
||||||
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
EpisodeItem.fromJson(Map<String, dynamic> json) {
|
||||||
@ -631,7 +642,7 @@ class EpisodeItem {
|
|||||||
cid = json['cid'];
|
cid = json['cid'];
|
||||||
title = json['title'];
|
title = json['title'];
|
||||||
attribute = json['attribute'];
|
attribute = json['attribute'];
|
||||||
page = Page.fromJson(json['page']);
|
page = Part.fromJson(json['page']);
|
||||||
bvid = json['bvid'];
|
bvid = json['bvid'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
348
lib/pages/bangumi/introduction/controller.dart
Normal file
348
lib/pages/bangumi/introduction/controller.dart
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/http/constants.dart';
|
||||||
|
import 'package:pilipala/http/search.dart';
|
||||||
|
import 'package:pilipala/http/user.dart';
|
||||||
|
import 'package:pilipala/http/video.dart';
|
||||||
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||||
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
|
class BangumiIntroController extends GetxController {
|
||||||
|
// 视频bvid
|
||||||
|
String bvid = Get.parameters['bvid']!;
|
||||||
|
var seasonId = Get.parameters['seasonId'] != null
|
||||||
|
? int.parse(Get.parameters['seasonId']!)
|
||||||
|
: null;
|
||||||
|
var epId = Get.parameters['epId'] != null
|
||||||
|
? int.parse(Get.parameters['epId']!)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
// 是否预渲染 骨架屏
|
||||||
|
bool preRender = false;
|
||||||
|
|
||||||
|
// 视频详情 上个页面传入
|
||||||
|
Map? videoItem = {};
|
||||||
|
BangumiInfoModel? bangumiItem;
|
||||||
|
|
||||||
|
// 请求状态
|
||||||
|
RxBool isLoading = false.obs;
|
||||||
|
|
||||||
|
// 视频详情 请求返回
|
||||||
|
Rx<VideoDetailData> videoDetail = VideoDetailData().obs;
|
||||||
|
Rx<BangumiInfoModel> bangumiDetail = BangumiInfoModel().obs;
|
||||||
|
|
||||||
|
// 请求返回的信息
|
||||||
|
String responseMsg = '请求异常';
|
||||||
|
|
||||||
|
// up主粉丝数
|
||||||
|
Map userStat = {'follower': '-'};
|
||||||
|
|
||||||
|
// 是否点赞
|
||||||
|
RxBool hasLike = false.obs;
|
||||||
|
// 是否投币
|
||||||
|
RxBool hasCoin = false.obs;
|
||||||
|
// 是否收藏
|
||||||
|
RxBool hasFav = false.obs;
|
||||||
|
Box user = GStrorage.user;
|
||||||
|
bool userLogin = false;
|
||||||
|
Rx<FavFolderData> favFolderData = FavFolderData().obs;
|
||||||
|
List addMediaIdsNew = [];
|
||||||
|
List delMediaIdsNew = [];
|
||||||
|
// 关注状态 默认未关注
|
||||||
|
RxMap followStatus = {}.obs;
|
||||||
|
int _tempThemeValue = -1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
if (Get.arguments.isNotEmpty) {
|
||||||
|
if (Get.arguments.containsKey('bangumiItem')) {
|
||||||
|
preRender = true;
|
||||||
|
bangumiItem = Get.arguments['bangumiItem'];
|
||||||
|
// bangumiItem!['pic'] = args.pic;
|
||||||
|
// if (args.title is String) {
|
||||||
|
// videoItem!['title'] = args.title;
|
||||||
|
// } else {
|
||||||
|
// String str = '';
|
||||||
|
// for (Map map in args.title) {
|
||||||
|
// str += map['text'];
|
||||||
|
// }
|
||||||
|
// videoItem!['title'] = str;
|
||||||
|
// }
|
||||||
|
// if (args.stat != null) {
|
||||||
|
// videoItem!['stat'] = args.stat;
|
||||||
|
// }
|
||||||
|
// videoItem!['pubdate'] = args.pubdate;
|
||||||
|
// videoItem!['owner'] = args.owner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userLogin = user.get(UserBoxKey.userLogin) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取番剧简介&选集
|
||||||
|
Future queryBangumiIntro() async {
|
||||||
|
var result = await SearchHttp.bangumiInfo(seasonId: seasonId, epId: epId);
|
||||||
|
if (result['status']) {
|
||||||
|
bangumiDetail.value = result['data'];
|
||||||
|
}
|
||||||
|
if (userLogin) {
|
||||||
|
// 获取点赞状态
|
||||||
|
// queryHasLikeVideo();
|
||||||
|
// 获取投币状态
|
||||||
|
// queryHasCoinVideo();
|
||||||
|
// 获取收藏状态
|
||||||
|
// queryHasFavVideo();
|
||||||
|
//
|
||||||
|
// queryFollowStatus();
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取up主粉丝数
|
||||||
|
Future queryUserStat() async {
|
||||||
|
var result = await UserHttp.userStat(mid: videoDetail.value.owner!.mid!);
|
||||||
|
if (result['status']) {
|
||||||
|
userStat = result['data'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取点赞状态
|
||||||
|
Future queryHasLikeVideo() async {
|
||||||
|
var result = await VideoHttp.hasLikeVideo(bvid: bvid);
|
||||||
|
// data num 被点赞标志 0:未点赞 1:已点赞
|
||||||
|
hasLike.value = result["data"] == 1 ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取投币状态
|
||||||
|
Future queryHasCoinVideo() async {
|
||||||
|
var result = await VideoHttp.hasCoinVideo(bvid: bvid);
|
||||||
|
hasCoin.value = result["data"]['multiply'] == 0 ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取收藏状态
|
||||||
|
Future queryHasFavVideo() async {
|
||||||
|
var result = await VideoHttp.hasFavVideo(aid: IdUtils.bv2av(bvid));
|
||||||
|
if (result['status']) {
|
||||||
|
hasFav.value = result["data"]['favoured'];
|
||||||
|
} else {
|
||||||
|
hasFav.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一键三连
|
||||||
|
Future actionOneThree() async {
|
||||||
|
if (user.get(UserBoxKey.userMid) == null) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasLike.value && hasCoin.value && hasFav.value) {
|
||||||
|
// 已点赞、投币、收藏
|
||||||
|
SmartDialog.showToast('🙏 UP已经收到了~');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SmartDialog.show(
|
||||||
|
useSystem: true,
|
||||||
|
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('一键三连 给UP送温暖'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => SmartDialog.dismiss(),
|
||||||
|
child: const Text('点错了')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
var result = await VideoHttp.oneThree(bvid: bvid);
|
||||||
|
if (result['status']) {
|
||||||
|
hasLike.value = result["data"]["like"];
|
||||||
|
hasCoin.value = result["data"]["coin"];
|
||||||
|
hasFav.value = result["data"]["fav"];
|
||||||
|
SmartDialog.showToast('三连成功 🎉');
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
}
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (取消)点赞
|
||||||
|
Future actionLikeVideo() async {
|
||||||
|
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
|
||||||
|
if (result['status']) {
|
||||||
|
// hasLike.value = result["data"] == 1 ? true : false;
|
||||||
|
if (!hasLike.value) {
|
||||||
|
SmartDialog.showToast('点赞成功 👍');
|
||||||
|
hasLike.value = true;
|
||||||
|
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
|
||||||
|
} else if (hasLike.value) {
|
||||||
|
SmartDialog.showToast('取消赞');
|
||||||
|
hasLike.value = false;
|
||||||
|
videoDetail.value.stat!.like = videoDetail.value.stat!.like! - 1;
|
||||||
|
}
|
||||||
|
hasLike.refresh();
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(result['msg']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 投币
|
||||||
|
Future actionCoinVideo() async {
|
||||||
|
if (user.get(UserBoxKey.userMid) == null) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
showDialog(
|
||||||
|
context: Get.context!,
|
||||||
|
builder: (context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text('选择投币个数'),
|
||||||
|
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
|
||||||
|
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
||||||
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
RadioListTile(
|
||||||
|
value: 1,
|
||||||
|
title: const Text('1枚'),
|
||||||
|
groupValue: _tempThemeValue,
|
||||||
|
onChanged: (value) {
|
||||||
|
_tempThemeValue = value!;
|
||||||
|
Get.appUpdate();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
value: 2,
|
||||||
|
title: const Text('2枚'),
|
||||||
|
groupValue: _tempThemeValue,
|
||||||
|
onChanged: (value) {
|
||||||
|
_tempThemeValue = value!;
|
||||||
|
Get.appUpdate();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
actions: [
|
||||||
|
TextButton(onPressed: () => Get.back(), child: const Text('取消')),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
var res = await VideoHttp.coinVideo(
|
||||||
|
bvid: bvid, multiply: _tempThemeValue);
|
||||||
|
if (res['status']) {
|
||||||
|
SmartDialog.showToast('投币成功 👏');
|
||||||
|
hasCoin.value = true;
|
||||||
|
videoDetail.value.stat!.coin =
|
||||||
|
videoDetail.value.stat!.coin! + _tempThemeValue;
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
}
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
child: const Text('确定'))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// (取消)收藏
|
||||||
|
Future actionFavVideo() async {
|
||||||
|
try {
|
||||||
|
for (var i in favFolderData.value.list!.toList()) {
|
||||||
|
if (i.favState == 1) {
|
||||||
|
addMediaIdsNew.add(i.id);
|
||||||
|
} else {
|
||||||
|
delMediaIdsNew.add(i.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// ignore: avoid_print
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
var result = await VideoHttp.favVideo(
|
||||||
|
aid: IdUtils.bv2av(bvid),
|
||||||
|
addIds: addMediaIdsNew.join(','),
|
||||||
|
delIds: delMediaIdsNew.join(','));
|
||||||
|
if (result['status']) {
|
||||||
|
if (result['data']['prompt']) {
|
||||||
|
addMediaIdsNew = [];
|
||||||
|
delMediaIdsNew = [];
|
||||||
|
Get.back();
|
||||||
|
// 重新获取收藏状态
|
||||||
|
queryHasFavVideo();
|
||||||
|
SmartDialog.showToast('✅ 操作成功');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分享视频
|
||||||
|
Future actionShareVideo() async {
|
||||||
|
var result = await Share.share('${HttpString.baseUrl}/video/$bvid')
|
||||||
|
.whenComplete(() {});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future queryVideoInFolder() async {
|
||||||
|
var result = await VideoHttp.videoInFolder(
|
||||||
|
mid: user.get(UserBoxKey.userMid), rid: IdUtils.bv2av(bvid));
|
||||||
|
if (result['status']) {
|
||||||
|
favFolderData.value = result['data'];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择文件夹
|
||||||
|
onChoose(bool checkValue, int index) {
|
||||||
|
feedBack();
|
||||||
|
List<FavFolderItemData> datalist = favFolderData.value.list!;
|
||||||
|
for (var i = 0; i < datalist.length; i++) {
|
||||||
|
if (i == index) {
|
||||||
|
datalist[i].favState = checkValue == true ? 1 : 0;
|
||||||
|
datalist[i].mediaCount = checkValue == true
|
||||||
|
? datalist[i].mediaCount! + 1
|
||||||
|
: datalist[i].mediaCount! - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
favFolderData.value.list = datalist;
|
||||||
|
favFolderData.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询关注状态
|
||||||
|
Future queryFollowStatus() async {
|
||||||
|
var result = await VideoHttp.hasFollow(mid: videoDetail.value.owner!.mid!);
|
||||||
|
if (result['status']) {
|
||||||
|
followStatus.value = result['data'];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改分P或番剧分集
|
||||||
|
Future changeSeasonOrbangu(bvid, cid, aid) async {
|
||||||
|
// 重新获取视频资源
|
||||||
|
VideoDetailController videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
|
videoDetailCtr.bvid = bvid;
|
||||||
|
videoDetailCtr.cid = cid;
|
||||||
|
videoDetailCtr.queryVideoUrl();
|
||||||
|
// 重新请求评论
|
||||||
|
VideoReplyController videoReplyCtr =
|
||||||
|
Get.find<VideoReplyController>(tag: Get.arguments['heroTag']);
|
||||||
|
videoReplyCtr.aid = aid;
|
||||||
|
videoReplyCtr.queryReplyList(type: 'init');
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/bangumi/introduction/index.dart
Normal file
4
lib/pages/bangumi/introduction/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library bangumi_intro;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
465
lib/pages/bangumi/introduction/view.dart
Normal file
465
lib/pages/bangumi/introduction/view.dart
Normal file
@ -0,0 +1,465 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
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:hive/hive.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||||
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
|
import 'package:pilipala/pages/bangumi/widgets/bangumi_panel.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/introduction/widgets/action_item.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/introduction/widgets/action_row_item.dart';
|
||||||
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class BangumiIntroPanel extends StatefulWidget {
|
||||||
|
const BangumiIntroPanel({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BangumiIntroPanel> createState() => _BangumiIntroPanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BangumiIntroPanelState extends State<BangumiIntroPanel>
|
||||||
|
with AutomaticKeepAliveClientMixin {
|
||||||
|
final BangumiIntroController bangumiIntroController =
|
||||||
|
Get.put(BangumiIntroController(), tag: Get.arguments['heroTag']);
|
||||||
|
BangumiInfoModel? bangumiDetail;
|
||||||
|
|
||||||
|
// 添加页面缓存
|
||||||
|
@override
|
||||||
|
bool get wantKeepAlive => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
bangumiIntroController.bangumiDetail.listen((value) {
|
||||||
|
bangumiDetail = value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
super.build(context);
|
||||||
|
return FutureBuilder(
|
||||||
|
future: bangumiIntroController.queryBangumiIntro(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.data['status']) {
|
||||||
|
// 请求成功
|
||||||
|
return BangumiInfo(
|
||||||
|
loadingStatus: false,
|
||||||
|
bangumiDetail: bangumiDetail,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return HttpError(
|
||||||
|
errMsg: snapshot.data['msg'],
|
||||||
|
fn: () => Get.back(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return BangumiInfo(loadingStatus: true, bangumiDetail: bangumiDetail);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BangumiInfo extends StatefulWidget {
|
||||||
|
final bool loadingStatus;
|
||||||
|
final BangumiInfoModel? bangumiDetail;
|
||||||
|
|
||||||
|
const BangumiInfo({
|
||||||
|
Key? key,
|
||||||
|
this.loadingStatus = false,
|
||||||
|
this.bangumiDetail,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BangumiInfo> createState() => _BangumiInfoState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BangumiInfoState extends State<BangumiInfo> {
|
||||||
|
late BangumiInfoModel? bangumiItem;
|
||||||
|
final BangumiIntroController bangumiIntroController =
|
||||||
|
Get.put(BangumiIntroController(), tag: Get.arguments['heroTag']);
|
||||||
|
bool isExpand = false;
|
||||||
|
|
||||||
|
late VideoDetailController? videoDetailCtr;
|
||||||
|
Box localCache = GStrorage.localCache;
|
||||||
|
late double sheetHeight;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
bangumiItem = bangumiIntroController.bangumiItem;
|
||||||
|
videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
|
sheetHeight = localCache.get('sheetHeight');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收藏
|
||||||
|
showFavBottomSheet() {
|
||||||
|
if (bangumiIntroController.user.get(UserBoxKey.userMid) == null) {
|
||||||
|
SmartDialog.showToast('账号未登录');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// showModalBottomSheet(
|
||||||
|
// context: context,
|
||||||
|
// useRootNavigator: true,
|
||||||
|
// isScrollControlled: true,
|
||||||
|
// builder: (context) {
|
||||||
|
// return FavPanel(ctr: videoIntroController);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
// 视频介绍
|
||||||
|
showIntroDetail() {
|
||||||
|
feedBack();
|
||||||
|
// showBottomSheet(
|
||||||
|
// context: context,
|
||||||
|
// enableDrag: true,
|
||||||
|
// builder: (BuildContext context) {
|
||||||
|
// return IntroDetail(videoDetail: widget.videoDetail!);
|
||||||
|
// },
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
ThemeData t = Theme.of(context);
|
||||||
|
return SliverPadding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 13),
|
||||||
|
sliver: SliverToBoxAdapter(
|
||||||
|
child: !widget.loadingStatus || bangumiItem != null
|
||||||
|
? Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
width: 105,
|
||||||
|
height: 160,
|
||||||
|
src: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.cover!
|
||||||
|
: bangumiItem!.cover!,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => showIntroDetail(),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 158,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
!widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.title!
|
||||||
|
: bangumiItem!.title!,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 20),
|
||||||
|
SizedBox(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(
|
||||||
|
EdgeInsets.zero),
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStateProperty.resolveWith(
|
||||||
|
(states) {
|
||||||
|
return t
|
||||||
|
.colorScheme.primaryContainer
|
||||||
|
.withOpacity(0.7);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
onPressed: () {},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.favorite_border_rounded,
|
||||||
|
color: t.colorScheme.primary,
|
||||||
|
size: 22,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
// const SizedBox(width: 6),
|
||||||
|
StatView(
|
||||||
|
theme: 'gray',
|
||||||
|
view: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.stat!['views']
|
||||||
|
: bangumiItem!.stat!['views'],
|
||||||
|
size: 'medium',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
StatDanMu(
|
||||||
|
theme: 'gray',
|
||||||
|
danmu: !widget.loadingStatus
|
||||||
|
? widget
|
||||||
|
.bangumiDetail!.stat!['danmakus']
|
||||||
|
: bangumiItem!.stat!['danmakus'],
|
||||||
|
size: 'medium',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
!widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.areas!
|
||||||
|
.first['name']
|
||||||
|
: bangumiItem!.areas!.first['name'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: t.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
!widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!
|
||||||
|
.publish!['pub_time_show']
|
||||||
|
: bangumiItem!
|
||||||
|
.publish!['pub_time_show'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: t.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
!widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.newEp!['desc']
|
||||||
|
: bangumiItem!.newEp!['desc'],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: t.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Text(
|
||||||
|
'简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}',
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: t.colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
if (bangumiItem != null &&
|
||||||
|
bangumiItem!.rating != null)
|
||||||
|
Text(
|
||||||
|
'评分 ${!widget.loadingStatus ? widget.bangumiDetail!.rating!['score']! : bangumiItem!.rating!['score']!}',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: t.colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
// 点赞收藏转发 布局样式1
|
||||||
|
// SingleChildScrollView(
|
||||||
|
// padding: const EdgeInsets.only(top: 7, bottom: 7),
|
||||||
|
// scrollDirection: Axis.horizontal,
|
||||||
|
// child: actionRow(
|
||||||
|
// context,
|
||||||
|
// bangumiIntroController,
|
||||||
|
// videoDetailCtr,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// 点赞收藏转发 布局样式2
|
||||||
|
actionGrid(context, bangumiIntroController),
|
||||||
|
// 番剧分p
|
||||||
|
if ((!widget.loadingStatus &&
|
||||||
|
widget.bangumiDetail!.episodes!.isNotEmpty) ||
|
||||||
|
bangumiItem != null &&
|
||||||
|
bangumiItem!.episodes!.isNotEmpty) ...[
|
||||||
|
BangumiPanel(
|
||||||
|
pages: bangumiItem != null
|
||||||
|
? bangumiItem!.episodes!
|
||||||
|
: widget.bangumiDetail!.episodes!,
|
||||||
|
cid: bangumiItem != null
|
||||||
|
? bangumiItem!.episodes!.first.cid
|
||||||
|
: widget.bangumiDetail!.episodes!.first.cid,
|
||||||
|
sheetHeight: sheetHeight,
|
||||||
|
changeFuc: (bvid, cid, aid) => bangumiIntroController
|
||||||
|
.changeSeasonOrbangu(bvid, cid, aid),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: const SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget actionGrid(BuildContext context, bangumiIntroController) {
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
return Material(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 16, bottom: 8),
|
||||||
|
child: SizedBox(
|
||||||
|
height: constraints.maxWidth / 5 * 0.8,
|
||||||
|
child: GridView.count(
|
||||||
|
primary: false,
|
||||||
|
padding: const EdgeInsets.all(0),
|
||||||
|
crossAxisCount: 5,
|
||||||
|
childAspectRatio: 1.25,
|
||||||
|
children: <Widget>[
|
||||||
|
Obx(
|
||||||
|
() => ActionItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||||
|
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
|
||||||
|
onTap: () => bangumiIntroController.actionLikeVideo(),
|
||||||
|
selectStatus: bangumiIntroController.hasLike.value,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.stat!['likes']!.toString()
|
||||||
|
: '-'),
|
||||||
|
),
|
||||||
|
ActionItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.clock),
|
||||||
|
onTap: () => () {},
|
||||||
|
selectStatus: false,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: '稍后再看'),
|
||||||
|
Obx(
|
||||||
|
() => ActionItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.b),
|
||||||
|
selectIcon: const Icon(FontAwesomeIcons.b),
|
||||||
|
onTap: () => bangumiIntroController.actionCoinVideo(),
|
||||||
|
selectStatus: bangumiIntroController.hasCoin.value,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.stat!['coins']!.toString()
|
||||||
|
: '-'),
|
||||||
|
),
|
||||||
|
Obx(
|
||||||
|
() => ActionItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.star),
|
||||||
|
selectIcon: const Icon(FontAwesomeIcons.solidStar),
|
||||||
|
// onTap: () => videoIntroController.actionFavVideo(),
|
||||||
|
onTap: () => showFavBottomSheet(),
|
||||||
|
selectStatus: bangumiIntroController.hasFav.value,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.stat!['favorite']!.toString()
|
||||||
|
: '-'),
|
||||||
|
),
|
||||||
|
ActionItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.shareFromSquare),
|
||||||
|
onTap: () => bangumiIntroController.actionShareVideo(),
|
||||||
|
selectStatus: false,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.stat!['share']!.toString()
|
||||||
|
: '-'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
|
||||||
|
return Row(children: [
|
||||||
|
Obx(
|
||||||
|
() => ActionRowItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.thumbsUp),
|
||||||
|
onTap: () => videoIntroController.actionLikeVideo(),
|
||||||
|
selectStatus: videoIntroController.hasLike.value,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.stat!['likes']!.toString()
|
||||||
|
: '-',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Obx(
|
||||||
|
() => ActionRowItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.b),
|
||||||
|
onTap: () => videoIntroController.actionCoinVideo(),
|
||||||
|
selectStatus: videoIntroController.hasCoin.value,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.stat!['coins']!.toString()
|
||||||
|
: '-',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Obx(
|
||||||
|
() => ActionRowItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.heart),
|
||||||
|
onTap: () => showFavBottomSheet(),
|
||||||
|
selectStatus: videoIntroController.hasFav.value,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.stat!['favorite']!.toString()
|
||||||
|
: '-',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
ActionRowItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.comment),
|
||||||
|
onTap: () {
|
||||||
|
videoDetailCtr.tabCtr.animateTo(1);
|
||||||
|
},
|
||||||
|
selectStatus: false,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
text: !widget.loadingStatus
|
||||||
|
? widget.bangumiDetail!.stat!['reply']!.toString()
|
||||||
|
: '-',
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
ActionRowItem(
|
||||||
|
icon: const Icon(FontAwesomeIcons.share),
|
||||||
|
onTap: () => videoIntroController.actionShareVideo(),
|
||||||
|
selectStatus: false,
|
||||||
|
loadingStatus: widget.loadingStatus,
|
||||||
|
// text: !widget.loadingStatus
|
||||||
|
// ? widget.videoDetail!.stat!.share!.toString()
|
||||||
|
// : '-',
|
||||||
|
text: '转发'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
230
lib/pages/bangumi/widgets/bangumi_panel.dart
Normal file
230
lib/pages/bangumi/widgets/bangumi_panel.dart
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
|
|
||||||
|
class BangumiPanel extends StatefulWidget {
|
||||||
|
final List<EpisodeItem> pages;
|
||||||
|
final int? cid;
|
||||||
|
final double? sheetHeight;
|
||||||
|
final Function? changeFuc;
|
||||||
|
|
||||||
|
const BangumiPanel({
|
||||||
|
super.key,
|
||||||
|
required this.pages,
|
||||||
|
this.cid,
|
||||||
|
this.sheetHeight,
|
||||||
|
this.changeFuc,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<BangumiPanel> createState() => _BangumiPanelState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BangumiPanelState extends State<BangumiPanel> {
|
||||||
|
late int currentIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
currentIndex = widget.pages.indexWhere((e) => e.cid == widget.cid!);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showBangumiPanel() {
|
||||||
|
showBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => Container(
|
||||||
|
height: widget.sheetHeight,
|
||||||
|
color: Theme.of(context).colorScheme.background,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
height: 45,
|
||||||
|
padding: const EdgeInsets.only(left: 14, right: 14),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'合集(${widget.pages.length})',
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Divider(
|
||||||
|
height: 1,
|
||||||
|
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Material(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: widget.pages.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return ListTile(
|
||||||
|
onTap: () => changeFucCall(widget.pages[index], index),
|
||||||
|
dense: false,
|
||||||
|
title: Text(
|
||||||
|
widget.pages[index].longTitle!,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: index == currentIndex
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: widget.pages[index].badge != null
|
||||||
|
? Image.asset(
|
||||||
|
'assets/images/big-vip.png',
|
||||||
|
height: 20,
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void changeFucCall(item, i) async {
|
||||||
|
if (item.badge != null) {
|
||||||
|
SmartDialog.showToast('需要大会员');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await widget.changeFuc!(
|
||||||
|
item.bvid,
|
||||||
|
item.cid,
|
||||||
|
item.aid,
|
||||||
|
);
|
||||||
|
currentIndex = i;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 10),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
const Text('合集 '),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
' 正在播放:${widget.pages[currentIndex].longTitle}',
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
SizedBox(
|
||||||
|
height: 34,
|
||||||
|
child: TextButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () => showBangumiPanel(),
|
||||||
|
child: Text(
|
||||||
|
'全${widget.pages.length}话',
|
||||||
|
style: const TextStyle(fontSize: 13),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SingleChildScrollView(
|
||||||
|
padding: const EdgeInsets.only(top: 7, bottom: 7),
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: MediaQuery.of(context).size.width,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
for (int i = 0; i < widget.pages.length; i++) ...[
|
||||||
|
Container(
|
||||||
|
width: 150,
|
||||||
|
margin: const EdgeInsets.only(right: 10),
|
||||||
|
child: Material(
|
||||||
|
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => changeFucCall(widget.pages[i], i),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8, horizontal: 10),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
if (i == currentIndex) ...[
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/live.gif',
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.primary,
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6)
|
||||||
|
],
|
||||||
|
Text(
|
||||||
|
'第${i + 1}话',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: i == currentIndex
|
||||||
|
? Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 2),
|
||||||
|
if (widget.pages[i].badge != null) ...[
|
||||||
|
Image.asset(
|
||||||
|
'assets/images/big-vip.png',
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 3),
|
||||||
|
Text(
|
||||||
|
widget.pages[i].longTitle!,
|
||||||
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
color: i == currentIndex
|
||||||
|
? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onSurface),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/badge.dart';
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
|
import 'package:pilipala/http/video.dart';
|
||||||
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
import 'package:pilipala/models/common/business_type.dart';
|
import 'package:pilipala/models/common/business_type.dart';
|
||||||
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/live/item.dart';
|
import 'package:pilipala/models/live/item.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
@ -47,6 +51,59 @@ class HistoryItem extends StatelessWidget {
|
|||||||
'/liveRoom?roomid=${videoItem.history.oid}',
|
'/liveRoom?roomid=${videoItem.history.oid}',
|
||||||
arguments: {'liveItem': liveItem},
|
arguments: {'liveItem': liveItem},
|
||||||
);
|
);
|
||||||
|
} else if (videoItem.badge == '番剧' ||
|
||||||
|
videoItem.tagName.contains('动画')) {
|
||||||
|
/// hack
|
||||||
|
var bvid = videoItem.history.bvid;
|
||||||
|
if (bvid != null && bvid != '') {
|
||||||
|
var result = await VideoHttp.videoIntro(bvid: bvid);
|
||||||
|
if (result['status']) {
|
||||||
|
String bvid = result['data'].bvid!;
|
||||||
|
int cid = result['data'].cid!;
|
||||||
|
String pic = result['data'].pic!;
|
||||||
|
String heroTag = Utils.makeHeroTag(cid);
|
||||||
|
var epid = result['data'].epId;
|
||||||
|
if (epid != null) {
|
||||||
|
Get.toNamed(
|
||||||
|
'/video?bvid=$bvid&cid=$cid&epId=${result['data'].epId}',
|
||||||
|
arguments: {
|
||||||
|
'pic': pic,
|
||||||
|
'heroTag': heroTag,
|
||||||
|
'videoType': SearchType.media_bangumi,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
int cid = videoItem.history.cid ??
|
||||||
|
// videoItem.history.oid ??
|
||||||
|
await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||||
|
Get.toNamed('/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: {'heroTag': heroTag, 'pic': videoItem.cover});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (videoItem.history.epid != '') {
|
||||||
|
SmartDialog.showLoading(msg: '获取中...');
|
||||||
|
var res =
|
||||||
|
await SearchHttp.bangumiInfo(epId: videoItem.history.epid);
|
||||||
|
SmartDialog.dismiss();
|
||||||
|
if (res['status']) {
|
||||||
|
EpisodeItem episode = res['data'].episodes.first;
|
||||||
|
String bvid = episode.bvid!;
|
||||||
|
int cid = episode.cid!;
|
||||||
|
String pic = episode.cover!;
|
||||||
|
String heroTag = Utils.makeHeroTag(cid);
|
||||||
|
Get.toNamed(
|
||||||
|
'/video?bvid=$bvid&cid=$cid&seasonId=${res['data'].seasonId}',
|
||||||
|
arguments: {
|
||||||
|
'pic': pic,
|
||||||
|
'heroTag': heroTag,
|
||||||
|
'videoType': SearchType.media_bangumi,
|
||||||
|
'bangumiItem': res['data'],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int cid = videoItem.history.cid ??
|
int cid = videoItem.history.cid ??
|
||||||
// videoItem.history.oid ??
|
// videoItem.history.oid ??
|
||||||
@ -149,7 +206,7 @@ class VideoContent extends StatelessWidget {
|
|||||||
maxLines: videoItem.videos > 1 ? 1 : 2,
|
maxLines: videoItem.videos > 1 ? 1 : 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
if (videoItem.videos > 1)
|
if (videoItem.showTitle != null)
|
||||||
Text(
|
Text(
|
||||||
videoItem.showTitle,
|
videoItem.showTitle,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
|
|||||||
@ -27,6 +27,7 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
late SearchPanelController? _searchPanelController;
|
late SearchPanelController? _searchPanelController;
|
||||||
|
|
||||||
bool _isLoadingMore = false;
|
bool _isLoadingMore = false;
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get wantKeepAlive => true;
|
bool get wantKeepAlive => true;
|
||||||
@ -53,6 +54,7 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
_futureBuilderFuture = _searchPanelController!.onSearch();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -63,7 +65,7 @@ class _SearchPanelState extends State<SearchPanel>
|
|||||||
await _searchPanelController!.onRefresh();
|
await _searchPanelController!.onRefresh();
|
||||||
},
|
},
|
||||||
child: FutureBuilder(
|
child: FutureBuilder(
|
||||||
future: _searchPanelController!.onSearch(),
|
future: _futureBuilderFuture,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
Map data = snapshot.data;
|
Map data = snapshot.data;
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import 'package:pilipala/common/widgets/badge.dart';
|
|||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/http/search.dart';
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/models/bangumi/info.dart';
|
import 'package:pilipala/models/bangumi/info.dart';
|
||||||
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
||||||
@ -19,8 +20,12 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
|||||||
var i = list![index];
|
var i = list![index];
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Get.toNamed('/video?bvid=${i.bvid}&cid=${i.cid}',
|
/// TODO 番剧详情页面
|
||||||
arguments: {'videoItem': i, 'heroTag': Utils.makeHeroTag(i.id)});
|
// Get.toNamed('/video?bvid=${i.bvid}&cid=${i.cid}', arguments: {
|
||||||
|
// 'videoItem': i,
|
||||||
|
// 'heroTag': Utils.makeHeroTag(i.id),
|
||||||
|
// 'videoType': SearchType.media_bangumi
|
||||||
|
// });
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
@ -107,10 +112,12 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
|||||||
String pic = episode.cover!;
|
String pic = episode.cover!;
|
||||||
String heroTag = Utils.makeHeroTag(cid);
|
String heroTag = Utils.makeHeroTag(cid);
|
||||||
Get.toNamed(
|
Get.toNamed(
|
||||||
'/video?bvid=$bvid&cid=$cid',
|
'/video?bvid=$bvid&cid=$cid&seasonId=${i.seasonId}',
|
||||||
arguments: {
|
arguments: {
|
||||||
'pic': pic,
|
'pic': pic,
|
||||||
'heroTag': heroTag,
|
'heroTag': heroTag,
|
||||||
|
'videoType': SearchType.media_bangumi,
|
||||||
|
'bangumiItem': res['data'],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import 'package:hive/hive.dart';
|
|||||||
import 'package:pilipala/http/constants.dart';
|
import 'package:pilipala/http/constants.dart';
|
||||||
import 'package:pilipala/http/video.dart';
|
import 'package:pilipala/http/video.dart';
|
||||||
import 'package:pilipala/models/common/reply_type.dart';
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/models/video/play/quality.dart';
|
import 'package:pilipala/models/video/play/quality.dart';
|
||||||
import 'package:pilipala/models/video/play/url.dart';
|
import 'package:pilipala/models/video/play/url.dart';
|
||||||
import 'package:pilipala/models/video/reply/item.dart';
|
import 'package:pilipala/models/video/reply/item.dart';
|
||||||
@ -22,6 +23,9 @@ class VideoDetailController extends GetxController
|
|||||||
// 视频aid
|
// 视频aid
|
||||||
String bvid = Get.parameters['bvid']!;
|
String bvid = Get.parameters['bvid']!;
|
||||||
int cid = int.parse(Get.parameters['cid']!);
|
int cid = int.parse(Get.parameters['cid']!);
|
||||||
|
// 视频类型 默认投稿视频
|
||||||
|
SearchType videoType = SearchType.video;
|
||||||
|
|
||||||
late PlayUrlModel data;
|
late PlayUrlModel data;
|
||||||
// 当前画质
|
// 当前画质
|
||||||
late VideoQuality currentVideoQa;
|
late VideoQuality currentVideoQa;
|
||||||
@ -74,6 +78,7 @@ class VideoDetailController extends GetxController
|
|||||||
bgCover.value = Get.arguments['pic'];
|
bgCover.value = Get.arguments['pic'];
|
||||||
}
|
}
|
||||||
heroTag = Get.arguments['heroTag'];
|
heroTag = Get.arguments['heroTag'];
|
||||||
|
videoType = Get.arguments['videoType'] ?? SearchType.video;
|
||||||
}
|
}
|
||||||
tabCtr = TabController(length: 2, vsync: this);
|
tabCtr = TabController(length: 2, vsync: this);
|
||||||
// queryVideoUrl();
|
// queryVideoUrl();
|
||||||
|
|||||||
@ -389,4 +389,13 @@ class VideoIntroController extends GetxController {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 修改分P或番剧分集
|
||||||
|
Future changeSeasonOrbangu(bvid, cid) async {
|
||||||
|
var _videoDetailCtr =
|
||||||
|
Get.find<VideoDetailController>(tag: Get.arguments['heroTag']);
|
||||||
|
_videoDetailCtr.bvid = bvid;
|
||||||
|
_videoDetailCtr.cid = cid;
|
||||||
|
_videoDetailCtr.queryVideoUrl();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,9 @@ class VideoReplyController extends GetxController {
|
|||||||
|
|
||||||
Future queryReplyList({type = 'init'}) async {
|
Future queryReplyList({type = 'init'}) async {
|
||||||
isLoadingMore = true;
|
isLoadingMore = true;
|
||||||
|
if (type == 'init') {
|
||||||
|
currentPage = 0;
|
||||||
|
}
|
||||||
var res = await ReplyHttp.replyList(
|
var res = await ReplyHttp.replyList(
|
||||||
oid: aid!,
|
oid: aid!,
|
||||||
pageNum: currentPage + 1,
|
pageNum: currentPage + 1,
|
||||||
|
|||||||
@ -281,7 +281,8 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
|
|||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return VideoReplyNewDialog(
|
return VideoReplyNewDialog(
|
||||||
oid: IdUtils.bv2av(Get.parameters['bvid']!),
|
oid: _videoReplyController.aid ??
|
||||||
|
IdUtils.bv2av(Get.parameters['bvid']!),
|
||||||
root: 0,
|
root: 0,
|
||||||
parent: 0,
|
parent: 0,
|
||||||
replyType: ReplyType.video,
|
replyType: ReplyType.video,
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/common/widgets/sliver_header.dart';
|
import 'package:pilipala/common/widgets/sliver_header.dart';
|
||||||
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
|
import 'package:pilipala/pages/bangumi/introduction/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
import 'package:pilipala/pages/video/detail/introduction/widgets/menu_row.dart';
|
||||||
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
import 'package:pilipala/pages/video/detail/reply/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/controller.dart';
|
import 'package:pilipala/pages/video/detail/controller.dart';
|
||||||
@ -324,16 +326,25 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
return CustomScrollView(
|
return CustomScrollView(
|
||||||
key: const PageStorageKey<String>('简介'),
|
key: const PageStorageKey<String>('简介'),
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
const VideoIntroPanel(),
|
if (videoDetailController.videoType ==
|
||||||
SliverPersistentHeader(
|
SearchType.video) ...[
|
||||||
floating: true,
|
const VideoIntroPanel(),
|
||||||
pinned: true,
|
] else if (videoDetailController.videoType ==
|
||||||
delegate: SliverHeaderDelegate(
|
SearchType.media_bangumi) ...[
|
||||||
height: 50,
|
const BangumiIntroPanel()
|
||||||
child:
|
],
|
||||||
const MenuRow(loadingStatus: false),
|
if (videoDetailController.videoType ==
|
||||||
|
SearchType.video) ...[
|
||||||
|
SliverPersistentHeader(
|
||||||
|
floating: true,
|
||||||
|
pinned: true,
|
||||||
|
delegate: SliverHeaderDelegate(
|
||||||
|
height: 50,
|
||||||
|
child:
|
||||||
|
const MenuRow(loadingStatus: false),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
const RelatedVideoPanel(),
|
const RelatedVideoPanel(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/models/video/play/quality.dart';
|
import 'package:pilipala/models/video/play/quality.dart';
|
||||||
import 'package:pilipala/models/video/play/url.dart';
|
import 'package:pilipala/models/video/play/url.dart';
|
||||||
import 'package:pilipala/pages/home/index.dart';
|
import 'package:pilipala/pages/main/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
|
|
||||||
@ -403,7 +403,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
size: 15,
|
size: 15,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
fuc: () => Get.offAll(const HomePage()),
|
fuc: () => Get.offAll(const MainApp()),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
// ComBtn(
|
// ComBtn(
|
||||||
|
|||||||
@ -505,15 +505,16 @@ class PlPlayerController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void onChangedSliderStart() {
|
void onChangedSliderStart() {
|
||||||
feedBack();
|
|
||||||
_isSliderMoving.value = true;
|
_isSliderMoving.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onUodatedSliderProgress(value) {
|
void onUodatedSliderProgress(Duration value) {
|
||||||
_sliderTempPosition.value = value;
|
_sliderTempPosition.value = value;
|
||||||
|
_sliderPosition.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onChangedSliderEnd() {
|
void onChangedSliderEnd() {
|
||||||
|
feedBack();
|
||||||
_isSliderMoving.value = false;
|
_isSliderMoving.value = false;
|
||||||
_hideTaskControls();
|
_hideTaskControls();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:auto_orientation/auto_orientation.dart';
|
// import 'package:auto_orientation/auto_orientation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
//横屏
|
//横屏
|
||||||
/// 低版本xcode不支持auto_orientation
|
/// 低版本xcode不支持auto_orientation
|
||||||
Future<void> landScape() async {
|
// Future<void> landScape() async {
|
||||||
if (Platform.isAndroid || Platform.isIOS) {
|
// if (Platform.isAndroid || Platform.isIOS) {
|
||||||
await AutoOrientation.landscapeAutoMode(forceSensor: true);
|
// await AutoOrientation.landscapeAutoMode(forceSensor: true);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
//竖屏
|
//竖屏
|
||||||
Future<void> verticalScreen() async {
|
Future<void> verticalScreen() async {
|
||||||
|
|||||||
@ -57,6 +57,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
Timer? _volumeTimer;
|
Timer? _volumeTimer;
|
||||||
|
|
||||||
double _distance = 0.0;
|
double _distance = 0.0;
|
||||||
|
// 初始手指落下位置
|
||||||
|
double _initTapPositoin = 0.0;
|
||||||
|
|
||||||
bool _volumeInterceptEventStream = false;
|
bool _volumeInterceptEventStream = false;
|
||||||
|
|
||||||
@ -153,7 +155,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
/// 进入全屏
|
/// 进入全屏
|
||||||
await enterFullScreen();
|
await enterFullScreen();
|
||||||
// 横屏
|
// 横屏
|
||||||
await landScape();
|
// await landScape();
|
||||||
} else {
|
} else {
|
||||||
// 竖屏
|
// 竖屏
|
||||||
await verticalScreen();
|
await verticalScreen();
|
||||||
@ -464,8 +466,28 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
_.setPlaybackSpeed(currentSpeed / 2);
|
_.setPlaybackSpeed(currentSpeed / 2);
|
||||||
},
|
},
|
||||||
// 水平位置 快进
|
// 水平位置 快进
|
||||||
onHorizontalDragUpdate: (DragUpdateDetails details) {},
|
onHorizontalDragUpdate: (DragUpdateDetails details) {
|
||||||
onHorizontalDragEnd: (DragEndDetails details) {},
|
final tapPosition = details.localPosition.dx;
|
||||||
|
int curSliderPosition = _.sliderPosition.value.inSeconds;
|
||||||
|
late int result;
|
||||||
|
if (tapPosition - _initTapPositoin > 0) {
|
||||||
|
// 快进
|
||||||
|
/// TODO 优化屏幕越小效果越明显
|
||||||
|
result = (curSliderPosition + 1)
|
||||||
|
.clamp(0, _.duration.value.inSeconds);
|
||||||
|
} else {
|
||||||
|
// 快退
|
||||||
|
result = (curSliderPosition - 1)
|
||||||
|
.clamp(0, _.duration.value.inSeconds);
|
||||||
|
}
|
||||||
|
_.onUodatedSliderProgress(Duration(seconds: result));
|
||||||
|
_.onChangedSliderStart();
|
||||||
|
_initTapPositoin = tapPosition;
|
||||||
|
},
|
||||||
|
onHorizontalDragEnd: (DragEndDetails details) {
|
||||||
|
_.onChangedSliderEnd();
|
||||||
|
_.seekTo(_.sliderPosition.value);
|
||||||
|
},
|
||||||
// 垂直方向 音量/亮度调节
|
// 垂直方向 音量/亮度调节
|
||||||
onVerticalDragUpdate: (DragUpdateDetails details) async {
|
onVerticalDragUpdate: (DragUpdateDetails details) async {
|
||||||
final totalWidth = MediaQuery.of(context).size.width;
|
final totalWidth = MediaQuery.of(context).size.width;
|
||||||
@ -546,7 +568,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
return Positioned(
|
return Positioned(
|
||||||
bottom: -3,
|
bottom: -3.5,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/index.dart';
|
import 'package:pilipala/plugin/pl_player/index.dart';
|
||||||
import 'package:pilipala/plugin/pl_player/widgets/play_pause_btn.dart';
|
import 'package:pilipala/plugin/pl_player/widgets/play_pause_btn.dart';
|
||||||
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
|
||||||
class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
||||||
final PlPlayerController? controller;
|
final PlPlayerController? controller;
|
||||||
@ -55,6 +56,7 @@ class BottomControl extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
barHeight: 3.0,
|
barHeight: 3.0,
|
||||||
thumbRadius: 5.5,
|
thumbRadius: 5.5,
|
||||||
onDragStart: (duration) {
|
onDragStart: (duration) {
|
||||||
|
feedBack();
|
||||||
_.onChangedSliderStart();
|
_.onChangedSliderStart();
|
||||||
},
|
},
|
||||||
onDragUpdate: (duration) {
|
onDragUpdate: (duration) {
|
||||||
|
|||||||
@ -49,14 +49,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.1"
|
version: "1.0.1"
|
||||||
auto_orientation:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: auto_orientation
|
|
||||||
sha256: cd56bb59b36fa54cc28ee254bc600524f022a4862f31d5ab20abd7bb1c54e678
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.3.1"
|
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@ -107,7 +107,7 @@ dependencies:
|
|||||||
universal_platform: ^1.0.0+1
|
universal_platform: ^1.0.0+1
|
||||||
# 进度条
|
# 进度条
|
||||||
audio_video_progress_bar: ^1.0.1
|
audio_video_progress_bar: ^1.0.1
|
||||||
auto_orientation: ^2.3.1
|
# auto_orientation: ^2.3.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user