Files
PiliPlus/lib/pages/video/detail/introduction/controller.dart
bggRGjQaUbCoE 2acbb17648 fix: quick fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-15 19:14:50 +08:00

1173 lines
37 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:math';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:get/get_navigation/src/dialog/dialog_route.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/models/video/ai.dart';
import 'package:PiliPlus/models/video_detail_res.dart';
import 'package:PiliPlus/pages/video/detail/controller.dart';
import 'package:PiliPlus/pages/video/detail/reply/index.dart';
import 'package:PiliPlus/plugin/pl_player/models/play_repeat.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:share_plus/share_plus.dart';
import '../../../../http/search.dart';
import '../../../../models/model_hot_video_item.dart';
import '../related/index.dart';
import 'widgets/group_panel.dart';
class VideoIntroController extends GetxController
with GetTickerProviderStateMixin {
// 视频bvid
late String bvid;
// 是否预渲染 骨架屏
bool preRender = false;
// 视频详情 上个页面传入
Map videoItem = {};
// 请求状态
RxBool isLoading = false.obs;
// 视频详情 请求返回
Rx<VideoDetailData> videoDetail = VideoDetailData().obs;
// up主粉丝数
Rx<Map<String, dynamic>> userStat =
Rx<Map<String, dynamic>>({'follower': '-'});
dynamic videoTags;
// 是否点赞
RxBool hasLike = false.obs;
// 是否点踩
RxBool hasDislike = false.obs;
// 是否投币
RxBool hasCoin = false.obs;
// 是否收藏
RxBool hasFav = false.obs;
bool isLogin = false;
Rx<FavFolderData> favFolderData = FavFolderData().obs;
List addMediaIdsNew = [];
List delMediaIdsNew = [];
// 关注状态 默认未关注
RxMap followStatus = {}.obs;
RxInt lastPlayCid = 0.obs;
dynamic userInfo;
// 同时观看
bool isShowOnlineTotal = false;
RxString total = '1'.obs;
Timer? timer;
String heroTag = '';
late ModelResult modelResult;
Rx<Map<String, dynamic>> queryVideoIntroData =
Rx<Map<String, dynamic>>({"status": true});
ExpandableController? expandableCtr;
late final showArgueMsg = GStorage.showArgueMsg;
late final enableAi =
GStorage.setting.get(SettingBoxKey.enableAi, defaultValue: false);
late final enableQuickFav =
GStorage.setting.get(SettingBoxKey.enableQuickFav, defaultValue: false);
@override
void onInit() {
super.onInit();
userInfo = GStorage.userInfo.get('userInfoCache');
try {
if (heroTag.isEmpty) {
heroTag = Get.arguments['heroTag'];
}
bvid = Get.parameters['bvid']!;
} catch (_) {}
if (Get.arguments.isNotEmpty) {
if (Get.arguments.containsKey('videoItem')) {
preRender = true;
var args = Get.arguments['videoItem'];
var keys = Get.arguments.keys.toList();
try {
if (args.pic != null && args.pic != '') {
videoItem['pic'] = args.pic;
} else if (args.cover != null && args.cover != '') {
videoItem['pic'] = args.cover;
}
} catch (_) {}
if (args.title is String) {
videoItem['title'] = args.title;
} else {
String str = '';
for (Map map in args.title) {
str += map['text'];
}
videoItem['title'] = str;
}
videoItem['stat'] = keys.contains('stat') ? args.stat : null;
videoItem['pubdate'] = keys.contains('pubdate') ? args.pubdate : null;
videoItem['owner'] = keys.contains('owner') ? args.owner : null;
}
}
isLogin = userInfo != null;
lastPlayCid.value = int.parse(Get.parameters['cid']!);
isShowOnlineTotal = GStorage.setting
.get(SettingBoxKey.enableOnlineTotal, defaultValue: false);
startTimer();
queryVideoIntro();
}
// 获取视频简介&分p
Future queryVideoIntro() async {
queryVideoTags();
var result = await VideoHttp.videoIntro(bvid: bvid);
if (result['status']) {
if (videoDetail.value.ugcSeason?.id == result['data']?.ugcSeason?.id) {
// keep reversed season
result['data']?.ugcSeason = videoDetail.value.ugcSeason;
result['data']?.isSeasonReversed = videoDetail.value.isSeasonReversed;
}
if (videoDetail.value.cid == result['data']?.cid) {
// keep reversed pages
result['data']?.pages = videoDetail.value.pages;
result['data']?.isPageReversed = videoDetail.value.isPageReversed;
}
videoDetail.value = result['data'];
videoItem['staff'] = result['data'].staff;
try {
final videoDetailController =
Get.find<VideoDetailController>(tag: heroTag);
if (videoDetailController.videoItem['pic'] == null ||
videoDetailController.videoItem['pic'] == '' ||
(videoDetailController.videoUrl.isNullOrEmpty &&
videoDetailController.isQuerying.not)) {
videoDetailController.videoItem['pic'] = result['data'].pic;
}
} catch (_) {}
if (videoDetail.value.pages != null &&
videoDetail.value.pages!.isNotEmpty &&
lastPlayCid.value == 0) {
lastPlayCid.value = videoDetail.value.pages!.first.cid!;
}
// Get.find<VideoDetailController>(tag: heroTag).tabs.value = [
// '简介',
// '评论 ${result['data']!.stat!.reply}'
// ];
// 获取到粉丝数再返回
await queryUserStat();
} else {
SmartDialog.showToast(
"${result['code']} ${result['msg']} ${result['data']}");
}
queryVideoIntroData.value = result;
if (isLogin) {
// 获取点赞状态
queryHasLikeVideo();
// 获取投币状态
queryHasCoinVideo();
// 获取收藏状态
queryHasFavVideo();
//
queryFollowStatus();
}
}
Future queryVideoTags() async {
var result = await UserHttp.videoTags(bvid: bvid);
if (result['status']) {
videoTags = result['data'];
// debugPrint('tags: ${result['data']}');
}
}
// 获取up主粉丝数
Future queryUserStat() async {
if (videoDetail.value.owner == null) {
return;
}
var result = await UserHttp.userStat(mid: videoDetail.value.owner!.mid!);
if (result['status']) {
userStat.value = result['data'];
userStat.refresh();
}
}
// 获取点赞状态
Future queryHasLikeVideo() async {
var result = await VideoHttp.hasLikeVideo(bvid: bvid);
// data num 被点赞标志 0未点赞 1已点赞 2已点踩
hasLike.value = result["data"] == 1;
hasDislike.value = result["data"] == 2;
}
// 获取投币状态
Future queryHasCoinVideo() async {
var result = await VideoHttp.hasCoinVideo(bvid: bvid);
if (result['status']) {
hasCoin.value = result["data"]['multiply'] != 0;
}
}
// 获取收藏状态
Future queryHasFavVideo() async {
/// fix 延迟查询
await Future.delayed(const Duration(milliseconds: 200));
var result = await VideoHttp.hasFavVideo(aid: IdUtils.bv2av(bvid));
if (result['status']) {
hasFav.value = result["data"]['favoured'];
} else {
hasFav.value = false;
}
}
// 一键三连
Future actionOneThree() async {
feedBack();
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
if (hasLike.value && hasCoin.value && hasFav.value) {
// 已点赞、投币、收藏
SmartDialog.showToast('已三连');
return false;
}
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']);
}
}
// (取消)点赞
Future actionLikeVideo() async {
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
if (videoDetail.value.stat?.like == null) {
// not init
return;
}
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(result['data']['toast']);
hasLike.value = true;
hasDislike.value = false;
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 actionDislikeVideo() async {
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
var result =
await VideoHttp.dislikeVideo(bvid: bvid, type: !hasDislike.value);
if (result['status']) {
// hasLike.value = result["data"] == 1 ? true : false;
if (!hasDislike.value) {
SmartDialog.showToast('点踩成功');
hasDislike.value = true;
hasLike.value = false;
} else {
SmartDialog.showToast('取消踩');
hasDislike.value = false;
}
// hasDislike.refresh();
} else {
SmartDialog.showToast(result['msg']);
}
}
void coinVideo(int coin) async {
if (videoDetail.value.stat?.coin == null) {
// not init
return;
}
var res = await VideoHttp.coinVideo(bvid: bvid, multiply: coin);
if (res['status']) {
SmartDialog.showToast('投币成功');
hasCoin.value = true;
videoDetail.value.stat!.coin = videoDetail.value.stat!.coin! + coin;
} else {
SmartDialog.showToast(res['msg']);
}
}
// 投币
Future actionCoinVideo() async {
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
Navigator.of(Get.context!).push(
GetDialogRoute(
pageBuilder: (buildContext, animation, secondaryAnimation) {
return PayCoinsPage(
callback: coinVideo,
copyright: (queryVideoIntroData.value['data'] as VideoDetailData?)
?.copyright ??
1,
);
},
transitionDuration: const Duration(milliseconds: 225),
transitionBuilder: (context, animation, secondaryAnimation, child) {
const begin = 0.0;
const end = 1.0;
const curve = Curves.linear;
var tween = Tween<double>(begin: begin, end: end)
.chain(CurveTween(curve: curve));
return FadeTransition(
opacity: animation.drive(tween),
child: child,
);
},
),
);
// showDialog(
// context: Get.context!,
// builder: (context) {
// return AlertDialog(
// title: const Text('选择投币个数'),
// contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 12),
// actions: [
// TextButton(
// onPressed: () => Get.back(),
// child: Text('取消',
// style: TextStyle(
// color: Theme.of(context).colorScheme.outline))),
// TextButton(
// onPressed: () async {
// coinVideo(1);
// Get.back();
// },
// child: const Text('投 1 枚')),
// TextButton(
// onPressed: () async {
// coinVideo(2);
// Get.back();
// },
// child: const Text('投 2 枚'))
// ],
// );
// },
// );
}
// (取消)收藏
Future actionFavVideo({type = 'choose'}) async {
// 收藏至默认文件夹
if (type == 'default') {
SmartDialog.showLoading(msg: '请求中');
queryVideoInFolder().then((res) async {
if (res['status']) {
int defaultFolderId = favFolderData.value.list!.first.id!;
int favStatus = favFolderData.value.list!.first.favState!;
var result = await VideoHttp.favVideo(
aid: IdUtils.bv2av(bvid),
addIds: favStatus == 0 ? '$defaultFolderId' : '',
delIds: favStatus == 1 ? '$defaultFolderId' : '',
);
SmartDialog.dismiss();
if (result['status']) {
// 重新获取收藏状态
await queryHasFavVideo();
SmartDialog.showToast('✅ 快速收藏/取消收藏成功');
} else {
SmartDialog.showToast(result['msg']);
}
} else {
SmartDialog.dismiss();
}
});
return;
}
try {
for (var i in favFolderData.value.list!.toList()) {
if (i.favState == 1) {
addMediaIdsNew.add(i.id);
} else {
delMediaIdsNew.add(i.id);
}
}
} catch (e) {
debugPrint(e.toString());
}
SmartDialog.showLoading(msg: '请求中');
var result = await VideoHttp.favVideo(
aid: IdUtils.bv2av(bvid),
addIds: addMediaIdsNew.join(','),
delIds: delMediaIdsNew.join(','),
);
SmartDialog.dismiss();
if (result['status']) {
addMediaIdsNew = [];
delMediaIdsNew = [];
Get.back();
// 重新获取收藏状态
await queryHasFavVideo();
SmartDialog.showToast('操作成功');
} else {
SmartDialog.showToast(result['msg']);
}
}
// 分享视频
Future actionShareVideo() async {
showDialog(
context: Get.context!,
builder: (context) {
String videoUrl = '${HttpString.baseUrl}/video/$bvid';
return AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(vertical: 12),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
title: const Text(
'复制链接',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
Utils.copyText(videoUrl);
},
),
ListTile(
title: const Text(
'其它app打开',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
Utils.launchURL(videoUrl);
},
),
ListTile(
title: const Text(
'分享视频',
style: TextStyle(fontSize: 14),
),
onTap: () {
Get.back();
Share.share('${videoDetail.value.title} '
'UP主: ${videoDetail.value.owner!.name!}'
' - $videoUrl');
},
),
],
),
);
});
}
Future queryVideoInFolder() async {
var result = await VideoHttp.videoInFolder(
mid: userInfo.mid, 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 {
if (videoDetail.value.owner == null) {
return;
}
var result = await VideoHttp.hasFollow(mid: videoDetail.value.owner!.mid!);
if (result['status']) {
followStatus.value = result['data'];
}
return result;
}
// 关注/取关up
Future actionRelationMod(BuildContext context) async {
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
int attr = followStatus['attribute'] ?? 0;
if (attr == 128) {
dynamic res = await VideoHttp.relationMod(
mid: videoDetail.value.owner?.mid ?? -1,
act: attr != 128 ? 5 : 6,
reSrc: 11,
);
if (res['status']) {
followStatus['attribute'] = 0;
followStatus.refresh();
}
return;
}
if (context.mounted) {
Utils.actionRelationMod(
context: context,
mid: videoDetail.value.owner?.mid,
isFollow: (followStatus['attribute'] ?? 0) != 0,
callback: (attribute) {
followStatus['attribute'] = attribute;
followStatus.refresh();
},
);
}
// MemberController _ = Get.put<MemberController>(MemberController(mid: mid),
// tag: mid.toString());
// await _.getInfo();
// if (context.mounted) await _.actionRelationMod(context);
// followStatus['attribute'] = _.attribute.value;
// followStatus.refresh();
// Get.delete<MemberController>(tag: mid.toString());
}
// 修改分P或番剧分集
Future changeSeasonOrbangu(epid, bvid, cid, aid, cover) async {
// 重新获取视频资源
final videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag)
..plPlayerController.pause()
..makeHeartBeat()
..playedTime = null
..videoUrl = null
..audioUrl = null
..updateMediaListHistory(aid)
..vttSubtitlesIndex = null
..savedDanmaku = null
..bvid = bvid
..oid.value = aid ?? IdUtils.bv2av(bvid)
..cid.value = cid
..danmakuCid.value = cid
..queryVideoUrl();
if (cover is String && cover.isNotEmpty) {
videoDetailCtr.videoItem['pic'] = cover;
}
// 重新请求相关视频
if (videoDetailCtr.showRelatedVideo) {
try {
Get.find<RelatedController>(tag: heroTag)
..bvid = bvid
..queryData();
} catch (_) {}
}
// 重新请求评论
if (videoDetailCtr.showReply) {
try {
Get.find<VideoReplyController>(tag: heroTag)
..aid = aid
..onRefresh();
} catch (_) {}
}
this.bvid = bvid;
lastPlayCid.value = cid;
queryVideoIntro();
if (isShowOnlineTotal) {
queryOnlineTotal();
}
}
void startTimer() {
if (isShowOnlineTotal) {
queryOnlineTotal();
timer ??= Timer.periodic(const Duration(seconds: 10), (Timer timer) {
queryOnlineTotal();
});
}
}
void canelTimer() {
timer?.cancel();
timer = null;
}
// 查看同时在看人数
Future queryOnlineTotal() async {
dynamic result = await VideoHttp.onlineTotal(
aid: IdUtils.bv2av(bvid),
bvid: bvid,
cid: lastPlayCid.value,
);
// dynamic result = await GrpcRepo.playerOnline(
// aid: IdUtils.bv2av(bvid),
// cid: lastPlayCid.value,
// );
if (result['status']) {
total.value = result['data'];
}
}
@override
void onClose() {
canelTimer();
expandableCtr?.dispose();
expandableCtr = null;
super.onClose();
}
/// 播放上一个
bool prevPlay() {
final List episodes = [];
bool isPages = false;
if ((videoDetail.value.pages?.length ?? 0) > 1) {
isPages = true;
final List<Part> pages = videoDetail.value.pages!;
episodes.addAll(pages);
} else if (videoDetail.value.ugcSeason != null) {
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
final List<SectionItem> sections = ugcSeason.sections!;
for (int i = 0; i < sections.length; i++) {
final List<EpisodeItem> episodesList = sections[i].episodes!;
episodes.addAll(episodesList);
}
}
final int currentIndex =
episodes.indexWhere((e) => e.cid == lastPlayCid.value);
int prevIndex = currentIndex - 1;
final videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
final PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat;
// 列表循环
if (prevIndex < 0) {
if (playRepeat == PlayRepeat.listCycle) {
prevIndex = episodes.length - 1;
} else {
return false;
}
}
final int cid = episodes[prevIndex].cid!;
final String rBvid = isPages ? bvid : episodes[prevIndex].bvid;
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[prevIndex].aid!;
changeSeasonOrbangu(null, rBvid, cid, rAid, null);
return true;
}
/// 列表循环或者顺序播放时,自动播放下一个
bool nextPlay() {
final List episodes = [];
bool isPages = false;
final videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
// part -> playall -> season
if ((videoDetail.value.pages?.length ?? 0) > 1) {
isPages = true;
final List<Part> pages = videoDetail.value.pages!;
episodes.addAll(pages);
} else if (videoDetailCtr.isPlayAll) {
episodes.addAll(videoDetailCtr.mediaList);
} else if (videoDetail.value.ugcSeason != null) {
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
final List<SectionItem> sections = ugcSeason.sections!;
for (int i = 0; i < sections.length; i++) {
final List<EpisodeItem> episodesList = sections[i].episodes!;
episodes.addAll(episodesList);
}
}
final PlayRepeat playRepeat = videoDetailCtr.plPlayerController.playRepeat;
if (episodes.isEmpty) {
if (playRepeat == PlayRepeat.autoPlayRelated &&
videoDetailCtr.showRelatedVideo) {
return playRelated();
}
return false;
}
final int currentIndex =
episodes.indexWhere((e) => e.cid == videoDetailCtr.cid.value);
int nextIndex = currentIndex + 1;
if (videoDetailCtr.isPlayAll && currentIndex == episodes.length - 2) {
videoDetailCtr.getMediaList();
}
// 列表循环
if (nextIndex >= episodes.length) {
if (playRepeat == PlayRepeat.listCycle) {
nextIndex = 0;
} else if (playRepeat == PlayRepeat.autoPlayRelated &&
videoDetailCtr.showRelatedVideo) {
return playRelated();
} else {
return false;
}
}
int cid = episodes[nextIndex].cid!;
while (cid == -1) {
SmartDialog.showToast('当前视频暂不支持播放,自动跳过');
nextIndex++;
if (nextIndex >= episodes.length) {
return false;
}
cid = episodes[nextIndex].cid!;
}
final String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
changeSeasonOrbangu(null, rBvid, cid, rAid, null);
return true;
}
bool playRelated() {
late RelatedController relatedCtr;
try {
relatedCtr = Get.find<RelatedController>(tag: heroTag);
if (relatedCtr.loadingState.value is! Success) {
return false;
}
if ((relatedCtr.loadingState.value as Success).response.isEmpty == true) {
SmartDialog.showToast('暂无相关视频,停止连播');
return false;
}
} catch (_) {
relatedCtr = Get.put(RelatedController(), tag: heroTag);
relatedCtr.queryData().then((value) {
if (value['status']) {
playRelated();
}
});
return false;
}
final HotVideoItemModel videoItem =
(relatedCtr.loadingState.value as Success).response[0];
try {
if (videoItem.cid != null) {
// Get.offNamed(
// '/video?bvid=${videoItem.bvid}&cid=${videoItem.cid}',
// arguments: {
// 'videoItem': videoItem,
// 'heroTag': heroTag,
// },
// );
changeSeasonOrbangu(
null,
videoItem.bvid,
videoItem.cid,
videoItem.aid,
videoItem.pic,
);
} else {
SearchHttp.ab2c(aid: videoItem.aid, bvid: videoItem.bvid).then(
(cid) => Get.offNamed(
'/video?bvid=${videoItem.bvid}&cid=${videoItem.cid}',
arguments: {
'videoItem': videoItem,
'heroTag': heroTag,
}),
);
}
} catch (err) {
SmartDialog.showToast(err.toString());
}
return true;
}
// 设置关注分组
void setFollowGroup() {
if (videoDetail.value.owner == null) {
return;
}
Get.bottomSheet(
GroupPanel(mid: videoDetail.value.owner!.mid!),
isScrollControlled: true,
);
}
// ai总结
Future aiConclusion() async {
SmartDialog.showLoading(msg: '正在获取AI总结');
final res = await VideoHttp.aiConclusion(
bvid: bvid,
cid: lastPlayCid.value,
upMid: videoDetail.value.owner?.mid,
);
SmartDialog.dismiss();
if (res['status']) {
modelResult = res['data'].modelResult;
} else {
SmartDialog.showToast("当前视频可能暂不支持AI视频总结");
}
return res;
}
}
class PayCoinsPage extends StatefulWidget {
const PayCoinsPage({
super.key,
required this.callback,
this.copyright = 1,
});
final Function callback;
final int copyright;
@override
State<PayCoinsPage> createState() => _PayCoinsPageState();
}
class _PayCoinsPageState extends State<PayCoinsPage>
with TickerProviderStateMixin {
bool _isPaying = false;
late final _controller = PageController(viewportFraction: 0.30);
int get _index => _controller.hasClients ? _controller.page?.round() ?? 0 : 0;
late AnimationController _slide22Controller;
late AnimationController _scale22Controller;
late AnimationController _coinSlideController;
late AnimationController _coinFadeController;
late AnimationController _boxAnimController;
final List _images = [
'assets/images/paycoins/ic_thunder_1.png',
'assets/images/paycoins/ic_thunder_2.png',
'assets/images/paycoins/ic_thunder_3.png',
];
late int _imageIndex = -1;
Timer? _timer;
bool get _showThunder => _imageIndex != -1 && _imageIndex != _images.length;
@override
void initState() {
super.initState();
_slide22Controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 50),
);
_scale22Controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 50),
);
_coinSlideController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
_coinFadeController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 100),
);
_boxAnimController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 50),
);
_scale();
}
@override
void dispose() {
_timer?.cancel();
_slide22Controller.dispose();
_scale22Controller.dispose();
_coinSlideController.dispose();
_coinFadeController.dispose();
_boxAnimController.dispose();
_controller.dispose();
super.dispose();
}
void _scale() {
_scale22Controller.forward().whenComplete(() {
_scale22Controller.reverse();
});
}
void _onScroll(int index) {
_controller.animateToPage(
index,
duration: const Duration(milliseconds: 200),
curve: Curves.ease,
);
_scale();
}
@override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return _buildBody(constraints.maxHeight > constraints.maxWidth);
});
}
Widget _buildBody(isV) => Stack(
alignment: Alignment.center,
children: [
Visibility(
visible: _showThunder,
maintainSize: true,
maintainAnimation: true,
maintainState: true,
child: Image.asset(_images[_showThunder ? _imageIndex : 0]),
),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
children: [
Visibility(
visible: !_isPaying && widget.copyright == 1,
maintainSize: true,
maintainAnimation: true,
maintainState: true,
child: GestureDetector(
onTap: _index == 0
? null
: () {
_onScroll(0);
},
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Image.asset(
width: 16,
height: 28,
_index == 0
? 'assets/images/paycoins/ic_left_disable.png'
: 'assets/images/paycoins/ic_left.png',
),
),
),
),
Expanded(
child: SizedBox(
height: 100,
child: PageView.builder(
itemCount: widget.copyright == 1 ? 2 : 1,
controller: _controller,
onPageChanged: (index) => setState(() {
_scale();
}),
itemBuilder: (context, index) {
return ListenableBuilder(
listenable: _controller,
builder: (context, child) {
double factor = index == 0 ? 1 : 0;
if (_controller.position.hasContentDimensions) {
factor = 1 - (_controller.page! - index).abs();
}
return Visibility(
visible: !_isPaying || _index == index,
child: Center(
child: SizedBox(
height: 70 + (factor * 30),
width: 70 + (factor * 30),
child: Stack(
alignment: Alignment.center,
children: [
SlideTransition(
position: _boxAnimController.drive(
Tween(
begin: const Offset(0.0, 0.0),
end: const Offset(0.0, -0.2),
),
),
child: Image.asset(
'assets/images/paycoins/ic_pay_coins_box.png',
),
),
SlideTransition(
position: _coinSlideController.drive(
Tween(
begin: const Offset(0.0, 0.0),
end: const Offset(0.0, -2),
),
),
child: FadeTransition(
opacity: Tween<double>(
begin: 1, end: 0)
.animate(_coinFadeController),
child: Image.asset(
height: 35 + (factor * 15),
width: 35 + (factor * 15),
index == 0
? 'assets/images/paycoins/ic_coins_one.png'
: 'assets/images/paycoins/ic_coins_two.png',
),
),
),
],
),
),
),
);
},
);
},
),
),
),
Visibility(
visible: !_isPaying && widget.copyright == 1,
maintainSize: true,
maintainAnimation: true,
maintainState: true,
child: GestureDetector(
onTap: _index == 1
? null
: () {
_onScroll(1);
},
child: Padding(
padding: const EdgeInsets.only(right: 12),
child: Image.asset(
width: 16,
height: 28,
_index == 1
? 'assets/images/paycoins/ic_right_disable.png'
: 'assets/images/paycoins/ic_right.png',
),
),
),
),
],
),
const SizedBox(height: 25),
GestureDetector(
behavior: HitTestBehavior.opaque,
onPanUpdate: _handlePanUpdate,
child: SizedBox(
width: double.infinity,
height: 140,
child: Center(
child: GestureDetector(
onTap: _onPayCoin,
onPanUpdate: (e) => _handlePanUpdate(e, true),
child: ScaleTransition(
scale: _scale22Controller.drive(
Tween(begin: 1, end: 1.2),
),
child: SlideTransition(
position: _slide22Controller.drive(
Tween(
begin: const Offset(0.0, 0.0),
end: const Offset(0.0, -0.2),
),
),
child: SizedBox(
width: 100,
height: 140,
child: Image.asset(
_index == 0
? 'assets/images/paycoins/ic_22_mario.png'
: 'assets/images/paycoins/ic_22_gun_sister.png',
),
),
),
),
),
),
),
),
SizedBox(
height:
(isV ? 50 : 0) + MediaQuery.of(context).padding.bottom),
],
),
],
);
void _handlePanUpdate(DragUpdateDetails e, [bool needV = false]) {
if (needV && e.delta.dy.abs() > max(2, e.delta.dx.abs())) {
if (e.delta.dy < 0) {
_onPayCoin();
}
} else if (widget.copyright == 1 &&
e.delta.dx.abs() > max(2, e.delta.dy.abs())) {
if (e.delta.dx > 0) {
if (_index == 1) {
_onScroll(0);
setState(() {});
}
} else {
if (_index == 0) {
_onScroll(1);
setState(() {});
}
}
}
}
void _onPayCoin() {
if (_isPaying) return;
setState(() {
_isPaying = true;
});
_slide22Controller.forward().whenComplete(() {
_slide22Controller.reverse().whenComplete(() {
if (_index == 1) {
_timer ??= Timer.periodic(const Duration(milliseconds: 50 ~/ 3), (_) {
if (_imageIndex != _images.length) {
setState(() {
_imageIndex = _imageIndex + 1;
});
} else {
_timer?.cancel();
}
});
}
_boxAnimController.forward().whenComplete(() {
_boxAnimController.reverse();
});
_coinSlideController.forward().whenComplete(() {
_coinFadeController.forward().whenComplete(() {
Get.back();
widget.callback(_index + 1);
});
});
});
});
}
}