mod: 侧边栏、动态重构,排行改为首页分区,平板、折叠屏、竖屏视频新适配,播放页可隐藏黑边、截图、点踩,弹幕粗细调整,默认关闭后台播放,弹窗接受返回

This commit is contained in:
orz12
2024-05-20 14:46:31 +08:00
parent fd51cddeca
commit 074bf03946
97 changed files with 4105 additions and 2672 deletions

View File

@@ -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 {
);
},
),
),
);
}
}

View File

@@ -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,

View File

@@ -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),
],
),
),

View File

@@ -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,
),

View File

@@ -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('确认'),

View File

@@ -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';

View 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'],
};
}
}
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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': '琥珀色'},

View File

@@ -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'),
},
];

View File

@@ -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,

View File

@@ -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),
}
];

View File

@@ -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,

View 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];
}

View 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'],
);
}
}

View File

@@ -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: [

View File

@@ -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('分享视频')),
],
);
});
}
// 选择文件夹

View File

@@ -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,
),
);
}
}

View File

@@ -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) {

View File

@@ -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),

View 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']);
}
}
}

View File

@@ -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();
}
}

View 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);
}
}
}

View File

@@ -0,0 +1,4 @@
library dynamics.tab;
export './controller.dart';
export './view.dart';

View 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),
);
}
}

View File

@@ -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(),
]));
}
}

View File

@@ -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(),

View File

@@ -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 ||

View File

@@ -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,
),
],
),
);
}),
);
}
}

View File

@@ -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,
),
],
),

View File

@@ -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']);
}

View File

@@ -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(),
),
),
);
}
},
),
]),
),
);
}
}

View File

@@ -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,
),
),
],
);
}
},
),

View File

@@ -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),

View File

@@ -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();

View File

@@ -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),

View File

@@ -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(

View File

@@ -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

View File

@@ -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>
),
],
),
],
),
);
}
}

View File

@@ -31,6 +31,7 @@ class HotController extends GetxController {
videoList.addAll(res['data']);
}
_currentPage += 1;
if (_currentPage == 2) queryHotFeed('onLoad');
}
isLoadingMore = false;
return res;

View File

@@ -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),

View File

@@ -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('确认'),

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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(),
];

View File

@@ -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,

View File

@@ -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, // 在应用模糊之前,框应该膨胀的量。

View File

@@ -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 ? '已拉黑' : '关注';

View File

@@ -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);
}
},
),
);
}

View File

@@ -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 {
),
),
],
),
);
}),
);

View File

@@ -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,

View File

@@ -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> {
}
},
),
)
],
),
);

View File

@@ -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'],

View File

@@ -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)));
}
},
),
// ],
// ),
),
],
),
),
);
},
));
}
}

View File

@@ -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(),

View File

@@ -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)),

View File

@@ -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 {

View File

@@ -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>
),
),
],
),
);
}
}

View File

@@ -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),

View File

@@ -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) {

View File

@@ -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,
))
]);
}
},
),

View File

@@ -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 *

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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('时长筛选'),

View File

@@ -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('确认'),
)

View File

@@ -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),

View File

@@ -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;

View File

@@ -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);

View File

@@ -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('确认添加'),
)

View File

@@ -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(

View File

@@ -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);

View File

@@ -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)),
),
)
],

View File

@@ -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),

View File

@@ -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('退出登录'),
),

View File

@@ -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,

View File

@@ -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']})");

View File

@@ -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('确认'),
)

View File

@@ -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: '评论',

View File

@@ -104,6 +104,7 @@ class IntroDetail extends StatelessWidget {
),
),
),
const SizedBox(height: 100),
],
),
),

View File

@@ -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) {

View File

@@ -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;
});
}
}

View File

@@ -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(

View File

@@ -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,

View File

@@ -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();

View File

@@ -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) {

View File

@@ -50,7 +50,7 @@ class AppBarAni extends StatelessWidget implements PreferredSizeWidget {
tileMode: TileMode.mirror,
),
),
child: child,
child: SafeArea(child: child),
),
);
}

View File

@@ -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()),
];
}

View File

@@ -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 {
// 清除缓存 图片缓存

View File

@@ -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: '正在下载原图');

View File

@@ -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;
}
}

View File

@@ -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 {

View File

@@ -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:

View File

@@ -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"

View File

@@ -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