mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
mod: 侧边栏、动态重构,排行改为首页分区,平板、折叠屏、竖屏视频新适配,播放页可隐藏黑边、截图、点踩,弹幕粗细调整,默认关闭后台播放,弹窗接受返回
This commit is contained in:
@@ -8,9 +8,6 @@ class VideoCardHSkeleton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Skeleton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace, 7, StyleString.safeSpace, 7),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double width =
|
||||
@@ -27,8 +24,7 @@ class VideoCardHSkeleton extends StatelessWidget {
|
||||
builder: (context, boxConstraints) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
borderRadius:
|
||||
BorderRadius.circular(StyleString.imgRadius.x),
|
||||
),
|
||||
@@ -64,17 +60,15 @@ class VideoCardHSkeleton extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 40,
|
||||
height: 13,
|
||||
margin: const EdgeInsets.only(right: 8),
|
||||
),
|
||||
Container(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
width: 40,
|
||||
height: 13,
|
||||
),
|
||||
@@ -88,7 +82,6 @@ class VideoCardHSkeleton extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../utils/download.dart';
|
||||
import '../constants.dart';
|
||||
import 'network_img_layer.dart';
|
||||
@@ -11,9 +14,10 @@ class OverlayPop extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double imgWidth = MediaQuery.sizeOf(context).width - 8 * 2;
|
||||
final double imgWidth = min(Get.height,Get.width) - 8 * 2;
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
width: imgWidth,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(10.0),
|
||||
@@ -70,6 +74,7 @@ class OverlayPop extends StatelessWidget {
|
||||
tooltip: '保存封面图',
|
||||
onPressed: () async {
|
||||
await DownloadUtils.downloadImg(
|
||||
context,
|
||||
videoItem.pic != null
|
||||
? videoItem.pic as String
|
||||
: videoItem.cover as String,
|
||||
|
||||
@@ -72,21 +72,9 @@ class VideoCardH extends StatelessWidget {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||
child: LayoutBuilder(
|
||||
builder:
|
||||
(BuildContext context, BoxConstraints boxConstraints) {
|
||||
final double width = (boxConstraints.maxWidth -
|
||||
StyleString.cardSpace *
|
||||
6 /
|
||||
MediaQuery.textScalerOf(context).scale(1.0)) /
|
||||
2;
|
||||
return Container(
|
||||
constraints: const BoxConstraints(minHeight: 88),
|
||||
height: width / StyleString.aspectRatio,
|
||||
child: Row(
|
||||
builder: (BuildContext context, BoxConstraints boxConstraints) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
@@ -96,8 +84,7 @@ class VideoCardH extends StatelessWidget {
|
||||
builder: (BuildContext context,
|
||||
BoxConstraints boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight =
|
||||
boxConstraints.maxHeight;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
@@ -140,20 +127,18 @@ class VideoCardH extends StatelessWidget {
|
||||
showPubdate: showPubdate,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
if (source == 'normal')
|
||||
Positioned(
|
||||
bottom: 1,
|
||||
bottom: 0,
|
||||
right: 10,
|
||||
child: VideoPopupMenu(
|
||||
size: 30,
|
||||
iconSize: 16,
|
||||
size: 29,
|
||||
iconSize: 17,
|
||||
videoItem: videoItem,
|
||||
),
|
||||
),
|
||||
@@ -182,6 +167,10 @@ class VideoContent extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String pubdate = showPubdate
|
||||
? Utils.dateFormat(videoItem.pubdate!, formatType: 'day')
|
||||
: '';
|
||||
if (pubdate != '') pubdate += ' ';
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
|
||||
@@ -247,7 +236,7 @@ class VideoContent extends StatelessWidget {
|
||||
Expanded(
|
||||
flex: 0,
|
||||
child: Text(
|
||||
"${showPubdate ? Utils.dateFormat(videoItem.pubdate!, formatType: 'day') + ' ' : ''} ${showOwner ? videoItem.owner.name : ''}",
|
||||
"${pubdate}${showOwner ? videoItem.owner.name : ''}",
|
||||
maxLines: 1,
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
@@ -276,7 +265,7 @@ class VideoContent extends StatelessWidget {
|
||||
if (source == 'normal') const SizedBox(width: 24),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
const SizedBox(height: 5),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -191,8 +191,8 @@ class VideoCardV extends StatelessWidget {
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: VideoPopupMenu(
|
||||
size: 30,
|
||||
iconSize: 16,
|
||||
size: 29,
|
||||
iconSize: 17,
|
||||
videoItem: videoItem,
|
||||
)),
|
||||
]);
|
||||
@@ -224,7 +224,8 @@ class VideoContent extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
const Spacer(),
|
||||
// const SizedBox(height: 2),
|
||||
VideoStat(
|
||||
videoItem: videoItem,
|
||||
),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import '../../http/user.dart';
|
||||
import '../../http/video.dart';
|
||||
@@ -11,7 +12,7 @@ class VideoPopupMenu extends StatelessWidget {
|
||||
final double? size;
|
||||
final double? iconSize;
|
||||
final dynamic videoItem;
|
||||
final double menuItemHeight = 50;
|
||||
final double menuItemHeight = 45;
|
||||
|
||||
const VideoPopupMenu({
|
||||
Key? key,
|
||||
@@ -53,10 +54,44 @@ class VideoPopupMenu extends StatelessWidget {
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
onTap: () async {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
Get.toNamed('/member?mid=${videoItem.owner.mid}', arguments: {
|
||||
'face': videoItem.owner.face,
|
||||
'heroTag': '${videoItem.owner.mid}',
|
||||
});
|
||||
},
|
||||
value: 'visit',
|
||||
height: menuItemHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(CupertinoIcons.person, size: 16),
|
||||
const SizedBox(width: 6),
|
||||
Text('访问:${videoItem.owner.name}',
|
||||
style: const TextStyle(fontSize: 13))
|
||||
],
|
||||
),
|
||||
),
|
||||
// 不感兴趣
|
||||
PopupMenuItem<String>(
|
||||
onTap: () async {
|
||||
// var res = await VideoHttp.dislike(bvid: videoItem.bvid as String);
|
||||
// SmartDialog.showToast(res['msg']);
|
||||
SmartDialog.showToast("暂未实现");
|
||||
},
|
||||
value: 'dislike',
|
||||
height: menuItemHeight,
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(CupertinoIcons.hand_thumbsdown, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text('不感兴趣', style: TextStyle(fontSize: 13))
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
onTap: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: Text(
|
||||
@@ -64,7 +99,7 @@ class VideoPopupMenu extends StatelessWidget {
|
||||
'\n\n注:被拉黑的Up可以在隐私设置-黑名单管理中解除'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'点错了',
|
||||
style: TextStyle(
|
||||
@@ -86,7 +121,7 @@ class VideoPopupMenu extends StatelessWidget {
|
||||
blackMidsList.insert(0, videoItem.owner.mid);
|
||||
GStrorage.setting
|
||||
.put(SettingBoxKey.blackMidsList, blackMidsList);
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
SmartDialog.showToast(res['msg'] ?? '成功');
|
||||
},
|
||||
child: const Text('确认'),
|
||||
|
||||
@@ -42,6 +42,12 @@ class Api {
|
||||
|
||||
// 视频点踩 web端不支持
|
||||
|
||||
// 点踩 Post(app端)
|
||||
/// access_key str APP登录Token 必要
|
||||
/// aid num 稿件avid 必要
|
||||
///
|
||||
static const String dislikeVideo = '${HttpString.appBaseUrl}/x/v2/view/dislike';
|
||||
|
||||
// 投币视频(web端)POST
|
||||
/// aid num 稿件avid 必要(可选) avid与bvid任选一个
|
||||
/// bvid str 稿件bvid 必要(可选) avid与bvid任选一个
|
||||
@@ -334,10 +340,26 @@ class Api {
|
||||
|
||||
static const String webDanmaku = '/x/v2/dm/web/seg.so';
|
||||
|
||||
//发送视频弹幕
|
||||
// 发送视频弹幕
|
||||
//https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/danmaku/action.md
|
||||
static const String shootDanmaku = '/x/v2/dm/post';
|
||||
|
||||
// 弹幕屏蔽查询(Get)
|
||||
static const String danmakuFilter = '/x/dm/filter/user';
|
||||
|
||||
// 弹幕屏蔽词添加(Post)
|
||||
// 表单内容:
|
||||
// type: 0(关键词)1(正则)2(用户)
|
||||
// filter: 屏蔽内容
|
||||
// csrf
|
||||
static const String danmakuFilterAdd = '/x/dm/filter/user/add';
|
||||
|
||||
// 弹幕屏蔽词删除(Post)
|
||||
// 表单内容:
|
||||
// ids: 被删除条目编号
|
||||
// csrf
|
||||
static const String danmakuFilterDel = '/x/dm/filter/user/del';
|
||||
|
||||
// up主分组
|
||||
static const String followUpTag = '/x/relation/tags';
|
||||
|
||||
|
||||
63
lib/http/danmaku_block.dart
Normal file
63
lib/http/danmaku_block.dart
Normal file
@@ -0,0 +1,63 @@
|
||||
import '../models/user/danmaku_block.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class DanmakuFilterHttp {
|
||||
static Future danmakuFilter() async {
|
||||
var res = await Request().get(Api.danmakuFilter);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': DanmakuBlockDataModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future danmakuFilterDel({required int ids}) async {
|
||||
var res = await Request().post(
|
||||
Api.danmakuFilterDel,
|
||||
queryParameters: {
|
||||
'ids': ids,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'msg': '操作成功',
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future danmakuFilterAdd({required String filter, required int type}) async {
|
||||
var res = await Request().post(
|
||||
Api.danmakuFilterAdd,
|
||||
queryParameters: {
|
||||
'type': type,
|
||||
'filter': filter,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': Rule.fromJson(res.data['data']),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,13 @@ import 'index.dart';
|
||||
class DynamicsHttp {
|
||||
static Future followDynamic({
|
||||
String? type,
|
||||
int? page,
|
||||
String? offset,
|
||||
int? mid,
|
||||
}) async {
|
||||
Map<String, dynamic> data = {
|
||||
'type': type ?? 'all',
|
||||
'page': page ?? 1,
|
||||
'timezone_offset': '-480',
|
||||
'offset': page == 1 ? '' : offset,
|
||||
'offset': offset,
|
||||
'features': 'itemOpusStyle'
|
||||
};
|
||||
if (mid != -1) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import '../models/user/fav_folder.dart';
|
||||
import '../models/video/ai.dart';
|
||||
import '../models/video/play/url.dart';
|
||||
import '../models/video_detail_res.dart';
|
||||
import '../utils/id_utils.dart';
|
||||
import '../utils/recommend_filter.dart';
|
||||
import '../utils/storage.dart';
|
||||
import '../utils/wbi_sign.dart';
|
||||
@@ -319,6 +320,29 @@ class VideoHttp {
|
||||
}
|
||||
}
|
||||
|
||||
// (取消)点踩
|
||||
static Future dislikeVideo({required String bvid, required bool type}) async {
|
||||
String? accessKey = GStrorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'];
|
||||
if (accessKey == null || accessKey == "") {
|
||||
return {'status': false, 'data': [], 'msg': "本操作使用app端接口,请前往【隐私设置】刷新access_key"};
|
||||
}
|
||||
var res = await Request().post(
|
||||
Api.dislikeVideo,
|
||||
queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
'dislike': type ? 0 : 1,
|
||||
'access_key': accessKey,
|
||||
},
|
||||
);
|
||||
print(res);
|
||||
if (res.data is! String && res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// (取消)收藏
|
||||
static Future favVideo(
|
||||
{required int aid, String? addIds, String? delIds}) async {
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
final List<Map<String, dynamic>> colorThemeTypes = [
|
||||
{'color': const Color.fromARGB(255, 92, 182, 123), 'label': '默认绿'},
|
||||
{'color': Colors.pink, 'label': '粉红色'},
|
||||
{'color': const Color.fromARGB(255, 251, 114, 153), 'label': '粉红色'},
|
||||
{'color': Colors.red, 'label': '红色'},
|
||||
{'color': Colors.orange, 'label': '橙色'},
|
||||
{'color': Colors.amber, 'label': '琥珀色'},
|
||||
|
||||
@@ -1,11 +1,56 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../../pages/dynamics/tab/controller.dart';
|
||||
import '../../pages/dynamics/tab/view.dart';
|
||||
|
||||
enum DynamicsType {
|
||||
all,
|
||||
video,
|
||||
pgc,
|
||||
article,
|
||||
up,
|
||||
}
|
||||
|
||||
extension BusinessTypeExtension on DynamicsType {
|
||||
String get values => ['all', 'video', 'pgc', 'article'][index];
|
||||
String get labels => ['全部', '投稿', '番剧', '专栏'][index];
|
||||
String get values => ['all', 'video', 'pgc', 'article', 'up'][index];
|
||||
String get labels => ['全部', '投稿', '番剧', '专栏', 'Up'][index];
|
||||
}
|
||||
|
||||
List tabsConfig = [
|
||||
{
|
||||
'value': DynamicsType.all,
|
||||
'label': '全部',
|
||||
'enabled': true,
|
||||
'ctr': Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'all'),
|
||||
'page': const DynamicsTabPage(dynamicsType: 'all'),
|
||||
},
|
||||
{
|
||||
'value': DynamicsType.video,
|
||||
'label': '投稿',
|
||||
'enabled': true,
|
||||
'ctr':
|
||||
Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'video'),
|
||||
'page': const DynamicsTabPage(dynamicsType: 'video'),
|
||||
},
|
||||
{
|
||||
'value': DynamicsType.pgc,
|
||||
'label': '番剧',
|
||||
'enabled': true,
|
||||
'ctr': Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'pgc'),
|
||||
'page': const DynamicsTabPage(dynamicsType: 'pgc'),
|
||||
},
|
||||
{
|
||||
'value': DynamicsType.article,
|
||||
'label': '专栏',
|
||||
'enabled': true,
|
||||
'ctr':
|
||||
Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'article'),
|
||||
'page': const DynamicsTabPage(dynamicsType: 'article'),
|
||||
},
|
||||
{
|
||||
'value': DynamicsType.up,
|
||||
'label': 'Up',
|
||||
'enabled': true,
|
||||
'ctr': Get.put<DynamicsTabController>(DynamicsTabController(), tag: 'up'),
|
||||
'page': const DynamicsTabPage(dynamicsType: 'up'),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -5,30 +5,17 @@ List defaultNavigationBars = [
|
||||
'id': 0,
|
||||
'icon': const Icon(
|
||||
Icons.home_outlined,
|
||||
size: 21,
|
||||
size: 23,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.home,
|
||||
size: 21,
|
||||
size: 23,
|
||||
),
|
||||
'label': "首页",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 1,
|
||||
'icon': const Icon(
|
||||
Icons.leaderboard_outlined,
|
||||
size: 21,
|
||||
),
|
||||
'selectIcon': const Icon(
|
||||
Icons.leaderboard,
|
||||
size: 21,
|
||||
),
|
||||
'label': "排行榜",
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 2,
|
||||
'icon': const Icon(
|
||||
Icons.motion_photos_on_outlined,
|
||||
size: 21,
|
||||
@@ -41,7 +28,7 @@ List defaultNavigationBars = [
|
||||
'count': 0,
|
||||
},
|
||||
{
|
||||
'id': 3,
|
||||
'id': 2,
|
||||
'icon': const Icon(
|
||||
Icons.video_collection_outlined,
|
||||
size: 20,
|
||||
|
||||
@@ -74,7 +74,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '全站',
|
||||
'type': RandType.all,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '0'),
|
||||
'page': const ZonePage(rid: 0),
|
||||
},
|
||||
{
|
||||
@@ -84,7 +84,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '国创',
|
||||
'type': RandType.creation,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '168'),
|
||||
'page': const ZonePage(rid: 168),
|
||||
},
|
||||
{
|
||||
@@ -94,7 +94,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '动画',
|
||||
'type': RandType.animation,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '1'),
|
||||
'page': const ZonePage(rid: 1),
|
||||
},
|
||||
{
|
||||
@@ -104,7 +104,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '音乐',
|
||||
'type': RandType.music,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '3'),
|
||||
'page': const ZonePage(rid: 3),
|
||||
},
|
||||
{
|
||||
@@ -114,7 +114,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '舞蹈',
|
||||
'type': RandType.dance,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '129'),
|
||||
'page': const ZonePage(rid: 129),
|
||||
},
|
||||
{
|
||||
@@ -124,7 +124,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '游戏',
|
||||
'type': RandType.game,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '4'),
|
||||
'page': const ZonePage(rid: 4),
|
||||
},
|
||||
{
|
||||
@@ -134,7 +134,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '知识',
|
||||
'type': RandType.knowledge,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '36'),
|
||||
'page': const ZonePage(rid: 36),
|
||||
},
|
||||
{
|
||||
@@ -144,7 +144,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '科技',
|
||||
'type': RandType.technology,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '188'),
|
||||
'page': const ZonePage(rid: 188),
|
||||
},
|
||||
{
|
||||
@@ -154,7 +154,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '运动',
|
||||
'type': RandType.sport,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '234'),
|
||||
'page': const ZonePage(rid: 234),
|
||||
},
|
||||
{
|
||||
@@ -164,7 +164,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '汽车',
|
||||
'type': RandType.car,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '223'),
|
||||
'page': const ZonePage(rid: 223),
|
||||
},
|
||||
{
|
||||
@@ -174,7 +174,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '生活',
|
||||
'type': RandType.life,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '160'),
|
||||
'page': const ZonePage(rid: 160),
|
||||
},
|
||||
{
|
||||
@@ -184,7 +184,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '美食',
|
||||
'type': RandType.food,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '211'),
|
||||
'page': const ZonePage(rid: 211),
|
||||
},
|
||||
{
|
||||
@@ -194,7 +194,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '动物',
|
||||
'type': RandType.animal,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '217'),
|
||||
'page': const ZonePage(rid: 217),
|
||||
},
|
||||
{
|
||||
@@ -204,7 +204,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '鬼畜',
|
||||
'type': RandType.madness,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '119'),
|
||||
'page': const ZonePage(rid: 119),
|
||||
},
|
||||
{
|
||||
@@ -214,7 +214,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '时尚',
|
||||
'type': RandType.fashion,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '155'),
|
||||
'page': const ZonePage(rid: 155),
|
||||
},
|
||||
{
|
||||
@@ -224,7 +224,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '娱乐',
|
||||
'type': RandType.entertainment,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '5'),
|
||||
'page': const ZonePage(rid: 5),
|
||||
},
|
||||
{
|
||||
@@ -234,7 +234,7 @@ List tabsConfig = [
|
||||
),
|
||||
'label': '影视',
|
||||
'type': RandType.film,
|
||||
'ctr': Get.put<ZoneController>,
|
||||
'ctr': Get.put<ZoneController>(ZoneController(), tag: '181'),
|
||||
'page': const ZonePage(rid: 181),
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPalaX/pages/rank/index.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/pages/bangumi/index.dart';
|
||||
@@ -5,11 +6,11 @@ import 'package:PiliPalaX/pages/hot/index.dart';
|
||||
import 'package:PiliPalaX/pages/live/index.dart';
|
||||
import 'package:PiliPalaX/pages/rcmd/index.dart';
|
||||
|
||||
enum TabType { live, rcmd, hot, bangumi }
|
||||
enum TabType { live, rcmd, hot, rank, bangumi }
|
||||
|
||||
extension TabTypeDesc on TabType {
|
||||
String get description => ['直播', '推荐', '热门', '番剧'][index];
|
||||
String get id => ['live', 'rcmd', 'hot', 'bangumi'][index];
|
||||
String get description => ['直播', '推荐', '热门', '分区', '番剧'][index];
|
||||
String get id => ['live', 'rcmd', 'hot', 'rank', 'bangumi'][index];
|
||||
}
|
||||
|
||||
List tabsConfig = [
|
||||
@@ -43,6 +44,16 @@ List tabsConfig = [
|
||||
'ctr': Get.find<HotController>,
|
||||
'page': const HotPage(),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.category_outlined,
|
||||
size: 15,
|
||||
),
|
||||
'label': '分区',
|
||||
'type': TabType.rank,
|
||||
'ctr': Get.find<RankController>,
|
||||
'page': const RankPage(),
|
||||
},
|
||||
{
|
||||
'icon': const Icon(
|
||||
Icons.play_circle_outlined,
|
||||
|
||||
15
lib/models/common/up_panel_position.dart
Normal file
15
lib/models/common/up_panel_position.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
enum UpPanelPosition {
|
||||
leftFixed,
|
||||
rightFixed,
|
||||
leftDrawer,
|
||||
rightDrawer,
|
||||
}
|
||||
|
||||
extension UpPanelPositionDesc on UpPanelPosition {
|
||||
String get values => ['left_fixed', 'right_fixed', 'left_drawer', 'right_drawer'][index];
|
||||
String get labels => ['左侧常驻','右侧常驻','左侧抽屉','右侧抽屉'][index];
|
||||
}
|
||||
|
||||
extension UpPanelPositionCode on UpPanelPosition {
|
||||
int get code => [0, 1, 2, 3][index];
|
||||
}
|
||||
94
lib/models/user/danmaku_block.dart
Normal file
94
lib/models/user/danmaku_block.dart
Normal file
@@ -0,0 +1,94 @@
|
||||
class DanmakuBlockDataModel {
|
||||
List<Rule>? rule;
|
||||
String? toast;
|
||||
int? valid;
|
||||
int? ver;
|
||||
|
||||
DanmakuBlockDataModel({this.rule, this.toast, this.valid, this.ver});
|
||||
|
||||
DanmakuBlockDataModel.fromJson(Map<String, dynamic> json) {
|
||||
if (json['rule'] != null) {
|
||||
rule = <Rule>[];
|
||||
json['rule'].forEach((v) {
|
||||
rule!.add(Rule.fromJson(v));
|
||||
});
|
||||
}
|
||||
toast = json['toast'];
|
||||
valid = json['valid'];
|
||||
ver = json['ver'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
if (rule != null) {
|
||||
data['rule'] = rule!.map((v) => v.toJson()).toList();
|
||||
}
|
||||
data['toast'] = toast;
|
||||
data['valid'] = valid;
|
||||
data['ver'] = ver;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class Rule {
|
||||
int? id;
|
||||
int? mid;
|
||||
int? type;
|
||||
String? filter;
|
||||
String? comment;
|
||||
int? ctime;
|
||||
int? mtime;
|
||||
|
||||
Rule(
|
||||
{this.id,
|
||||
this.mid,
|
||||
this.type,
|
||||
this.filter,
|
||||
this.comment,
|
||||
this.ctime,
|
||||
this.mtime});
|
||||
|
||||
Rule.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
mid = json['mid'];
|
||||
type = json['type'];
|
||||
filter = json['filter'];
|
||||
comment = json['comment'];
|
||||
ctime = json['ctime'];
|
||||
mtime = json['mtime'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final Map<String, dynamic> data = <String, dynamic>{};
|
||||
data['id'] = id;
|
||||
data['mid'] = mid;
|
||||
data['type'] = type;
|
||||
data['filter'] = filter;
|
||||
data['comment'] = comment;
|
||||
data['ctime'] = ctime;
|
||||
data['mtime'] = mtime;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
class SimpleRule {
|
||||
final int id;
|
||||
final int type;
|
||||
final String filter;
|
||||
SimpleRule(this.id, this.type, this.filter);
|
||||
Map<String, dynamic> toMap() {
|
||||
return {
|
||||
'id': id,
|
||||
'type': type,
|
||||
'filter': filter,
|
||||
};
|
||||
}
|
||||
|
||||
factory SimpleRule.fromMap(Map<String, dynamic> map) {
|
||||
return SimpleRule(
|
||||
map['id'],
|
||||
map['type'],
|
||||
map['filter'],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => _aboutController.feedback(),
|
||||
onTap: () => _aboutController.feedback(context),
|
||||
leading: const Icon(Icons.feedback_outlined),
|
||||
title: const Text('问题反馈'),
|
||||
trailing: Icon(
|
||||
@@ -174,7 +174,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
await CacheManage().clearCacheAll();
|
||||
await CacheManage().clearCacheAll(context);
|
||||
getCacheSize();
|
||||
},
|
||||
leading: const Icon(Icons.delete_outline),
|
||||
@@ -185,17 +185,17 @@ class _AboutPageState extends State<AboutPage> {
|
||||
title: const Text('导入/导出设置'),
|
||||
dense: false,
|
||||
leading: const Icon(Icons.import_export_outlined),
|
||||
onTap: () {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
builder: (BuildContext context) {
|
||||
onTap: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: const Text('导入/导出设置'),
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('导出设置至剪贴板'),
|
||||
onTap: () async {
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
String data = await GStrorage.exportAllSettings();
|
||||
Clipboard.setData(ClipboardData(text: data));
|
||||
SmartDialog.showToast('已复制到剪贴板');
|
||||
@@ -204,30 +204,35 @@ class _AboutPageState extends State<AboutPage> {
|
||||
ListTile(
|
||||
title: const Text('从剪贴板导入设置'),
|
||||
onTap: () async {
|
||||
SmartDialog.dismiss();
|
||||
ClipboardData? data = await Clipboard.getData('text/plain');
|
||||
if (data == null || data.text == null || data.text!.isEmpty) {
|
||||
Get.back();
|
||||
ClipboardData? data =
|
||||
await Clipboard.getData('text/plain');
|
||||
if (data == null ||
|
||||
data.text == null ||
|
||||
data.text!.isEmpty) {
|
||||
SmartDialog.showToast('剪贴板无数据');
|
||||
return;
|
||||
}
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
builder: (BuildContext context) {
|
||||
if (!context.mounted) return;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('是否导入如下设置?'),
|
||||
content: Text(data.text!),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
SmartDialog.dismiss();
|
||||
try{
|
||||
await GStrorage.importAllSettings(data.text!);
|
||||
Get.back();
|
||||
try {
|
||||
await GStrorage.importAllSettings(
|
||||
data.text!);
|
||||
SmartDialog.showToast('导入成功');
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('导入失败:$e');
|
||||
@@ -249,27 +254,27 @@ class _AboutPageState extends State<AboutPage> {
|
||||
ListTile(
|
||||
title: const Text('重置所有设置'),
|
||||
leading: const Icon(Icons.settings_backup_restore_outlined),
|
||||
onTap: () {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
builder: (BuildContext context) {
|
||||
onTap: () async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('重置所有设置'),
|
||||
content: const Text('是否重置所有设置?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.back();
|
||||
GStrorage.setting.clear();
|
||||
GStrorage.localCache.clear();
|
||||
GStrorage.video.clear();
|
||||
SmartDialog.showToast('重置成功');
|
||||
SmartDialog.dismiss();
|
||||
},
|
||||
child: const Text('确定'),
|
||||
),
|
||||
@@ -384,11 +389,10 @@ class AboutController extends GetxController {
|
||||
}
|
||||
|
||||
// 问题反馈
|
||||
feedback() {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
feedback(BuildContext context) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: const Text('问题反馈'),
|
||||
children: [
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
@@ -231,9 +232,29 @@ class BangumiIntroController extends GetxController {
|
||||
|
||||
// 分享视频
|
||||
Future actionShareVideo() async {
|
||||
var result = await Share.share('${HttpString.baseUrl}/video/$bvid')
|
||||
.whenComplete(() {});
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
String videoUrl = '${HttpString.baseUrl}/video/$bvid';
|
||||
return AlertDialog(
|
||||
title: const Text('分享方式'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: videoUrl));
|
||||
SmartDialog.showToast('已复制');
|
||||
},
|
||||
child: const Text('复制链接到剪贴板')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var result =
|
||||
await Share.share(videoUrl).whenComplete(() {});
|
||||
return result;
|
||||
},
|
||||
child: const Text('分享视频')),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// 选择文件夹
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPalaX/plugin/pl_player/index.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
@@ -283,56 +284,35 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
: bangumiItem!.stat!['danmakus'],
|
||||
size: 'medium',
|
||||
),
|
||||
if (isLandscape) ...[
|
||||
const SizedBox(width: 6),
|
||||
AreasAndPubTime(
|
||||
widget: widget,
|
||||
bangumiItem: bangumiItem,
|
||||
t: t),
|
||||
const SizedBox(width: 6),
|
||||
NewEpDesc(
|
||||
widget: widget,
|
||||
bangumiItem: bangumiItem,
|
||||
t: t),
|
||||
]
|
||||
],
|
||||
),
|
||||
SizedBox(height: isLandscape ? 2 : 6),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? (widget.bangumiDetail!.areas!
|
||||
.isNotEmpty
|
||||
? widget.bangumiDetail!.areas!
|
||||
.first['name']
|
||||
: '')
|
||||
: (bangumiItem!.areas!.isNotEmpty
|
||||
? 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(height: 4),
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? widget.bangumiDetail!.newEp!['desc']
|
||||
: bangumiItem!.newEp!['desc'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
),
|
||||
// const SizedBox(height: 10),
|
||||
if (!isLandscape)
|
||||
AreasAndPubTime(
|
||||
widget: widget,
|
||||
bangumiItem: bangumiItem,
|
||||
t: t),
|
||||
if (!isLandscape)
|
||||
NewEpDesc(
|
||||
widget: widget,
|
||||
bangumiItem: bangumiItem,
|
||||
t: t),
|
||||
const Spacer(),
|
||||
Text(
|
||||
'简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}',
|
||||
maxLines: isLandscape ? 1 : 3,
|
||||
maxLines: isLandscape ? 2 : 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
@@ -525,3 +505,73 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
class AreasAndPubTime extends StatelessWidget {
|
||||
const AreasAndPubTime({
|
||||
super.key,
|
||||
required this.widget,
|
||||
required this.bangumiItem,
|
||||
required this.t,
|
||||
});
|
||||
|
||||
final BangumiInfo widget;
|
||||
final BangumiInfoModel? bangumiItem;
|
||||
final ThemeData t;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Text(
|
||||
!widget.loadingStatus
|
||||
? (widget.bangumiDetail!.areas!.isNotEmpty
|
||||
? widget.bangumiDetail!.areas!.first['name']
|
||||
: '')
|
||||
: (bangumiItem!.areas!.isNotEmpty
|
||||
? 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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class NewEpDesc extends StatelessWidget {
|
||||
const NewEpDesc({
|
||||
super.key,
|
||||
required this.widget,
|
||||
required this.bangumiItem,
|
||||
required this.t,
|
||||
});
|
||||
|
||||
final BangumiInfo widget;
|
||||
final BangumiInfoModel? bangumiItem;
|
||||
final ThemeData t;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Text(
|
||||
!widget.loadingStatus
|
||||
? widget.bangumiDetail!.newEp!['desc']
|
||||
: bangumiItem!.newEp!['desc'],
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 268,
|
||||
height: Grid.maxRowWidth * 1,
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFutureFollow,
|
||||
builder:
|
||||
@@ -135,8 +135,8 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
itemCount: list.length,
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
width: Get.size.width / 3,
|
||||
height: 254,
|
||||
width: Grid.maxRowWidth / 2,
|
||||
height: Grid.maxRowWidth * 1,
|
||||
margin: EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: index ==
|
||||
@@ -219,17 +219,16 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
}
|
||||
|
||||
Widget contentGrid(ctr, bangumiList) {
|
||||
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace - 2,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 最大宽度
|
||||
maxCrossAxisExtent: Grid.maxRowWidth / 3 * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(context, Grid.maxRowWidth / 3 * 2, StyleString.safeSpace) / 0.65+
|
||||
MediaQuery.textScalerOf(context).scale(60),
|
||||
childAspectRatio: 0.65,
|
||||
mainAxisExtent: MediaQuery.textScalerOf(context).scale(60),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
|
||||
@@ -36,6 +36,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
||||
late double fontSizeVal;
|
||||
late double danmakuDurationVal;
|
||||
late double strokeWidth;
|
||||
late int fontWeight;
|
||||
int latestAddedPosition = -1;
|
||||
|
||||
@override
|
||||
@@ -68,6 +69,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
||||
opacityVal = playerController.opacityVal;
|
||||
fontSizeVal = playerController.fontSizeVal;
|
||||
strokeWidth = playerController.strokeWidth;
|
||||
fontWeight = playerController.fontWeight;
|
||||
danmakuDurationVal = playerController.danmakuDurationVal;
|
||||
}
|
||||
|
||||
@@ -132,6 +134,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
||||
},
|
||||
option: DanmakuOption(
|
||||
fontSize: 15 * fontSizeVal,
|
||||
fontWeight: fontWeight,
|
||||
area: showArea,
|
||||
opacity: opacityVal,
|
||||
hideTop: blockTypes.contains(5),
|
||||
|
||||
182
lib/pages/danmaku_block/index.dart
Normal file
182
lib/pages/danmaku_block/index.dart
Normal file
@@ -0,0 +1,182 @@
|
||||
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:PiliPalaX/utils/storage.dart';
|
||||
|
||||
import '../../http/danmaku_block.dart';
|
||||
import '../../models/user/danmaku_block.dart';
|
||||
|
||||
class DanmakuBlockPage extends StatefulWidget {
|
||||
const DanmakuBlockPage({super.key});
|
||||
|
||||
@override
|
||||
State<DanmakuBlockPage> createState() => _DanmakuBlockPageState();
|
||||
}
|
||||
|
||||
class _DanmakuBlockPageState extends State<DanmakuBlockPage> {
|
||||
final DanmakuBlockController _danmakuBlockController =
|
||||
Get.put(DanmakuBlockController());
|
||||
final ScrollController scrollController = ScrollController();
|
||||
Box setting = GStrorage.setting;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_danmakuBlockController.queryDanmakuFilter();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
List<Map<String, dynamic>> simpleRuleList =
|
||||
_danmakuBlockController.danmakuRules.map<Map<String, dynamic>>((e) {
|
||||
return SimpleRule(e.id!, e.type!, e.filter!).toMap();
|
||||
}).toList();
|
||||
setting.put(SettingBoxKey.danmakuFilterRule, simpleRuleList);
|
||||
scrollController.removeListener(() {});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Obx(
|
||||
() => TabBar(
|
||||
controller: _danmakuBlockController.tabController,
|
||||
dividerColor: Colors.transparent,
|
||||
tabs: [
|
||||
Tab(text: '文本(${_danmakuBlockController.textRules.length})'),
|
||||
Tab(text: '正则(${_danmakuBlockController.regexRules.length})'),
|
||||
Tab(text: '用户(${_danmakuBlockController.userRules.length})'),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async =>
|
||||
await _danmakuBlockController.queryDanmakuFilter(),
|
||||
child: TabBarView(
|
||||
controller: _danmakuBlockController.tabController,
|
||||
children: [
|
||||
Obx(() => tabViewBuilder(0, _danmakuBlockController.textRules)),
|
||||
Obx(() => tabViewBuilder(1, _danmakuBlockController.regexRules)),
|
||||
Obx(() => tabViewBuilder(2, _danmakuBlockController.userRules)),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
Widget tabViewBuilder(int index, List<SimpleRule> list) {
|
||||
return ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: list.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ListTile(
|
||||
title: Text(
|
||||
list[index].filter,
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () async {
|
||||
await _danmakuBlockController.danmakuFilterDel(
|
||||
1, list[index].id);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DanmakuBlockController extends GetxController
|
||||
with GetTickerProviderStateMixin {
|
||||
RxList<Rule> danmakuRules = <Rule>[].obs;
|
||||
RxList<SimpleRule> textRules = <SimpleRule>[].obs;
|
||||
RxList<SimpleRule> regexRules = <SimpleRule>[].obs;
|
||||
RxList<SimpleRule> userRules = <SimpleRule>[].obs;
|
||||
late TabController tabController;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
tabController = TabController(length: 3, vsync: this);
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
tabController.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
Future queryDanmakuFilter() async {
|
||||
var result = await DanmakuFilterHttp.danmakuFilter();
|
||||
if (result['status']) {
|
||||
danmakuRules.value = result['data'].rule;
|
||||
danmakuRules.map((e) {
|
||||
SimpleRule simpleRule = SimpleRule(e.id!, e.type!, e.filter!);
|
||||
switch (e.type!) {
|
||||
case 0:
|
||||
textRules.add(simpleRule);
|
||||
break;
|
||||
case 1:
|
||||
regexRules.add(simpleRule);
|
||||
break;
|
||||
case 2:
|
||||
userRules.add(simpleRule);
|
||||
break;
|
||||
default:
|
||||
SmartDialog.showToast('未知的规则类型:${e.type},内容为:${e.filter}');
|
||||
}
|
||||
}).toList();
|
||||
SmartDialog.showToast(result['data'].toast);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Future danmakuFilterDel(int type, int id) async {
|
||||
var result = await DanmakuFilterHttp.danmakuFilterDel(ids: id);
|
||||
if (result['status']) {
|
||||
danmakuRules.removeWhere((e) => e.id == id);
|
||||
switch (type) {
|
||||
case 0:
|
||||
textRules.removeWhere((e) => e.id == id);
|
||||
break;
|
||||
case 1:
|
||||
regexRules.removeWhere((e) => e.id == id);
|
||||
break;
|
||||
case 2:
|
||||
userRules.removeWhere((e) => e.id == id);
|
||||
break;
|
||||
}
|
||||
SmartDialog.showToast(result['msg']);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
Future danmakuFilterAdd({required String filter, required int type}) async {
|
||||
var result =
|
||||
await DanmakuFilterHttp.danmakuFilterAdd(filter: filter, type: type);
|
||||
if (result['status']) {
|
||||
Rule data = result['data'];
|
||||
danmakuRules.add(data);
|
||||
SimpleRule simpleRule = SimpleRule(data.id!, data.type!, data.filter!);
|
||||
switch (data.type!) {
|
||||
case 0:
|
||||
textRules.add(simpleRule);
|
||||
break;
|
||||
case 1:
|
||||
regexRules.add(simpleRule);
|
||||
break;
|
||||
case 2:
|
||||
userRules.add(simpleRule);
|
||||
break;
|
||||
}
|
||||
SmartDialog.showToast('添加成功');
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'package:PiliPalaX/pages/dynamics/tab/index.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -17,39 +18,17 @@ import 'package:PiliPalaX/utils/id_utils.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
|
||||
class DynamicsController extends GetxController {
|
||||
int page = 1;
|
||||
class DynamicsController extends GetxController
|
||||
with GetTickerProviderStateMixin {
|
||||
String? offset = '';
|
||||
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs;
|
||||
Rx<DynamicsType> dynamicsType = DynamicsType.values[0].obs;
|
||||
RxString dynamicsTypeLabel = '全部'.obs;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
Rx<FollowUpModel> upData = FollowUpModel().obs;
|
||||
// 默认获取全部动态
|
||||
RxInt mid = (-1).obs;
|
||||
Rx<UpItem> upInfo = UpItem().obs;
|
||||
List filterTypeList = [
|
||||
{
|
||||
'label': DynamicsType.all.labels,
|
||||
'value': DynamicsType.all,
|
||||
'enabled': true
|
||||
},
|
||||
{
|
||||
'label': DynamicsType.video.labels,
|
||||
'value': DynamicsType.video,
|
||||
'enabled': true
|
||||
},
|
||||
{
|
||||
'label': DynamicsType.pgc.labels,
|
||||
'value': DynamicsType.pgc,
|
||||
'enabled': true
|
||||
},
|
||||
{
|
||||
'label': DynamicsType.article.labels,
|
||||
'value': DynamicsType.article,
|
||||
'enabled': true
|
||||
},
|
||||
];
|
||||
late TabController tabController;
|
||||
RxList<int> tempBannedList = <int>[].obs;
|
||||
late List<Widget> tabsPageList;
|
||||
bool flag = false;
|
||||
RxInt initialValue = 0.obs;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
@@ -63,53 +42,23 @@ class DynamicsController extends GetxController {
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
userLogin.value = userInfo != null;
|
||||
super.onInit();
|
||||
initialValue.value =
|
||||
setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0);
|
||||
dynamicsType = DynamicsType.values[initialValue.value].obs;
|
||||
|
||||
tabController = TabController(
|
||||
length: tabsConfig.length,
|
||||
vsync: this,
|
||||
initialIndex:
|
||||
setting.get(SettingBoxKey.defaultDynamicType, defaultValue: 0));
|
||||
tabsPageList = tabsConfig.map((e) {
|
||||
return e['page'] as Widget;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Future queryFollowDynamic({type = 'init'}) async {
|
||||
if (!userLogin.value) {
|
||||
return {'status': false, 'msg': '账号未登录'};
|
||||
}
|
||||
if (type == 'init') {
|
||||
dynamicsList.clear();
|
||||
}
|
||||
// 下拉刷新数据渲染时会触发onLoad
|
||||
if (type == 'onLoad' && page == 1) {
|
||||
return;
|
||||
}
|
||||
isLoadingDynamic.value = true;
|
||||
var res = await DynamicsHttp.followDynamic(
|
||||
page: type == 'init' ? 1 : page,
|
||||
type: dynamicsType.value.values,
|
||||
offset: offset,
|
||||
mid: mid.value,
|
||||
);
|
||||
isLoadingDynamic.value = false;
|
||||
if (res['status']) {
|
||||
if (type == 'onLoad' && res['data'].items.isEmpty) {
|
||||
SmartDialog.showToast('没有更多了');
|
||||
return;
|
||||
}
|
||||
if (type == 'init') {
|
||||
dynamicsList.value = res['data'].items;
|
||||
} else {
|
||||
dynamicsList.addAll(res['data'].items);
|
||||
}
|
||||
offset = res['data'].offset;
|
||||
page++;
|
||||
}
|
||||
return res;
|
||||
void refreshNotifier() {
|
||||
queryFollowUp();
|
||||
}
|
||||
|
||||
onSelectType(value) async {
|
||||
dynamicsType.value = filterTypeList[value]['value'];
|
||||
dynamicsList.value = [DynamicItemModel()];
|
||||
page = 1;
|
||||
initialValue.value = value;
|
||||
await queryFollowDynamic();
|
||||
scrollController.jumpTo(0);
|
||||
}
|
||||
|
||||
pushDetail(item, floor, {action = 'all'}) async {
|
||||
@@ -276,21 +225,27 @@ class DynamicsController extends GetxController {
|
||||
}
|
||||
|
||||
onSelectUp(mid) async {
|
||||
dynamicsType.value = DynamicsType.values[0];
|
||||
dynamicsList.value = [DynamicItemModel()];
|
||||
page = 1;
|
||||
queryFollowDynamic();
|
||||
if (mid == this.mid.value) {
|
||||
this.mid.refresh();
|
||||
return;
|
||||
}
|
||||
this.mid.value = mid;
|
||||
tabController.index = (mid == -1 ? 0 : 4);
|
||||
}
|
||||
|
||||
onRefresh() async {
|
||||
page = 1;
|
||||
print('onRefresh');
|
||||
await queryFollowUp();
|
||||
await queryFollowDynamic();
|
||||
print(tabsConfig[tabController.index]['ctr']);
|
||||
await Future.wait(<Future>[
|
||||
queryFollowUp(),
|
||||
tabsConfig[tabController.index]['ctr'].onRefresh()
|
||||
]);
|
||||
}
|
||||
|
||||
// 返回顶部并刷新
|
||||
void animateToTop() async {
|
||||
tabsConfig[tabController.index]['ctr'].animateToTop();
|
||||
if (!scrollController.hasClients) return;
|
||||
if (scrollController.offset >=
|
||||
MediaQuery.of(Get.context!).size.height * 5) {
|
||||
scrollController.jumpTo(0);
|
||||
@@ -299,14 +254,4 @@ class DynamicsController extends GetxController {
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
}
|
||||
}
|
||||
|
||||
// 重置搜索
|
||||
void resetSearch() {
|
||||
mid.value = -1;
|
||||
dynamicsType.value = DynamicsType.values[0];
|
||||
initialValue.value = 0;
|
||||
SmartDialog.showToast('还原默认加载');
|
||||
dynamicsList.value = [DynamicItemModel()];
|
||||
queryFollowDynamic();
|
||||
}
|
||||
}
|
||||
|
||||
76
lib/pages/dynamics/tab/controller.dart
Normal file
76
lib/pages/dynamics/tab/controller.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
// import 'package:hive/hive.dart';
|
||||
|
||||
import '../../../http/dynamics.dart';
|
||||
import '../../../models/dynamics/result.dart';
|
||||
// import '../../../utils/storage.dart';
|
||||
|
||||
class DynamicsTabController extends GetxController {
|
||||
String offset = '';
|
||||
ScrollController scrollController = ScrollController();
|
||||
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs;
|
||||
RxBool isLoadingMore = false.obs;
|
||||
String dynamicsType = 'all';
|
||||
// Box userInfoCache = GStrorage.userInfo;
|
||||
// bool userLogin = false;
|
||||
int mid = -1;
|
||||
|
||||
Future queryFollowDynamic(String type, String dynamicsType, int? mid) async {
|
||||
this.dynamicsType = dynamicsType;
|
||||
if (mid != null) this.mid = mid;
|
||||
if (type != 'onLoad') {
|
||||
dynamicsList.clear();
|
||||
offset = '';
|
||||
}
|
||||
// // 下拉刷新数据渲染时会触发onLoad
|
||||
// if (type == 'onLoad' && page == 1) {
|
||||
// return;
|
||||
// }
|
||||
isLoadingMore.value = true;
|
||||
var res = await DynamicsHttp.followDynamic(
|
||||
type: dynamicsType == "up" ? "all" : dynamicsType,
|
||||
offset: offset,
|
||||
mid: dynamicsType == "up" ? mid : -1,
|
||||
);
|
||||
isLoadingMore.value = false;
|
||||
if (res['status']) {
|
||||
if (type == 'onLoad' && res['data'].items.isEmpty) {
|
||||
SmartDialog.showToast('没有更多了');
|
||||
return;
|
||||
}
|
||||
if (type == 'onLoad') {
|
||||
dynamicsList.addAll(res['data'].items);
|
||||
} else {
|
||||
dynamicsList.value = res['data'].items;
|
||||
}
|
||||
// print('dynamicsList: $dynamicsList');
|
||||
dynamicsList.refresh();
|
||||
offset = res['data'].offset;
|
||||
// print("page: $page[dynamicsType]!");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
Future onRefresh() async {
|
||||
await queryFollowDynamic('onRefresh', dynamicsType, mid);
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
await queryFollowDynamic('onLoad', dynamicsType, mid);
|
||||
}
|
||||
|
||||
// 返回顶部并刷新
|
||||
void animateToTop() async {
|
||||
if (scrollController.offset >=
|
||||
MediaQuery.of(Get.context!).size.height * 5) {
|
||||
scrollController.jumpTo(0);
|
||||
} else {
|
||||
await scrollController.animateTo(0,
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
4
lib/pages/dynamics/tab/index.dart
Normal file
4
lib/pages/dynamics/tab/index.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
library dynamics.tab;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
234
lib/pages/dynamics/tab/view.dart
Normal file
234
lib/pages/dynamics/tab/view.dart
Normal file
@@ -0,0 +1,234 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/pages/home/index.dart';
|
||||
import 'package:PiliPalaX/pages/main/index.dart';
|
||||
import 'package:PiliPalaX/common/widgets/no_data.dart';
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:waterfall_flow/waterfall_flow.dart';
|
||||
|
||||
import '../../../common/skeleton/dynamic_card.dart';
|
||||
import '../../../models/dynamics/result.dart';
|
||||
import '../../../utils/grid.dart';
|
||||
|
||||
import '../index.dart';
|
||||
import '../widgets/dynamic_panel.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
class DynamicsTabPage extends StatefulWidget {
|
||||
const DynamicsTabPage({Key? key, required this.dynamicsType})
|
||||
: super(key: key);
|
||||
|
||||
final String dynamicsType;
|
||||
|
||||
@override
|
||||
State<DynamicsTabPage> createState() => _DynamicsTabPageState();
|
||||
}
|
||||
|
||||
class _DynamicsTabPageState extends State<DynamicsTabPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late DynamicsTabController _dynamicsTabController;
|
||||
late Future _futureBuilderFuture;
|
||||
late ScrollController scrollController;
|
||||
late bool dynamicsWaterfallFlow;
|
||||
late final DynamicsController dynamicsController;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_dynamicsTabController =
|
||||
Get.put(DynamicsTabController(), tag: widget.dynamicsType);
|
||||
dynamicsController = Get.find<DynamicsController>();
|
||||
|
||||
_futureBuilderFuture = _dynamicsTabController.queryFollowDynamic(
|
||||
'init', widget.dynamicsType, dynamicsController.mid.value);
|
||||
scrollController = _dynamicsTabController.scrollController
|
||||
..addListener(() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
if (!_dynamicsTabController.isLoadingMore.value) {
|
||||
EasyThrottle.throttle('_dynamicsTabController_onLoad',
|
||||
const Duration(milliseconds: 500), () {
|
||||
_dynamicsTabController.isLoadingMore.value = true;
|
||||
_dynamicsTabController.onLoad();
|
||||
_dynamicsTabController.isLoadingMore.value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
dynamicsController.mid.listen((mid) {
|
||||
print('midListen: $mid');
|
||||
scrollController.jumpTo(0);
|
||||
_futureBuilderFuture = _dynamicsTabController.queryFollowDynamic(
|
||||
'init', widget.dynamicsType, mid);
|
||||
});
|
||||
dynamicsWaterfallFlow = GStrorage.setting
|
||||
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.removeListener(() {});
|
||||
dynamicsController.mid.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
// print(widget.dynamicsType + widget.mid.value.toString());
|
||||
return RefreshIndicator(
|
||||
// key:
|
||||
// ValueKey<String>(widget.dynamicsType + widget.mid.value.toString()),
|
||||
onRefresh: () async {
|
||||
dynamicsWaterfallFlow = GStrorage.setting
|
||||
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
|
||||
await Future.wait(<Future>[
|
||||
_dynamicsTabController.onRefresh(),
|
||||
dynamicsController.queryFollowUp()
|
||||
]);
|
||||
},
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _dynamicsTabController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
// print(snapshot);
|
||||
// print(widget.dynamicsType + "${widget.mid?.value}");
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const NoData();
|
||||
}
|
||||
Map data = snapshot.data;
|
||||
// print('data: $data');
|
||||
if (data['status']) {
|
||||
List<DynamicItemModel> list =
|
||||
_dynamicsTabController.dynamicsList;
|
||||
// print('list: $list');
|
||||
return Obx(
|
||||
() {
|
||||
if (list.isEmpty) {
|
||||
if (_dynamicsTabController.isLoadingMore.value) {
|
||||
return skeleton();
|
||||
}
|
||||
return const NoData();
|
||||
}
|
||||
if (!dynamicsWaterfallFlow) {
|
||||
return SliverCrossAxisGroup(
|
||||
slivers: [
|
||||
const SliverFillRemaining(),
|
||||
SliverConstrainedCrossAxis(
|
||||
maxExtent: Grid.maxRowWidth * 2,
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
if ((dynamicsController
|
||||
.tabController.index ==
|
||||
4 &&
|
||||
dynamicsController.mid.value !=
|
||||
-1) ||
|
||||
!dynamicsController.tempBannedList
|
||||
.contains(list[index]
|
||||
.modules
|
||||
?.moduleAuthor
|
||||
?.mid)) {
|
||||
return DynamicPanel(
|
||||
item: list[index]);
|
||||
}
|
||||
return const SizedBox();
|
||||
},
|
||||
childCount: list.length,
|
||||
),
|
||||
)),
|
||||
const SliverFillRemaining(),
|
||||
],
|
||||
);
|
||||
}
|
||||
return SliverWaterfallFlow.extent(
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
//cacheExtent: 0.0,
|
||||
crossAxisSpacing: StyleString.cardSpace / 2,
|
||||
mainAxisSpacing: StyleString.cardSpace / 2,
|
||||
|
||||
lastChildLayoutTypeBuilder: (index) =>
|
||||
index == list.length
|
||||
? LastChildLayoutType.foot
|
||||
: LastChildLayoutType.none,
|
||||
children: [
|
||||
if (dynamicsController.tabController.index == 4 &&
|
||||
dynamicsController.mid.value != -1) ...[
|
||||
for (var i in list) DynamicPanel(item: i),
|
||||
] else ...[
|
||||
for (var i in list)
|
||||
if (!dynamicsController.tempBannedList
|
||||
.contains(i.modules?.moduleAuthor?.mid))
|
||||
DynamicPanel(item: i),
|
||||
]
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () {
|
||||
// setState(() {
|
||||
_futureBuilderFuture =
|
||||
_dynamicsTabController.queryFollowDynamic(
|
||||
'init',
|
||||
widget.dynamicsType,
|
||||
dynamicsController.mid.value);
|
||||
// });
|
||||
dynamicsController.onRefresh();
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return skeleton();
|
||||
}
|
||||
},
|
||||
)
|
||||
],
|
||||
));
|
||||
}
|
||||
|
||||
Widget skeleton() {
|
||||
if (!dynamicsWaterfallFlow) {
|
||||
return SliverCrossAxisGroup(slivers: [
|
||||
const SliverFillRemaining(),
|
||||
SliverConstrainedCrossAxis(
|
||||
maxExtent: Grid.maxRowWidth * 2,
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const DynamicCardSkeleton();
|
||||
}, childCount: 10)),
|
||||
),
|
||||
const SliverFillRemaining()
|
||||
]);
|
||||
}
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
crossAxisSpacing: StyleString.cardSpace / 2,
|
||||
mainAxisSpacing: StyleString.cardSpace / 2,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio,
|
||||
mainAxisExtent: 50),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const DynamicCardSkeleton();
|
||||
}, childCount: 10),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,15 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:custom_sliding_segmented_control/custom_sliding_segmented_control.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:PiliPalaX/models/common/dynamics_type.dart';
|
||||
import 'package:PiliPalaX/models/common/up_panel_position.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:waterfall_flow/waterfall_flow.dart';
|
||||
import 'package:PiliPalaX/common/skeleton/dynamic_card.dart';
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/common/widgets/no_data.dart';
|
||||
import 'package:PiliPalaX/models/dynamics/result.dart';
|
||||
import 'package:PiliPalaX/pages/main/index.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
|
||||
import '../../common/constants.dart';
|
||||
import '../../utils/grid.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/dynamic_panel.dart';
|
||||
import 'widgets/up_panel.dart';
|
||||
|
||||
class DynamicsPage extends StatefulWidget {
|
||||
@@ -29,12 +20,12 @@ class DynamicsPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DynamicsPageState extends State<DynamicsPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
with AutomaticKeepAliveClientMixin, SingleTickerProviderStateMixin {
|
||||
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||
late Future _futureBuilderFuture;
|
||||
late Future _futureBuilderFutureUp;
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
late ScrollController scrollController;
|
||||
late UpPanelPosition upPanelPosition;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
@@ -42,269 +33,125 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||
_futureBuilderFutureUp = _dynamicsController.queryFollowUp();
|
||||
scrollController = _dynamicsController.scrollController;
|
||||
StreamController<bool> mainStream =
|
||||
Get.find<MainController>().bottomBarStream;
|
||||
scrollController.addListener(
|
||||
() async {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
EasyThrottle.throttle(
|
||||
'queryFollowDynamic', const Duration(seconds: 1), () {
|
||||
_dynamicsController.queryFollowDynamic(type: 'onLoad');
|
||||
});
|
||||
}
|
||||
|
||||
final ScrollDirection direction =
|
||||
scrollController.position.userScrollDirection;
|
||||
if (direction == ScrollDirection.forward) {
|
||||
mainStream.add(true);
|
||||
} else if (direction == ScrollDirection.reverse) {
|
||||
mainStream.add(false);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// _dynamicsController.tabController =
|
||||
// TabController(vsync: this, length: DynamicsType.values.length);
|
||||
// ..addListener(() {
|
||||
// if (!_dynamicsController.tabController.indexIsChanging) {
|
||||
// // if (!mounted) return;
|
||||
// // print('indexChanging: ${_dynamicsController.tabController.index}');
|
||||
// _dynamicsController
|
||||
// .onSelectType(_dynamicsController.tabController.index);
|
||||
// }
|
||||
// });
|
||||
_dynamicsController.userLogin.listen((status) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_futureBuilderFuture = _dynamicsController.queryFollowDynamic();
|
||||
_futureBuilderFutureUp = _dynamicsController.queryFollowUp();
|
||||
});
|
||||
}
|
||||
});
|
||||
upPanelPosition = UpPanelPosition.values[setting.get(
|
||||
SettingBoxKey.upPanelPosition,
|
||||
defaultValue: UpPanelPosition.leftFixed.code)];
|
||||
print('upPanelPosition: $upPanelPosition');
|
||||
scrollController = _dynamicsController.scrollController;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
scrollController.removeListener(() {});
|
||||
_dynamicsController.tabController.removeListener(() {});
|
||||
_dynamicsController.tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
titleSpacing: 0,
|
||||
title: SizedBox(
|
||||
height: 34,
|
||||
child: Stack(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Obx(() {
|
||||
if (_dynamicsController.mid.value != -1 &&
|
||||
_dynamicsController.upInfo.value.uname != null) {
|
||||
return SizedBox(
|
||||
height: 36,
|
||||
child: AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
'${_dynamicsController.upInfo.value.uname!}的动态',
|
||||
key: ValueKey<String>(
|
||||
_dynamicsController.upInfo.value.uname!),
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge!
|
||||
.fontSize,
|
||||
)),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
}),
|
||||
Obx(
|
||||
() => _dynamicsController.userLogin.value
|
||||
? Visibility(
|
||||
visible: _dynamicsController.mid.value == -1,
|
||||
child: Theme(
|
||||
data: ThemeData(
|
||||
splashColor:
|
||||
Colors.transparent, // 点击时的水波纹颜色设置为透明
|
||||
highlightColor:
|
||||
Colors.transparent, // 点击时的背景高亮颜色设置为透明
|
||||
),
|
||||
child: CustomSlidingSegmentedControl<int>(
|
||||
initialValue:
|
||||
_dynamicsController.initialValue.value,
|
||||
children: {
|
||||
0: Text(
|
||||
'全部',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize),
|
||||
),
|
||||
1: Text('投稿',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize)),
|
||||
2: Text('番剧',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize)),
|
||||
3: Text('专栏',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.fontSize)),
|
||||
},
|
||||
padding: 13.0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.surfaceVariant
|
||||
.withOpacity(0.7),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
thumbDecoration: BoxDecoration(
|
||||
color:
|
||||
Theme.of(context).colorScheme.background,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
onValueChanged: (v) {
|
||||
feedBack();
|
||||
_dynamicsController.onSelectType(v);
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
: Text('动态',
|
||||
style: Theme.of(context).textTheme.titleMedium),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () => _dynamicsController.onRefresh(),
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _dynamicsController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
Widget upPanelPart() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Container(
|
||||
//抽屉模式增加底色
|
||||
color: upPanelPosition.code > 1? Theme.of(context).colorScheme.surface: Colors.transparent,
|
||||
width: 56,
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFutureUp,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SliverToBoxAdapter(child: SizedBox());
|
||||
return const SizedBox();
|
||||
}
|
||||
Map data = snapshot.data;
|
||||
if (data['status']) {
|
||||
return Obx(() => UpPanel(_dynamicsController.upData.value));
|
||||
return Obx(() => UpPanel(
|
||||
_dynamicsController.upData.value,
|
||||
scrollController));
|
||||
} else {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 80),
|
||||
);
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 90,
|
||||
return const SizedBox(
|
||||
width: 56,
|
||||
child: UpPanelSkeleton(),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
},
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
print('upPanelPosition1: $upPanelPosition');
|
||||
super.build(context);
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 50,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
title: SizedBox(
|
||||
height: 50,
|
||||
child: TabBar(
|
||||
controller: _dynamicsController.tabController,
|
||||
isScrollable: true,
|
||||
dividerColor: Colors.transparent,
|
||||
dividerHeight: 0,
|
||||
tabAlignment: TabAlignment.center,
|
||||
indicatorColor: Theme.of(context).colorScheme.primary,
|
||||
labelColor: Theme.of(context).colorScheme.primary,
|
||||
unselectedLabelColor: Theme.of(context).colorScheme.onSurface,
|
||||
labelStyle: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SliverToBoxAdapter(child: SizedBox());
|
||||
tabs: DynamicsType.values
|
||||
.map((e) => Tab(text: e.labels))
|
||||
.toList(),
|
||||
onTap: (index) {
|
||||
print('index: $index');
|
||||
feedBack();
|
||||
tabsConfig[_dynamicsController.tabController.index]['ctr'].animateToTop();
|
||||
// _dynamicsController.tabController
|
||||
// _dynamicsController.tabController.index = index;
|
||||
// _dynamicsController.onSelectType(index);
|
||||
// _
|
||||
}
|
||||
Map data = snapshot.data;
|
||||
if (data['status']) {
|
||||
List<DynamicItemModel> list =
|
||||
_dynamicsController.dynamicsList;
|
||||
return Obx(
|
||||
() {
|
||||
if (list.isEmpty) {
|
||||
if (_dynamicsController.isLoadingDynamic.value) {
|
||||
return skeleton();
|
||||
} else {
|
||||
return const NoData();
|
||||
}
|
||||
} else {
|
||||
return SliverWaterfallFlow.extent(
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
//cacheExtent: 0.0,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
|
||||
/// follow max child trailing layout offset and layout with full cross axis extend
|
||||
/// last child as loadmore item/no more item in [GridView] and [WaterfallFlow]
|
||||
/// with full cross axis extend
|
||||
// LastChildLayoutType.fullCrossAxisExtend,
|
||||
|
||||
/// as foot at trailing and layout with full cross axis extend
|
||||
/// show no more item at trailing when children are not full of viewport
|
||||
/// if children is full of viewport, it's the same as fullCrossAxisExtend
|
||||
// LastChildLayoutType.foot,
|
||||
lastChildLayoutTypeBuilder: (index) =>
|
||||
index == list.length
|
||||
? LastChildLayoutType.foot
|
||||
: LastChildLayoutType.none,
|
||||
children: [
|
||||
for (var i in list) DynamicPanel(item: i),
|
||||
]);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_dynamicsController.queryFollowDynamic();
|
||||
_futureBuilderFutureUp =
|
||||
_dynamicsController.queryFollowUp();
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return skeleton();
|
||||
}
|
||||
},
|
||||
)),
|
||||
),
|
||||
const SliverToBoxAdapter(child: SizedBox(height: 40))
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget skeleton() {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const DynamicCardSkeleton();
|
||||
}, childCount: 5),
|
||||
);
|
||||
drawer: upPanelPosition == UpPanelPosition.leftDrawer ?
|
||||
upPanelPart(): null,
|
||||
drawerEnableOpenDragGesture: true,
|
||||
endDrawer: upPanelPosition == UpPanelPosition.rightDrawer ?
|
||||
upPanelPart(): null,
|
||||
endDrawerEnableOpenDragGesture: true,
|
||||
body: Row(children: [
|
||||
if (upPanelPosition == UpPanelPosition.leftFixed)
|
||||
upPanelPart(),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _dynamicsController.tabController,
|
||||
children: _dynamicsController.tabsPageList,
|
||||
)),
|
||||
if (upPanelPosition == UpPanelPosition.rightFixed)
|
||||
upPanelPart(),
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,15 @@ import 'package:PiliPalaX/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPalaX/http/user.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
import '../../../http/constants.dart';
|
||||
import '../controller.dart';
|
||||
|
||||
class AuthorPanel extends StatelessWidget {
|
||||
final dynamic item;
|
||||
const AuthorPanel({super.key, required this.item});
|
||||
final Function? addBannedList;
|
||||
const AuthorPanel({super.key, required this.item, this.addBannedList});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -77,7 +82,6 @@ class AuthorPanel extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
if (item.type == 'DYNAMIC_TYPE_AV')
|
||||
SizedBox(
|
||||
width: 32,
|
||||
height: 32,
|
||||
@@ -132,6 +136,7 @@ class MorePanel extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
if (item.type == 'DYNAMIC_TYPE_AV')
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
try {
|
||||
@@ -151,6 +156,37 @@ class MorePanel extends StatelessWidget {
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'分享动态',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
leading: const Icon(Icons.share_outlined, size: 19),
|
||||
onTap: () async {
|
||||
var result = await Share.share(
|
||||
'${HttpString.baseUrl}/dynamic/${item.idStr} UP主: ${item.modules.moduleAuthor.name}')
|
||||
.whenComplete(() {});
|
||||
return result;
|
||||
},
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(
|
||||
'临时屏蔽:${item.modules.moduleAuthor.name}',
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
leading: const Icon(Icons.visibility_off_outlined, size: 19),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DynamicsController dynamicsController =
|
||||
Get.find<DynamicsController>();
|
||||
dynamicsController.tempBannedList
|
||||
.add(item.modules.moduleAuthor.mid);
|
||||
SmartDialog.showToast(
|
||||
'已临时屏蔽${item.modules.moduleAuthor.name}(${item.modules.moduleAuthor.mid}),重启恢复');
|
||||
},
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
const Divider(thickness: 0.1, height: 1),
|
||||
ListTile(
|
||||
onTap: () => Get.back(),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPalaX/models/dynamics/result.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPalaX/pages/dynamics/index.dart';
|
||||
@@ -9,7 +10,7 @@ import 'forward_panel.dart';
|
||||
class DynamicPanel extends StatelessWidget {
|
||||
final dynamic item;
|
||||
final String? source;
|
||||
DynamicPanel({this.item, this.source, Key? key}) : super(key: key);
|
||||
DynamicPanel({required this.item, this.source, Key? key}) : super(key: key);
|
||||
final DynamicsController _dynamicsController = Get.put(DynamicsController());
|
||||
|
||||
@override
|
||||
@@ -18,19 +19,20 @@ class DynamicPanel extends StatelessWidget {
|
||||
padding: source == 'detail'
|
||||
? const EdgeInsets.only(bottom: 12)
|
||||
: EdgeInsets.zero,
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
width: 8,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.05),
|
||||
),
|
||||
),
|
||||
),
|
||||
// decoration: BoxDecoration(
|
||||
// border: Border(
|
||||
// bottom: BorderSide(
|
||||
// width: 8,
|
||||
// color: Theme.of(context).dividerColor.withOpacity(0.05),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
child: Material(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context).cardColor.withOpacity(0.5),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(0),
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _dynamicsController.pushDetail(item, 1),
|
||||
@@ -38,7 +40,7 @@ class DynamicPanel extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 8),
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 6),
|
||||
child: AuthorPanel(item: item),
|
||||
),
|
||||
if (item!.modules!.moduleDynamic!.desc != null ||
|
||||
|
||||
@@ -11,26 +11,27 @@ import 'package:PiliPalaX/utils/utils.dart';
|
||||
|
||||
class UpPanel extends StatefulWidget {
|
||||
final FollowUpModel? upData;
|
||||
const UpPanel(this.upData, {Key? key}) : super(key: key);
|
||||
final ScrollController scrollController;
|
||||
const UpPanel(this.upData, this.scrollController, {Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<UpPanel> createState() => _UpPanelState();
|
||||
}
|
||||
|
||||
class _UpPanelState extends State<UpPanel> {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
int currentMid = -1;
|
||||
late double contentWidth = 56;
|
||||
List<UpItem> upList = [];
|
||||
List<LiveUserItem> liveList = [];
|
||||
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
var userInfo;
|
||||
bool _showLiveItems = false;
|
||||
late DynamicsController dynamicsController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
dynamicsController = Get.find<DynamicsController>();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -39,68 +40,63 @@ class _UpPanelState extends State<UpPanel> {
|
||||
if (widget.upData!.liveUsers != null) {
|
||||
liveList = widget.upData!.liveUsers!.items!;
|
||||
}
|
||||
return SliverPersistentHeader(
|
||||
floating: true,
|
||||
pinned: false,
|
||||
delegate: _SliverHeaderDelegate(
|
||||
height: 126,
|
||||
// return const SizedBox();
|
||||
return CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: widget.scrollController,
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 45,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(const EdgeInsets.only()),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
padding: const EdgeInsets.only(left: 16, right: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Text('最新关注'),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
feedBack();
|
||||
Get.toNamed('/follow?mid=${userInfo.mid}');
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 5, bottom: 5),
|
||||
child: Text(
|
||||
'查看全部',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'Live(${liveList.length})',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
),
|
||||
semanticsLabel:
|
||||
'${_showLiveItems ? '展开' : '收起'}直播中的${liveList.length}个Up',
|
||||
),
|
||||
Icon(_showLiveItems ? Icons.expand_less : Icons.expand_more,
|
||||
size: 12),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_showLiveItems = !_showLiveItems;
|
||||
});
|
||||
},
|
||||
),
|
||||
Container(
|
||||
height: 90,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: scrollController,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
if (liveList.isNotEmpty) ...[
|
||||
),
|
||||
),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
),
|
||||
SliverGrid(
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 1,
|
||||
mainAxisExtent: 76,
|
||||
crossAxisSpacing: 0,
|
||||
mainAxisSpacing: 0,
|
||||
),
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
if (_showLiveItems && liveList.isNotEmpty) ...[
|
||||
for (int i = 0; i < liveList.length; i++) ...[
|
||||
upItemBuild(liveList[i], i)
|
||||
],
|
||||
VerticalDivider(
|
||||
indent: 20,
|
||||
endIndent: 40,
|
||||
width: 26,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
],
|
||||
upItemBuild(
|
||||
UpItem(face: '', uname: '全部动态', mid: -1), 0),
|
||||
upItemBuild(UpItem(face: '', uname: '全部动态', mid: -1), 0),
|
||||
upItemBuild(
|
||||
UpItem(
|
||||
face: userInfo.face,
|
||||
@@ -111,23 +107,14 @@ class _UpPanelState extends State<UpPanel> {
|
||||
for (int i = 0; i < upList.length; i++) ...[
|
||||
upItemBuild(upList[i], i + 2)
|
||||
],
|
||||
const SizedBox(width: 10),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Container(
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface
|
||||
.withOpacity(0.5),
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
Widget upItemBuild(data, i) {
|
||||
@@ -137,28 +124,27 @@ class _UpPanelState extends State<UpPanel> {
|
||||
feedBack();
|
||||
if (data.type == 'up') {
|
||||
currentMid = data.mid;
|
||||
Get.find<DynamicsController>().mid.value = data.mid;
|
||||
Get.find<DynamicsController>().upInfo.value = data;
|
||||
Get.find<DynamicsController>().onSelectUp(data.mid);
|
||||
int liveLen = liveList.length;
|
||||
int upLen = upList.length;
|
||||
double itemWidth = contentWidth + itemPadding.horizontal;
|
||||
double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
double moveDistance = 0.0;
|
||||
if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
|
||||
} else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
|
||||
moveDistance =
|
||||
(i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
|
||||
} else {
|
||||
moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
|
||||
}
|
||||
// dynamicsController.mid.value = data.mid;
|
||||
dynamicsController.upInfo.value = data;
|
||||
dynamicsController.onSelectUp(data.mid);
|
||||
// int liveLen = liveList.length;
|
||||
// int upLen = upList.length;
|
||||
// double itemWidth = contentWidth + itemPadding.horizontal;
|
||||
// double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
// double moveDistance = 0.0;
|
||||
// if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
|
||||
// } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
|
||||
// moveDistance =
|
||||
// (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
|
||||
// } else {
|
||||
// moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
|
||||
// }
|
||||
data.hasUpdate = false;
|
||||
scrollController.animateTo(
|
||||
moveDistance,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
|
||||
// scrollController.animateTo(
|
||||
// moveDistance,
|
||||
// duration: const Duration(milliseconds: 500),
|
||||
// curve: Curves.easeInOut,
|
||||
// );
|
||||
setState(() {});
|
||||
} else if (data.type == 'live') {
|
||||
LiveItemModel liveItem = LiveItemModel.fromJson({
|
||||
@@ -182,10 +168,8 @@ class _UpPanelState extends State<UpPanel> {
|
||||
Get.toNamed('/member?mid=${data.mid}',
|
||||
arguments: {'face': data.face, 'heroTag': heroTag});
|
||||
},
|
||||
child: Padding(
|
||||
padding: itemPadding,
|
||||
child: AnimatedOpacity(
|
||||
opacity: isCurrent ? 1 : 0.3,
|
||||
opacity: isCurrent ? 1 : 0.6,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
child: Column(
|
||||
@@ -203,44 +187,42 @@ class _UpPanelState extends State<UpPanel> {
|
||||
isLabelVisible: data.type == 'live' ||
|
||||
(data.type == 'up' && (data.hasUpdate ?? false)),
|
||||
backgroundColor: data.type == 'live'
|
||||
? Theme.of(context).colorScheme.secondaryContainer
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.secondaryContainer
|
||||
.withOpacity(0.7)
|
||||
: Theme.of(context).colorScheme.primary,
|
||||
child: data.face != ''
|
||||
? NetworkImgLayer(
|
||||
width: 50,
|
||||
height: 50,
|
||||
width: 38,
|
||||
height: 38,
|
||||
src: data.face,
|
||||
type: 'avatar',
|
||||
)
|
||||
: const CircleAvatar(
|
||||
radius: 25,
|
||||
radius: 19,
|
||||
backgroundImage: AssetImage(
|
||||
'assets/images/noface.jpeg',
|
||||
'assets/images/logo/logo_android_2.png',
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: SizedBox(
|
||||
width: contentWidth,
|
||||
child: Text(
|
||||
const SizedBox(height: 3),
|
||||
Text(
|
||||
data.uname,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.clip,
|
||||
maxLines: 2,
|
||||
softWrap: true,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: currentMid == data.mid
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.outline,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize),
|
||||
),
|
||||
),
|
||||
height: 1.1,
|
||||
fontSize: 12.5),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -273,14 +255,7 @@ class UpPanelSkeleton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: 10,
|
||||
itemBuilder: ((context, index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 0),
|
||||
child: Column(
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
@@ -299,9 +274,6 @@ class UpPanelSkeleton extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +105,8 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: Container(
|
||||
height: 80,
|
||||
padding: const EdgeInsets.fromLTRB(12, 0, 10, 10),
|
||||
height: 70,
|
||||
padding: const EdgeInsets.fromLTRB(10, 0, 8, 8),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
@@ -139,17 +139,17 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
||||
'时长${Utils.durationReadFormat(content.durationText)}',
|
||||
),
|
||||
if (content.durationText != null)
|
||||
const SizedBox(width: 10),
|
||||
const SizedBox(width: 6),
|
||||
Text(content.stat.play + '次围观'),
|
||||
const SizedBox(width: 10),
|
||||
const SizedBox(width: 6),
|
||||
Text(content.stat.danmu + '条弹幕')
|
||||
],
|
||||
),
|
||||
),
|
||||
Image.asset(
|
||||
'assets/images/play.png',
|
||||
width: 60,
|
||||
height: 60,
|
||||
width: 50,
|
||||
height: 50,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -29,7 +29,7 @@ class FansController extends GetxController {
|
||||
}
|
||||
|
||||
Future queryFans(type) async {
|
||||
if (type == 'init') {
|
||||
if (type == 'init' || type == 'refresh') {
|
||||
pn = 1;
|
||||
loadingText.value == '加载中...';
|
||||
}
|
||||
@@ -49,11 +49,14 @@ class FansController extends GetxController {
|
||||
} else if (type == 'onLoad') {
|
||||
fansList.addAll(res['data'].list);
|
||||
}
|
||||
print(total);
|
||||
print('fansList: ${fansList.length}, total: $total');
|
||||
if ((pn == 1 && total < ps) || res['data'].list.isEmpty) {
|
||||
loadingText.value = '没有更多了';
|
||||
}
|
||||
pn += 1;
|
||||
if (total > ps && pn == 2) {
|
||||
queryFans('onLoad');
|
||||
}
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/common/widgets/no_data.dart';
|
||||
import 'package:PiliPalaX/models/fans/result.dart';
|
||||
|
||||
import '../../common/constants.dart';
|
||||
import '../../utils/grid.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/fan_item.dart';
|
||||
|
||||
@@ -60,48 +62,36 @@ class _FansPageState extends State<FansPage> {
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async => await _fansController.queryFans('init'),
|
||||
child: FutureBuilder(
|
||||
child: CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.data != null) {
|
||||
var data = snapshot.data;
|
||||
if (data['status']) {
|
||||
return Obx(() {
|
||||
List<FansItemModel> list = _fansController.fansList;
|
||||
return Obx(
|
||||
() => list.isNotEmpty
|
||||
? ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: list.length + 1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (index == list.length) {
|
||||
return Container(
|
||||
height:
|
||||
MediaQuery.of(context).padding.bottom + 60,
|
||||
padding: EdgeInsets.only(
|
||||
bottom:
|
||||
MediaQuery.of(context).padding.bottom),
|
||||
child: Center(
|
||||
child: Obx(
|
||||
() => Text(
|
||||
_fansController.loadingText.value,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return list.isNotEmpty
|
||||
? SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent:
|
||||
Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: 56),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return fanItem(item: list[index]);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const CustomScrollView(
|
||||
slivers: [NoData()],
|
||||
),
|
||||
);
|
||||
childCount: list.length,
|
||||
))
|
||||
: const NoData();
|
||||
});
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
@@ -110,11 +100,19 @@ class _FansPageState extends State<FansPage> {
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 200,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:PiliPalaX/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPalaX/common/skeleton/video_card_v.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -68,17 +70,12 @@ class _FavPageState extends State<FavPage> {
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context,
|
||||
Grid.maxRowWidth * 2,
|
||||
StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
childCount:
|
||||
_favController.favFolderData.value.list!.length,
|
||||
@@ -103,7 +100,25 @@ class _FavPageState extends State<FavPage> {
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return const Text('请求中');
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return const VideoCardHSkeleton();
|
||||
},
|
||||
childCount: 10,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -215,16 +215,13 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
? const SliverToBoxAdapter(child: SizedBox())
|
||||
: SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context,
|
||||
Grid.maxRowWidth * 2,
|
||||
StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio:
|
||||
StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate:
|
||||
SliverChildBuilderDelegate((context, index) {
|
||||
return FavVideoCardH(
|
||||
@@ -244,7 +241,13 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 10),
|
||||
|
||||
@@ -51,18 +51,17 @@ class HistoryController extends GetxController {
|
||||
}
|
||||
|
||||
// 暂停观看历史
|
||||
Future onPauseHistory() async {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
Future onPauseHistory(BuildContext context) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content:
|
||||
Text(!pauseStatus.value ? '啊叻?你要暂停历史记录功能吗?' : '啊叻?要恢复历史记录功能吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('取消')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
@@ -75,7 +74,7 @@ class HistoryController extends GetxController {
|
||||
pauseStatus.value = !pauseStatus.value;
|
||||
localCache.put(LocalCacheKey.historyPause, pauseStatus.value);
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: Text(!pauseStatus.value ? '确认暂停' : '确认恢复'),
|
||||
)
|
||||
@@ -97,17 +96,16 @@ class HistoryController extends GetxController {
|
||||
}
|
||||
|
||||
// 清空观看历史
|
||||
Future onClearHistory() async {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
Future onClearHistory(BuildContext context) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('啊叻?你要清空历史记录功能吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('取消')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
@@ -117,7 +115,7 @@ class HistoryController extends GetxController {
|
||||
if (res.data['code'] == 0) {
|
||||
SmartDialog.showToast('清空观看历史');
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
historyList.clear();
|
||||
},
|
||||
child: const Text('确认清空'),
|
||||
@@ -158,17 +156,16 @@ class HistoryController extends GetxController {
|
||||
}
|
||||
|
||||
// 删除选中的记录
|
||||
Future onDelCheckedHistory() async {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
Future onDelCheckedHistory(BuildContext context) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('确认删除所选历史记录吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
@@ -179,7 +176,7 @@ class HistoryController extends GetxController {
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
/// TODO 优化
|
||||
await SmartDialog.dismiss();
|
||||
Get.back();
|
||||
SmartDialog.showLoading(msg: '请求中');
|
||||
List<HisListItem> result =
|
||||
historyList.where((e) => e.checked!).toList();
|
||||
|
||||
@@ -87,10 +87,10 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
// 处理菜单项选择的逻辑
|
||||
switch (type) {
|
||||
case 'pause':
|
||||
_historyController.onPauseHistory();
|
||||
_historyController.onPauseHistory(context);
|
||||
break;
|
||||
case 'clear':
|
||||
_historyController.onClearHistory();
|
||||
_historyController.onClearHistory(context);
|
||||
break;
|
||||
case 'del':
|
||||
_historyController.onDelHistory();
|
||||
@@ -162,7 +162,7 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
child: const Text('全选'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => _historyController.onDelCheckedHistory(),
|
||||
onPressed: () => _historyController.onDelCheckedHistory(context),
|
||||
child: Text(
|
||||
'删除',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||
@@ -194,16 +194,13 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
() => _historyController.historyList.isNotEmpty
|
||||
? SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context,
|
||||
Grid.maxRowWidth * 2,
|
||||
StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio:
|
||||
StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return HistoryItem(
|
||||
@@ -231,7 +228,13 @@ class _HistoryPageState extends State<HistoryPage> {
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 10),
|
||||
|
||||
@@ -86,14 +86,12 @@ class _HistorySearchPageState extends State<HistorySearchPage> {
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
Obx(() => SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(context,
|
||||
Grid.maxRowWidth * 2, StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return HistoryItem(
|
||||
|
||||
@@ -67,8 +67,11 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
|
||||
void setTabConfig() async {
|
||||
defaultTabs = [...tabsConfig];
|
||||
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
|
||||
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']).map<String>((i) => i.toString()).toList();
|
||||
tabbarSort = settingStorage
|
||||
.get(SettingBoxKey.tabbarSort,
|
||||
defaultValue: ['live', 'rcmd', 'hot', 'rank', 'bangumi'])
|
||||
.map<String>((i) => i.toString())
|
||||
.toList();
|
||||
defaultTabs.retainWhere(
|
||||
(item) => tabbarSort.contains((item['type'] as TabType).id));
|
||||
defaultTabs.sort((a, b) => tabbarSort
|
||||
|
||||
@@ -46,54 +46,31 @@ class _HomePageState extends State<HomePage>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
|
||||
// 设置状态栏图标的亮度
|
||||
if (_homeController.enableGradientBg) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: currentBrightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
));
|
||||
}
|
||||
// Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
|
||||
// // 设置状态栏图标的亮度
|
||||
// if (_homeController.enableGradientBg) {
|
||||
// SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
// statusBarIconBrightness: currentBrightness == Brightness.light
|
||||
// ? Brightness.dark
|
||||
// : Brightness.light,
|
||||
// ));
|
||||
// }
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: _homeController.enableGradientBg
|
||||
? null
|
||||
: AppBar(toolbarHeight: 0, elevation: 0),
|
||||
body: Stack(
|
||||
children: [
|
||||
// gradient background
|
||||
if (_homeController.enableGradientBg) ...[
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Opacity(
|
||||
opacity: 0.6,
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.9),
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.5),
|
||||
Theme.of(context).colorScheme.surface
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
stops: const [0, 0.0034, 0.34]),
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 0,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness:
|
||||
MediaQuery.of(context).platformBrightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Column(
|
||||
body: Column(
|
||||
children: [
|
||||
CustomAppBar(
|
||||
stream: _homeController.hideSearchBar
|
||||
@@ -115,8 +92,7 @@ class _HomePageState extends State<HomePage>
|
||||
child: TabBar(
|
||||
controller: _homeController.tabController,
|
||||
tabs: [
|
||||
for (var i in _homeController.tabs)
|
||||
Tab(text: i['label'])
|
||||
for (var i in _homeController.tabs) Tab(text: i['label'])
|
||||
],
|
||||
isScrollable: true,
|
||||
dividerColor: Colors.transparent,
|
||||
@@ -145,8 +121,6 @@ class _HomePageState extends State<HomePage>
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ class HotController extends GetxController {
|
||||
videoList.addAll(res['data']);
|
||||
}
|
||||
_currentPage += 1;
|
||||
if (_currentPage == 2) queryHotFeed('onLoad');
|
||||
}
|
||||
isLoadingMore = false;
|
||||
return res;
|
||||
|
||||
@@ -82,8 +82,8 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
// 单列布局 EdgeInsets.zero
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0),
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace, StyleString.safeSpace - 5, 0, 0),
|
||||
sliver: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
@@ -92,19 +92,12 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 最大宽度
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context,
|
||||
Grid.maxRowWidth * 2,
|
||||
StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return VideoCardH(
|
||||
videoItem: _hotController.videoList[index],
|
||||
@@ -135,7 +128,12 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 10),
|
||||
|
||||
@@ -23,18 +23,17 @@ class LaterController extends GetxController {
|
||||
return res;
|
||||
}
|
||||
|
||||
Future toViewDel({int? aid}) async {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
Future toViewDel(BuildContext context, {int? aid}) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: Text(
|
||||
aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
@@ -51,7 +50,7 @@ class LaterController extends GetxController {
|
||||
queryLaterList();
|
||||
}
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
child: Text(aid != null ? '确认移除' : '确认删除'),
|
||||
@@ -63,17 +62,16 @@ class LaterController extends GetxController {
|
||||
}
|
||||
|
||||
// 一键清空
|
||||
Future toViewClear() async {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
Future toViewClear(BuildContext context) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('清空确认'),
|
||||
content: const Text('确定要清空你的稍后再看列表吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
@@ -85,7 +83,7 @@ class LaterController extends GetxController {
|
||||
if (res['status']) {
|
||||
laterList.clear();
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
child: const Text('确认'),
|
||||
|
||||
@@ -47,7 +47,7 @@ class _LaterPageState extends State<LaterPage> {
|
||||
Obx(
|
||||
() => _laterController.laterList.isNotEmpty
|
||||
? TextButton(
|
||||
onPressed: () => _laterController.toViewDel(),
|
||||
onPressed: () => _laterController.toViewDel(context),
|
||||
child: const Text('移除已看'),
|
||||
)
|
||||
: const SizedBox(),
|
||||
@@ -56,7 +56,7 @@ class _LaterPageState extends State<LaterPage> {
|
||||
() => _laterController.laterList.isNotEmpty
|
||||
? IconButton(
|
||||
tooltip: '一键清空',
|
||||
onPressed: () => _laterController.toViewClear(),
|
||||
onPressed: () => _laterController.toViewClear(context),
|
||||
icon: Icon(
|
||||
Icons.clear_all_outlined,
|
||||
size: 21,
|
||||
@@ -72,7 +72,10 @@ class _LaterPageState extends State<LaterPage> {
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _laterController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
SliverPadding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
|
||||
sliver: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
@@ -83,25 +86,27 @@ class _LaterPageState extends State<LaterPage> {
|
||||
!_laterController.isLoading.value
|
||||
? SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context,
|
||||
maxCrossAxisExtent:
|
||||
Grid.maxRowWidth * 2,
|
||||
StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
delegate:
|
||||
SliverChildBuilderDelegate((context, index) {
|
||||
var videoItem = _laterController.laterList[index];
|
||||
childAspectRatio:
|
||||
StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
var videoItem =
|
||||
_laterController.laterList[index];
|
||||
return VideoCardH(
|
||||
videoItem: videoItem,
|
||||
source: 'later',
|
||||
longPress: () => _laterController.toViewDel(
|
||||
longPress: () =>
|
||||
_laterController.toViewDel(context,
|
||||
aid: videoItem.aid));
|
||||
}, childCount: _laterController.laterList.length),
|
||||
},
|
||||
childCount:
|
||||
_laterController.laterList.length),
|
||||
)
|
||||
: _laterController.isLoading.value
|
||||
? const SliverToBoxAdapter(
|
||||
@@ -113,20 +118,27 @@ class _LaterPageState extends State<LaterPage> {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {
|
||||
_futureBuilderFuture = _laterController.queryLaterList();
|
||||
_futureBuilderFuture =
|
||||
_laterController.queryLaterList();
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 10),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
)),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 10,
|
||||
|
||||
@@ -141,15 +141,12 @@ class _LivePageState extends State<LivePage>
|
||||
|
||||
Widget contentGrid(ctr, liveList) {
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
// 行间距
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 最大宽度
|
||||
maxCrossAxisExtent: Grid.maxRowWidth,
|
||||
mainAxisExtent: Grid.calculateActualWidth(context, Grid.maxRowWidth, StyleString.safeSpace) / StyleString.aspectRatio+
|
||||
MediaQuery.textScalerOf(context).scale(80),
|
||||
childAspectRatio: StyleString.aspectRatio,
|
||||
mainAxisExtent: MediaQuery.textScalerOf(context).scale(80),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
|
||||
@@ -111,6 +111,7 @@ class LoginPageController extends GetxController {
|
||||
Future getCaptcha(oncall) async {
|
||||
SmartDialog.showLoading(msg: '请求中...');
|
||||
var result = await LoginHttp.queryCaptcha();
|
||||
SmartDialog.dismiss();
|
||||
if (result['status']) {
|
||||
CaptchaDataModel captchaData = result['data'];
|
||||
var registerData = Gt3RegisterData(
|
||||
@@ -119,7 +120,6 @@ class LoginPageController extends GetxController {
|
||||
success: true,
|
||||
);
|
||||
captcha.addEventHandler(onShow: (Map<String, dynamic> message) async {
|
||||
SmartDialog.dismiss();
|
||||
}, onClose: (Map<String, dynamic> message) async {
|
||||
SmartDialog.showToast('关闭验证');
|
||||
}, onResult: (Map<String, dynamic> message) async {
|
||||
|
||||
@@ -12,13 +12,14 @@ import 'package:PiliPalaX/pages/media/index.dart';
|
||||
import 'package:PiliPalaX/pages/rank/index.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:PiliPalaX/utils/utils.dart';
|
||||
import 'package:path/path.dart';
|
||||
import '../../models/common/dynamic_badge_mode.dart';
|
||||
import '../../models/common/nav_bar_config.dart';
|
||||
|
||||
class MainController extends GetxController {
|
||||
List<Widget> pages = <Widget>[
|
||||
const HomePage(),
|
||||
const RankPage(),
|
||||
// const RankPage(),
|
||||
const DynamicsPage(),
|
||||
const MediaPage(),
|
||||
];
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:PiliPalaX/common/constants.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:PiliPalaX/models/common/dynamic_badge_mode.dart';
|
||||
import 'package:PiliPalaX/pages/dynamics/index.dart';
|
||||
import 'package:PiliPalaX/pages/home/index.dart';
|
||||
import 'package:PiliPalaX/pages/media/index.dart';
|
||||
import 'package:PiliPalaX/pages/rank/index.dart';
|
||||
import 'package:PiliPalaX/utils/event_bus.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
@@ -24,7 +23,6 @@ class MainApp extends StatefulWidget {
|
||||
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
final MainController _mainController = Get.put(MainController());
|
||||
final HomeController _homeController = Get.put(HomeController());
|
||||
final RankController _rankController = Get.put(RankController());
|
||||
final DynamicsController _dynamicController = Get.put(DynamicsController());
|
||||
final MediaController _mediaController = Get.put(MediaController());
|
||||
|
||||
@@ -32,6 +30,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
Box setting = GStrorage.setting;
|
||||
late bool enableMYBar;
|
||||
late bool adaptiveNavBar;
|
||||
late bool enableGradientBg;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -42,6 +41,8 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
|
||||
adaptiveNavBar =
|
||||
setting.get(SettingBoxKey.adaptiveNavBar, defaultValue: false);
|
||||
enableGradientBg = setting.get(SettingBoxKey.enableGradientBg,
|
||||
defaultValue: true);
|
||||
}
|
||||
|
||||
void setIndex(int value) async {
|
||||
@@ -63,20 +64,20 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
_homeController.flag = false;
|
||||
}
|
||||
|
||||
if (currentPage is RankPage) {
|
||||
if (_rankController.flag) {
|
||||
// 单击返回顶部 双击并刷新
|
||||
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
||||
_rankController.onRefresh();
|
||||
} else {
|
||||
_rankController.animateToTop();
|
||||
}
|
||||
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||
}
|
||||
_rankController.flag = true;
|
||||
} else {
|
||||
_rankController.flag = false;
|
||||
}
|
||||
// if (currentPage is RankPage) {
|
||||
// if (_rankController.flag) {
|
||||
// // 单击返回顶部 双击并刷新
|
||||
// if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
|
||||
// _rankController.onRefresh();
|
||||
// } else {
|
||||
// _rankController.animateToTop();
|
||||
// }
|
||||
// _lastSelectTime = DateTime.now().millisecondsSinceEpoch;
|
||||
// }
|
||||
// _rankController.flag = true;
|
||||
// } else {
|
||||
// _rankController.flag = false;
|
||||
// }
|
||||
|
||||
if (currentPage is DynamicsPage) {
|
||||
if (_dynamicController.flag) {
|
||||
@@ -113,46 +114,85 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
onPopInvoked: (bool didPop) async {
|
||||
_mainController.onBackPressed(context);
|
||||
},
|
||||
child: adaptiveNavBar
|
||||
? AdaptiveScaffold(
|
||||
body: (_) => PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _mainController.pageController,
|
||||
onPageChanged: (index) {
|
||||
_mainController.selectedIndex = index;
|
||||
setState(() {});
|
||||
},
|
||||
children: _mainController.pages,
|
||||
child: Scaffold(
|
||||
extendBody: true,
|
||||
body: Stack(children: [
|
||||
// gradient background
|
||||
if (enableGradientBg) ...[
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Opacity(
|
||||
opacity: 0.6,
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.9),
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.5),
|
||||
Theme.of(context).colorScheme.surface
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
stops: const [0, 0.0034, 0.34]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (adaptiveNavBar) ...[
|
||||
SizedBox(
|
||||
width: 55,
|
||||
child: NavigationRail(
|
||||
groupAlignment: 0.0,
|
||||
minWidth: 40.0,
|
||||
backgroundColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.surface
|
||||
.withOpacity(0.2),
|
||||
selectedIndex: _mainController.selectedIndex,
|
||||
onDestinationSelected: (value) => setIndex(value),
|
||||
labelType: NavigationRailLabelType.none,
|
||||
destinations: _mainController.navigationBars
|
||||
.map((e) => NavigationDestination(
|
||||
.map((e) => NavigationRailDestination(
|
||||
icon: Badge(
|
||||
label: _mainController.dynamicBadgeType ==
|
||||
DynamicBadgeMode.number
|
||||
? Text(e['count'].toString())
|
||||
: null,
|
||||
padding: const EdgeInsets.fromLTRB(2, 0, 2, 0),
|
||||
isLabelVisible: _mainController.dynamicBadgeType !=
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(2, 0, 2, 0),
|
||||
isLabelVisible:
|
||||
_mainController.dynamicBadgeType !=
|
||||
DynamicBadgeMode.hidden &&
|
||||
e['count'] > 0,
|
||||
child: e['icon'],
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
textColor:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
textColor: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface,
|
||||
),
|
||||
selectedIcon: e['selectIcon'],
|
||||
label: e['label'],
|
||||
label: Text(e['label']),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 6),
|
||||
))
|
||||
.toList(),
|
||||
onSelectedIndexChange: (value) => setIndex(value),
|
||||
selectedIndex: _mainController.selectedIndex,
|
||||
extendedNavigationRailWidth: 180,
|
||||
transitionDuration: const Duration(milliseconds: 500),
|
||||
useDrawer: true)
|
||||
: Scaffold(
|
||||
extendBody: true,
|
||||
body: PageView(
|
||||
)),
|
||||
],
|
||||
Expanded(
|
||||
child: PageView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
controller: _mainController.pageController,
|
||||
onPageChanged: (index) {
|
||||
@@ -161,7 +201,13 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
|
||||
},
|
||||
children: _mainController.pages,
|
||||
),
|
||||
bottomNavigationBar: StreamBuilder(
|
||||
),
|
||||
],
|
||||
)
|
||||
]),
|
||||
bottomNavigationBar: adaptiveNavBar
|
||||
? null
|
||||
: StreamBuilder(
|
||||
stream: _mainController.hideTabBar
|
||||
? _mainController.bottomBarStream.stream
|
||||
: StreamController<bool>.broadcast().stream,
|
||||
|
||||
@@ -62,7 +62,8 @@ class _MediaPageState extends State<MediaPage>
|
||||
super.build(context);
|
||||
Color primary = Theme.of(context).colorScheme.primary;
|
||||
return Scaffold(
|
||||
appBar: AppBar(toolbarHeight: 30),
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(toolbarHeight: 30, backgroundColor: Colors.transparent),
|
||||
body: SingleChildScrollView(
|
||||
controller: mediaController.scrollController,
|
||||
child: Column(
|
||||
@@ -74,7 +75,8 @@ class _MediaPageState extends State<MediaPage>
|
||||
child: Text(
|
||||
'媒体库',
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.titleLarge!.fontSize,
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.titleLarge!.fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -88,8 +90,7 @@ class _MediaPageState extends State<MediaPage>
|
||||
Icons.settings_outlined,
|
||||
size: 20,
|
||||
),
|
||||
)
|
||||
),
|
||||
)),
|
||||
for (var i in mediaController.list) ...[
|
||||
ListTile(
|
||||
onTap: () => i['onTap'](),
|
||||
@@ -145,14 +146,19 @@ class _MediaPageState extends State<MediaPage>
|
||||
),
|
||||
if (mediaController.favFolderData.value.count != null)
|
||||
TextSpan(
|
||||
text: mediaController.favFolderData.value.count
|
||||
.toString(),
|
||||
text: "${mediaController.favFolderData.value.count} ",
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
WidgetSpan(
|
||||
child: Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -275,10 +281,16 @@ class FavFolderItem extends StatelessWidget {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface
|
||||
.withOpacity(0.4),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onInverseSurface
|
||||
.withOpacity(0.4),
|
||||
offset: const Offset(4, -12), // 阴影与容器的距离
|
||||
blurRadius: 0.0, // 高斯的标准偏差与盒子的形状卷积。
|
||||
spreadRadius: 0.0, // 在应用模糊之前,框应该膨胀的量。
|
||||
|
||||
@@ -68,7 +68,7 @@ class MemberController extends GetxController {
|
||||
}
|
||||
|
||||
// 关注/取关up
|
||||
Future actionRelationMod() async {
|
||||
Future actionRelationMod(BuildContext context) async {
|
||||
if (userInfo == null) {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
@@ -78,19 +78,18 @@ class MemberController extends GetxController {
|
||||
return;
|
||||
}
|
||||
if (attribute.value == 128) {
|
||||
blockUser();
|
||||
blockUser(context);
|
||||
return;
|
||||
}
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'点错了',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
@@ -98,6 +97,7 @@ class MemberController extends GetxController {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
await VideoHttp.relationMod(
|
||||
mid: mid,
|
||||
act: memberInfo.value.isFollowed! ? 2 : 1,
|
||||
@@ -105,7 +105,6 @@ class MemberController extends GetxController {
|
||||
);
|
||||
memberInfo.value.isFollowed = !memberInfo.value.isFollowed!;
|
||||
relationSearch();
|
||||
SmartDialog.dismiss();
|
||||
memberInfo.update((val) {});
|
||||
},
|
||||
child: const Text('确认'),
|
||||
@@ -146,21 +145,20 @@ class MemberController extends GetxController {
|
||||
}
|
||||
|
||||
// 拉黑用户
|
||||
Future blockUser() async {
|
||||
Future blockUser(BuildContext context) async {
|
||||
if (userInfo == null) {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
}
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'点错了',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
@@ -168,12 +166,12 @@ class MemberController extends GetxController {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
var res = await VideoHttp.relationMod(
|
||||
mid: mid,
|
||||
act: attribute.value != 128 ? 5 : 6,
|
||||
reSrc: 11,
|
||||
);
|
||||
SmartDialog.dismiss();
|
||||
if (res['status']) {
|
||||
attribute.value = attribute.value != 128 ? 128 : 0;
|
||||
attributeText.value = attribute.value == 128 ? '已拉黑' : '关注';
|
||||
|
||||
@@ -63,6 +63,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool isHorizontal = context.width > context.height;
|
||||
return Scaffold(
|
||||
primary: true,
|
||||
body: Column(
|
||||
@@ -118,7 +119,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
if (_memberController.ownerMid != _memberController.mid) ...[
|
||||
PopupMenuItem(
|
||||
onTap: () => _memberController.blockUser(),
|
||||
onTap: () => _memberController.blockUser(context),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -158,29 +159,42 @@ class _MemberPageState extends State<MemberPage>
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
profileWidget(),
|
||||
|
||||
/// 动态链接
|
||||
ListTile(
|
||||
profileWidget(isHorizontal),
|
||||
Row(children: [
|
||||
const Spacer(),
|
||||
InkWell(
|
||||
onTap: _memberController.pushDynamicsPage,
|
||||
title: const Text('Ta的动态'),
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
child: const Row(
|
||||
children: [
|
||||
Text('Ta的动态', style: TextStyle(height: 2)),
|
||||
SizedBox(width: 5),
|
||||
Icon(Icons.arrow_forward_ios, size: 19),
|
||||
],
|
||||
),
|
||||
|
||||
/// 视频
|
||||
ListTile(
|
||||
),
|
||||
const Spacer(),
|
||||
InkWell(
|
||||
onTap: _memberController.pushArchivesPage,
|
||||
title: const Text('Ta的投稿'),
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
child: const Row(
|
||||
children: [
|
||||
Text('Ta的投稿', style: TextStyle(height: 2)),
|
||||
SizedBox(width: 5),
|
||||
Icon(Icons.arrow_forward_ios, size: 19),
|
||||
],
|
||||
),
|
||||
|
||||
/// 专栏
|
||||
ListTile(
|
||||
),
|
||||
const Spacer(),
|
||||
InkWell(
|
||||
onTap: () {},
|
||||
title: const Text('Ta的专栏'),
|
||||
child: const Row(
|
||||
children: [
|
||||
Text('Ta的专栏', style: TextStyle(height: 2)),
|
||||
SizedBox(width: 5),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
]),
|
||||
MediaQuery.removePadding(
|
||||
removeTop: true,
|
||||
removeBottom: true,
|
||||
@@ -279,29 +293,64 @@ class _MemberPageState extends State<MemberPage>
|
||||
);
|
||||
}
|
||||
|
||||
Widget profileWidget() {
|
||||
Widget profileWidget(bool isHorizontal) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 18, right: 18, bottom: 20),
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
Map data = snapshot.data!;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children: [profilePanelAndDetailInfo(isHorizontal, false)]),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return profilePanelAndDetailInfo(isHorizontal, true);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget profilePanelAndDetailInfo(bool isHorizontal, bool loadingStatus) {
|
||||
if (isHorizontal) {
|
||||
return Row(
|
||||
children: [
|
||||
Column(
|
||||
Expanded(
|
||||
child: ProfilePanel(
|
||||
ctr: _memberController, loadingStatus: loadingStatus)),
|
||||
const SizedBox(width: 20),
|
||||
Expanded(child: profileDetailInfo()),
|
||||
],
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ProfilePanel(ctr: _memberController),
|
||||
ProfilePanel(ctr: _memberController, loadingStatus: loadingStatus),
|
||||
const SizedBox(height: 20),
|
||||
profileDetailInfo(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget profileDetailInfo() {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
_memberController.memberInfo.value.name!,
|
||||
_memberController.memberInfo.value.name ?? '',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
@@ -325,38 +374,33 @@ class _MemberPageState extends State<MemberPage>
|
||||
semanticLabel: '男',
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
if (_memberController.memberInfo.value.level != null)
|
||||
Image.asset(
|
||||
'assets/images/lv/lv${_memberController.memberInfo.value.level}.png',
|
||||
height: 11,
|
||||
semanticLabel:
|
||||
'等级${_memberController.memberInfo.value.level}',
|
||||
semanticLabel: '等级${_memberController.memberInfo.value.level}',
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
if (_memberController.memberInfo.value.vip?.status == 1) ...[
|
||||
if (_memberController
|
||||
.memberInfo.value.vip!.status ==
|
||||
1 &&
|
||||
_memberController.memberInfo.value.vip!
|
||||
.label!['img_label_uri_hans'] !=
|
||||
'') ...[
|
||||
.memberInfo.value.vip?.label?['img_label_uri_hans'] !=
|
||||
'')
|
||||
Image.network(
|
||||
_memberController.memberInfo.value.vip!
|
||||
.label!['img_label_uri_hans'],
|
||||
_memberController
|
||||
.memberInfo.value.vip!.label!['img_label_uri_hans'],
|
||||
height: 20,
|
||||
semanticLabel: _memberController
|
||||
.memberInfo.value.vip!.label!['text'],
|
||||
semanticLabel:
|
||||
_memberController.memberInfo.value.vip!.label!['text'],
|
||||
),
|
||||
] else if (_memberController
|
||||
.memberInfo.value.vip!.status ==
|
||||
1 &&
|
||||
_memberController.memberInfo.value.vip!
|
||||
.label!['img_label_uri_hans_static'] !=
|
||||
'') ...[
|
||||
if (_memberController.memberInfo.value.vip
|
||||
?.label?['img_label_uri_hans_static'] !=
|
||||
'')
|
||||
Image.network(
|
||||
_memberController.memberInfo.value.vip!
|
||||
.label!['img_label_uri_hans_static'],
|
||||
height: 20,
|
||||
semanticLabel: _memberController
|
||||
.memberInfo.value.vip!.label!['text'],
|
||||
semanticLabel:
|
||||
_memberController.memberInfo.value.vip!.label!['text'],
|
||||
),
|
||||
],
|
||||
TextButton(
|
||||
@@ -371,24 +415,19 @@ class _MemberPageState extends State<MemberPage>
|
||||
)),
|
||||
onPressed: () {
|
||||
Clipboard.setData(
|
||||
ClipboardData(
|
||||
text: _memberController.mid.toString()),
|
||||
ClipboardData(text: _memberController.mid.toString()),
|
||||
);
|
||||
SmartDialog.showToast(
|
||||
'已复制${_memberController.mid}至剪贴板');
|
||||
SmartDialog.showToast('已复制${_memberController.mid}至剪贴板');
|
||||
}),
|
||||
],
|
||||
),
|
||||
if (_memberController
|
||||
.memberInfo.value.official!['title'] !=
|
||||
'') ...[
|
||||
if (_memberController.memberInfo.value.official != null &&
|
||||
_memberController.memberInfo.value.official!['title'] != '') ...[
|
||||
const SizedBox(height: 6),
|
||||
Text.rich(
|
||||
maxLines: 2,
|
||||
TextSpan(
|
||||
text: _memberController
|
||||
.memberInfo.value.official!['role'] ==
|
||||
1
|
||||
text: _memberController.memberInfo.value.official!['role'] == 1
|
||||
? '个人认证:'
|
||||
: '企业认证:',
|
||||
style: TextStyle(
|
||||
@@ -396,8 +435,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: _memberController
|
||||
.memberInfo.value.official!['title'],
|
||||
text: _memberController.memberInfo.value.official!['title'],
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -405,24 +443,10 @@ class _MemberPageState extends State<MemberPage>
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 6),
|
||||
if (_memberController.memberInfo.value.sign != '')
|
||||
SelectableText(
|
||||
_memberController.memberInfo.value.sign!,
|
||||
_memberController.memberInfo.value.sign ?? '',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return ProfilePanel(ctr: _memberController, loadingStatus: true);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,7 @@ class ProfilePanel extends StatelessWidget {
|
||||
MemberInfoModel memberInfo = ctr.memberInfo.value;
|
||||
return Builder(
|
||||
builder: ((context) {
|
||||
return Padding(
|
||||
padding:
|
||||
EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
|
||||
child: Row(
|
||||
return Row(
|
||||
children: [
|
||||
Hero(
|
||||
tag: ctr.heroTag!,
|
||||
@@ -181,14 +178,12 @@ class ProfilePanel extends StatelessWidget {
|
||||
Obx(
|
||||
() => Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () => ctr.actionRelationMod(),
|
||||
onPressed: () => ctr.actionRelationMod(context),
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: ctr.attribute.value == -1
|
||||
? Colors.transparent
|
||||
: ctr.attribute.value != 0
|
||||
? Theme.of(context)
|
||||
.colorScheme
|
||||
.outline
|
||||
? Theme.of(context).colorScheme.outline
|
||||
: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimary,
|
||||
@@ -232,8 +227,7 @@ class ProfilePanel extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(left: 80, right: 80),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.onPrimary,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
child: const Text('个人中心(web)'),
|
||||
)
|
||||
@@ -243,8 +237,7 @@ class ProfilePanel extends StatelessWidget {
|
||||
onPressed: () {},
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(left: 80, right: 80),
|
||||
foregroundColor:
|
||||
Theme.of(context).colorScheme.outline,
|
||||
foregroundColor: Theme.of(context).colorScheme.outline,
|
||||
backgroundColor:
|
||||
Theme.of(context).colorScheme.onInverseSurface,
|
||||
),
|
||||
@@ -255,7 +248,6 @@ class ProfilePanel extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:PiliPalaX/common/widgets/badge.dart';
|
||||
import 'package:PiliPalaX/models/member/seasons.dart';
|
||||
import 'package:PiliPalaX/pages/member_seasons/widgets/item.dart';
|
||||
|
||||
import '../../../utils/grid.dart';
|
||||
|
||||
class MemberSeasonsPanel extends StatelessWidget {
|
||||
final MemberSeasonsDataModel? data;
|
||||
const MemberSeasonsPanel({super.key, this.data});
|
||||
@@ -38,10 +40,9 @@ class MemberSeasonsPanel extends StatelessWidget {
|
||||
size: 'small',
|
||||
text: item.meta!.total.toString(),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: 35,
|
||||
height: 35,
|
||||
width: 30,
|
||||
height: 30,
|
||||
child: IconButton(
|
||||
tooltip: '前往',
|
||||
onPressed: () => Get.toNamed(
|
||||
@@ -50,7 +51,7 @@ class MemberSeasonsPanel extends StatelessWidget {
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.arrow_forward,
|
||||
Icons.arrow_forward_ios,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
@@ -61,12 +62,12 @@ class MemberSeasonsPanel extends StatelessWidget {
|
||||
LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2, // Use a fixed count for GridView
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth,
|
||||
childAspectRatio: 0.94,
|
||||
mainAxisExtent: 0,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
|
||||
@@ -65,7 +65,10 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: _memberArchivesController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
SliverPadding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
|
||||
sliver: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (BuildContext context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
@@ -77,16 +80,14 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
|
||||
() => list.isNotEmpty
|
||||
? SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context,
|
||||
maxCrossAxisExtent:
|
||||
Grid.maxRowWidth * 2,
|
||||
StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio:
|
||||
StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, index) {
|
||||
return VideoCardH(
|
||||
@@ -117,6 +118,7 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import 'package:PiliPalaX/utils/utils.dart';
|
||||
import '../../common/constants.dart';
|
||||
import '../../common/widgets/http_error.dart';
|
||||
import '../../utils/grid.dart';
|
||||
import '../../utils/storage.dart';
|
||||
import '../dynamics/widgets/dynamic_panel.dart';
|
||||
import 'package:waterfall_flow/waterfall_flow.dart';
|
||||
|
||||
@@ -22,6 +23,7 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
|
||||
late Future _futureBuilderFuture;
|
||||
late ScrollController scrollController;
|
||||
late int mid;
|
||||
late bool dynamicsWaterfallFlow;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -44,6 +46,8 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
|
||||
}
|
||||
},
|
||||
);
|
||||
dynamicsWaterfallFlow = GStrorage.setting
|
||||
.get(SettingBoxKey.dynamicsWaterfallFlow, defaultValue: true);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -72,9 +76,29 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
|
||||
Map data = snapshot.data as Map;
|
||||
List list = _memberDynamicController.dynamicsList;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => list.isNotEmpty
|
||||
? SliverWaterfallFlow.extent(
|
||||
return Obx(() {
|
||||
if (list.isEmpty) {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
if (!dynamicsWaterfallFlow) {
|
||||
return SliverCrossAxisGroup(
|
||||
slivers: [
|
||||
const SliverFillRemaining(),
|
||||
SliverConstrainedCrossAxis(
|
||||
maxExtent: Grid.maxRowWidth * 2,
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return DynamicPanel(item: list[index]);
|
||||
},
|
||||
childCount: list.length,
|
||||
),
|
||||
)),
|
||||
const SliverFillRemaining(),
|
||||
],
|
||||
);
|
||||
}
|
||||
return SliverWaterfallFlow.extent(
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
//cacheExtent: 0.0,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
@@ -95,9 +119,8 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
|
||||
: LastChildLayoutType.none,
|
||||
children: [
|
||||
for (var i in list) DynamicPanel(item: i),
|
||||
])
|
||||
: const SliverToBoxAdapter(),
|
||||
);
|
||||
]);
|
||||
});
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: snapshot.data['msg'],
|
||||
|
||||
@@ -7,6 +7,8 @@ import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/common/widgets/no_data.dart';
|
||||
import 'package:PiliPalaX/common/widgets/video_card_h.dart';
|
||||
|
||||
import '../../common/constants.dart';
|
||||
import '../../utils/grid.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
class MemberSearchPage extends StatefulWidget {
|
||||
@@ -82,116 +84,98 @@ class _MemberSearchPageState extends State<MemberSearchPage>
|
||||
),
|
||||
),
|
||||
body: Obx(
|
||||
() => Column(
|
||||
children: _memberSearchCtr.loadingStatus.value == 'init'
|
||||
? [
|
||||
Expanded(
|
||||
child: Center(
|
||||
() {
|
||||
if (_memberSearchCtr.loadingStatus.value == 'init') {
|
||||
return Center(
|
||||
child: Text('搜索「${_memberSearchCtr.uname.value}」的动态、视频'),
|
||||
),
|
||||
),
|
||||
]
|
||||
: [
|
||||
// TabBar(
|
||||
// controller: _tabController,
|
||||
// tabs: const [
|
||||
// Tab(text: "视频"),
|
||||
// Tab(text: "动态"),
|
||||
// ],
|
||||
// ),
|
||||
Expanded(
|
||||
child:
|
||||
// TabBarView(
|
||||
// controller: _tabController,
|
||||
// children: [
|
||||
);
|
||||
}
|
||||
return CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
controller: scrollController,
|
||||
slivers: <Widget>[
|
||||
FutureBuilder(
|
||||
future: _memberSearchCtr.searchArchives(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
Map data = snapshot.data as Map;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
return SliverPadding(
|
||||
padding:
|
||||
const EdgeInsets.all(StyleString.safeSpace),
|
||||
sliver: Obx(
|
||||
() => _memberSearchCtr.archiveList.isNotEmpty
|
||||
? ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount:
|
||||
_memberSearchCtr.archiveList.length +
|
||||
1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index ==
|
||||
_memberSearchCtr
|
||||
.archiveList.length) {
|
||||
return Container(
|
||||
height: MediaQuery.of(context)
|
||||
.padding
|
||||
.bottom +
|
||||
60,
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context)
|
||||
.padding
|
||||
.bottom),
|
||||
child: Center(
|
||||
child: Obx(
|
||||
() => Text(
|
||||
_memberSearchCtr
|
||||
.loadingText.value,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.outline,
|
||||
fontSize: 13),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
? SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString
|
||||
.safeSpace,
|
||||
crossAxisSpacing: StyleString
|
||||
.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth *
|
||||
2,
|
||||
childAspectRatio:
|
||||
StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return VideoCardH(
|
||||
videoItem: _memberSearchCtr
|
||||
.archiveList[index]);
|
||||
}
|
||||
},
|
||||
)
|
||||
childCount: _memberSearchCtr.archiveList
|
||||
.length))
|
||||
: _memberSearchCtr.loadingStatus.value ==
|
||||
'loading'
|
||||
? ListView.builder(
|
||||
itemCount: 10,
|
||||
itemBuilder: (context, index) {
|
||||
? SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing:
|
||||
StyleString.cardSpace,
|
||||
crossAxisSpacing:
|
||||
StyleString.safeSpace,
|
||||
maxCrossAxisExtent:
|
||||
Grid.maxRowWidth * 2,
|
||||
childAspectRatio:
|
||||
StyleString.aspectRatio *
|
||||
2.1,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
},
|
||||
)
|
||||
: const CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
NoData(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}, childCount: 10))
|
||||
: const NoData(),
|
||||
));
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
HttpError(
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return ListView.builder(
|
||||
itemCount: 10,
|
||||
itemBuilder: (context, index) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.all(StyleString.safeSpace),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio:
|
||||
StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate:
|
||||
SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
},
|
||||
);
|
||||
}, childCount: 10)));
|
||||
}
|
||||
},
|
||||
),
|
||||
// ],
|
||||
// ),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,7 +151,10 @@ class _MinePageState extends State<MinePage> {
|
||||
semanticsLabel: '头像',
|
||||
width: 85,
|
||||
height: 85)
|
||||
: Image.asset('assets/images/noface.jpeg',semanticLabel: "默认头像",),
|
||||
: Image.asset(
|
||||
'assets/images/noface.jpeg',
|
||||
semanticLabel: "默认头像",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -183,8 +186,8 @@ class _MinePageState extends State<MinePage> {
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.outline)),
|
||||
TextSpan(
|
||||
text: (_mineController.userInfo.value.money ?? '-')
|
||||
.toString(),
|
||||
text:
|
||||
(_mineController.userInfo.value.money ?? '-').toString(),
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).colorScheme.primary)),
|
||||
]))
|
||||
@@ -256,12 +259,12 @@ class _MinePageState extends State<MinePage> {
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
fontWeight: FontWeight.bold);
|
||||
return SizedBox(
|
||||
height: constraints.maxWidth / 3 * 0.6,
|
||||
height: constraints.maxWidth * 0.33 * 0.6,
|
||||
child: GridView.count(
|
||||
primary: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
crossAxisCount: 3,
|
||||
childAspectRatio: 1.67,
|
||||
childAspectRatio: 1.66,
|
||||
children: <Widget>[
|
||||
InkWell(
|
||||
onTap: () => _mineController.pushDynamic(),
|
||||
|
||||
@@ -90,7 +90,8 @@ class _ImagePreviewState extends State<ImagePreview>
|
||||
ListTile(
|
||||
onTap: () {
|
||||
Get.back();
|
||||
DownloadUtils.downloadImg(_previewController.currentImgUrl);
|
||||
DownloadUtils.downloadImg(
|
||||
context, _previewController.currentImgUrl);
|
||||
},
|
||||
dense: true,
|
||||
title: const Text('保存到手机', style: TextStyle(fontSize: 14)),
|
||||
|
||||
@@ -34,7 +34,7 @@ class RankController extends GetxController with GetTickerProviderStateMixin {
|
||||
void animateToTop() {
|
||||
int index = tabController.index;
|
||||
var ctr = tabsCtrList[index];
|
||||
ctr().animateToTop();
|
||||
ctr.animateToTop();
|
||||
}
|
||||
|
||||
void setTabConfig() async {
|
||||
|
||||
@@ -35,41 +35,63 @@ class _RankPageState extends State<RankPage>
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_rankController.tabController.removeListener(() {});
|
||||
_rankController.tabController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 0,
|
||||
elevation: 0,
|
||||
systemOverlayStyle: SystemUiOverlayStyle(
|
||||
// Customize the status bar here
|
||||
statusBarIconBrightness:
|
||||
MediaQuery.of(context).platformBrightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
),
|
||||
),
|
||||
body: Row(
|
||||
return Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: StyleString.cardSpace,
|
||||
),
|
||||
// SizedBox(
|
||||
// width: 55,
|
||||
// child: NavigationRail(
|
||||
//
|
||||
// backgroundColor: Colors.transparent,
|
||||
// minWidth: 50.0,
|
||||
// // elevation: 0,
|
||||
// selectedIndex: _selectedTabIndex,
|
||||
// onDestinationSelected: (int index) {
|
||||
// feedBack();
|
||||
// if (_selectedTabIndex == index) {
|
||||
// _rankController.tabsCtrList[index]().animateToTop();
|
||||
// } else {
|
||||
// setState(() {
|
||||
// _rankController.tabController.index = index;
|
||||
// _selectedTabIndex = index;
|
||||
// });
|
||||
// }
|
||||
// },
|
||||
// labelType: NavigationRailLabelType.none,
|
||||
// destinations: [
|
||||
// for (var tab in _rankController.tabs)
|
||||
// NavigationRailDestination(
|
||||
// padding: EdgeInsets.zero,
|
||||
// icon: Text(tab['label']),
|
||||
// // selectedIcon: Text(tab['label']),
|
||||
// label: const SizedBox.shrink(),
|
||||
// ),
|
||||
// ],
|
||||
// trailing: const SizedBox(height: 100),
|
||||
// )),
|
||||
LayoutBuilder(builder: (context, constraint) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(minHeight: constraint.maxHeight + 100),
|
||||
constraints: BoxConstraints(minHeight: constraint.maxHeight),
|
||||
child: IntrinsicHeight(
|
||||
child: NavigationRail(
|
||||
minWidth: 55.0,
|
||||
backgroundColor: Colors.transparent,
|
||||
minWidth: 50.0,
|
||||
// elevation: 0,
|
||||
selectedIndex: _selectedTabIndex,
|
||||
onDestinationSelected: (int index) {
|
||||
feedBack();
|
||||
@@ -91,6 +113,7 @@ class _RankPageState extends State<RankPage>
|
||||
label: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
trailing: const SizedBox(height: 100),
|
||||
))));
|
||||
}),
|
||||
Expanded(
|
||||
@@ -100,7 +123,6 @@ class _RankPageState extends State<RankPage>
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ class ZonePage extends StatefulWidget {
|
||||
class _ZonePageState extends State<ZonePage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late ZoneController _zoneController;
|
||||
List videoList = [];
|
||||
Future? _futureBuilderFuture;
|
||||
late ScrollController scrollController;
|
||||
|
||||
@@ -81,12 +80,12 @@ class _ZonePageState extends State<ZonePage>
|
||||
return await _zoneController.onRefresh();
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _zoneController.scrollController,
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
SliverPadding(
|
||||
// 单列布局 EdgeInsets.zero
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0),
|
||||
const EdgeInsets.fromLTRB(StyleString.safeSpace, StyleString.safeSpace, 0, 0),
|
||||
sliver: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
@@ -95,19 +94,12 @@ class _ZonePageState extends State<ZonePage>
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 最大宽度
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(context,
|
||||
Grid.maxRowWidth * 2, StyleString.cardSpace,
|
||||
screenWidthOffset:
|
||||
StyleString.cardSpace + 55) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return VideoCardH(
|
||||
videoItem: _zoneController.videoList[index],
|
||||
@@ -138,7 +130,13 @@ class _ZonePageState extends State<ZonePage>
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 10),
|
||||
|
||||
@@ -146,17 +146,15 @@ class _RcmdPageState extends State<RcmdPage>
|
||||
|
||||
Widget contentGrid(ctr, videoList) {
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
// 行间距
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
// 列间距
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 最大宽度
|
||||
maxCrossAxisExtent: Grid.maxRowWidth,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context, Grid.maxRowWidth, StyleString.safeSpace) /
|
||||
StyleString.aspectRatio +
|
||||
MediaQuery.textScalerOf(context).scale(90),
|
||||
childAspectRatio: StyleString.aspectRatio,
|
||||
mainAxisExtent: MediaQuery.textScalerOf(context).scale(90),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import 'package:PiliPalaX/common/skeleton/video_card_h.dart';
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/models/common/search_type.dart';
|
||||
|
||||
import '../../common/constants.dart';
|
||||
import '../../utils/grid.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/article_panel.dart';
|
||||
import 'widgets/live_panel.dart';
|
||||
@@ -132,11 +134,18 @@ class _SearchPanelState extends State<SearchPanel>
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return ListView.builder(
|
||||
addAutomaticKeepAlives: false,
|
||||
addRepaintBoundaries: false,
|
||||
itemCount: 15,
|
||||
itemBuilder: (context, index) {
|
||||
return CustomScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
switch (widget.searchType) {
|
||||
case SearchType.video:
|
||||
return const VideoCardHSkeleton();
|
||||
@@ -150,7 +159,9 @@ class _SearchPanelState extends State<SearchPanel>
|
||||
return const VideoCardHSkeleton();
|
||||
}
|
||||
},
|
||||
);
|
||||
childCount: 15,
|
||||
))
|
||||
]);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -12,14 +12,12 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
|
||||
color: Theme.of(context).colorScheme.outline);
|
||||
return CustomScrollView(controller: ctr.scrollController, slivers: [
|
||||
SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context, Grid.maxRowWidth * 2, StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return InkWell(
|
||||
@@ -32,8 +30,8 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
|
||||
});
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace),
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
final double width = (boxConstraints.maxWidth -
|
||||
StyleString.cardSpace *
|
||||
|
||||
@@ -13,14 +13,12 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
|
||||
child: GridView.builder(
|
||||
primary: false,
|
||||
controller: ctr!.scrollController,
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
maxCrossAxisExtent: Grid.maxRowWidth,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context, Grid.maxRowWidth, StyleString.safeSpace) /
|
||||
StyleString.aspectRatio +
|
||||
MediaQuery.textScalerOf(context).scale(80),
|
||||
childAspectRatio: StyleString.aspectRatio,
|
||||
mainAxisExtent: MediaQuery.textScalerOf(context).scale(80),
|
||||
),
|
||||
itemCount: list.length,
|
||||
itemBuilder: (context, index) {
|
||||
|
||||
@@ -19,10 +19,10 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
||||
slivers: [
|
||||
SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: 157,
|
||||
mainAxisExtent: 160,
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate((BuildContext context, int index) {
|
||||
var i = list![index];
|
||||
@@ -36,8 +36,8 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
||||
// });
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace, 7, StyleString.safeSpace, 2),
|
||||
padding: const EdgeInsets.fromLTRB(StyleString.safeSpace,
|
||||
StyleString.safeSpace, StyleString.safeSpace, 2),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -125,7 +125,10 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
|
||||
if (res['status']) {
|
||||
EpisodeItem episode =
|
||||
res['data'].episodes.first;
|
||||
int? epId = res['data'].userStatus?.progress?.lastEpId;
|
||||
int? epId = res['data']
|
||||
.userStatus
|
||||
?.progress
|
||||
?.lastEpId;
|
||||
if (epId == null) {
|
||||
epId = episode.epId;
|
||||
} else {
|
||||
|
||||
@@ -28,7 +28,8 @@ class SearchVideoPanel extends StatelessWidget {
|
||||
Container(
|
||||
width: context.width,
|
||||
height: 34,
|
||||
padding: const EdgeInsets.only(left: 8, top: 0, right: 12),
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, top: 0, right: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
@@ -69,7 +70,7 @@ class SearchVideoPanel extends StatelessWidget {
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () => controller.onShowFilterDialog(ctr),
|
||||
onPressed: () => controller.onShowFilterDialog(context, ctr),
|
||||
icon: Icon(
|
||||
Icons.filter_list_outlined,
|
||||
size: 18,
|
||||
@@ -84,22 +85,23 @@ class SearchVideoPanel extends StatelessWidget {
|
||||
child: CustomScrollView(
|
||||
controller: ctr.scrollController,
|
||||
slivers: [
|
||||
SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(StyleString.safeSpace),
|
||||
sliver: SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(context,
|
||||
Grid.maxRowWidth * 2, StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return VideoCardH(videoItem: list[index], showPubdate: true);
|
||||
return VideoCardH(
|
||||
videoItem: list[index], showPubdate: true);
|
||||
},
|
||||
childCount: list.length,
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
)),
|
||||
],
|
||||
@@ -176,10 +178,10 @@ class VideoPanelController extends GetxController {
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
onShowFilterDialog(searchPanelCtr) {
|
||||
SmartDialog.show(
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
onShowFilterDialog(BuildContext context, SearchPanelController searchPanelCtr) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
TextStyle textStyle = Theme.of(context).textTheme.titleMedium!;
|
||||
return AlertDialog(
|
||||
title: const Text('时长筛选'),
|
||||
|
||||
@@ -48,17 +48,16 @@ class SettingController extends GetxController {
|
||||
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0);
|
||||
}
|
||||
|
||||
loginOut() async {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
loginOut(BuildContext context) async {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('确认要退出登录吗'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('点错了'),
|
||||
),
|
||||
TextButton(
|
||||
@@ -73,7 +72,7 @@ class SettingController extends GetxController {
|
||||
.put(LocalCacheKey.accessKey, {'mid': -1, 'value': ''});
|
||||
|
||||
await LoginUtils.refreshLoginStatus(false);
|
||||
SmartDialog.dismiss().then((value) => Get.back());
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确认'),
|
||||
)
|
||||
|
||||
@@ -54,10 +54,9 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
||||
var systemProxyHost = '';
|
||||
var systemProxyPort = '';
|
||||
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('设置代理'),
|
||||
content: Column(
|
||||
@@ -101,7 +100,7 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: Text(
|
||||
'取消',
|
||||
@@ -112,7 +111,7 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
||||
onPressed: () async {
|
||||
setting.put(SettingBoxKey.systemProxyHost, systemProxyHost);
|
||||
setting.put(SettingBoxKey.systemProxyPort, systemProxyPort);
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
// Request.dio;
|
||||
},
|
||||
child: const Text('确认'),
|
||||
@@ -210,6 +209,13 @@ class _ExtraSettingState extends State<ExtraSetting> {
|
||||
setKey: SettingBoxKey.disableLikeMsg,
|
||||
defaultVal: false,
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '默认展示评论区',
|
||||
subTitle: '在视频详情页默认切换至评论区页(仅tab型布局)',
|
||||
leading: Icon(Icons.mode_comment_outlined),
|
||||
setKey: SettingBoxKey.defaultShowComment,
|
||||
defaultVal: false,
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('评论展示', style: titleStyle),
|
||||
|
||||
@@ -12,7 +12,8 @@ class FontSizeSelectPage extends StatefulWidget {
|
||||
|
||||
class _FontSizeSelectPageState extends State<FontSizeSelectPage> {
|
||||
Box setting = GStrorage.setting;
|
||||
List<double> list = [0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3];
|
||||
List<double> list = List.generate(12, (index) => 0.85 + index * 0.05);
|
||||
//[0.85, 0.9, 0.95, 1.0, 1.05, 1.1, 1.15, 1.2, 1.25, 1.3, 1.35];
|
||||
late double minsize;
|
||||
late double maxSize;
|
||||
late double currentSize;
|
||||
|
||||
@@ -21,7 +21,7 @@ class _TabbarSetPageState extends State<TabbarSetPage> {
|
||||
super.initState();
|
||||
defaultTabs = tabsConfig;
|
||||
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
|
||||
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
|
||||
defaultValue: ['live', 'rcmd', 'hot', 'rank', 'bangumi']);
|
||||
// 对 tabData 进行排序
|
||||
defaultTabs.sort((a, b) {
|
||||
int indexA = tabbarSort.indexOf((a['type'] as TabType).id);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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:PiliPalaX/pages/setting/widgets/switch_item.dart';
|
||||
import 'package:PiliPalaX/plugin/pl_player/index.dart';
|
||||
@@ -76,10 +77,9 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||
// 添加自定义倍速
|
||||
void onAddSpeed() {
|
||||
double customSpeed = 1.0;
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('添加倍速'),
|
||||
content: Column(
|
||||
@@ -103,7 +103,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
@@ -112,7 +112,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
|
||||
await videoStorage.put(
|
||||
VideoBoxKey.customSpeedsList, customSpeedsList);
|
||||
setState(() {});
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确认添加'),
|
||||
)
|
||||
|
||||
@@ -120,7 +120,7 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '竖屏扩大展示',
|
||||
subTitle: '小屏竖屏视频宽高比由16:9扩大至4:5(!暂不支持临时收起)',
|
||||
subTitle: '小屏竖屏视频宽高比由16:9扩大至1:1(不支持收起);横屏适配时,扩大至9:16',
|
||||
leading: Icon(Icons.expand_outlined),
|
||||
setKey: SettingBoxKey.enableVerticalExpand,
|
||||
defaultVal: false,
|
||||
@@ -151,7 +151,7 @@ class _PlaySettingState extends State<PlaySetting> {
|
||||
subTitle: '进入后台时继续播放',
|
||||
leading: Icon(Icons.motion_photos_pause_outlined),
|
||||
setKey: SettingBoxKey.continuePlayInBackground,
|
||||
defaultVal: true,
|
||||
defaultVal: false,
|
||||
),
|
||||
if (Platform.isAndroid)
|
||||
SetSwitchItem(
|
||||
|
||||
@@ -121,7 +121,8 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
)),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
SmartDialog.show(
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('查看详情'),
|
||||
@@ -130,7 +131,7 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确认'),
|
||||
)
|
||||
@@ -150,9 +151,9 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
}
|
||||
|
||||
void import_export_cookies(TextStyle titleStyle, TextStyle subTitleStyle) {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
builder: (BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: const Text('导入/导出cookie', style: TextStyle(color: Colors.red)),
|
||||
children: [
|
||||
@@ -175,13 +176,14 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
),
|
||||
dense: false,
|
||||
onTap: () async {
|
||||
await SmartDialog.dismiss();
|
||||
Navigator.of(context).pop();
|
||||
if (!userLogin) {
|
||||
SmartDialog.showToast('请先登录');
|
||||
return;
|
||||
}
|
||||
final String cookie = await CookieTool.exportCookie();
|
||||
await SmartDialog.show(
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('导出cookie(危险)',
|
||||
@@ -190,7 +192,7 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await SmartDialog.dismiss();
|
||||
Navigator.of(context).pop();
|
||||
await Clipboard.setData(
|
||||
ClipboardData(text: cookie));
|
||||
},
|
||||
@@ -199,7 +201,7 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await SmartDialog.dismiss();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('取消'),
|
||||
),
|
||||
@@ -223,13 +225,14 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
),
|
||||
dense: false,
|
||||
onTap: () async {
|
||||
await SmartDialog.dismiss();
|
||||
ClipboardData? data = await Clipboard.getData('text/plain');
|
||||
if (data == null || data.text == null || data.text == '') {
|
||||
SmartDialog.showToast('未检测到剪贴板内容');
|
||||
return;
|
||||
}
|
||||
await SmartDialog.show(
|
||||
if (!context.mounted) return;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('导入剪贴板中的cookie'),
|
||||
@@ -237,13 +240,13 @@ class _PrivacySettingState extends State<PrivacySetting> {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await SmartDialog.dismiss();
|
||||
Get.back();
|
||||
final String cookie = data.text!;
|
||||
try {
|
||||
await CookieTool.importCookie(cookie);
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:PiliPalaX/models/common/rcmd_type.dart';
|
||||
import 'package:PiliPalaX/pages/setting/widgets/select_dialog.dart';
|
||||
import 'package:PiliPalaX/utils/recommend_filter.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'widgets/switch_item.dart';
|
||||
|
||||
@@ -93,8 +94,9 @@ class _RecommendSettingState extends State<RecommendSetting> {
|
||||
return;
|
||||
}
|
||||
// 显示一个确认框,告知用户可能会导致账号被风控
|
||||
SmartDialog.show(
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
if (!context.mounted) return;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
@@ -104,14 +106,20 @@ class _RecommendSettingState extends State<RecommendSetting> {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
result = null;
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('取消'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
SmartDialog.dismiss();
|
||||
await MemberHttp.cookieToKey();
|
||||
Get.back();
|
||||
var res = await MemberHttp.cookieToKey();
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
} else {
|
||||
SmartDialog.showToast(
|
||||
'获取access_key失败:${res['msg']}');
|
||||
}
|
||||
},
|
||||
child: const Text('确定'),
|
||||
),
|
||||
@@ -254,10 +262,9 @@ class _RecommendSettingState extends State<RecommendSetting> {
|
||||
'* 其它(如热门视频、手动搜索、链接跳转等)均不受过滤器影响。\n'
|
||||
'* 设定较严苛的条件可导致推荐项数锐减或多次请求,请酌情选择。\n'
|
||||
'* 后续可能会增加更多过滤条件,敬请期待。',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelSmall!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline.withOpacity(0.7)),
|
||||
style: Theme.of(context).textTheme.labelSmall!.copyWith(
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline.withOpacity(0.7)),
|
||||
),
|
||||
)
|
||||
],
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:PiliPalaX/utils/global_data.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
|
||||
import '../../models/common/dynamic_badge_mode.dart';
|
||||
import '../../models/common/up_panel_position.dart';
|
||||
import '../../plugin/pl_player/utils/fullscreen.dart';
|
||||
import '../../models/common/nav_bar_config.dart';
|
||||
import 'controller.dart';
|
||||
@@ -34,13 +35,18 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
late int picQuality;
|
||||
late ThemeType _tempThemeValue;
|
||||
late double maxRowWidth;
|
||||
late UpPanelPosition upPanelPosition;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
|
||||
_tempThemeValue = settingController.themeType.value;
|
||||
maxRowWidth = setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double;
|
||||
maxRowWidth =
|
||||
setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double;
|
||||
upPanelPosition = UpPanelPosition.values[setting.get(
|
||||
SettingBoxKey.upPanelPosition,
|
||||
defaultValue: UpPanelPosition.leftFixed.code)];
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -70,15 +76,15 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
callFn: (value) {
|
||||
if (value) {
|
||||
autoScreen();
|
||||
SmartDialog.showToast('已开启横屏适配');
|
||||
SmartDialog.showToast('已开启横屏适配,推荐将全屏方式设为【不改变当前方向】');
|
||||
} else {
|
||||
AutoOrientation.portraitUpMode();
|
||||
SmartDialog.showToast('已关闭横屏适配');
|
||||
}
|
||||
}),
|
||||
const SetSwitchItem(
|
||||
title: '自适应底栏/侧边栏',
|
||||
subTitle: '横竖屏自动切换(其它底栏设置失效)',
|
||||
title: '改用侧边栏',
|
||||
subTitle: '开启后底栏被替换,且底栏相关设置失效',
|
||||
leading: Icon(Icons.chrome_reader_mode_outlined),
|
||||
setKey: SettingBoxKey.adaptiveNavBar,
|
||||
defaultVal: false,
|
||||
@@ -90,22 +96,6 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
setKey: SettingBoxKey.enableMYBar,
|
||||
defaultVal: true,
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '首页顶栏收起',
|
||||
subTitle: '首页列表滑动时,收起顶栏',
|
||||
leading: Icon(Icons.vertical_align_top_outlined),
|
||||
setKey: SettingBoxKey.hideSearchBar,
|
||||
defaultVal: false,
|
||||
needReboot: true,
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '首页底栏收起',
|
||||
subTitle: '首页列表滑动时,收起底栏',
|
||||
leading: Icon(Icons.vertical_align_bottom_outlined),
|
||||
setKey: SettingBoxKey.hideTabBar,
|
||||
defaultVal: false,
|
||||
needReboot: true,
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '首页背景渐变',
|
||||
setKey: SettingBoxKey.enableGradientBg,
|
||||
@@ -126,8 +116,7 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
divisions: 35,
|
||||
suffix: 'dp',
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
if (result != null) {
|
||||
maxRowWidth = result;
|
||||
setting.put(SettingBoxKey.maxRowWidth, result);
|
||||
@@ -137,13 +126,80 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
},
|
||||
leading: const Icon(Icons.calendar_view_week_outlined),
|
||||
dense: false,
|
||||
title: Text('列表宽度(dp)上限', style: titleStyle),
|
||||
title: Text('列表宽度(dp)限制', style: titleStyle),
|
||||
subtitle: Text(
|
||||
'当前:${maxRowWidth.toInt()}dp,屏幕宽度:${MediaQuery.of(context).size.width.toPrecision(2)}dp。'
|
||||
'宽度越小列数越多,横条、大卡会2倍折算',
|
||||
style: subTitleStyle,
|
||||
),
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '播放页移除安全边距',
|
||||
subTitle: '隐藏状态栏、撑满屏幕,但播放控件仍处于安全域内',
|
||||
leading: Icon(Icons.crop_outlined),
|
||||
setKey: SettingBoxKey.videoPlayerRemoveSafeArea,
|
||||
defaultVal: false,
|
||||
needReboot: true,
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '动态页启用瀑布流',
|
||||
subTitle: '关闭会显示为单列',
|
||||
leading: Icon(Icons.view_array_outlined),
|
||||
setKey: SettingBoxKey.dynamicsWaterfallFlow,
|
||||
defaultVal: true,
|
||||
needReboot: true,
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
title: Text('动态页Up主显示位置', style: titleStyle),
|
||||
leading: const Icon(Icons.person_outlined),
|
||||
subtitle: Text('当前:${upPanelPosition.labels}', style: subTitleStyle),
|
||||
onTap: () async {
|
||||
UpPanelPosition? result = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SelectDialog<UpPanelPosition>(
|
||||
title: '动态页Up主显示位置',
|
||||
value: upPanelPosition,
|
||||
values: UpPanelPosition.values.map((e) {
|
||||
return {'title': e.labels, 'value': e};
|
||||
}).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
if (result != null) {
|
||||
upPanelPosition = result;
|
||||
setting.put(SettingBoxKey.upPanelPosition, result.code);
|
||||
SmartDialog.showToast('重启生效');
|
||||
setState(() {});
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
onTap: () => settingController.setDynamicBadgeMode(context),
|
||||
title: Text('动态未读标记', style: titleStyle),
|
||||
leading: const Icon(Icons.motion_photos_on_outlined),
|
||||
subtitle: Obx(() => Text(
|
||||
'当前标记样式:${settingController.dynamicBadgeType.value.description}',
|
||||
style: subTitleStyle)),
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '首页顶栏收起',
|
||||
subTitle: '首页列表滑动时,收起顶栏',
|
||||
leading: Icon(Icons.vertical_align_top_outlined),
|
||||
setKey: SettingBoxKey.hideSearchBar,
|
||||
defaultVal: false,
|
||||
needReboot: true,
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '首页底栏收起',
|
||||
subTitle: '首页列表滑动时,收起底栏',
|
||||
leading: Icon(Icons.vertical_align_bottom_outlined),
|
||||
setKey: SettingBoxKey.hideTabBar,
|
||||
defaultVal: false,
|
||||
needReboot: true,
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
onTap: () {
|
||||
@@ -235,6 +291,9 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
leading: const Icon(Icons.opacity_outlined),
|
||||
title: Text('气泡提示不透明度', style: titleStyle),
|
||||
subtitle: Text('自定义气泡提示(Toast)不透明度', style: subTitleStyle),
|
||||
trailing: Obx(() => Text(
|
||||
settingController.toastOpacity.value.toStringAsFixed(1),
|
||||
style: Theme.of(context).textTheme.titleSmall)),
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
@@ -263,15 +322,6 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
'当前模式:${settingController.themeType.value.description}',
|
||||
style: subTitleStyle)),
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
onTap: () => settingController.setDynamicBadgeMode(context),
|
||||
title: Text('动态未读标记', style: titleStyle),
|
||||
leading: const Icon(Icons.motion_photos_on_outlined),
|
||||
subtitle: Obx(() => Text(
|
||||
'当前标记样式:${settingController.dynamicBadgeType.value.description}',
|
||||
style: subTitleStyle)),
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
onTap: () => Get.toNamed('/colorSetting'),
|
||||
@@ -281,13 +331,6 @@ class _StyleSettingState extends State<StyleSetting> {
|
||||
'当前主题:${colorSelectController.type.value == 0 ? '动态取色' : '指定颜色'}',
|
||||
style: subTitleStyle)),
|
||||
),
|
||||
const SetSwitchItem(
|
||||
title: '默认展示评论区',
|
||||
subTitle: '在视频详情页默认切换至评论区页',
|
||||
leading: Icon(Icons.mode_comment_outlined),
|
||||
setKey: SettingBoxKey.defaultShowComment,
|
||||
defaultVal: false,
|
||||
),
|
||||
ListTile(
|
||||
dense: false,
|
||||
onTap: () => settingController.seteDefaultHomePage(context),
|
||||
|
||||
@@ -56,7 +56,7 @@ class SettingPage extends StatelessWidget {
|
||||
leading: const Icon(Icons.style_outlined),
|
||||
dense: false,
|
||||
title: const Text('外观设置'),
|
||||
subtitle: Text('横屏适配(平板)、列宽、首页、主题、字号、图片、动态红点、帧率等', style: subTitleStyle),
|
||||
subtitle: Text('横屏适配(平板)、侧栏、列宽、首页、动态红点、主题、字号、图片、帧率等', style: subTitleStyle),
|
||||
),
|
||||
ListTile(
|
||||
onTap: () => Get.toNamed('/extraSetting'),
|
||||
@@ -81,7 +81,7 @@ class SettingPage extends StatelessWidget {
|
||||
visible: settingController.userLogin.value,
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.logout_outlined),
|
||||
onTap: () => settingController.loginOut(),
|
||||
onTap: () => settingController.loginOut(context),
|
||||
dense: false,
|
||||
title: const Text('退出登录'),
|
||||
),
|
||||
|
||||
@@ -58,17 +58,12 @@ class _SubPageState extends State<SubPage> {
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverGrid(
|
||||
gridDelegate:
|
||||
SliverGridDelegateWithMaxCrossAxisExtent(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.cardSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
mainAxisExtent: Grid.calculateActualWidth(
|
||||
context,
|
||||
Grid.maxRowWidth * 2,
|
||||
StyleString.safeSpace) /
|
||||
2.1 /
|
||||
StyleString.aspectRatio),
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
childCount:
|
||||
_subController.subFolderData.value.list!.length,
|
||||
|
||||
@@ -266,7 +266,7 @@ class VideoDetailController extends GetxController
|
||||
type: DataSourceType.network,
|
||||
httpHeaders: {
|
||||
'user-agent':
|
||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 13_3_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.4 Safari/605.1.15',
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
|
||||
'referer': HttpString.baseUrl
|
||||
},
|
||||
),
|
||||
@@ -329,7 +329,7 @@ class VideoDetailController extends GetxController
|
||||
return result;
|
||||
}
|
||||
final List<VideoItem> allVideosList = data.dash!.video!;
|
||||
print("allVideosList:${allVideosList}");
|
||||
// print("allVideosList:${allVideosList}");
|
||||
// 当前可播放的最高质量视频
|
||||
int currentHighVideoQa = allVideosList.first.quality!.code;
|
||||
// 预设的画质为null,则当前可用的最高质量
|
||||
@@ -426,6 +426,7 @@ class VideoDetailController extends GetxController
|
||||
} else {
|
||||
if (result['code'] == -404) {
|
||||
isShowCover.value = false;
|
||||
SmartDialog.showToast('视频不存在或已被删除');
|
||||
}
|
||||
if (result['code'] == 87008) {
|
||||
SmartDialog.showToast("当前视频可能是专属视频,可能需包月充电观看(${result['msg']})");
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
@@ -42,6 +43,8 @@ class VideoIntroController extends GetxController {
|
||||
|
||||
// 是否点赞
|
||||
RxBool hasLike = false.obs;
|
||||
// 是否点踩
|
||||
RxBool hasDislike = false.obs;
|
||||
// 是否投币
|
||||
RxBool hasCoin = false.obs;
|
||||
// 是否收藏
|
||||
@@ -145,15 +148,16 @@ class VideoIntroController extends GetxController {
|
||||
// 获取点赞状态
|
||||
Future queryHasLikeVideo() async {
|
||||
var result = await VideoHttp.hasLikeVideo(bvid: bvid);
|
||||
// data num 被点赞标志 0:未点赞 1:已点赞
|
||||
hasLike.value = result["data"] == 1 ? true : false;
|
||||
// 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 ? false : true;
|
||||
hasCoin.value = result["data"]['multiply'] != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +174,7 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
|
||||
// 一键三连
|
||||
Future actionOneThree() async {
|
||||
Future actionOneThree(BuildContext context) async {
|
||||
if (userInfo == null) {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
return;
|
||||
@@ -180,19 +184,19 @@ class VideoIntroController extends GetxController {
|
||||
SmartDialog.showToast('🙏 UP已经收到了~');
|
||||
return false;
|
||||
}
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('一键三连 给UP送温暖'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: const Text('点错了')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Get.back();
|
||||
var result = await VideoHttp.oneThree(bvid: bvid);
|
||||
if (result['status']) {
|
||||
hasLike.value = result["data"]["like"];
|
||||
@@ -202,7 +206,6 @@ class VideoIntroController extends GetxController {
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
},
|
||||
child: const Text('确认'),
|
||||
)
|
||||
@@ -224,6 +227,7 @@ class VideoIntroController extends GetxController {
|
||||
if (!hasLike.value) {
|
||||
SmartDialog.showToast('点赞成功');
|
||||
hasLike.value = true;
|
||||
hasDislike.value = false;
|
||||
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
|
||||
} else if (hasLike.value) {
|
||||
SmartDialog.showToast('取消赞');
|
||||
@@ -236,6 +240,29 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
|
||||
// 投币
|
||||
Future actionCoinVideo() async {
|
||||
if (userInfo == null) {
|
||||
@@ -350,10 +377,33 @@ class VideoIntroController extends GetxController {
|
||||
|
||||
// 分享视频
|
||||
Future actionShareVideo() async {
|
||||
var result = await Share.share(
|
||||
'${videoDetail.value.title} UP主: ${videoDetail.value.owner!.name!} - ${HttpString.baseUrl}/video/$bvid')
|
||||
showDialog(
|
||||
context: Get.context!,
|
||||
builder: (context) {
|
||||
String videoUrl = '${HttpString.baseUrl}/video/$bvid';
|
||||
return AlertDialog(
|
||||
title: const Text('分享方式'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: videoUrl));
|
||||
SmartDialog.showToast('已复制');
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('复制链接')),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
var result = await Share.share('${videoDetail.value.title} '
|
||||
'UP主: ${videoDetail.value.owner!.name!}'
|
||||
' - $videoUrl')
|
||||
.whenComplete(() {});
|
||||
Get.back();
|
||||
return result;
|
||||
},
|
||||
child: const Text('分享视频')),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future queryVideoInFolder() async {
|
||||
@@ -394,7 +444,7 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
|
||||
// 关注/取关up
|
||||
Future actionRelationMod() async {
|
||||
Future actionRelationMod(BuildContext context) async {
|
||||
feedBack();
|
||||
if (userInfo == null) {
|
||||
SmartDialog.showToast('账号未登录');
|
||||
@@ -413,16 +463,15 @@ class VideoIntroController extends GetxController {
|
||||
actionStatus = 0;
|
||||
break;
|
||||
}
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: Text(currentStatus == 0 ? '关注UP主?' : '取消关注UP主?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'点错了',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
@@ -465,7 +514,7 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
}
|
||||
}
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: const Text('确认'),
|
||||
)
|
||||
|
||||
@@ -244,6 +244,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData t = Theme.of(context);
|
||||
final Color outline = t.colorScheme.outline;
|
||||
bool isHorizontal = context.width > context.height * 1.25;
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10),
|
||||
@@ -252,20 +253,132 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: onPushMember,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 1, horizontal: 0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: loadingStatus
|
||||
? owner.face
|
||||
: widget.videoDetail!.owner!.face,
|
||||
width: 30,
|
||||
height: 30,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
owner.name,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: t.colorScheme.primary),
|
||||
// semanticsLabel: "Up主:${owner.name}",
|
||||
),
|
||||
const SizedBox(height: 0),
|
||||
Text(
|
||||
follower,
|
||||
semanticsLabel: "$follower粉丝",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: outline,
|
||||
),
|
||||
),
|
||||
]),
|
||||
const Spacer(),
|
||||
Obx(() => AnimatedOpacity(
|
||||
opacity: loadingStatus ||
|
||||
videoIntroController
|
||||
.followStatus.isEmpty
|
||||
? 0
|
||||
: 1,
|
||||
duration: const Duration(milliseconds: 50),
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
child: Obx(
|
||||
() => videoIntroController
|
||||
.followStatus.isNotEmpty
|
||||
? TextButton(
|
||||
onPressed: () =>
|
||||
videoIntroController
|
||||
.actionRelationMod(
|
||||
context),
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 8),
|
||||
foregroundColor:
|
||||
followStatus['attribute'] !=
|
||||
0
|
||||
? outline
|
||||
: t.colorScheme
|
||||
.onPrimary,
|
||||
backgroundColor:
|
||||
followStatus['attribute'] !=
|
||||
0
|
||||
? t.colorScheme
|
||||
.onInverseSurface
|
||||
: t.colorScheme
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
child: Text(
|
||||
followStatus['attribute'] != 0
|
||||
? '已关注'
|
||||
: '关注',
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme
|
||||
.labelMedium!.fontSize),
|
||||
),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: () =>
|
||||
videoIntroController
|
||||
.actionRelationMod(
|
||||
context),
|
||||
child: const Text('关注'),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
if (isHorizontal)
|
||||
Expanded(
|
||||
child: actionGrid(context, videoIntroController)),
|
||||
]),
|
||||
const SizedBox(height: 8),
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => showIntroDetail(),
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
!loadingStatus
|
||||
? widget.videoDetail!.title
|
||||
: videoItem['title'],
|
||||
style: const TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: t.colorScheme.outline,
|
||||
),
|
||||
]),
|
||||
),
|
||||
Stack(
|
||||
children: [
|
||||
@@ -363,7 +476,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
// ),
|
||||
// ),
|
||||
// 点赞收藏转发 布局样式2
|
||||
actionGrid(context, videoIntroController),
|
||||
if (!isHorizontal) actionGrid(context, videoIntroController),
|
||||
// 合集
|
||||
if (!loadingStatus &&
|
||||
widget.videoDetail!.ugcSeason != null) ...[
|
||||
@@ -387,93 +500,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
changeFuc: videoIntroController.changeSeasonOrbangu,
|
||||
))
|
||||
],
|
||||
GestureDetector(
|
||||
onTap: onPushMember,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 8, horizontal: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
type: 'avatar',
|
||||
src: loadingStatus
|
||||
? owner.face
|
||||
: widget.videoDetail!.owner!.face,
|
||||
width: 34,
|
||||
height: 34,
|
||||
fadeInDuration: Duration.zero,
|
||||
fadeOutDuration: Duration.zero,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
owner.name,
|
||||
style: const TextStyle(fontSize: 13),
|
||||
// semanticsLabel: "Up主:${owner.name}",
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
follower,
|
||||
semanticsLabel: "粉丝数:$follower",
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme.labelSmall!.fontSize,
|
||||
color: outline,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Obx(() => AnimatedOpacity(
|
||||
opacity: loadingStatus ||
|
||||
videoIntroController
|
||||
.followStatus.isEmpty
|
||||
? 0
|
||||
: 1,
|
||||
duration: const Duration(milliseconds: 50),
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
child: Obx(
|
||||
() => videoIntroController
|
||||
.followStatus.isNotEmpty
|
||||
? TextButton(
|
||||
onPressed: videoIntroController
|
||||
.actionRelationMod,
|
||||
style: TextButton.styleFrom(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 8, right: 8),
|
||||
foregroundColor:
|
||||
followStatus['attribute'] != 0
|
||||
? outline
|
||||
: t.colorScheme.onPrimary,
|
||||
backgroundColor:
|
||||
followStatus['attribute'] != 0
|
||||
? t.colorScheme
|
||||
.onInverseSurface
|
||||
: t.colorScheme
|
||||
.primary, // 设置按钮背景色
|
||||
),
|
||||
child: Text(
|
||||
followStatus['attribute'] != 0
|
||||
? '已关注'
|
||||
: '关注',
|
||||
style: TextStyle(
|
||||
fontSize: t.textTheme
|
||||
.labelMedium!.fontSize),
|
||||
),
|
||||
)
|
||||
: ElevatedButton(
|
||||
onPressed: videoIntroController
|
||||
.actionRelationMod,
|
||||
child: const Text('关注'),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox(
|
||||
height: 100,
|
||||
height: 130,
|
||||
child: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
@@ -503,6 +533,16 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
? Utils.numFormat(widget.videoDetail!.stat!.like!)
|
||||
: '-'),
|
||||
),
|
||||
Obx(
|
||||
() => ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.thumbsDown),
|
||||
selectIcon: const Icon(FontAwesomeIcons.solidThumbsDown),
|
||||
onTap: handleState(videoIntroController.actionDislikeVideo),
|
||||
selectStatus: videoIntroController.hasDislike.value,
|
||||
loadingStatus: loadingStatus,
|
||||
semanticsLabel: '点踩',
|
||||
text: "点踩"),
|
||||
),
|
||||
// ActionItem(
|
||||
// icon: const Icon(FontAwesomeIcons.clock),
|
||||
// onTap: () => videoIntroController.actionShareVideo(),
|
||||
@@ -536,7 +576,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
),
|
||||
ActionItem(
|
||||
icon: const Icon(FontAwesomeIcons.comment),
|
||||
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
|
||||
onTap: () => videoDetailCtr.tabCtr
|
||||
.animateTo(videoDetailCtr.tabCtr.index == 1 ? 0 : 1),
|
||||
selectStatus: false,
|
||||
loadingStatus: loadingStatus,
|
||||
semanticsLabel: '评论',
|
||||
|
||||
@@ -104,6 +104,7 @@ class IntroDetail extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 100),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:PiliPalaX/common/widgets/animated_dialog.dart';
|
||||
import 'package:PiliPalaX/common/widgets/http_error.dart';
|
||||
import 'package:PiliPalaX/common/widgets/overlay_pop.dart';
|
||||
import 'package:PiliPalaX/common/widgets/video_card_h.dart';
|
||||
import '../../../../common/constants.dart';
|
||||
import '../../../../utils/grid.dart';
|
||||
import './controller.dart';
|
||||
|
||||
class RelatedVideoPanel extends StatefulWidget {
|
||||
@@ -33,18 +35,26 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FutureBuilder(
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.all(StyleString.safeSpace),
|
||||
sliver: FutureBuilder(
|
||||
future: _futureBuilder,
|
||||
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SliverToBoxAdapter(child: SizedBox());
|
||||
}
|
||||
if (snapshot.data!['status'] && snapshot.data != null) {
|
||||
if (snapshot.data!['status'] && snapshot.hasData) {
|
||||
RxList relatedVideoList = _releatedController.relatedVideoList;
|
||||
// 请求成功
|
||||
return Obx(
|
||||
() => SliverList(
|
||||
() => SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
if (index == relatedVideoList.length) {
|
||||
return SizedBox(
|
||||
@@ -80,14 +90,20 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel>
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
return SliverGrid(
|
||||
gridDelegate: SliverGridDelegateWithExtentAndRatio(
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
maxCrossAxisExtent: Grid.maxRowWidth * 2,
|
||||
childAspectRatio: StyleString.aspectRatio * 2.3,
|
||||
mainAxisExtent: 0),
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 5),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
));
|
||||
}
|
||||
|
||||
OverlayEntry _createPopupDialog(videoItem) {
|
||||
|
||||
@@ -59,6 +59,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
late bool enableVerticalExpand;
|
||||
late bool autoPiP;
|
||||
late bool pipNoDanmaku;
|
||||
late bool removeSafeArea;
|
||||
final Floating floating = Floating();
|
||||
// 生命周期监听
|
||||
// late final AppLifecycleListener _lifecycleListener;
|
||||
@@ -66,7 +67,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
RxBool isFullScreen = false.obs;
|
||||
late StreamSubscription<bool> fullScreenStatusListener;
|
||||
late final MethodChannel onUserLeaveHintListener;
|
||||
StreamSubscription<Duration>? _bufferedListener;
|
||||
// StreamSubscription<Duration>? _bufferedListener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -97,6 +98,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
pipNoDanmaku = setting.get(SettingBoxKey.pipNoDanmaku, defaultValue: true);
|
||||
enableVerticalExpand =
|
||||
setting.get(SettingBoxKey.enableVerticalExpand, defaultValue: false);
|
||||
removeSafeArea = setting.get(SettingBoxKey.videoPlayerRemoveSafeArea,
|
||||
defaultValue: false);
|
||||
if (removeSafeArea) hideStatusBar();
|
||||
videoSourceInit();
|
||||
appbarStreamListen();
|
||||
// lifecycleListener();
|
||||
@@ -146,6 +150,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
if (status == PlayerStatus.completed) {
|
||||
shutdownTimerService.handleWaitingFinished();
|
||||
bool notExitFlag = false;
|
||||
|
||||
/// 顺序播放 列表循环
|
||||
if (plPlayerController!.playRepeat != PlayRepeat.pause &&
|
||||
plPlayerController!.playRepeat != PlayRepeat.singleCycle) {
|
||||
@@ -246,7 +251,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
AutoOrientation.portraitUpMode();
|
||||
}
|
||||
shutdownTimerService.handleWaitingFinished();
|
||||
_bufferedListener?.cancel();
|
||||
// _bufferedListener?.cancel();
|
||||
if (plPlayerController != null) {
|
||||
plPlayerController!.removeStatusLister(playerListener);
|
||||
fullScreenStatusListener.cancel();
|
||||
@@ -262,7 +267,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
@override
|
||||
// 离开当前页面时
|
||||
void didPushNext() async {
|
||||
_bufferedListener?.cancel();
|
||||
// _bufferedListener?.cancel();
|
||||
|
||||
/// 开启
|
||||
if (setting.get(SettingBoxKey.enableAutoBrightness, defaultValue: false)
|
||||
@@ -392,10 +397,57 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
return const SizedBox();
|
||||
}
|
||||
});
|
||||
Widget manualPlayerWidget = Obx(
|
||||
() => Visibility(
|
||||
visible: videoDetailController.isShowCover.value &&
|
||||
videoDetailController.isEffective.value,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: AppBar(
|
||||
primary: false,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: '稍后再看',
|
||||
onPressed: () async {
|
||||
var res = await UserHttp.toViewLater(
|
||||
bvid: videoDetailController.bvid);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
icon: const Icon(Icons.history_outlined),
|
||||
),
|
||||
const SizedBox(width: 14)
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: 10,
|
||||
child: IconButton(
|
||||
tooltip: '播放',
|
||||
onPressed: () => handlePlay(),
|
||||
icon: Image.asset(
|
||||
'assets/images/play.png',
|
||||
width: 60,
|
||||
height: 60,
|
||||
)),
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
Widget childWhenDisabled = SafeArea(
|
||||
top: MediaQuery.of(context).orientation == Orientation.portrait &&
|
||||
top: !removeSafeArea &&
|
||||
MediaQuery.of(context).orientation == Orientation.portrait &&
|
||||
isFullScreen.value == true,
|
||||
bottom: MediaQuery.of(context).orientation == Orientation.portrait &&
|
||||
bottom: !removeSafeArea &&
|
||||
MediaQuery.of(context).orientation == Orientation.portrait &&
|
||||
isFullScreen.value == true,
|
||||
left: false, //isFullScreen != true,
|
||||
right: false, //isFullScreen != true,
|
||||
@@ -404,17 +456,26 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
key: videoDetailController.scaffoldKey,
|
||||
// backgroundColor: Colors.black,
|
||||
appBar: removeSafeArea
|
||||
? null
|
||||
: AppBar(
|
||||
backgroundColor: Colors.black,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(0),
|
||||
child: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
toolbarHeight: 0,
|
||||
systemOverlayStyle: const SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light),
|
||||
),
|
||||
// appBar: PreferredSize(
|
||||
// preferredSize: const Size.fromHeight(0),
|
||||
// child: AppBar(
|
||||
// backgroundColor: Colors.transparent,
|
||||
// elevation: 0,
|
||||
// systemOverlayStyle: const SystemUiOverlayStyle(
|
||||
// statusBarColor: Colors.transparent,
|
||||
// statusBarIconBrightness: Brightness.light),
|
||||
),
|
||||
),
|
||||
// ),
|
||||
// ),
|
||||
body: Column(
|
||||
children: [
|
||||
Obx(
|
||||
@@ -424,7 +485,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
// print(videoDetailController.tabCtr.index);
|
||||
if (enableVerticalExpand &&
|
||||
plPlayerController?.direction.value == 'vertical') {
|
||||
videoheight = context.width * 5 / 4;
|
||||
videoheight = context.width;
|
||||
}
|
||||
if (MediaQuery.of(context).orientation ==
|
||||
Orientation.landscape &&
|
||||
@@ -439,15 +500,17 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
!isFullScreen.value &&
|
||||
isShowing &&
|
||||
mounted) {
|
||||
showStatusBar();
|
||||
if (!removeSafeArea) showStatusBar();
|
||||
}
|
||||
return SizedBox(
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
height: MediaQuery.of(context).orientation ==
|
||||
Orientation.landscape ||
|
||||
isFullScreen.value == true
|
||||
? MediaQuery.sizeOf(context).height -
|
||||
(MediaQuery.of(context).orientation ==
|
||||
Orientation.landscape
|
||||
Orientation.landscape ||
|
||||
removeSafeArea
|
||||
? 0
|
||||
: MediaQuery.of(context).padding.top)
|
||||
: videoheight,
|
||||
@@ -498,59 +561,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: videoDetailController
|
||||
.isShowCover.value &&
|
||||
videoDetailController
|
||||
.isEffective.value,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: AppBar(
|
||||
primary: false,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
backgroundColor:
|
||||
Colors.transparent,
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: '稍后再看',
|
||||
onPressed: () async {
|
||||
var res = await UserHttp
|
||||
.toViewLater(
|
||||
bvid:
|
||||
videoDetailController
|
||||
.bvid);
|
||||
SmartDialog.showToast(
|
||||
res['msg']);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.history_outlined),
|
||||
),
|
||||
const SizedBox(width: 14)
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: 10,
|
||||
child: IconButton(
|
||||
tooltip: '播放',
|
||||
onPressed: () => handlePlay(),
|
||||
icon: Image.asset(
|
||||
'assets/images/play.png',
|
||||
width: 60,
|
||||
height: 60,
|
||||
)),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
manualPlayerWidget,
|
||||
]
|
||||
],
|
||||
)),
|
||||
@@ -629,15 +640,308 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
],
|
||||
),
|
||||
);
|
||||
Widget childWhenDisabledAlmostSquareInner = Obx(() {
|
||||
if (enableVerticalExpand &&
|
||||
plPlayerController?.direction.value == 'vertical') {
|
||||
final double videoheight = context.height -
|
||||
(removeSafeArea
|
||||
? 0
|
||||
: (MediaQuery.of(context).padding.top +
|
||||
MediaQuery.of(context).padding.bottom));
|
||||
final double videowidth = videoheight * 9 / 16;
|
||||
return Row(children: [
|
||||
SizedBox(
|
||||
height: videoheight,
|
||||
width: isFullScreen.value == true ? context.width : videowidth,
|
||||
child: PopScope(
|
||||
canPop: isFullScreen.value != true,
|
||||
onPopInvoked: (bool didPop) {
|
||||
if (isFullScreen.value == true) {
|
||||
plPlayerController!.triggerFullScreen(status: false);
|
||||
}
|
||||
if (MediaQuery.of(context).orientation ==
|
||||
Orientation.landscape &&
|
||||
!horizontalScreen) {
|
||||
verticalScreenForTwoSeconds();
|
||||
}
|
||||
},
|
||||
child: Stack(children: <Widget>[
|
||||
if (isShowing) plPlayer,
|
||||
|
||||
/// 关闭自动播放时 手动播放
|
||||
if (!videoDetailController.autoPlay.value) ...<Widget>[
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: videoDetailController.isShowCover.value,
|
||||
child: Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
handlePlay();
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
type: 'emote',
|
||||
src: videoDetailController.videoItem['pic'],
|
||||
width: videowidth,
|
||||
height: videoheight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
manualPlayerWidget,
|
||||
]
|
||||
]),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
controller: videoDetailController.tabCtr,
|
||||
children: <Widget>[
|
||||
CustomScrollView(
|
||||
key: const PageStorageKey<String>('简介'),
|
||||
slivers: <Widget>[
|
||||
if (videoDetailController.videoType ==
|
||||
SearchType.video) ...[
|
||||
const VideoIntroPanel(),
|
||||
] else if (videoDetailController.videoType ==
|
||||
SearchType.media_bangumi) ...[
|
||||
Obx(() => BangumiIntroPanel(
|
||||
cid: videoDetailController.cid.value)),
|
||||
],
|
||||
SliverToBoxAdapter(
|
||||
child: Divider(
|
||||
indent: 12,
|
||||
endIndent: 12,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.06),
|
||||
),
|
||||
),
|
||||
const RelatedVideoPanel(),
|
||||
],
|
||||
),
|
||||
Obx(
|
||||
() => VideoReplyPanel(
|
||||
bvid: videoDetailController.bvid,
|
||||
oid: videoDetailController.oid.value,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
]);
|
||||
}
|
||||
final double videoheight = context.height / 2.5;
|
||||
final double videowidth = context.width;
|
||||
return Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: videowidth,
|
||||
height: isFullScreen.value == true
|
||||
? context.height -
|
||||
(removeSafeArea
|
||||
? 0
|
||||
: (MediaQuery.of(context).padding.top +
|
||||
MediaQuery.of(context).padding.bottom))
|
||||
: videoheight,
|
||||
child: PopScope(
|
||||
canPop: isFullScreen.value != true,
|
||||
onPopInvoked: (bool didPop) {
|
||||
if (isFullScreen.value == true) {
|
||||
plPlayerController!.triggerFullScreen(status: false);
|
||||
}
|
||||
if (MediaQuery.of(context).orientation ==
|
||||
Orientation.landscape &&
|
||||
!horizontalScreen) {
|
||||
verticalScreenForTwoSeconds();
|
||||
}
|
||||
},
|
||||
child: Stack(children: <Widget>[
|
||||
if (isShowing) plPlayer,
|
||||
|
||||
/// 关闭自动播放时 手动播放
|
||||
if (!videoDetailController.autoPlay.value) ...<Widget>[
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: videoDetailController.isShowCover.value,
|
||||
child: Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
handlePlay();
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
type: 'emote',
|
||||
src: videoDetailController.videoItem['pic'],
|
||||
width: videowidth,
|
||||
height: videoheight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
manualPlayerWidget,
|
||||
]
|
||||
]),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
key: PageStorageKey<String>('简介${videoDetailController.bvid}'),
|
||||
slivers: <Widget>[
|
||||
if (videoDetailController.videoType == SearchType.video) ...[
|
||||
const VideoIntroPanel(),
|
||||
const RelatedVideoPanel(),
|
||||
] else if (videoDetailController.videoType ==
|
||||
SearchType.media_bangumi) ...[
|
||||
Obx(() =>
|
||||
BangumiIntroPanel(cid: videoDetailController.cid.value)),
|
||||
]
|
||||
],
|
||||
)),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => VideoReplyPanel(
|
||||
bvid: videoDetailController.bvid,
|
||||
oid: videoDetailController.oid.value,
|
||||
),
|
||||
),
|
||||
)
|
||||
]))
|
||||
],
|
||||
);
|
||||
});
|
||||
Widget childWhenDisabledLandscapeInner = Obx(() {
|
||||
// 系数是以下三个方程(分别代表特定平板、折叠屏内屏、普通手机横屏尺寸)的近似解
|
||||
// 820x+1180y+983.67z=450
|
||||
// 1812x+2176y+1985.68z=680
|
||||
// 1080x+2340y+1589.72z=560
|
||||
final double videoheight = sqrt(context.height * context.width) * 12.555 -
|
||||
context.height * 7.690 -
|
||||
context.width * 4.741;
|
||||
final double videowidth = videoheight * 16 / 9;
|
||||
if (enableVerticalExpand &&
|
||||
plPlayerController?.direction.value == 'vertical') {
|
||||
final double videoheight = context.height -
|
||||
(removeSafeArea
|
||||
? 0
|
||||
: (MediaQuery.of(context).padding.top +
|
||||
MediaQuery.of(context).padding.bottom));
|
||||
final double videowidth = videoheight * 9 / 16;
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: videoheight,
|
||||
width: isFullScreen.value == true ? context.width : videowidth,
|
||||
child: PopScope(
|
||||
canPop: isFullScreen.value != true,
|
||||
onPopInvoked: (bool didPop) {
|
||||
if (isFullScreen.value == true) {
|
||||
plPlayerController!.triggerFullScreen(status: false);
|
||||
}
|
||||
if (MediaQuery.of(context).orientation ==
|
||||
Orientation.landscape &&
|
||||
!horizontalScreen) {
|
||||
verticalScreenForTwoSeconds();
|
||||
}
|
||||
},
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
if (isShowing) plPlayer,
|
||||
|
||||
/// 关闭自动播放时 手动播放
|
||||
if (!videoDetailController.autoPlay.value) ...<Widget>[
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: videoDetailController.isShowCover.value,
|
||||
child: Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
handlePlay();
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
type: 'emote',
|
||||
src: videoDetailController.videoItem['pic'],
|
||||
width: videowidth,
|
||||
height: videoheight,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
manualPlayerWidget,
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Row(children: [
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
key: PageStorageKey<String>('简介${videoDetailController.bvid}'),
|
||||
slivers: <Widget>[
|
||||
if (videoDetailController.videoType == SearchType.video) ...[
|
||||
const VideoIntroPanel(),
|
||||
const RelatedVideoPanel(),
|
||||
] else if (videoDetailController.videoType ==
|
||||
SearchType.media_bangumi) ...[
|
||||
Obx(() => BangumiIntroPanel(
|
||||
cid: videoDetailController.cid.value)),
|
||||
]
|
||||
],
|
||||
)),
|
||||
Expanded(
|
||||
child: Obx(
|
||||
() => VideoReplyPanel(
|
||||
bvid: videoDetailController.bvid,
|
||||
oid: videoDetailController.oid.value,
|
||||
),
|
||||
),
|
||||
)
|
||||
]))
|
||||
// Expanded(
|
||||
// child: TabBarView(
|
||||
// physics: const BouncingScrollPhysics(),
|
||||
// controller: videoDetailController.tabCtr,
|
||||
// children: <Widget>[
|
||||
// CustomScrollView(
|
||||
// key: const PageStorageKey<String>('简介'),
|
||||
// slivers: <Widget>[
|
||||
// if (videoDetailController.videoType ==
|
||||
// SearchType.video) ...[
|
||||
// const VideoIntroPanel(),
|
||||
// ] else if (videoDetailController.videoType ==
|
||||
// SearchType.media_bangumi) ...[
|
||||
// Obx(() => BangumiIntroPanel(
|
||||
// cid: videoDetailController.cid.value)),
|
||||
// ],
|
||||
// SliverToBoxAdapter(
|
||||
// child: Divider(
|
||||
// indent: 12,
|
||||
// endIndent: 12,
|
||||
// color: Theme.of(context).dividerColor.withOpacity(0.06),
|
||||
// ),
|
||||
// ),
|
||||
// const RelatedVideoPanel(),
|
||||
// ],
|
||||
// ),
|
||||
// Obx(
|
||||
// () => VideoReplyPanel(
|
||||
// bvid: videoDetailController.bvid,
|
||||
// oid: videoDetailController.oid.value,
|
||||
// ),
|
||||
// )
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
);
|
||||
}
|
||||
final double videowidth =
|
||||
max(context.height / context.width * 1.04, 1 / 2) * context.width;
|
||||
final double videoheight = videowidth * 9 / 16;
|
||||
return Row(
|
||||
children: [
|
||||
Column(
|
||||
@@ -689,57 +993,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
),
|
||||
),
|
||||
),
|
||||
Obx(
|
||||
() => Visibility(
|
||||
visible: videoDetailController
|
||||
.isShowCover.value &&
|
||||
videoDetailController.isEffective.value,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: AppBar(
|
||||
primary: false,
|
||||
foregroundColor: Colors.white,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: '稍后再看',
|
||||
onPressed: () async {
|
||||
var res =
|
||||
await UserHttp.toViewLater(
|
||||
bvid:
|
||||
videoDetailController
|
||||
.bvid);
|
||||
SmartDialog.showToast(
|
||||
res['msg']);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.history_outlined),
|
||||
),
|
||||
const SizedBox(width: 14)
|
||||
],
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 12,
|
||||
bottom: 10,
|
||||
child: IconButton(
|
||||
tooltip: '播放',
|
||||
onPressed: () => handlePlay(),
|
||||
icon: Image.asset(
|
||||
'assets/images/play.png',
|
||||
width: 60,
|
||||
height: 60,
|
||||
)),
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
manualPlayerWidget,
|
||||
]
|
||||
],
|
||||
))),
|
||||
@@ -750,8 +1004,10 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
? 0
|
||||
: context.height -
|
||||
videoheight -
|
||||
MediaQuery.of(context).padding.top -
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
(removeSafeArea
|
||||
? 0
|
||||
: (MediaQuery.of(context).padding.top +
|
||||
MediaQuery.of(context).padding.bottom)),
|
||||
child: CustomScrollView(
|
||||
key: PageStorageKey<String>(
|
||||
'简介${videoDetailController.bvid}'),
|
||||
@@ -759,6 +1015,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
if (videoDetailController.videoType ==
|
||||
SearchType.video) ...[
|
||||
const VideoIntroPanel(),
|
||||
const RelatedVideoPanel(),
|
||||
] else if (videoDetailController.videoType ==
|
||||
SearchType.media_bangumi) ...[
|
||||
Obx(() => BangumiIntroPanel(
|
||||
@@ -772,56 +1029,85 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
width: isFullScreen.value == true
|
||||
? 0
|
||||
: (context.width -
|
||||
MediaQuery.of(context).padding.left -
|
||||
MediaQuery.of(context).padding.right -
|
||||
videowidth),
|
||||
videowidth -
|
||||
(removeSafeArea
|
||||
? 0
|
||||
: (MediaQuery.of(context).padding.left +
|
||||
MediaQuery.of(context).padding.right))),
|
||||
height: context.height -
|
||||
MediaQuery.of(context).padding.top -
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
child: TabBarView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
controller: videoDetailController.tabCtr,
|
||||
children: <Widget>[
|
||||
if (videoDetailController.videoType == SearchType.video)
|
||||
const CustomScrollView(
|
||||
slivers: [
|
||||
RelatedVideoPanel(),
|
||||
],
|
||||
),
|
||||
(removeSafeArea
|
||||
? 0
|
||||
: (MediaQuery.of(context).padding.top +
|
||||
MediaQuery.of(context).padding.bottom)),
|
||||
child:
|
||||
// TabBarView(
|
||||
// physics: const BouncingScrollPhysics(),
|
||||
// controller: videoDetailController.tabCtr,
|
||||
// children: <Widget>[
|
||||
// if (videoDetailController.videoType == SearchType.video)
|
||||
// const CustomScrollView(
|
||||
// slivers: [
|
||||
// RelatedVideoPanel(),
|
||||
// ],
|
||||
// ),
|
||||
Obx(
|
||||
() => VideoReplyPanel(
|
||||
bvid: videoDetailController.bvid,
|
||||
oid: videoDetailController.oid.value,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
// ],
|
||||
// ),
|
||||
)
|
||||
],
|
||||
);
|
||||
});
|
||||
Widget childWhenDisabledLandscape = Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: SafeArea(
|
||||
left: isFullScreen.value != true,
|
||||
right: isFullScreen.value != true,
|
||||
Widget childWhenDisabledLandscape = SafeArea(
|
||||
left: !removeSafeArea && isFullScreen.value != true,
|
||||
right: !removeSafeArea && isFullScreen.value != true,
|
||||
top: !removeSafeArea,
|
||||
bottom: !removeSafeArea,
|
||||
child: Stack(children: [
|
||||
Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
key: videoDetailController.scaffoldKey,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
appBar: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(0),
|
||||
child: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundColor: Colors.black,
|
||||
appBar: removeSafeArea
|
||||
? null
|
||||
: AppBar(
|
||||
backgroundColor: Colors.black,
|
||||
elevation: 0,
|
||||
// systemOverlayStyle: const SystemUiOverlayStyle(
|
||||
// statusBarColor: Colors.transparent,
|
||||
// statusBarIconBrightness: Brightness.dark),
|
||||
toolbarHeight: 0,
|
||||
systemOverlayStyle: const SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light),
|
||||
),
|
||||
body: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: childWhenDisabledLandscapeInner))
|
||||
]));
|
||||
Widget childWhenDisabledAlmostSquare = SafeArea(
|
||||
left: !removeSafeArea && isFullScreen.value != true,
|
||||
right: !removeSafeArea && isFullScreen.value != true,
|
||||
top: !removeSafeArea,
|
||||
bottom: !removeSafeArea,
|
||||
child: Stack(children: [
|
||||
Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
key: videoDetailController.scaffoldKey,
|
||||
backgroundColor: Colors.black,
|
||||
appBar: removeSafeArea
|
||||
? null
|
||||
: AppBar(
|
||||
backgroundColor: Colors.black,
|
||||
elevation: 0,
|
||||
toolbarHeight: 0,
|
||||
systemOverlayStyle: const SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.light),
|
||||
),
|
||||
body: childWhenDisabledLandscapeInner)
|
||||
])));
|
||||
body: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
child: childWhenDisabledAlmostSquareInner))
|
||||
]));
|
||||
Widget childWhenEnabled = Obx(
|
||||
() => !videoDetailController.autoPlay.value
|
||||
? const SizedBox()
|
||||
@@ -851,7 +1137,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
),
|
||||
),
|
||||
);
|
||||
if (!horizontalScreen) {
|
||||
Widget autoChoose(Widget childWhenDisabled) {
|
||||
if (Platform.isAndroid) {
|
||||
return PiPSwitcher(
|
||||
childWhenDisabled: childWhenDisabled,
|
||||
@@ -862,34 +1148,59 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
||||
return childWhenDisabled;
|
||||
}
|
||||
|
||||
return OrientationBuilder(
|
||||
builder: (BuildContext context, Orientation orientation) {
|
||||
if (!horizontalScreen) {
|
||||
return autoChoose(childWhenDisabled);
|
||||
}
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
if (!isShowing) {
|
||||
return ColoredBox(color: Theme.of(context).colorScheme.background);
|
||||
}
|
||||
if (orientation == Orientation.landscape) {
|
||||
if (!horizontalScreen) {
|
||||
hideStatusBar();
|
||||
videoDetailController.hiddenReplyReplyPanel();
|
||||
if (constraints.maxWidth > constraints.maxHeight * 1.25) {
|
||||
// hideStatusBar();
|
||||
// videoDetailController.hiddenReplyReplyPanel();
|
||||
return autoChoose(childWhenDisabledLandscape);
|
||||
} else if (constraints.maxWidth * (9 / 16) <
|
||||
(2 / 5) * constraints.maxHeight) {
|
||||
if (!isFullScreen.value) {
|
||||
if (!removeSafeArea) showStatusBar();
|
||||
}
|
||||
return autoChoose(childWhenDisabled);
|
||||
} else {
|
||||
if (!isFullScreen.value) {
|
||||
showStatusBar();
|
||||
if (!removeSafeArea) showStatusBar();
|
||||
}
|
||||
return autoChoose(childWhenDisabledAlmostSquare);
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
return PiPSwitcher(
|
||||
childWhenDisabled:
|
||||
!horizontalScreen || orientation == Orientation.portrait
|
||||
? childWhenDisabled
|
||||
: childWhenDisabledLandscape,
|
||||
childWhenEnabled: childWhenEnabled,
|
||||
floating: floating,
|
||||
);
|
||||
}
|
||||
return !horizontalScreen || orientation == Orientation.portrait
|
||||
? childWhenDisabled
|
||||
: childWhenDisabledLandscape;
|
||||
//
|
||||
// final Orientation orientation =
|
||||
// constraints.maxWidth > constraints.maxHeight * 1.25
|
||||
// ? Orientation.landscape
|
||||
// : Orientation.portrait;
|
||||
// if (orientation == Orientation.landscape) {
|
||||
// if (!horizontalScreen) {
|
||||
// hideStatusBar();
|
||||
// videoDetailController.hiddenReplyReplyPanel();
|
||||
// }
|
||||
// } else {
|
||||
// if (!isFullScreen.value) {
|
||||
// showStatusBar();
|
||||
// }
|
||||
// }
|
||||
// if (Platform.isAndroid) {
|
||||
// return PiPSwitcher(
|
||||
// childWhenDisabled:
|
||||
// !horizontalScreen || orientation == Orientation.portrait
|
||||
// ? childWhenDisabled
|
||||
// : childWhenDisabledLandscape,
|
||||
// childWhenEnabled: childWhenEnabled,
|
||||
// floating: floating,
|
||||
// );
|
||||
// }
|
||||
// return !horizontalScreen || orientation == Orientation.portrait
|
||||
// ? childWhenDisabled
|
||||
// : childWhenDisabledLandscape;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
builder: (BuildContext context) {
|
||||
// TODO: 支持更多类型和颜色的弹幕
|
||||
return AlertDialog(
|
||||
title: const Text('发送弹幕(测试)'),
|
||||
title: const Text('发送弹幕'),
|
||||
content: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return TextField(
|
||||
@@ -733,6 +733,8 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
double danmakuDurationVal = widget.controller!.danmakuDurationVal;
|
||||
// 弹幕描边
|
||||
double strokeWidth = widget.controller!.strokeWidth;
|
||||
// 字体粗细
|
||||
int fontWeight = widget.controller!.fontWeight;
|
||||
|
||||
final DanmakuController danmakuController =
|
||||
widget.controller!.danmakuController!;
|
||||
@@ -762,7 +764,20 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
child: Center(child: Text('弹幕设置', style: titleStyle)),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
Text('智能云屏蔽 $danmakuWeight 级'),
|
||||
const Spacer(),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
padding: EdgeInsets.zero,
|
||||
minimumSize: Size.zero,
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
),
|
||||
onPressed: () => Get.toNamed('/danmakuBlock'),
|
||||
child: const Text("屏蔽管理"))
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 0,
|
||||
@@ -909,6 +924,45 @@ class _HeaderControlState extends State<HeaderControl> {
|
||||
),
|
||||
),
|
||||
),
|
||||
Text('字体粗细 ${fontWeight + 1}(可能无法精确调节)'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 0,
|
||||
bottom: 6,
|
||||
left: 10,
|
||||
right: 10,
|
||||
),
|
||||
child: SliderTheme(
|
||||
data: SliderThemeData(
|
||||
trackShape: MSliderTrackShape(),
|
||||
thumbColor: Theme.of(context).colorScheme.primary,
|
||||
activeTrackColor: Theme.of(context).colorScheme.primary,
|
||||
trackHeight: 10,
|
||||
thumbShape: const RoundSliderThumbShape(
|
||||
enabledThumbRadius: 6.0),
|
||||
),
|
||||
child: Slider(
|
||||
min: 0,
|
||||
max: 8,
|
||||
value: fontWeight.toDouble(),
|
||||
divisions: 9,
|
||||
label: '${fontWeight + 1}',
|
||||
onChanged: (double val) {
|
||||
fontWeight = val.toInt();
|
||||
widget.controller!.fontWeight = fontWeight;
|
||||
widget.controller?.putDanmakuSettings();
|
||||
setState(() {});
|
||||
try {
|
||||
final DanmakuOption currentOption =
|
||||
danmakuController.option;
|
||||
final DanmakuOption updatedOption =
|
||||
currentOption.copyWith(fontWeight: fontWeight);
|
||||
danmakuController.updateOption(updatedOption);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Text('描边粗细 $strokeWidth'),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
|
||||
@@ -213,7 +213,9 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
.content['title'] ??
|
||||
sessionList[i]
|
||||
.lastMsg
|
||||
.content['reply_content'])
|
||||
.content[
|
||||
'reply_content']) ??
|
||||
sessionList[i].lastMsg.content
|
||||
: '不支持的消息类型',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
||||
@@ -243,6 +243,7 @@ class PlPlayerController {
|
||||
late double opacityVal;
|
||||
late double fontSizeVal;
|
||||
late double strokeWidth;
|
||||
late int fontWeight;
|
||||
late double danmakuDurationVal;
|
||||
late List<double> speedsList;
|
||||
double? defaultDuration;
|
||||
@@ -286,12 +287,10 @@ class PlPlayerController {
|
||||
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
|
||||
danmakuWeight.value =
|
||||
setting.get(SettingBoxKey.danmakuWeight, defaultValue: 0);
|
||||
blockTypes =
|
||||
setting.get(SettingBoxKey.danmakuBlockType, defaultValue: []);
|
||||
blockTypes = setting.get(SettingBoxKey.danmakuBlockType, defaultValue: []);
|
||||
showArea = setting.get(SettingBoxKey.danmakuShowArea, defaultValue: 0.5);
|
||||
// 不透明度
|
||||
opacityVal =
|
||||
setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0);
|
||||
opacityVal = setting.get(SettingBoxKey.danmakuOpacity, defaultValue: 1.0);
|
||||
// 字体大小
|
||||
fontSizeVal =
|
||||
setting.get(SettingBoxKey.danmakuFontScale, defaultValue: 1.0);
|
||||
@@ -300,6 +299,8 @@ class PlPlayerController {
|
||||
setting.get(SettingBoxKey.danmakuDuration, defaultValue: 4.0);
|
||||
// 描边粗细
|
||||
strokeWidth = setting.get(SettingBoxKey.strokeWidth, defaultValue: 1.5);
|
||||
// 弹幕字体粗细
|
||||
fontWeight = setting.get(SettingBoxKey.fontWeight, defaultValue: 5);
|
||||
playRepeat = PlayRepeat.values.toList().firstWhere(
|
||||
(e) =>
|
||||
e.value ==
|
||||
@@ -312,7 +313,7 @@ class PlPlayerController {
|
||||
.get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
|
||||
// 后台播放
|
||||
_continuePlayInBackground.value =
|
||||
setting.get(SettingBoxKey.continuePlayInBackground, defaultValue: true);
|
||||
setting.get(SettingBoxKey.continuePlayInBackground, defaultValue: false);
|
||||
if (!enableAutoLongPressSpeed) {
|
||||
_longPressSpeed.value = videoStorage
|
||||
.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 3.0);
|
||||
@@ -458,7 +459,6 @@ class PlPlayerController {
|
||||
bufferSize: bufferSize,
|
||||
),
|
||||
);
|
||||
|
||||
var pp = player.platform as NativePlayer;
|
||||
// 解除倍速限制
|
||||
await pp.setProperty("af", "scaletempo2=max-speed=8");
|
||||
@@ -515,7 +515,7 @@ class PlPlayerController {
|
||||
configuration: VideoControllerConfiguration(
|
||||
enableHardwareAcceleration: enableHA,
|
||||
androidAttachSurfaceAfterVideoParameters: false,
|
||||
hwdec: hwdec,
|
||||
hwdec: enableHA ? hwdec: null,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -991,8 +991,7 @@ class PlPlayerController {
|
||||
|
||||
/// 设置后台播放
|
||||
Future<void> setBackgroundPlay(bool val) async {
|
||||
_continuePlayInBackground.value = val;
|
||||
setting.put(SettingBoxKey.continuePlayInBackground, val);
|
||||
setting.put(SettingBoxKey.enableBackgroundPlay, val);
|
||||
videoPlayerServiceHandler.revalidateSetting();
|
||||
}
|
||||
|
||||
@@ -1150,6 +1149,7 @@ class PlPlayerController {
|
||||
setting.put(SettingBoxKey.danmakuFontScale, fontSizeVal);
|
||||
setting.put(SettingBoxKey.danmakuDuration, danmakuDurationVal);
|
||||
setting.put(SettingBoxKey.strokeWidth, strokeWidth);
|
||||
setting.put(SettingBoxKey.fontWeight, fontWeight);
|
||||
}
|
||||
|
||||
Future<void> dispose({String type = 'single'}) async {
|
||||
@@ -1161,6 +1161,7 @@ class PlPlayerController {
|
||||
return;
|
||||
}
|
||||
_playerCount.value = 0;
|
||||
pause();
|
||||
try {
|
||||
_timer?.cancel();
|
||||
_timerForVolume?.cancel();
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:PiliPalaX/pages/video/detail/introduction/controller.dart';
|
||||
import 'package:PiliPalaX/utils/id_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:flutter_volume_controller/flutter_volume_controller.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -17,6 +18,7 @@ import 'package:PiliPalaX/plugin/pl_player/models/fullscreen_mode.dart';
|
||||
import 'package:PiliPalaX/plugin/pl_player/utils.dart';
|
||||
import 'package:PiliPalaX/utils/feed_back.dart';
|
||||
import 'package:PiliPalaX/utils/storage.dart';
|
||||
import 'package:saver_gallery/saver_gallery.dart';
|
||||
import 'package:screen_brightness/screen_brightness.dart';
|
||||
|
||||
import '../../common/widgets/audio_video_progress_bar.dart';
|
||||
@@ -330,8 +332,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
episodes.addAll(pages);
|
||||
changeFucCall = videoIntroController!.changeSeasonOrbangu;
|
||||
} else if (isBangumi) {
|
||||
episodes.addAll(bangumiIntroController!.bangumiDetail.value.episodes!);
|
||||
changeFucCall = bangumiIntroController!.changeSeasonOrbangu;
|
||||
episodes.addAll(bangumiIntroController!
|
||||
.bangumiDetail.value.episodes!);
|
||||
changeFucCall =
|
||||
bangumiIntroController!.changeSeasonOrbangu;
|
||||
}
|
||||
ListSheet(
|
||||
episodes: episodes,
|
||||
@@ -500,11 +504,11 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
controls: NoVideoControls,
|
||||
pauseUponEnteringBackgroundMode: !_.continuePlayInBackground.value,
|
||||
resumeUponEnteringForegroundMode: true,
|
||||
subtitleViewConfiguration: const SubtitleViewConfiguration(
|
||||
// 字幕尺寸调节
|
||||
subtitleViewConfiguration: SubtitleViewConfiguration(
|
||||
style: subTitleStyle,
|
||||
padding: EdgeInsets.all(24.0),
|
||||
textScaleFactor: 1.0,
|
||||
),
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
textScaleFactor: MediaQuery.textScaleFactorOf(context)),
|
||||
fit: _.videoFit.value,
|
||||
),
|
||||
),
|
||||
@@ -848,10 +852,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
),
|
||||
|
||||
// 头部、底部控制条
|
||||
SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Obx(
|
||||
Obx(
|
||||
() => Column(
|
||||
children: [
|
||||
if (widget.headerControl != null || _.headerControl != null)
|
||||
@@ -879,7 +880,6 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// 进度条 live模式下禁用
|
||||
|
||||
@@ -981,7 +981,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FractionalTranslation(
|
||||
translation: const Offset(1, 0.0),
|
||||
translation: const Offset(1, -0.4),
|
||||
child: Visibility(
|
||||
visible: _.showControls.value,
|
||||
child: ComBtn(
|
||||
@@ -1000,6 +1000,42 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 截图
|
||||
Obx(
|
||||
() => Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FractionalTranslation(
|
||||
translation: const Offset(-1, -0.4),
|
||||
child: Visibility(
|
||||
visible: _.showControls.value && _.isFullScreen.value,
|
||||
child: ComBtn(
|
||||
tooltip: '截图',
|
||||
icon: const Icon(
|
||||
Icons.photo_camera,
|
||||
size: 20,
|
||||
color: Colors.white,
|
||||
),
|
||||
fuc: () => {
|
||||
_.videoPlayerController
|
||||
?.screenshot(format: 'image/png')
|
||||
.then((value) {
|
||||
if (value != null) {
|
||||
SmartDialog.showToast('截图成功');
|
||||
String _name = DateTime.now().toString();
|
||||
SaverGallery.saveImage(value,
|
||||
name: _name,
|
||||
androidRelativePath: "Pictures/Screenshots",
|
||||
androidExistNotSave: false);
|
||||
SmartDialog.showToast('$_name.png已保存到相册/截图');
|
||||
}
|
||||
})
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
//
|
||||
Obx(() {
|
||||
if (_.dataStatus.loading || _.isBuffering.value) {
|
||||
|
||||
@@ -50,7 +50,7 @@ class AppBarAni extends StatelessWidget implements PreferredSizeWidget {
|
||||
tileMode: TileMode.mirror,
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
child: SafeArea(child: child),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import 'package:PiliPalaX/pages/setting/pages/logs.dart';
|
||||
|
||||
import '../pages/about/index.dart';
|
||||
import '../pages/blacklist/index.dart';
|
||||
import '../pages/danmaku_block/index.dart';
|
||||
import '../pages/dynamics/detail/index.dart';
|
||||
import '../pages/dynamics/index.dart';
|
||||
import '../pages/fan/index.dart';
|
||||
@@ -184,6 +185,8 @@ class Routes {
|
||||
CustomGetPage(name: '/subscription', page: () => const SubPage()),
|
||||
// 订阅详情
|
||||
CustomGetPage(name: '/subDetail', page: () => const SubDetailPage()),
|
||||
// 弹幕屏蔽管理
|
||||
CustomGetPage(name: '/danmakuBlock', page: () => const DanmakuBlockPage()),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class CacheManage {
|
||||
CacheManage._internal();
|
||||
@@ -76,17 +77,16 @@ class CacheManage {
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
Future<bool> clearCacheAll() async {
|
||||
bool cleanStatus = await SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
Future<bool> clearCacheAll(BuildContext context) async {
|
||||
bool cleanStatus = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: (() => {SmartDialog.dismiss()}),
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
@@ -94,7 +94,7 @@ class CacheManage {
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
SmartDialog.showLoading(msg: '正在清除...');
|
||||
try {
|
||||
// 清除缓存 图片缓存
|
||||
|
||||
@@ -10,15 +10,15 @@ import 'dart:io';
|
||||
|
||||
class DownloadUtils {
|
||||
// 获取存储权限
|
||||
static Future<bool> requestStoragePer() async {
|
||||
static Future<bool> requestStoragePer(BuildContext context) async {
|
||||
await Permission.storage.request();
|
||||
PermissionStatus status = await Permission.storage.status;
|
||||
if (status == PermissionStatus.denied ||
|
||||
status == PermissionStatus.permanentlyDenied) {
|
||||
SmartDialog.show(
|
||||
useSystem: true,
|
||||
animationType: SmartAnimationType.centerFade_otherSlide,
|
||||
builder: (BuildContext context) {
|
||||
if (!context.mounted) return false;
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
title: const Text('提示'),
|
||||
content: const Text('存储权限未授权'),
|
||||
@@ -69,21 +69,22 @@ class DownloadUtils {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<bool> checkPermissionDependOnSdkInt() async {
|
||||
static Future<bool> checkPermissionDependOnSdkInt(BuildContext context) async {
|
||||
if (Platform.isAndroid) {
|
||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
if (androidInfo.version.sdkInt <= 32) {
|
||||
return await requestStoragePer();
|
||||
if (!context.mounted) return false;
|
||||
return await requestStoragePer(context);
|
||||
} else {
|
||||
return await requestPhotoPer();
|
||||
}
|
||||
}
|
||||
return await requestStoragePer();
|
||||
return await requestStoragePer(context);
|
||||
}
|
||||
static Future<bool> downloadImg(String imgUrl,
|
||||
static Future<bool> downloadImg(BuildContext context, String imgUrl,
|
||||
{String imgType = 'cover'}) async {
|
||||
try {
|
||||
if (!await checkPermissionDependOnSdkInt()) {
|
||||
if (!await checkPermissionDependOnSdkInt(context)) {
|
||||
// // return false;
|
||||
}
|
||||
SmartDialog.showLoading(msg: '正在下载原图');
|
||||
|
||||
@@ -1,18 +1,112 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'storage.dart';
|
||||
class Grid {
|
||||
static double maxRowWidth = GStrorage.setting.get(SettingBoxKey.maxRowWidth, defaultValue: 240.0) as double;
|
||||
//
|
||||
// static double calculateActualWidth(BuildContext context, double maxCrossAxisExtent, double crossAxisSpacing, {double? screenWidthOffset}) {
|
||||
// double screenWidth = MediaQuery.of(context).size.width;
|
||||
// if (screenWidthOffset != null) {
|
||||
// screenWidth -= screenWidthOffset;
|
||||
// }
|
||||
// if (GStrorage.setting.get(SettingBoxKey.adaptiveNavBar, defaultValue: false) as bool) {
|
||||
// screenWidth -= 55;
|
||||
// }
|
||||
// int columnCount = ((screenWidth - crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil();
|
||||
// if (columnCount < 1){
|
||||
// columnCount = 1;
|
||||
// }
|
||||
// double columnWidth = (screenWidth - crossAxisSpacing) ~/ columnCount - crossAxisSpacing;
|
||||
// return columnWidth;
|
||||
// }
|
||||
}
|
||||
class SliverGridDelegateWithExtentAndRatio extends SliverGridDelegate {
|
||||
/// Creates a delegate that makes grid layouts with tiles that have a maximum
|
||||
/// cross-axis extent.
|
||||
///
|
||||
/// The [maxCrossAxisExtent], [mainAxisExtent], [mainAxisSpacing],
|
||||
/// and [crossAxisSpacing] arguments must not be negative.
|
||||
/// The [childAspectRatio] argument must be greater than zero.
|
||||
const SliverGridDelegateWithExtentAndRatio({
|
||||
required this.maxCrossAxisExtent,
|
||||
this.mainAxisSpacing = 0.0,
|
||||
this.crossAxisSpacing = 0.0,
|
||||
this.childAspectRatio = 1.0,
|
||||
this.mainAxisExtent = 0.0,
|
||||
}) : assert(maxCrossAxisExtent > 0),
|
||||
assert(mainAxisSpacing >= 0),
|
||||
assert(crossAxisSpacing >= 0),
|
||||
assert(childAspectRatio > 0);
|
||||
|
||||
static double calculateActualWidth(BuildContext context, double maxCrossAxisExtent, double crossAxisSpacing, {double? screenWidthOffset}) {
|
||||
double screenWidth = MediaQuery.of(context).size.width;
|
||||
if (screenWidthOffset != null) {
|
||||
screenWidth -= screenWidthOffset;
|
||||
/// The maximum extent of tiles in the cross axis.
|
||||
///
|
||||
/// This delegate will select a cross-axis extent for the tiles that is as
|
||||
/// large as possible subject to the following conditions:
|
||||
///
|
||||
/// - The extent evenly divides the cross-axis extent of the grid.
|
||||
/// - The extent is at most [maxCrossAxisExtent].
|
||||
///
|
||||
/// For example, if the grid is vertical, the grid is 500.0 pixels wide, and
|
||||
/// [maxCrossAxisExtent] is 150.0, this delegate will create a grid with 4
|
||||
/// columns that are 125.0 pixels wide.
|
||||
final double maxCrossAxisExtent;
|
||||
|
||||
/// The number of logical pixels between each child along the main axis.
|
||||
final double mainAxisSpacing;
|
||||
|
||||
/// The number of logical pixels between each child along the cross axis.
|
||||
final double crossAxisSpacing;
|
||||
|
||||
/// The ratio of the cross-axis to the main-axis extent of each child.
|
||||
final double childAspectRatio;
|
||||
|
||||
|
||||
/// The extent of each tile in the main axis. If provided, it would add
|
||||
/// after [childAspectRatio] is used.
|
||||
final double mainAxisExtent;
|
||||
|
||||
|
||||
|
||||
bool _debugAssertIsValid(double crossAxisExtent) {
|
||||
assert(crossAxisExtent > 0.0);
|
||||
assert(maxCrossAxisExtent > 0.0);
|
||||
assert(mainAxisSpacing >= 0.0);
|
||||
assert(crossAxisSpacing >= 0.0);
|
||||
assert(childAspectRatio > 0.0);
|
||||
return true;
|
||||
}
|
||||
int columnCount = ((screenWidth - crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil();
|
||||
if (columnCount < 1){
|
||||
columnCount = 1;
|
||||
|
||||
@override
|
||||
SliverGridLayout getLayout(SliverConstraints constraints) {
|
||||
assert(_debugAssertIsValid(constraints.crossAxisExtent));
|
||||
int crossAxisCount = ((constraints.crossAxisExtent - crossAxisSpacing) / (maxCrossAxisExtent + crossAxisSpacing)).ceil();
|
||||
// Ensure a minimum count of 1, can be zero and result in an infinite extent
|
||||
// below when the window size is 0.
|
||||
crossAxisCount = max(1, crossAxisCount);
|
||||
final double usableCrossAxisExtent = max(
|
||||
0.0,
|
||||
constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1),
|
||||
);
|
||||
final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
|
||||
final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio + mainAxisExtent;
|
||||
return SliverGridRegularTileLayout(
|
||||
crossAxisCount: crossAxisCount,
|
||||
mainAxisStride: childMainAxisExtent + mainAxisSpacing,
|
||||
crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
|
||||
childMainAxisExtent: childMainAxisExtent,
|
||||
childCrossAxisExtent: childCrossAxisExtent,
|
||||
reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
|
||||
);
|
||||
}
|
||||
double columnWidth = (screenWidth - crossAxisSpacing) ~/ columnCount - crossAxisSpacing;
|
||||
return columnWidth;
|
||||
|
||||
@override
|
||||
bool shouldRelayout(SliverGridDelegateWithExtentAndRatio oldDelegate) {
|
||||
return oldDelegate.maxCrossAxisExtent != maxCrossAxisExtent
|
||||
|| oldDelegate.mainAxisSpacing != mainAxisSpacing
|
||||
|| oldDelegate.crossAxisSpacing != crossAxisSpacing
|
||||
|| oldDelegate.childAspectRatio != childAspectRatio
|
||||
|| oldDelegate.mainAxisExtent != mainAxisExtent;
|
||||
}
|
||||
}
|
||||
@@ -145,6 +145,7 @@ class SettingBoxKey {
|
||||
/// 其他
|
||||
autoUpdate = 'autoUpdate',
|
||||
autoClearCache = 'autoClearCache',
|
||||
defaultShowComment = 'defaultShowComment',
|
||||
replySortType = 'replySortType',
|
||||
defaultDynamicType = 'defaultDynamicType',
|
||||
enableHotKey = 'enableHotKey',
|
||||
@@ -156,7 +157,7 @@ class SettingBoxKey {
|
||||
disableLikeMsg = 'disableLikeMsg',
|
||||
defaultHomePage = 'defaultHomePage',
|
||||
|
||||
// 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细
|
||||
// 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 字体粗细
|
||||
danmakuWeight = 'danmakuWeight',
|
||||
danmakuBlockType = 'danmakuBlockType',
|
||||
danmakuShowArea = 'danmakuShowArea',
|
||||
@@ -164,6 +165,8 @@ class SettingBoxKey {
|
||||
danmakuFontScale = 'danmakuFontScale',
|
||||
danmakuDuration = 'danmakuDuration',
|
||||
strokeWidth = 'strokeWidth',
|
||||
fontWeight = 'fontWeight',
|
||||
danmakuFilterRule = 'danmakuFilterRule',
|
||||
|
||||
// 代理host port
|
||||
systemProxyHost = 'systemProxyHost',
|
||||
@@ -177,6 +180,9 @@ class SettingBoxKey {
|
||||
enableSingleRow = 'enableSingleRow', // 首页单列
|
||||
displayMode = 'displayMode',
|
||||
maxRowWidth = 'maxRowWidth', // 首页列最大宽度(dp)
|
||||
videoPlayerRemoveSafeArea = 'videoPlayerHideStatusBar',
|
||||
dynamicsWaterfallFlow = 'dynamicsWaterfallFlow', // 动态瀑布流
|
||||
upPanelPosition = 'upPanelPosition', // up主面板位置
|
||||
adaptiveNavBar = 'adaptiveNavBar',
|
||||
enableMYBar = 'enableMYBar',
|
||||
hideSearchBar = 'hideSearchBar', // 收起顶栏
|
||||
@@ -184,8 +190,7 @@ class SettingBoxKey {
|
||||
tabbarSort = 'tabbarSort', // 首页tabbar
|
||||
dynamicBadgeMode = 'dynamicBadgeMode',
|
||||
hiddenSettingUnlocked = 'hiddenSettingUnlocked',
|
||||
enableGradientBg = 'enableGradientBg',
|
||||
defaultShowComment = 'defaultShowComment';
|
||||
enableGradientBg = 'enableGradientBg';
|
||||
}
|
||||
|
||||
class LocalCacheKey {
|
||||
|
||||
@@ -351,7 +351,7 @@ class Utils {
|
||||
child: Text(
|
||||
"点此查看完整更新(即commit)内容",
|
||||
style:
|
||||
TextStyle(color: Theme.of(context).primaryColor),
|
||||
TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
)),
|
||||
],
|
||||
),
|
||||
@@ -361,7 +361,7 @@ class Utils {
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setting.put(SettingBoxKey.autoUpdate, false);
|
||||
SmartDialog.dismiss();
|
||||
Get.back();
|
||||
},
|
||||
child: Text(
|
||||
'不再提醒',
|
||||
@@ -370,7 +370,7 @@ class Utils {
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: () => Get.back(),
|
||||
child: Text(
|
||||
'取消',
|
||||
style:
|
||||
|
||||
202
pubspec.lock
202
pubspec.lock
@@ -37,26 +37,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
|
||||
sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.4.10"
|
||||
version: "3.5.1"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
version: "2.5.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6
|
||||
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
version: "1.5.3"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -69,10 +69,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: audio_service
|
||||
sha256: a4d989f1225ea9621898d60f23236dcbfc04876fa316086c23c5c4af075dbac4
|
||||
sha256: "4547c312a94f9cb2c48b60823fb190767cbd63454a83c73049384d5d3cba4650"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.18.12"
|
||||
version: "0.18.13"
|
||||
audio_service_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -85,18 +85,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: audio_service_web
|
||||
sha256: "523e64ddc914c714d53eec2da85bba1074f08cf26c786d4efb322de510815ea7"
|
||||
sha256: "9d7d5ae5f98a5727f2580fad73062f2484f400eef6cef42919413268e62a363e"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
version: "0.1.2"
|
||||
audio_session:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: audio_session
|
||||
sha256: "6fdf255ed3af86535c96452c33ecff1245990bb25a605bfb1958661ccc3d467f"
|
||||
sha256: a49af9981eec5d7cd73b37bacb6ee73f8143a6a9f9bd5b6021e6c346b9b6cf4e
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.1.18"
|
||||
version: "0.1.19"
|
||||
auto_orientation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -149,10 +149,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: "581bacf68f89ec8792f5e5a0b2c4decd1c948e97ce659dc783688c8a88fbec21"
|
||||
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.8"
|
||||
version: "2.4.9"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -173,10 +173,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: built_value
|
||||
sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6
|
||||
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "8.9.0"
|
||||
version: "8.9.2"
|
||||
cached_network_image:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -197,18 +197,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cached_network_image_web
|
||||
sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316"
|
||||
sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.2.0"
|
||||
catcher_2:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: catcher_2
|
||||
sha256: "0691d0a5a2b7ccbe434ff67071218bbff86d264d215c6afc10f404cd6d6e7a50"
|
||||
sha256: "2c2c6f8cf8c817730cd1dbb010d55292396930e7a3d42c04c3039e3fd411a2f8"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.2.6"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -317,26 +317,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.0.6"
|
||||
version: "1.0.8"
|
||||
custom_sliding_segmented_control:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: custom_sliding_segmented_control
|
||||
sha256: "05b73fa48d57218bfdf806bad68a859812b216cd81fe81c6cbefde89f39eb257"
|
||||
sha256: "8b29c39e053136cdb899cf1b049d11e5ac0f4622f6444ae4f80b6ba72a640763"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.8.1"
|
||||
version: "1.8.2"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: dart_style
|
||||
sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368"
|
||||
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.3.4"
|
||||
version: "2.3.6"
|
||||
dbus:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -365,10 +365,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio
|
||||
sha256: "797e1e341c3dd2f69f2dad42564a6feff3bfb87187d05abb93b9609e6f1645c3"
|
||||
sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
version: "5.4.3+1"
|
||||
dio_cookie_manager:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -381,10 +381,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dio_http2_adapter
|
||||
sha256: "3bb35e81eb8a688eb1cb15beb97f46823698b44037e7b55227aa1060f5593adc"
|
||||
sha256: ea2f5e7906a157cb049abce95f89a693459f82e5b376be4636086368e3a350f1
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.5.2"
|
||||
dismissible_page:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -397,10 +397,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dynamic_color
|
||||
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
|
||||
sha256: eae98052fa6e2826bdac3dd2e921c6ce2903be15c6b7f8b6d8a5d49b5086298d
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.6.9"
|
||||
version: "1.7.0"
|
||||
easy_debounce:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -510,14 +510,6 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_adaptive_scaffold:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_adaptive_scaffold
|
||||
sha256: "600bbe237530a249f957f7d0f36273c20bd38d137e28e098c5231c30cadbe927"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "0.1.10+1"
|
||||
flutter_cache_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -575,26 +567,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
|
||||
sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.17"
|
||||
version: "2.0.19"
|
||||
flutter_smart_dialog:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_smart_dialog
|
||||
sha256: e9ee69eeac16165d142f1974b4db05ca9846cffafb7c94674a38ec07d7e6cda1
|
||||
sha256: "9b23a0b23b52a259f2901997eaf0b169bf5c61ff2178204872709610e9f6c0be"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.9.6"
|
||||
version: "4.9.6+1"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_svg
|
||||
sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c
|
||||
sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
version: "2.0.10+1"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@@ -604,10 +596,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_volume_controller
|
||||
sha256: "0f10cc759499cb6c3e152a8f6ff8e5ce385b99db7e1f586d1a29d8e6c11f4082"
|
||||
sha256: fa4c36dfe7ef7f423704f34ab8e64e00b4a30a90aa6e56f251e9dba649efcd7f
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.2"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@@ -617,10 +609,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fluttertoast
|
||||
sha256: dfdde255317af381bfc1c486ed968d5a43a2ded9c931e87cbecd88767d6a71c1
|
||||
sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "8.2.4"
|
||||
version: "8.2.5"
|
||||
font_awesome_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -633,10 +625,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: frontend_server_client
|
||||
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
|
||||
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "4.0.0"
|
||||
get:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -745,10 +737,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "49a0d4b0c12402853d3f227fe7c315601b238d126aa4caa5dbb2dcf99421aa4a"
|
||||
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.1.6"
|
||||
version: "4.1.7"
|
||||
intl:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -777,10 +769,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.8.1"
|
||||
version: "4.9.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -817,10 +809,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: logger
|
||||
sha256: "6bbb9d6f7056729537a4309bda2e74e18e5d9f14302489cc1e93f33b3fe32cac"
|
||||
sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.0.2+1"
|
||||
version: "2.3.0"
|
||||
logging:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -833,10 +825,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mailer
|
||||
sha256: "57f6dd1496699999a7bfd0aa6be0645384f477f4823e16d4321c40a434346382"
|
||||
sha256: d25d89555c1031abacb448f07b801d7c01b4c21d4558e944b12b64394c84a3cb
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
version: "6.1.0"
|
||||
marquee:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -971,8 +963,8 @@ packages:
|
||||
description:
|
||||
path: "."
|
||||
ref: master
|
||||
resolved-ref: d1cb3f0190ca67ec4d7fd372dac96f4f17a81a1a
|
||||
url: "https://github.com/guozhigq/flutter_ns_danmaku.git"
|
||||
resolved-ref: e12184de6f4ab4647d098c561b0508c47ce359e8
|
||||
url: "https://github.com/orz12/flutter_ns_danmaku.git"
|
||||
source: git
|
||||
version: "0.0.5"
|
||||
octo_image:
|
||||
@@ -1027,18 +1019,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
version: "2.2.4"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1075,26 +1067,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: permission_handler
|
||||
sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6"
|
||||
sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "11.2.0"
|
||||
version: "11.3.1"
|
||||
permission_handler_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb"
|
||||
sha256: "8bb852cd759488893805c3161d0b2b5db55db52f773dbb014420b304055ba2c5"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "12.0.3"
|
||||
version: "12.0.6"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_apple
|
||||
sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830
|
||||
sha256: e9ad66020b89ff1b63908f247c2c6f931c6e62699b756ef8b3c4569350cd8662
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
version: "9.4.4"
|
||||
permission_handler_html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1107,10 +1099,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_platform_interface
|
||||
sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c"
|
||||
sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
version: "4.2.1"
|
||||
permission_handler_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1147,10 +1139,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.7.4"
|
||||
version: "3.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1211,10 +1203,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: saver_gallery
|
||||
sha256: cceebad1f792adad4eb5015b415dcd521779a772ea574f0d7fc534b128deac83
|
||||
sha256: "0f740608072053a0da3b19cc5812a87e36f5c3c0b959d2475c4eb3d697f4a782"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "3.0.3"
|
||||
screen_brightness:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1275,10 +1267,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sentry
|
||||
sha256: a7946f4a90b0feb47214981d881b98149e05f6c576da9f2a2f33945bf561de25
|
||||
sha256: fd1fbfe860c05f5c52820ec4dbf2b6473789e83ead26cfc18bca4fe80bf3f008
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "7.16.0"
|
||||
version: "8.2.0"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1291,10 +1283,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
|
||||
sha256: "251eb156a8b5fa9ce033747d73535bf53911071f8d3b6f4f0b578505ce0d4496"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.3.1"
|
||||
version: "3.4.0"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1472,26 +1464,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: c512655380d241a337521703af62d2c122bf7b77a46ff7dd750092aa9433499c
|
||||
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.2.4"
|
||||
version: "6.2.6"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
|
||||
sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.2.2"
|
||||
version: "6.3.1"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "75bb6fe3f60070407704282a2d295630cab232991eb52542b18347a8a941df03"
|
||||
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "6.2.4"
|
||||
version: "6.2.5"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1504,18 +1496,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
|
||||
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.2.0"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.3.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1544,26 +1536,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics
|
||||
sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172"
|
||||
sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.9+2"
|
||||
version: "1.1.11+1"
|
||||
vector_graphics_codec:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_codec
|
||||
sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d"
|
||||
sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.9+2"
|
||||
version: "1.1.11+1"
|
||||
vector_graphics_compiler:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_graphics_compiler
|
||||
sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad"
|
||||
sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "1.1.9+2"
|
||||
version: "1.1.11+1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1648,18 +1640,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: d81b68e88cc353e546afb93fb38958e3717282c5ac6e5d3be4a4aef9fc3c1413
|
||||
sha256: "4445dea609bcdf4bcf6b0521e8c936abb34711d9e9b73c5ff8a6fd4eecb0db3c"
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "4.5.0"
|
||||
version: "4.6.0"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: "4ea3c4e1b8ed590162b15b8a61b41b1ef3ff179a314627c16ce40c086d94b8af"
|
||||
sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.14.0"
|
||||
version: "3.16.1"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1672,10 +1664,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: "4d062ad505390ecef1c4bfb6001cd857a51e00912cc9dfb66edb1886a9ebd80c"
|
||||
sha256: f12f8d8a99784b863e8b85e4a9a5e3cf1839d6803d2c0c3e0533a8f3c5a992a7
|
||||
url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
|
||||
source: hosted
|
||||
version: "3.10.2"
|
||||
version: "3.13.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1717,5 +1709,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.2.0 <4.0.0"
|
||||
dart: ">=3.2.3 <4.0.0"
|
||||
flutter: ">=3.16.0"
|
||||
|
||||
@@ -37,8 +37,6 @@ dependencies:
|
||||
cupertino_icons: ^1.0.5
|
||||
# 动态取色
|
||||
dynamic_color: ^1.6.8
|
||||
# Adaptive scaffold
|
||||
flutter_adaptive_scaffold: ^0.1.10+1
|
||||
|
||||
get: ^4.6.5
|
||||
|
||||
@@ -117,7 +115,7 @@ dependencies:
|
||||
# 弹幕
|
||||
ns_danmaku:
|
||||
git:
|
||||
url: https://github.com/guozhigq/flutter_ns_danmaku.git
|
||||
url: https://github.com/orz12/flutter_ns_danmaku.git
|
||||
ref: master
|
||||
# 状态栏图标控制
|
||||
status_bar_control: ^3.2.1
|
||||
|
||||
Reference in New Issue
Block a user