Compare commits

..

83 Commits

Author SHA1 Message Date
e791210039 v1.0.17 更新 2024-01-25 23:16:10 +08:00
01fa3c1cb3 mod: 关于页面增加官网链接 2024-01-25 23:13:16 +08:00
e052c6eafe feat: 首页tabbar 编辑排序 2024-01-21 23:49:32 +08:00
dad4a28eb8 feat: 未读动态计数 2024-01-21 20:30:51 +08:00
7428cde108 mod: flutter 3.16 特性迁移 2024-01-21 18:50:25 +08:00
9e40e162ac feat: 优先返回首页 2024-01-21 16:21:38 +08:00
27c954ec95 fix: 两次返回退出应用 issues #303 2024-01-21 16:09:08 +08:00
ec98e5c73c Merge pull request #433 from orz12/mod-video-detail-reply
mod: 视频评论输入框高度自适应
2024-01-21 14:54:44 +08:00
538b3d88aa mod: 代码整理 2024-01-21 14:01:33 +08:00
aa4e251295 fix: 视频详情页null 2024-01-21 11:37:19 +08:00
32f84dc703 fix: 首页状态栏图标色 2024-01-20 22:48:40 +08:00
c8e157b2d6 fix: 首页tabController index不及时 2024-01-20 22:31:56 +08:00
5ca841de4e mod: ignore文件 2024-01-20 14:10:02 +08:00
ca6091b90d mod: 视频评论输入框:可随键盘高度与文本行数移动和缩起;实现失去焦点功能;添加滚动条 2024-01-20 13:51:17 +08:00
5169bc360c merge main 2024-01-20 13:44:32 +08:00
4673f6dc5b Merge pull request #418 from orz12/fix-customspeed-dynamic-not-double
fix:自定义倍速后白屏
2024-01-20 13:41:04 +08:00
5da6f2b021 Merge pull request #430 from orz12/feat-add-blacklist-on-recommended&related-video-card
feat: 推荐、相关视频卡片添加拉黑Up功能
2024-01-20 13:38:47 +08:00
53ce81673b Merge pull request #421 from orz12/opt-videodetail-widget-flicker
opt: 【关注】按钮与自动播放界面跳变
2024-01-20 11:51:09 +08:00
e991f36853 Merge pull request #407 from orz12/feat-shutdown-timer-service
feat: 添加定时关闭功能
2024-01-20 11:46:52 +08:00
211b7812de Merge pull request #431 from orz12/fix-notlogin-1080p
fix: 免登录看1080p
2024-01-20 11:45:42 +08:00
70cf27789f fix: 免登录看1080p 2024-01-19 13:53:58 +08:00
931a513ac5 feat: 推荐、相关视频卡片添加拉黑Up功能 2024-01-18 14:00:27 +08:00
c4bf7d3a3b Merge pull request #408 from orz12/opt-deep-router-danmaku
尝试优化视频导航有多层时弹幕所占资源
2024-01-17 00:08:51 +08:00
b9cfcf9c9e Merge pull request #425 from orz12/mod-upgrade-web-rcmd
mod: 升级web端推荐至V8,对齐官网版本
2024-01-17 00:07:46 +08:00
a418f457f5 mod: 升级web端推荐至V8,对齐官网版本 2024-01-16 01:32:55 +08:00
83e68c8ee3 Merge pull request #422 from orz12/fix-snackBarTheme-without-light-theme
fix: up设置分组没有日间模式,以及颜色不统一
2024-01-15 23:33:19 +08:00
d26066ff84 Merge pull request #419 from orz12/fix-season-timeformat
fix: 专栏时间没有年份
2024-01-15 23:32:24 +08:00
4000e1b9dc fix: up设置分组没有日间模式,以及颜色不统一 2024-01-15 21:02:46 +08:00
02bdb46625 opt: 【关注】按钮与自动播放界面跳变
获得数据后再淡入显示,避免关注按钮跳变(如已关注状态下,会先经历默认的高亮未关注状态,再跳变为暗色已关注)
自动播放开启时取消显示封面(与官方一致),避免黑屏-亮屏(加载出封面)-黑屏(缓冲视频流)-亮屏(显示视频)的闪烁过程
2024-01-15 20:27:51 +08:00
96737ded5b fix: 专栏时间没有年份 2024-01-15 02:14:43 +08:00
6654094480 opt: 代码优化 2024-01-15 00:59:19 +08:00
0cc25203b1 fix: 设置自定义倍速后白屏
原因:List<double>并非List<dynamic>,赋值会产生错误
2024-01-15 00:58:52 +08:00
9294c8bcdf mod: 动态长图 2024-01-14 19:17:17 +08:00
8f661337f5 opt: 图片渲染内存 2024-01-14 18:06:56 +08:00
db6662c980 Merge branch 'fix' 2024-01-13 22:12:39 +08:00
2dc7cf28c9 fix: 评论区@跟jumpUrl共存时链接解析 issues #404 2024-01-10 23:58:14 +08:00
c9fd6304fd 尝试优化多层弹幕所占资源 2024-01-10 10:33:28 +08:00
4d1e4511a3 feat: 添加定时关闭功能 2024-01-10 02:25:21 +08:00
449aa69033 Merge branch 'main' of github.com:guozhigq/pilipala 2024-01-09 23:14:41 +08:00
5fa32f1e2b fix: 双击播放无声 2024-01-09 23:13:49 +08:00
71bb4b30d2 mod: 进度条防抖 issues #362 2024-01-09 08:23:55 +08:00
8b0cb4c909 Merge pull request #401 from orz12/fix-first-request-without-cookie
fix: 首屏请求无cookie
2024-01-09 00:22:55 +08:00
79729e4b30 fix: iOS代理 2024-01-08 23:41:12 +08:00
7cf9e66a5b Merge pull request #397 from orz12/fix-handleplay-without-playerListener
fix: 手动播放没有监听播放状态的问题
2024-01-08 22:24:42 +08:00
917cff6311 Merge branch 'fix' 2024-01-08 22:21:09 +08:00
7c120aa0cc fix: launcher启动 2024-01-08 22:20:31 +08:00
2c7ce60f42 fix: 首屏请求无cookie 2024-01-08 19:02:19 +08:00
e4ebe6e145 修复手动播放没有监听播放状态的问题 2024-01-08 11:35:43 +08:00
5d0ca3f84c Merge pull request #395 from orz12/opt-danmu-and-autoplay
opt: 封面点击播放
2024-01-08 08:28:40 +08:00
49dfb2605c Merge pull request #396 from Integral-Tech/fix-abiCodes
Fix abiCodes
2024-01-08 08:28:09 +08:00
cfddcd79bd Fix abiCodes 2024-01-07 23:51:15 +08:00
a07a1697c2 Update header_control.dart 2024-01-07 23:37:21 +08:00
21259e260d Merge branch 'main' into opt-danmu-and-autoplay 2024-01-07 23:33:50 +08:00
438b392cfc mod: 发送弹幕样式 2024-01-07 22:24:17 +08:00
ac69896f9d Merge pull request #384 from orz12/opt-hidden-repeat-progressbar
opt: 控制条与常驻进度条互斥
2024-01-07 21:09:37 +08:00
f5263b090a Merge pull request #378 from Integral-Tech/versionCode-added
Add versionCode
2024-01-07 21:09:01 +08:00
bde7e35623 Merge branch 'design' 2024-01-07 21:06:48 +08:00
a0488f2c75 Update Flutter version 2024-01-07 21:05:40 +08:00
e8f7995b32 mod: 搜索页跳转 2024-01-07 20:50:15 +08:00
042a0a848d mod: 首页样式 2024-01-07 20:15:39 +08:00
a98d8537c7 opt: 封面点击播放+弹幕发送标识 2024-01-07 17:04:20 +08:00
8ebb4cc70e Merge pull request #365 from GuMengYu/home_page
Improve: 首页样式
2024-01-07 16:21:58 +08:00
fa8fd42e9a mod: format code 2024-01-07 12:58:24 +08:00
7a71798055 mod: snackBarTheme darkTheme 2024-01-06 16:51:28 +08:00
7c82193f22 mod: up设置分组适配暗黑模式 2024-01-06 15:46:37 +08:00
f5d928e0f3 mod: 补充scheme 2024-01-06 15:42:30 +08:00
e0aeefa203 fix: 评论区内容匹配 issues #385 2024-01-06 12:59:20 +08:00
d75d560d32 Merge pull request #383 from orz12/fix-statusbar-color-and-icon
fix: 非全屏透明appbar无图标、横屏视频竖屏全屏播放时有白色状态栏
2024-01-06 10:52:17 +08:00
7d9edc5f40 Merge pull request #387 from orz12/fix-chat-owner-decider
fix: 私信误判发送方
2024-01-06 10:51:52 +08:00
5a337fb145 fix: 私信误判发送方 2024-01-06 09:04:27 +08:00
6983c40f5f opt:移除冗余设置 2024-01-06 08:06:12 +08:00
aa94bf27ff opt: 控制条与常驻进度条互斥 2024-01-05 14:02:32 +08:00
50a46aa89d fix: 非全屏透明appbar无图标、横屏视频竖屏全屏播放时有白色状态栏
之前修复图标不显示的思路不太对(appbar改成白底),现在改成了透明底但图标显色,更沉浸,顺便修复问题
2024-01-05 12:02:24 +08:00
e79cd2df25 fix: 评论区链接解析 issues #381 2024-01-04 22:44:33 +08:00
cd2b7c62ee Merge branch 'main' into fix 2024-01-04 22:32:16 +08:00
483f02c7d2 mod: 动态内容增加投稿跳转 2024-01-04 00:08:42 +08:00
ec58fbc3cc mod: web端推荐观看数单位转换 2024-01-03 23:53:44 +08:00
e910f5b7e7 fix: 搜索结果为null时页面异常 2024-01-03 23:50:38 +08:00
6591d35c74 fix: 连续跳转search页面controller未重建 2024-01-03 23:45:05 +08:00
a2524e0a60 Merge branch 'main' into versionCode-added 2024-01-03 10:45:42 +08:00
9a9644c3eb Add abiCodes 2024-01-02 19:10:06 +08:00
d0f8d55c9f Add versionCode 2024-01-02 15:13:58 +08:00
367d8b844a improve: 首页样式改动
- 渐变的背景
- tab 调整
2024-01-01 17:48:55 +08:00
132 changed files with 2855 additions and 1788 deletions

View File

@ -36,7 +36,7 @@ jobs:
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2
with:
flutter-version: 3.16.4
flutter-version: 3.16.5
channel: any
- name: 下载项目依赖

106
.gitignore vendored
View File

@ -21,6 +21,29 @@ migrate_working_dir/
# is commented out by default.
#.vscode/
# Flutter repo-specific
/bin/cache/
/bin/internal/bootstrap.bat
/bin/internal/bootstrap.sh
/bin/mingit/
/dev/benchmarks/mega_gallery/
/dev/bots/.recipe_deps
/dev/bots/android_tools/
/dev/devicelab/ABresults*.json
/dev/docs/doc/
/dev/docs/api_docs.zip
/dev/docs/flutter.docs.zip
/dev/docs/lib/
/dev/docs/pubspec.yaml
/dev/integration_tests/**/xcuserdata
/dev/integration_tests/**/Pods
/packages/flutter/coverage/
version
analysis_benchmark.json
# packages file containing multi-root paths
.packages.generated
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
@ -31,14 +54,83 @@ migrate_working_dir/
.pub-cache/
.pub/
/build/
# Symbolication related
app.*.symbols
flutter_*.png
linked_*.ds
unlinked.ds
unlinked_spec.ds
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# Android related
**/android/**/gradle-wrapper.jar
.gradle/
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
**/android/key.properties
*.jks
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/Flutter/ephemeral/
**/Pods/
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/macos/Flutter/ephemeral
**/xcuserdata/
# Windows
**/windows/flutter/generated_plugin_registrant.cc
**/windows/flutter/generated_plugin_registrant.h
**/windows/flutter/generated_plugins.cmake
# Linux
**/linux/flutter/generated_plugin_registrant.cc
**/linux/flutter/generated_plugin_registrant.h
**/linux/flutter/generated_plugins.cmake
# Coverage
coverage/
# Symbols
app.*.symbols
# Exceptions to above rules.
!**/ios/**/default.mode1v3
!**/ios/**/default.mode2v3
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
!.vscode/settings.json

View File

@ -95,3 +95,14 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) {
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
}
}
}

View File

@ -67,9 +67,9 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bilibili" android:host="forward" />

39
change_log/1.0.17.0125.md Normal file
View File

@ -0,0 +1,39 @@
## 1.0.17
### 功能
+ 视频全屏时隐藏进度条
+ 动态内容增加投稿跳转
+ 未开启自动播放时点击封面播放
+ 弹幕发送标识
+ 定时关闭
+ 推荐视频卡片拉黑up功能
+ 首页tabbar编辑排序
### 修复
+ 连续跳转搜索页未刷新
+ 搜索结果为空时页面异常
+ 评论区链接解析
+ 视频全屏状态栏背景色
+ 私信对话气泡位置
+ 设置up关注分组样式
+ 每次推荐请求数据相同
+ iOS代理网络异常
+ 双击切换播放状态无声
+ 设置自定义倍速白屏
+ 免登录查看1080p
### 优化
+ 首页web端推荐观看数展示
+ 首页web端推荐接口更新
+ 首页样式
+ 搜索页跳转
+ 弹幕资源优化
+ 图片渲染占用内存优化(部分)
+ 两次返回退出应用
+ schame 补充
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -20,7 +20,7 @@ class ContentContainer extends StatelessWidget {
builder: (BuildContext context, BoxConstraints constraints) {
return SingleChildScrollView(
clipBehavior: childClipBehavior ?? Clip.hardEdge,
physics: isScrollable ? null : NeverScrollableScrollPhysics(),
physics: isScrollable ? null : const NeverScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: constraints.copyWith(
minHeight: constraints.maxHeight,
@ -34,7 +34,7 @@ class ContentContainer extends StatelessWidget {
child: contentWidget!,
)
else
Spacer(),
const Spacer(),
if (bottomWidget != null) bottomWidget!,
],
),

View File

@ -2,16 +2,17 @@ import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/storage.dart';
Box setting = GStrorage.setting;
Box<dynamic> setting = GStrorage.setting;
class CustomToast extends StatelessWidget {
const CustomToast({super.key, required this.msg});
final String msg;
const CustomToast({Key? key, required this.msg}) : super(key: key);
@override
Widget build(BuildContext context) {
double toastOpacity =
setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0);
final double toastOpacity =
setting.get(SettingBoxKey.defaultToastOp, defaultValue: 1.0) as double;
return Container(
margin:
EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom + 30),

View File

@ -1,45 +1,46 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:get/get.dart';
import 'network_img_layer.dart';
// ignore: must_be_immutable
class HtmlRender extends StatelessWidget {
String? htmlContent;
final int? imgCount;
final List? imgList;
HtmlRender({
const HtmlRender({
this.htmlContent,
this.imgCount,
this.imgList,
super.key,
});
final String? htmlContent;
final int? imgCount;
final List<String>? imgList;
@override
Widget build(BuildContext context) {
return Html(
data: htmlContent,
onLinkTap: (url, buildContext, attributes) => {},
onLinkTap: (String? url, Map<String, String> buildContext, attributes) {},
extensions: [
TagExtension(
tagsToExtend: {"img"},
builder: (extensionContext) {
tagsToExtend: <String>{'img'},
builder: (ExtensionContext extensionContext) {
try {
Map attributes = extensionContext.attributes;
List key = attributes.keys.toList();
String? imgUrl = key.contains('src')
? attributes['src']
: attributes['data-src'];
if (imgUrl!.startsWith('//')) {
final Map<String, dynamic> attributes =
extensionContext.attributes;
final List<dynamic> key = attributes.keys.toList();
String imgUrl = key.contains('src')
? attributes['src'] as String
: attributes['data-src'] as String;
if (imgUrl.startsWith('//')) {
imgUrl = 'https:$imgUrl';
}
if (imgUrl.startsWith('http://')) {
imgUrl = imgUrl.replaceAll('http://', 'https://');
}
imgUrl = imgUrl.contains('@') ? imgUrl.split('@').first : imgUrl;
bool isEmote = imgUrl.contains('/emote/');
bool isMall = imgUrl.contains('/mall/');
final bool isEmote = imgUrl.contains('/emote/');
final bool isMall = imgUrl.contains('/mall/');
if (isMall) {
return const SizedBox();
}
@ -58,38 +59,37 @@ class HtmlRender extends StatelessWidget {
src: imgUrl,
);
} catch (err) {
print(err);
return const SizedBox();
}
},
),
],
style: {
"html": Style(
'html': Style(
fontSize: FontSize.medium,
lineHeight: LineHeight.percent(140),
),
"body": Style(margin: Margins.zero, padding: HtmlPaddings.zero),
"a": Style(
'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero),
'a': Style(
color: Theme.of(context).colorScheme.primary,
textDecoration: TextDecoration.none,
),
"p": Style(
'p': Style(
margin: Margins.only(bottom: 10),
),
"span": Style(
'span': Style(
fontSize: FontSize.medium,
height: Height(1.65),
),
"div": Style(height: Height.auto()),
"li > p": Style(
'div': Style(height: Height.auto()),
'li > p': Style(
display: Display.inline,
),
"li": Style(
'li': Style(
padding: HtmlPaddings.only(bottom: 4),
textAlign: TextAlign.justify,
),
"img": Style(margin: Margins.only(top: 4, bottom: 4)),
'img': Style(margin: Margins.only(top: 4, bottom: 4)),
},
);
}

View File

@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'network_img_layer.dart';
class LiveCard extends StatelessWidget {
// ignore: prefer_typing_uninitialized_variables
final liveItem;
final dynamic liveItem;
const LiveCard({
Key? key,
@ -14,7 +14,7 @@ class LiveCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(liveItem.roomid);
final String heroTag = Utils.makeHeroTag(liveItem.roomid);
return Card(
elevation: 0,
@ -23,7 +23,6 @@ class LiveCard extends StatelessWidget {
borderRadius: BorderRadius.circular(0),
side: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.08),
width: 1,
),
),
margin: EdgeInsets.zero,
@ -33,15 +32,16 @@ class LiveCard extends StatelessWidget {
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
child: LayoutBuilder(builder:
(BuildContext context, BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: liveItem.cover,
src: liveItem.cover as String,
type: 'emote',
width: maxWidth,
height: maxHeight,
@ -58,7 +58,7 @@ class LiveCard extends StatelessWidget {
// view: liveItem.stat.view,
// danmaku: liveItem.stat.danmaku,
// duration: liveItem.duration,
online: liveItem.online,
online: liveItem.online as int,
),
),
),
@ -90,7 +90,7 @@ class LiveContent extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
liveItem.title,
liveItem.title as String,
textAlign: TextAlign.start,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
maxLines: 2,
@ -99,7 +99,7 @@ class LiveContent extends StatelessWidget {
SizedBox(
width: double.infinity,
child: Text(
liveItem.uname,
liveItem.uname as String,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
@ -114,9 +114,9 @@ class LiveContent extends StatelessWidget {
}
class LiveStat extends StatelessWidget {
final int? online;
const LiveStat({super.key, required this.online});
const LiveStat({Key? key, required this.online}) : super(key: key);
final int? online;
@override
Widget build(BuildContext context) {
@ -136,7 +136,7 @@ class LiveStat extends StatelessWidget {
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
children: <Widget>[
// Row(
// children: [
// StatView(

View File

@ -1,78 +1,101 @@
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/extension.dart';
import '../../utils/storage.dart';
import '../constants.dart';
Box setting = GStrorage.setting;
Box<dynamic> setting = GStrorage.setting;
class NetworkImgLayer extends StatelessWidget {
final String? src;
final double? width;
final double? height;
final double? cacheW;
final double? cacheH;
final String? type;
final Duration? fadeOutDuration;
final Duration? fadeInDuration;
final int? quality;
const NetworkImgLayer({
Key? key,
super.key,
this.src,
required this.width,
required this.height,
this.cacheW,
this.cacheH,
this.type,
this.fadeOutDuration,
this.fadeInDuration,
// 图片质量 默认1%
this.quality,
}) : super(key: key);
this.origAspectRatio,
});
final String? src;
final double width;
final double height;
final String? type;
final Duration? fadeOutDuration;
final Duration? fadeInDuration;
final int? quality;
final double? origAspectRatio;
@override
Widget build(BuildContext context) {
double pr = MediaQuery.of(context).devicePixelRatio;
int picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
final String imageUrl =
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? 100}q.webp';
int? memCacheWidth, memCacheHeight;
double aspectRatio = (width / height).toDouble();
// double pr = 2;
return src != ''
void setMemCacheSizes() {
if (aspectRatio > 1) {
memCacheHeight = height.cacheSize(context);
} else if (aspectRatio < 1) {
memCacheWidth = width.cacheSize(context);
} else {
if (origAspectRatio != null && origAspectRatio! > 1) {
memCacheWidth = width.cacheSize(context);
} else if (origAspectRatio != null && origAspectRatio! < 1) {
memCacheHeight = height.cacheSize(context);
} else {
memCacheWidth = width.cacheSize(context);
memCacheHeight = height.cacheSize(context);
}
}
}
setMemCacheSizes();
if (memCacheWidth == null && memCacheHeight == null) {
memCacheWidth = width.toInt();
}
return src != '' && src != null
? ClipRRect(
clipBehavior: Clip.hardEdge,
borderRadius: BorderRadius.circular(type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x),
clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular(
type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x,
),
child: CachedNetworkImage(
imageUrl:
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? picQuality}q.webp',
width: width ?? double.infinity,
height: height ?? double.infinity,
alignment: Alignment.center,
maxWidthDiskCache: ((cacheW ?? width!) * pr).toInt(),
// maxHeightDiskCache: (cacheH ?? height!).toInt(),
memCacheWidth: ((cacheW ?? width!) * pr).toInt(),
// memCacheHeight: (cacheH ?? height!).toInt(),
imageUrl: imageUrl,
width: width,
height: height,
memCacheWidth: memCacheWidth,
memCacheHeight: memCacheHeight,
fit: BoxFit.cover,
fadeOutDuration:
fadeOutDuration ?? const Duration(milliseconds: 200),
fadeOutDuration ?? const Duration(milliseconds: 120),
fadeInDuration:
fadeInDuration ?? const Duration(milliseconds: 200),
// filterQuality: FilterQuality.high,
errorWidget: (context, url, error) => placeholder(context),
placeholder: (context, url) => placeholder(context),
fadeInDuration ?? const Duration(milliseconds: 120),
filterQuality: FilterQuality.high,
errorWidget: (BuildContext context, String url, Object error) =>
placeholder(context),
placeholder: (BuildContext context, String url) =>
placeholder(context),
),
)
: placeholder(context);
}
Widget placeholder(context) {
Widget placeholder(BuildContext context) {
return Container(
width: width ?? double.infinity,
height: height ?? double.infinity,
clipBehavior: Clip.hardEdge,
width: width,
height: height,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
borderRadius: BorderRadius.circular(type == 'avatar'
@ -82,13 +105,16 @@ class NetworkImgLayer extends StatelessWidget {
: StyleString.imgRadius.x),
),
child: Center(
child: Image.asset(
type == 'avatar'
? 'assets/images/noface.jpeg'
: 'assets/images/loading.png',
width: 300,
height: 300,
)),
child: Image.asset(
type == 'avatar'
? 'assets/images/noface.jpeg'
: 'assets/images/loading.png',
width: width,
height: height,
cacheWidth: width.cacheSize(context),
cacheHeight: height.cacheSize(context),
),
),
);
}
}

View File

@ -1,16 +1,17 @@
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/download.dart';
import '../../utils/download.dart';
import '../constants.dart';
import 'network_img_layer.dart';
class OverlayPop extends StatelessWidget {
const OverlayPop({super.key, this.videoItem, this.closeFn});
final dynamic videoItem;
final Function? closeFn;
const OverlayPop({super.key, this.videoItem, this.closeFn});
@override
Widget build(BuildContext context) {
double imgWidth = MediaQuery.of(context).size.width - 8 * 2;
final double imgWidth = MediaQuery.sizeOf(context).width - 8 * 2;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8),
decoration: BoxDecoration(
@ -19,7 +20,6 @@ class OverlayPop extends StatelessWidget {
),
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
@ -27,7 +27,7 @@ class OverlayPop extends StatelessWidget {
NetworkImgLayer(
width: imgWidth,
height: imgWidth / StyleString.aspectRatio,
src: videoItem.pic!,
src: videoItem.pic! as String,
quality: 100,
),
Positioned(
@ -61,7 +61,7 @@ class OverlayPop extends StatelessWidget {
children: [
Expanded(
child: Text(
videoItem.title!,
videoItem.title! as String,
),
),
const SizedBox(width: 4),
@ -69,7 +69,10 @@ class OverlayPop extends StatelessWidget {
tooltip: '保存封面图',
onPressed: () async {
await DownloadUtils.downloadImg(
videoItem.pic ?? videoItem.cover);
videoItem.pic != null
? videoItem.pic as String
: videoItem.cover as String,
);
// closeFn!();
},
icon: const Icon(Icons.download, size: 20),

View File

@ -17,8 +17,8 @@ class PullToRefreshHeader extends StatelessWidget {
this.info,
this.lastRefreshTime, {
this.color,
Key? key,
}) : super(key: key);
super.key,
});
final PullToRefreshScrollNotificationInfo? info;
final DateTime? lastRefreshTime;
@ -28,7 +28,7 @@ class PullToRefreshHeader extends StatelessWidget {
Widget build(BuildContext context) {
final PullToRefreshScrollNotificationInfo? infos = info;
if (infos == null) {
return Container();
return const SizedBox();
}
String text = '';
if (infos.mode == PullToRefreshIndicatorMode.armed) {
@ -65,7 +65,6 @@ class PullToRefreshHeader extends StatelessWidget {
top: top,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: Container(

View File

@ -1,17 +1,29 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/http/search.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import '../../http/search.dart';
import '../../http/user.dart';
import '../../http/video.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
import 'network_img_layer.dart';
import 'stat/danmu.dart';
import 'stat/view.dart';
// 视频卡片 - 水平布局
class VideoCardH extends StatelessWidget {
const VideoCardH({
super.key,
required this.videoItem,
this.longPress,
this.longPressEnd,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
this.showDanmaku = true,
this.showPubdate = false,
});
// ignore: prefer_typing_uninitialized_variables
final videoItem;
final Function()? longPress;
@ -22,23 +34,11 @@ class VideoCardH extends StatelessWidget {
final bool showDanmaku;
final bool showPubdate;
const VideoCardH({
Key? key,
required this.videoItem,
this.longPress,
this.longPressEnd,
this.source = 'normal',
this.showOwner = true,
this.showView = true,
this.showDanmaku = true,
this.showPubdate = false,
}) : super(key: key);
@override
Widget build(BuildContext context) {
int aid = videoItem.aid;
String bvid = videoItem.bvid;
String heroTag = Utils.makeHeroTag(aid);
final int aid = videoItem.aid;
final String bvid = videoItem.bvid;
final String heroTag = Utils.makeHeroTag(aid);
return GestureDetector(
onLongPress: () {
if (longPress != null) {
@ -53,7 +53,7 @@ class VideoCardH extends StatelessWidget {
child: InkWell(
onTap: () async {
try {
int cid =
final int cid =
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
Get.toNamed('/video?bvid=$bvid&cid=$cid',
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
@ -65,11 +65,11 @@ class VideoCardH extends StatelessWidget {
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width = (boxConstraints.maxWidth -
builder: (BuildContext context, BoxConstraints boxConstraints) {
final double width = (boxConstraints.maxWidth -
StyleString.cardSpace *
6 /
MediaQuery.of(context).textScaleFactor) /
MediaQuery.textScalerOf(context).scale(1.0)) /
2;
return Container(
constraints: const BoxConstraints(minHeight: 88),
@ -77,29 +77,28 @@ class VideoCardH extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
children: <Widget>[
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
builder: (BuildContext context,
BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.pic,
src: videoItem.pic as String,
width: maxWidth,
height: maxHeight,
),
),
PBadge(
text: Utils.timeFormat(videoItem.duration!),
top: null,
right: 6.0,
bottom: 6.0,
left: null,
type: 'gray',
),
// if (videoItem.rcmdReason != null &&
@ -159,7 +158,7 @@ class VideoContent extends StatelessWidget {
children: [
if (videoItem.title is String) ...[
Text(
videoItem.title,
videoItem.title as String,
textAlign: TextAlign.start,
style: const TextStyle(
fontWeight: FontWeight.w500,
@ -172,9 +171,9 @@ class VideoContent extends StatelessWidget {
maxLines: 2,
text: TextSpan(
children: [
for (var i in videoItem.title) ...[
for (final i in videoItem.title) ...[
TextSpan(
text: i['text'],
text: i['text'] as String,
style: TextStyle(
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
@ -216,7 +215,7 @@ class VideoContent extends StatelessWidget {
Row(
children: [
Text(
videoItem.owner.name,
videoItem.owner.name as String,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
@ -230,14 +229,14 @@ class VideoContent extends StatelessWidget {
if (showView) ...[
StatView(
theme: 'gray',
view: videoItem.stat.view,
view: videoItem.stat.view as int,
),
const SizedBox(width: 8),
],
if (showDanmaku)
StatDanMu(
theme: 'gray',
danmu: videoItem.stat.danmaku,
danmu: videoItem.stat.danmaku as int,
),
const Spacer(),
@ -267,7 +266,6 @@ class VideoContent extends StatelessWidget {
height: 24,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '稍后再看',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
@ -281,11 +279,11 @@ class VideoContent extends StatelessWidget {
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.bvid);
bvid: videoItem.bvid as String);
SmartDialog.showToast(res['msg']);
},
value: 'pause',
height: 35,
height: 40,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
@ -294,6 +292,59 @@ class VideoContent extends StatelessWidget {
],
),
),
const PopupMenuDivider(),
PopupMenuItem<String>(
onTap: () async {
SmartDialog.show(
useSystem: true,
animationType:
SmartAnimationType.centerFade_otherSlide,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('提示'),
content: Text(
'确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?'
'\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'),
actions: [
TextButton(
onPressed: () => SmartDialog.dismiss(),
child: Text(
'点错了',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline),
),
),
TextButton(
onPressed: () async {
var res = await VideoHttp.relationMod(
mid: videoItem.owner.mid,
act: 5,
reSrc: 11,
);
SmartDialog.dismiss();
SmartDialog.showToast(
res['msg'] ?? '成功');
},
child: const Text('确认'),
)
],
);
},
);
},
value: 'pause',
height: 40,
child: Row(
children: [
const Icon(Icons.block, size: 16),
const SizedBox(width: 6),
Text('拉黑:${videoItem.owner.name}',
style: const TextStyle(fontSize: 13))
],
),
),
],
),
),

View File

@ -1,17 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/http/dynamics.dart';
import 'package:pilipala/http/search.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import '../../http/dynamics.dart';
import '../../http/search.dart';
import '../../http/user.dart';
import '../../http/video.dart';
import '../../models/common/search_type.dart';
import '../../utils/id_utils.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
import 'network_img_layer.dart';
// 视频卡片 - 垂直布局
class VideoCardV extends StatelessWidget {
@ -217,15 +216,10 @@ class VideoContent extends StatelessWidget {
),
if (videoItem.goto == 'av' && crossAxisCount == 1) ...[
const SizedBox(width: 10),
WatchLater(
VideoPopupMenu(
size: 32,
iconSize: 18,
callFn: () async {
int aid = videoItem.param;
var res =
await UserHttp.toViewLater(bvid: IdUtils.av2bv(aid));
SmartDialog.showToast(res['msg']);
},
videoItem: videoItem,
),
],
],
@ -301,15 +295,10 @@ class VideoContent extends StatelessWidget {
const Spacer(),
],
if (videoItem.goto == 'av' && crossAxisCount != 1) ...[
WatchLater(
VideoPopupMenu(
size: 24,
iconSize: 14,
callFn: () async {
int aid = videoItem.param;
var res =
await UserHttp.toViewLater(bvid: IdUtils.av2bv(aid));
SmartDialog.showToast(res['msg']);
},
videoItem: videoItem,
),
] else ...[
const SizedBox(height: 24)
@ -337,7 +326,8 @@ class VideoStat extends StatelessWidget {
maxLines: 1,
text: TextSpan(
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
fontSize: MediaQuery.textScalerOf(context)
.scale(Theme.of(context).textTheme.labelSmall!.fontSize!),
color: Theme.of(context).colorScheme.outline,
),
children: [
@ -351,16 +341,16 @@ class VideoStat extends StatelessWidget {
}
}
class WatchLater extends StatelessWidget {
class VideoPopupMenu extends StatelessWidget {
final double? size;
final double? iconSize;
final Function? callFn;
final dynamic videoItem;
const WatchLater({
const VideoPopupMenu({
Key? key,
required this.size,
required this.iconSize,
this.callFn,
required this.videoItem,
}) : super(key: key);
@override
@ -370,7 +360,6 @@ class WatchLater extends StatelessWidget {
height: size,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '稍后再看',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
@ -381,9 +370,13 @@ class WatchLater extends StatelessWidget {
onSelected: (String type) {},
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
PopupMenuItem<String>(
onTap: () => callFn!(),
onTap: () async {
var res =
await UserHttp.toViewLater(bvid: videoItem.bvid as String);
SmartDialog.showToast(res['msg']);
},
value: 'pause',
height: 35,
height: 40,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
@ -392,6 +385,55 @@ class WatchLater extends StatelessWidget {
],
),
),
const PopupMenuDivider(),
PopupMenuItem<String>(
onTap: () async {
SmartDialog.show(
useSystem: true,
animationType: SmartAnimationType.centerFade_otherSlide,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('提示'),
content: Text(
'确定拉黑:${videoItem.owner.name}(${videoItem.owner.mid})?'
'\n\n被拉黑的Up可以在隐私设置-黑名单管理中解除'),
actions: [
TextButton(
onPressed: () => SmartDialog.dismiss(),
child: Text(
'点错了',
style: TextStyle(
color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
var res = await VideoHttp.relationMod(
mid: videoItem.owner.mid,
act: 5,
reSrc: 11,
);
SmartDialog.dismiss();
SmartDialog.showToast(res['msg'] ?? '成功');
},
child: const Text('确认'),
)
],
);
},
);
},
value: 'pause',
height: 40,
child: Row(
children: [
const Icon(Icons.block, size: 16),
const SizedBox(width: 6),
Text('拉黑:${videoItem.owner.name}',
style: const TextStyle(fontSize: 13))
],
),
),
],
),
);

View File

@ -467,4 +467,7 @@ class Api {
/// page_size
static const getSeasonDetailApi =
'/x/polymer/web-space/seasons_archives_list';
/// 获取未读动态数
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
}

View File

@ -1,5 +1,5 @@
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/bangumi/list.dart';
import '../models/bangumi/list.dart';
import 'index.dart';
class BangumiHttp {
static Future bangumiList({int? page}) async {

View File

@ -1,5 +1,5 @@
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/user/black.dart';
import '../models/user/black.dart';
import 'index.dart';
class BlackHttp {
static Future blackList({required int pn, int? ps}) async {

17
lib/http/common.dart Normal file
View File

@ -0,0 +1,17 @@
import 'index.dart';
class CommonHttp {
static Future unReadDynamic() async {
var res = await Request().get(Api.getUnreadDynamic,
data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']['dyn_basic_infos']};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
}

View File

@ -1,9 +1,6 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/danmaku/dm.pb.dart';
import 'constants.dart';
import '../models/danmaku/dm.pb.dart';
import 'index.dart';
class DanmakaHttp {
// 获取视频弹幕
@ -24,21 +21,23 @@ class DanmakaHttp {
);
return DmSegMobileReply.fromBuffer(response.data);
}
static Future shootDanmaku({
int type = 1,//弹幕类选择(1视频弹幕 2漫画弹幕)
required int oid,// 视频cid
required String msg,//弹幕文本(长度小于 100 字符)
int mode = 1,// 弹幕类型(1滚动弹幕 4底端弹幕 5顶端弹幕 6逆向弹幕(不能使用) 7高级弹幕 8代码弹幕不能使用 9BAS弹幕pool必须为2)
int type = 1, //弹幕类选择(1视频弹幕 2漫画弹幕)
required int oid, // 视频cid
required String msg, //弹幕文本(长度小于 100 字符)
int mode =
1, // 弹幕类型(1滚动弹幕 4底端弹幕 5顶端弹幕 6逆向弹幕(不能使用) 7高级弹幕 8代码弹幕不能使用 9BAS弹幕pool必须为2)
// String? aid,// 稿件avid
// String? bvid,// bvid与aid必须有一个
required String bvid,
int? progress,// 弹幕出现在视频内的时间单位为毫秒默认为0
int? color,// 弹幕颜色(默认白色16777215
int? fontsize,// 弹幕字号默认25
int? pool,// 弹幕池选择0普通池 1字幕池 2特殊池代码/BAS弹幕默认普通池0
int? progress, // 弹幕出现在视频内的时间单位为毫秒默认为0
int? color, // 弹幕颜色(默认白色16777215
int? fontsize, // 弹幕字号默认25
int? pool, // 弹幕池选择0普通池 1字幕池 2特殊池代码/BAS弹幕默认普通池0
//int? rnd,// 当前时间戳*1000000若无此项则发送弹幕冷却时间限制为90s若有此项则发送弹幕冷却时间限制为5s
int? colorful,//60001专属渐变彩色需要会员
int? checkbox_type,//是否带 UP 身份标识0普通4带有标识
int? colorful, //60001专属渐变彩色需要会员
int? checkbox_type, //是否带 UP 身份标识0普通4带有标识
// String? csrf,//CSRF Token位于 Cookie Cookie 方式必要
// String? access_key,// APP 登录 Token APP 方式必要
}) async {

View File

@ -1,6 +1,6 @@
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/models/dynamics/up.dart';
import '../models/dynamics/result.dart';
import '../models/dynamics/up.dart';
import 'index.dart';
class DynamicsHttp {
static Future followDynamic({

View File

@ -1,5 +1,5 @@
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/fans/result.dart';
import '../models/fans/result.dart';
import 'index.dart';
class FanHttp {
static Future fans({int? vmid, int? pn, int? ps, String? orderType}) async {

View File

@ -1,5 +1,5 @@
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/follow/result.dart';
import '../models/follow/result.dart';
import 'index.dart';
class FollowHttp {
static Future followings(

View File

@ -1,6 +1,6 @@
import 'package:html/dom.dart';
import 'package:html/parser.dart';
import 'package:pilipala/http/index.dart';
import 'index.dart';
class HtmlHttp {
// article
@ -15,7 +15,7 @@ class HtmlHttp {
Match match = regex.firstMatch(response.data)!;
String matchedString = match.group(0)!;
response = await Request().get(
'https:$matchedString' + '/',
'https:$matchedString/',
extra: {'ua': 'pc'},
);
}

View File

@ -1,17 +1,17 @@
// ignore_for_file: avoid_print
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'dart:async';
import 'package:dio/dio.dart';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:dio_http2_adapter/dio_http2_adapter.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/http/constants.dart';
import 'package:pilipala/http/interceptor.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
// import 'package:dio_http2_adapter/dio_http2_adapter.dart';
import 'package:hive/hive.dart';
import '../utils/storage.dart';
import '../utils/utils.dart';
import 'constants.dart';
import 'interceptor.dart';
class Request {
static final Request _instance = Request._internal();
@ -20,25 +20,25 @@ class Request {
factory Request() => _instance;
Box setting = GStrorage.setting;
static Box localCache = GStrorage.localCache;
late dynamic enableSystemProxy;
late bool enableSystemProxy;
late String systemProxyHost;
late String systemProxyPort;
/// 设置cookie
static setCookie() async {
Box userInfoCache = GStrorage.userInfo;
var cookiePath = await Utils.getCookiePath();
var cookieJar = PersistCookieJar(
final String cookiePath = await Utils.getCookiePath();
final PersistCookieJar cookieJar = PersistCookieJar(
ignoreExpires: true,
storage: FileStorage(cookiePath),
);
cookieManager = CookieManager(cookieJar);
dio.interceptors.add(cookieManager);
var cookie = await cookieManager.cookieJar
final List<Cookie> cookie = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.baseUrl));
var userInfo = userInfoCache.get('userInfoCache');
final userInfo = userInfoCache.get('userInfoCache');
if (userInfo != null && userInfo.mid != null) {
var cookie2 = await cookieManager.cookieJar
final List<Cookie> cookie2 = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.tUrl));
if (cookie2.isEmpty) {
try {
@ -57,14 +57,15 @@ class Request {
log("setCookie, ${e.toString()}");
}
}
var cookieString =
cookie.map((cookie) => '${cookie.name}=${cookie.value}').join('; ');
final String cookieString = cookie
.map((Cookie cookie) => '${cookie.name}=${cookie.value}')
.join('; ');
dio.options.headers['cookie'] = cookieString;
}
// 从cookie中获取 csrf token
static Future<String> getCsrf() async {
var cookies = await cookieManager.cookieJar
List<Cookie> cookies = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.apiBaseUrl));
String token = '';
if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) {
@ -73,7 +74,7 @@ class Request {
return token;
}
static setOptionsHeaders(userInfo, status) {
static setOptionsHeaders(userInfo, bool status) {
if (status) {
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
}
@ -100,30 +101,31 @@ class Request {
headers: {},
);
enableSystemProxy =
setting.get(SettingBoxKey.enableSystemProxy, defaultValue: false);
enableSystemProxy = setting.get(SettingBoxKey.enableSystemProxy,
defaultValue: false) as bool;
systemProxyHost =
localCache.get(LocalCacheKey.systemProxyHost, defaultValue: '');
systemProxyPort =
localCache.get(LocalCacheKey.systemProxyPort, defaultValue: '');
dio = Dio(options)
dio = Dio(options);
/// fix 第三方登录 302重定向 跟iOS代理问题冲突
..httpClientAdapter = Http2Adapter(
ConnectionManager(
idleTimeout: const Duration(milliseconds: 10000),
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
),
);
/// fix 第三方登录 302重定向 跟iOS代理问题冲突
// ..httpClientAdapter = Http2Adapter(
// ConnectionManager(
// idleTimeout: const Duration(milliseconds: 10000),
// onClientCreate: (_, ClientSetting config) =>
// config.onBadCertificate = (_) => true,
// ),
// );
/// 设置代理
if (enableSystemProxy) {
dio.httpClientAdapter = IOHttpClientAdapter(
createHttpClient: () {
final client = HttpClient();
final HttpClient client = HttpClient();
// Config the client.
client.findProxy = (uri) {
client.findProxy = (Uri uri) {
// return 'PROXY host:port';
return 'PROXY $systemProxyHost:$systemProxyPort';
};
@ -145,7 +147,7 @@ class Request {
));
dio.transformer = BackgroundTransformer();
dio.options.validateStatus = (status) {
dio.options.validateStatus = (int? status) {
return status! >= 200 && status < 300 ||
HttpString.validateStatusCodes.contains(status);
};
@ -156,7 +158,7 @@ class Request {
*/
get(url, {data, options, cancelToken, extra}) async {
Response response;
Options options = Options();
final Options options = Options();
ResponseType resType = ResponseType.json;
if (extra != null) {
resType = extra!['resType'] ?? ResponseType.json;

View File

@ -1,10 +1,10 @@
// ignore_for_file: avoid_print
import 'package:dio/dio.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/storage.dart';
import '../utils/storage.dart';
// import 'package:get/get.dart' hide Response;
class ApiInterceptor extends Interceptor {
@ -21,16 +21,16 @@ class ApiInterceptor extends Interceptor {
void onResponse(Response response, ResponseInterceptorHandler handler) {
try {
if (response.statusCode == 302) {
List<String> locations = response.headers['location']!;
final List<String> locations = response.headers['location']!;
if (locations.isNotEmpty) {
if (locations.first.startsWith('https://www.mcbbs.net')) {
final uri = Uri.parse(locations.first);
final accessKey = uri.queryParameters['access_key'];
final mid = uri.queryParameters['mid'];
final Uri uri = Uri.parse(locations.first);
final String? accessKey = uri.queryParameters['access_key'];
final String? mid = uri.queryParameters['mid'];
try {
Box localCache = GStrorage.localCache;
localCache.put(
LocalCacheKey.accessKey, {'mid': mid, 'value': accessKey});
localCache.put(LocalCacheKey.accessKey,
<String, String?>{'mid': mid, 'value': accessKey});
} catch (_) {}
}
}
@ -53,47 +53,53 @@ class ApiInterceptor extends Interceptor {
super.onError(err, handler);
}
static Future dioError(DioException error) async {
static Future<String> dioError(DioException error) async {
switch (error.type) {
case DioExceptionType.badCertificate:
return '证书有误!';
case DioExceptionType.badResponse:
return '服务器异常,请稍后重试!';
case DioExceptionType.cancel:
return "请求已被取消,请重新请求";
return '请求已被取消,请重新请求';
case DioExceptionType.connectionError:
return '连接错误,请检查网络设置';
case DioExceptionType.connectionTimeout:
return "网络连接超时,请检查网络设置";
return '网络连接超时,请检查网络设置';
case DioExceptionType.receiveTimeout:
return "响应超时,请稍后重试!";
return '响应超时,请稍后重试!';
case DioExceptionType.sendTimeout:
return "发送请求超时,请检查网络设置";
return '发送请求超时,请检查网络设置';
case DioExceptionType.unknown:
var res = await checkConect();
return res + " \n 网络异常,请稍后重试!";
default:
return "Dio异常";
final String res = await checkConect();
return '$res \n 网络异常,请稍后重试!';
// default:
// return 'Dio异常';
}
}
static Future<dynamic> checkConect() async {
final connectivityResult = await (Connectivity().checkConnectivity());
static Future<String> checkConect() async {
final ConnectivityResult connectivityResult =
await Connectivity().checkConnectivity();
if (connectivityResult == ConnectivityResult.mobile) {
return 'connected with mobile network';
} else if (connectivityResult == ConnectivityResult.wifi) {
return 'connected with wifi network';
} else if (connectivityResult == ConnectivityResult.ethernet) {
// I am connected to a ethernet network.
return '';
} else if (connectivityResult == ConnectivityResult.vpn) {
// I am connected to a vpn network.
// Note for iOS and macOS:
// There is no separate network interface type for [vpn].
// It returns [other] on any device (also simulator)
return '';
} else if (connectivityResult == ConnectivityResult.other) {
// I am connected to a network which is not in the above mentioned networks.
return '';
} else if (connectivityResult == ConnectivityResult.none) {
return 'not connected to any network';
} else {
return '';
}
}
}

View File

@ -1,7 +1,7 @@
import 'package:pilipala/http/api.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/models/live/room_info.dart';
import '../models/live/item.dart';
import '../models/live/room_info.dart';
import 'api.dart';
import 'init.dart';
class LiveHttp {
static Future liveList(

View File

@ -1,13 +1,12 @@
import 'dart:convert';
import 'dart:math';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'package:encrypt/encrypt.dart';
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/login/index.dart';
import 'package:pilipala/utils/login.dart';
import 'package:uuid/uuid.dart';
import '../models/login/index.dart';
import '../utils/login.dart';
import 'index.dart';
class LoginHttp {
static Future queryCaptcha() async {

View File

@ -1,17 +1,17 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/models/follow/result.dart';
import 'package:pilipala/models/member/archive.dart';
import 'package:pilipala/models/member/coin.dart';
import 'package:pilipala/models/member/info.dart';
import 'package:pilipala/models/member/seasons.dart';
import 'package:pilipala/models/member/tags.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/utils/wbi_sign.dart';
import '../common/constants.dart';
import '../models/dynamics/result.dart';
import '../models/follow/result.dart';
import '../models/member/archive.dart';
import '../models/member/coin.dart';
import '../models/member/info.dart';
import '../models/member/seasons.dart';
import '../models/member/tags.dart';
import '../utils/storage.dart';
import '../utils/utils.dart';
import '../utils/wbi_sign.dart';
import 'index.dart';
class MemberHttp {
static Future memberInfo({

View File

@ -1,8 +1,8 @@
import 'package:pilipala/http/api.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/msg/account.dart';
import 'package:pilipala/models/msg/session.dart';
import 'package:pilipala/utils/wbi_sign.dart';
import '../models/msg/account.dart';
import '../models/msg/session.dart';
import '../utils/wbi_sign.dart';
import 'api.dart';
import 'init.dart';
class MsgHttp {
// 会话列表

View File

@ -1,6 +1,6 @@
import 'package:pilipala/http/api.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/video/reply/data.dart';
import '../models/video/reply/data.dart';
import 'api.dart';
import 'init.dart';
class ReplyHttp {
static Future replyList({

View File

@ -1,13 +1,12 @@
import 'dart:convert';
import 'package:hive/hive.dart';
import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/bangumi/info.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/models/search/hot.dart';
import 'package:pilipala/models/search/result.dart';
import 'package:pilipala/models/search/suggest.dart';
import 'package:pilipala/utils/storage.dart';
import '../models/bangumi/info.dart';
import '../models/common/search_type.dart';
import '../models/search/hot.dart';
import '../models/search/result.dart';
import '../models/search/suggest.dart';
import '../utils/storage.dart';
import 'index.dart';
class SearchHttp {
static Box setting = GStrorage.setting;
@ -129,25 +128,28 @@ class SearchHttp {
}
}
static Future ab2c({int? aid, String? bvid}) async {
static Future<int> ab2c({int? aid, String? bvid}) async {
Map<String, dynamic> data = {};
if (aid != null) {
data['aid'] = aid;
} else if (bvid != null) {
data['bvid'] = bvid;
}
var res = await Request().get(Api.ab2c, data: {...data});
final dynamic res =
await Request().get(Api.ab2c, data: <String, dynamic>{...data});
return res.data['data'].first['cid'];
}
static Future bangumiInfo({int? seasonId, int? epId}) async {
Map<String, dynamic> data = {};
static Future<Map<String, dynamic>> bangumiInfo(
{int? seasonId, int? epId}) async {
final Map<String, dynamic> data = {};
if (seasonId != null) {
data['season_id'] = seasonId;
} else if (epId != null) {
data['ep_id'] = epId;
}
var res = await Request().get(Api.bangumiInfo, data: {...data});
final dynamic res =
await Request().get(Api.bangumiInfo, data: <String, dynamic>{...data});
if (res.data['code'] == 0) {
return {
'status': true,

View File

@ -1,14 +1,13 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/http/api.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/model_hot_video_item.dart';
import 'package:pilipala/models/user/fav_detail.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/models/user/history.dart';
import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/models/user/stat.dart';
import 'package:pilipala/utils/wbi_sign.dart';
import '../common/constants.dart';
import '../models/model_hot_video_item.dart';
import '../models/user/fav_detail.dart';
import '../models/user/fav_folder.dart';
import '../models/user/history.dart';
import '../models/user/info.dart';
import '../models/user/stat.dart';
import 'api.dart';
import 'init.dart';
class UserHttp {
static Future<dynamic> userStat({required int mid}) async {

View File

@ -1,19 +1,18 @@
import 'dart:developer';
import 'package:hive/hive.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/http/api.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/home/rcmd/result.dart';
import 'package:pilipala/models/model_hot_video_item.dart';
import 'package:pilipala/models/model_rec_video_item.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/models/video/ai.dart';
import 'package:pilipala/models/video/play/url.dart';
import 'package:pilipala/models/video_detail_res.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/wbi_sign.dart';
import '../common/constants.dart';
import '../models/common/reply_type.dart';
import '../models/home/rcmd/result.dart';
import '../models/model_hot_video_item.dart';
import '../models/model_rec_video_item.dart';
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/storage.dart';
import '../utils/wbi_sign.dart';
import 'api.dart';
import 'init.dart';
/// res.data['code'] == 0 请求正常返回结果
/// res.data['data'] 为结果
@ -33,20 +32,27 @@ class VideoHttp {
Api.recommendListWeb,
data: {
'version': 1,
'feed_version': 'V3',
'feed_version': 'V8',
'homepage_ver': 1,
'ps': ps,
'fresh_idx': freshIdx,
'fresh_type': 999999
'brush': freshIdx,
'fresh_type': 4
},
);
if (res.data['code'] == 0) {
List<RecVideoItemModel> list = [];
List<int> blackMidsList =
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
for (var i in res.data['data']['item']) {
list.add(RecVideoItemModel.fromJson(i));
//过滤掉live与ad以及拉黑用户
if (i['goto'] == 'av' && !blackMidsList.contains(i['owner']['mid'])) {
list.add(RecVideoItemModel.fromJson(i));
}
}
return {'status': true, 'data': list};
} else {
return {'status': false, 'data': [], 'msg': ''};
return {'status': false, 'data': [], 'msg': res.data['message']};
}
} catch (err) {
return {'status': false, 'data': [], 'msg': err.toString()};
@ -134,6 +140,12 @@ class VideoHttp {
data['bvid'] = bvid;
}
// 免登录查看1080p
if (userInfoCache.get('userInfoCache') == null &&
setting.get(SettingBoxKey.p1080, defaultValue: true)) {
data['try_look'] = 1;
}
Map params = await WbiSign().makSign({
...data,
'fourk': 1,
@ -142,11 +154,6 @@ class VideoHttp {
'web_location': 1550101,
});
// 免登录查看1080p
if (userInfoCache.get('userInfoCache') == null &&
setting.get(SettingBoxKey.p1080, defaultValue: true)) {
data['try_look'] = 1;
}
try {
var res = await Request().get(Api.videoUrl, data: params);
if (res.data['code'] == 0) {

View File

@ -30,6 +30,8 @@ void main() async {
.then((_) async {
await GStrorage.init();
await setupServiceLocator();
Request();
await Request.setCookie();
runApp(const MyApp());
// 小白条、导航栏沉浸
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
@ -38,7 +40,6 @@ void main() async {
systemNavigationBarDividerColor: Colors.transparent,
statusBarColor: Colors.transparent,
));
await Request.setCookie();
Data.init();
GStrorage.lazyInit();
PiliSchame.init();
@ -112,6 +113,13 @@ class MyApp extends StatelessWidget {
? darkColorScheme
: lightColorScheme,
useMaterial3: true,
snackBarTheme: SnackBarThemeData(
actionTextColor: lightColorScheme.primary,
backgroundColor: lightColorScheme.secondaryContainer,
closeIconColor: lightColorScheme.secondary,
contentTextStyle: TextStyle(color: lightColorScheme.secondary),
elevation: 20,
),
pageTransitionsTheme: const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: ZoomPageTransitionsBuilder(
@ -126,6 +134,13 @@ class MyApp extends StatelessWidget {
? lightColorScheme
: darkColorScheme,
useMaterial3: true,
snackBarTheme: SnackBarThemeData(
actionTextColor: darkColorScheme.primary,
backgroundColor: darkColorScheme.secondaryContainer,
closeIconColor: darkColorScheme.secondary,
contentTextStyle: TextStyle(color: darkColorScheme.secondary),
elevation: 20,
),
),
localizationsDelegates: const [
GlobalCupertinoLocalizations.delegate,
@ -141,9 +156,8 @@ class MyApp extends StatelessWidget {
return FlutterSmartDialog(
toastBuilder: (String msg) => CustomToast(msg: msg),
child: MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaleFactor:
MediaQuery.of(context).textScaleFactor * textScale),
data: MediaQuery.of(context)
.copyWith(textScaler: TextScaler.linear(textScale)),
child: child!,
),
);

View File

@ -9,6 +9,7 @@ enum TabType { live, rcmd, hot, bangumi }
extension TabTypeDesc on TabType {
String get description => ['直播', '推荐', '热门', '番剧'][index];
String get id => ['live', 'rcmd', 'hot', 'bangumi'][index];
}
List tabsConfig = [

View File

@ -77,14 +77,14 @@ class Stat {
this.danmu,
});
@HiveField(0)
int? view;
String? view;
@HiveField(1)
int? like;
@HiveField(2)
int? danmu;
Stat.fromJson(Map<String, dynamic> json) {
view = json["view"];
view = Utils.numFormat(json["view"]);
like = json["like"];
danmu = json['danmaku'];
}

View File

@ -87,7 +87,7 @@ class StatAdapter extends TypeAdapter<Stat> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return Stat(
view: fields[0] as int?,
view: fields[0] as String?,
like: fields[1] as int?,
danmu: fields[2] as int?,
);

View File

@ -20,7 +20,7 @@ class _AboutPageState extends State<AboutPage> {
@override
Widget build(BuildContext context) {
Color outline = Theme.of(context).colorScheme.outline;
final Color outline = Theme.of(context).colorScheme.outline;
TextStyle subTitleStyle =
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
return Scaffold(
@ -29,7 +29,6 @@ class _AboutPageState extends State<AboutPage> {
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'assets/images/logo/logo_android_2.png',
@ -47,7 +46,7 @@ class _AboutPageState extends State<AboutPage> {
const SizedBox(height: 20),
Obx(
() => ListTile(
title: const Text("当前版本"),
title: const Text('当前版本'),
trailing: Text(_aboutController.currentVersion.value,
style: subTitleStyle),
),
@ -87,6 +86,14 @@ class _AboutPageState extends State<AboutPage> {
style: subTitleStyle,
),
),
ListTile(
onTap: () => _aboutController.webSiteUrl(),
title: const Text('访问官网'),
trailing: Text(
'https://pilipalanet.mysxl.cn',
style: subTitleStyle,
),
),
ListTile(
onTap: () => _aboutController.panDownload(),
title: const Text('网盘下载'),
@ -245,4 +252,12 @@ class AboutController extends GetxController {
print(e);
}
}
// 官网
webSiteUrl() {
launchUrl(
Uri.parse('https://pilipalanet.mysxl.cn'),
mode: LaunchMode.externalApplication,
);
}
}

View File

@ -63,8 +63,8 @@ class BangumiIntroController extends GetxController {
@override
void onInit() {
super.onInit();
if (Get.arguments.isNotEmpty) {
if (Get.arguments.containsKey('bangumiItem')) {
if (Get.arguments.isNotEmpty as bool) {
if (Get.arguments.containsKey('bangumiItem') as bool) {
preRender = true;
bangumiItem = Get.arguments['bangumiItem'];
// bangumiItem!['pic'] = args.pic;

View File

@ -51,12 +51,11 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
cid = widget.cid!;
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
bangumiIntroController.bangumiDetail.listen((value) {
bangumiIntroController.bangumiDetail.listen((BangumiInfoModel value) {
bangumiDetail = value;
});
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
videoDetailCtr.cid.listen((p0) {
print('🐶🐶$p0');
videoDetailCtr.cid.listen((int p0) {
cid = p0;
setState(() {});
});
@ -67,7 +66,7 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
super.build(context);
return FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data['status']) {
// 请求成功
@ -83,7 +82,7 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
// errMsg: snapshot.data['msg'],
// fn: () => Get.back(),
// );
return SizedBox();
return const SizedBox();
}
} else {
return BangumiInfo(
@ -98,16 +97,16 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
}
class BangumiInfo extends StatefulWidget {
final bool loadingStatus;
final BangumiInfoModel? bangumiDetail;
final int? cid;
const BangumiInfo({
Key? key,
super.key,
this.loadingStatus = false,
this.bangumiDetail,
this.cid,
}) : super(key: key);
});
final bool loadingStatus;
final BangumiInfoModel? bangumiDetail;
final int? cid;
@override
State<BangumiInfo> createState() => _BangumiInfoState();
@ -123,12 +122,15 @@ class _BangumiInfoState extends State<BangumiInfo> {
int? cid;
bool isProcessing = false;
void Function()? handleState(Future Function() action) {
return isProcessing ? null : () async {
setState(() => isProcessing = true);
await action();
setState(() => isProcessing = false);
};
return isProcessing
? null
: () async {
setState(() => isProcessing = true);
await action();
setState(() => isProcessing = false);
};
}
@override
void initState() {
super.initState();
@ -155,7 +157,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
builder: (BuildContext context) {
return FavPanel(ctr: bangumiIntroController);
},
);
@ -175,7 +177,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
@override
Widget build(BuildContext context) {
ThemeData t = Theme.of(context);
final ThemeData t = Theme.of(context);
return SliverPadding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 20),
@ -185,7 +187,6 @@ class _BangumiInfoState extends State<BangumiInfo> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
@ -244,7 +245,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith(
(states) {
(Set<MaterialState> states) {
return t
.colorScheme.primaryContainer
.withOpacity(0.7);
@ -386,7 +387,8 @@ class _BangumiInfoState extends State<BangumiInfo> {
}
Widget actionGrid(BuildContext context, bangumiIntroController) {
return LayoutBuilder(builder: (context, constraints) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Material(
child: Padding(
padding: const EdgeInsets.only(top: 16, bottom: 8),
@ -394,7 +396,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
height: constraints.maxWidth / 5 * 0.8,
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(0),
padding: EdgeInsets.zero,
crossAxisCount: 5,
childAspectRatio: 1.25,
children: <Widget>[
@ -402,7 +404,8 @@ class _BangumiInfoState extends State<BangumiInfo> {
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: handleState(bangumiIntroController.actionLikeVideo),
onTap:
handleState(bangumiIntroController.actionLikeVideo),
selectStatus: bangumiIntroController.hasLike.value,
loadingStatus: false,
text: !widget.loadingStatus
@ -413,7 +416,8 @@ class _BangumiInfoState extends State<BangumiInfo> {
() => ActionItem(
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: handleState(bangumiIntroController.actionCoinVideo),
onTap:
handleState(bangumiIntroController.actionCoinVideo),
selectStatus: bangumiIntroController.hasCoin.value,
loadingStatus: false,
text: !widget.loadingStatus

View File

@ -4,6 +4,7 @@ import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
import 'package:nil/nil.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/home/index.dart';
@ -74,7 +75,7 @@ class _BangumiPageState extends State<BangumiPage>
super.build(context);
return RefreshIndicator(
onRefresh: () async {
await _bangumidController.queryBangumiListFeed(type: 'init');
await _bangumidController.queryBangumiListFeed();
return _bangumidController.queryBangumiFollow();
},
child: CustomScrollView(
@ -112,10 +113,11 @@ class _BangumiPageState extends State<BangumiPage>
),
),
SizedBox(
height: 258,
height: 268,
child: FutureBuilder(
future: _futureBuilderFutureFollow,
builder: (context, snapshot) {
builder:
(BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.data == null) {
@ -156,10 +158,10 @@ class _BangumiPageState extends State<BangumiPage>
),
);
} else {
return const SizedBox();
return nil;
}
} else {
return const SizedBox();
return nil;
}
},
),
@ -188,7 +190,7 @@ class _BangumiPageState extends State<BangumiPage>
StyleString.safeSpace, 0, StyleString.safeSpace, 0),
sliver: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
@ -206,7 +208,7 @@ class _BangumiPageState extends State<BangumiPage>
},
),
),
LoadingMore()
const LoadingMore()
],
),
);
@ -222,13 +224,13 @@ class _BangumiPageState extends State<BangumiPage>
// 列数
crossAxisCount: 3,
mainAxisExtent: Get.size.width / 3 / 0.65 +
32 * MediaQuery.of(context).textScaleFactor,
MediaQuery.textScalerOf(context).scale(32.0),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return bangumiList!.isNotEmpty
? BangumiCardV(bangumiItem: bangumiList[index])
: const SizedBox();
: nil;
},
childCount: bangumiList!.isNotEmpty ? bangumiList!.length : 10,
),

View File

@ -8,11 +8,6 @@ import 'package:pilipala/utils/storage.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class BangumiPanel extends StatefulWidget {
final List<EpisodeItem> pages;
final int? cid;
final double? sheetHeight;
final Function? changeFuc;
const BangumiPanel({
super.key,
required this.pages,
@ -21,6 +16,11 @@ class BangumiPanel extends StatefulWidget {
this.changeFuc,
});
final List<EpisodeItem> pages;
final int? cid;
final double? sheetHeight;
final Function? changeFuc;
@override
State<BangumiPanel> createState() => _BangumiPanelState();
}
@ -50,10 +50,10 @@ class _BangumiPanelState extends State<BangumiPanel> {
}
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
videoDetailCtr.cid.listen((p0) {
videoDetailCtr.cid.listen((int p0) {
cid = p0;
setState(() {});
currentIndex = widget.pages.indexWhere((e) => e.cid == cid);
currentIndex = widget.pages.indexWhere((EpisodeItem e) => e.cid == cid);
scrollToIndex();
});
}
@ -106,7 +106,8 @@ class _BangumiPanelState extends State<BangumiPanel> {
child: Material(
child: ScrollablePositionedList.builder(
itemCount: widget.pages.length,
itemBuilder: (context, index) => ListTile(
itemBuilder: (BuildContext context, int index) =>
ListTile(
onTap: () {
setState(() {
changeFucCall(widget.pages[index], index);
@ -212,78 +213,74 @@ class _BangumiPanelState extends State<BangumiPanel> {
SizedBox(
height: 60,
child: ListView.builder(
controller: listViewScrollCtr,
scrollDirection: Axis.horizontal,
itemCount: widget.pages.length,
itemExtent: 150,
itemBuilder: ((context, i) {
return Container(
width: 150,
margin: const EdgeInsets.only(right: 10),
child: Material(
color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: BorderRadius.circular(6),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => changeFucCall(widget.pages[i], i),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (i == currentIndex) ...[
Image.asset(
'assets/images/live.png',
color:
Theme.of(context).colorScheme.primary,
height: 12,
),
const SizedBox(width: 6)
],
Text(
'${i + 1}',
style: TextStyle(
fontSize: 13,
color: i == currentIndex
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.onSurface),
controller: listViewScrollCtr,
scrollDirection: Axis.horizontal,
itemCount: widget.pages.length,
itemExtent: 150,
itemBuilder: (BuildContext context, int i) {
return Container(
width: 150,
margin: const EdgeInsets.only(right: 10),
child: Material(
color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: BorderRadius.circular(6),
clipBehavior: Clip.hardEdge,
child: InkWell(
onTap: () => changeFucCall(widget.pages[i], i),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: [
if (i == currentIndex) ...<Widget>[
Image.asset(
'assets/images/live.png',
color: Theme.of(context).colorScheme.primary,
height: 12,
),
const SizedBox(width: 2),
if (widget.pages[i].badge != null) ...[
Image.asset(
'assets/images/big-vip.png',
height: 16,
),
],
const SizedBox(width: 6)
],
),
const SizedBox(height: 3),
Text(
widget.pages[i].longTitle!,
maxLines: 1,
style: TextStyle(
fontSize: 13,
color: i == currentIndex
? Theme.of(context).colorScheme.primary
: Theme.of(context)
.colorScheme
.onSurface),
overflow: TextOverflow.ellipsis,
)
],
),
Text(
'${i + 1}',
style: TextStyle(
fontSize: 13,
color: i == currentIndex
? Theme.of(context).colorScheme.primary
: Theme.of(context)
.colorScheme
.onSurface),
),
const SizedBox(width: 2),
if (widget.pages[i].badge != null) ...[
Image.asset(
'assets/images/big-vip.png',
height: 16,
),
],
],
),
const SizedBox(height: 3),
Text(
widget.pages[i].longTitle!,
maxLines: 1,
style: TextStyle(
fontSize: 13,
color: i == currentIndex
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface),
overflow: TextOverflow.ellipsis,
)
],
),
),
),
);
})),
),
);
},
),
)
],
);

View File

@ -11,17 +11,16 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
// 视频卡片 - 垂直布局
class BangumiCardV extends StatelessWidget {
// ignore: prefer_typing_uninitialized_variables
final bangumiItem;
final Function()? longPress;
final Function()? longPressEnd;
const BangumiCardV({
Key? key,
super.key,
required this.bangumiItem,
this.longPress,
this.longPressEnd,
}) : super(key: key);
});
final bangumiItem;
final Function()? longPress;
final Function()? longPressEnd;
@override
Widget build(BuildContext context) {
@ -43,9 +42,9 @@ class BangumiCardV extends StatelessWidget {
// },
child: InkWell(
onTap: () async {
int seasonId = bangumiItem.seasonId;
final int seasonId = bangumiItem.seasonId;
SmartDialog.showLoading(msg: '获取中...');
var res = await SearchHttp.bangumiInfo(seasonId: seasonId);
final res = await SearchHttp.bangumiInfo(seasonId: seasonId);
SmartDialog.dismiss().then((value) {
if (res['status']) {
if (res['data'].episodes.isEmpty) {
@ -81,8 +80,8 @@ class BangumiCardV extends StatelessWidget {
child: AspectRatio(
aspectRatio: 0.65,
child: LayoutBuilder(builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
@ -124,9 +123,9 @@ class BangumiCardV extends StatelessWidget {
}
class BangumiContent extends StatelessWidget {
const BangumiContent({super.key, required this.bangumiItem});
// ignore: prefer_typing_uninitialized_variables
final bangumiItem;
const BangumiContent({Key? key, required this.bangumiItem}) : super(key: key);
@override
Widget build(BuildContext context) {
return Expanded(

View File

@ -70,7 +70,7 @@ class _BlackListPageState extends State<BlackListPage> {
onRefresh: () async => await _blackListController.queryBlacklist(),
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
if (data['status']) {

View File

@ -1,26 +1,23 @@
import 'package:pilipala/http/danmaku.dart';
import 'package:pilipala/models/danmaku/dm.pb.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
class PlDanmakuController {
PlDanmakuController(this.cid);
final int cid;
Map<int,List<DanmakuElem>> dmSegMap = {};
Map<int, List<DanmakuElem>> dmSegMap = {};
// 已请求的段落标记
List<bool> requestedSeg = [];
bool get initiated => requestedSeg.isNotEmpty;
static int SEGMENT_LENGTH = 60 * 6 * 1000;
static int segmentLength = 60 * 6 * 1000;
void initiate(int videoDuration, int progress) {
if (requestedSeg.isEmpty) {
int segCount = (videoDuration / SEGMENT_LENGTH).ceil();
int segCount = (videoDuration / segmentLength).ceil();
requestedSeg = List<bool>.generate(segCount, (index) => false);
}
queryDanmaku(
calcSegment(progress)
);
queryDanmaku(calcSegment(progress));
}
void dispose() {
@ -29,17 +26,17 @@ class PlDanmakuController {
}
int calcSegment(int progress) {
return progress ~/ SEGMENT_LENGTH;
return progress ~/ segmentLength;
}
void queryDanmaku(int segmentIndex) async {
assert(requestedSeg[segmentIndex] == false);
requestedSeg[segmentIndex] = true;
DmSegMobileReply result =
await DanmakaHttp.queryDanmaku(cid: cid, segmentIndex: segmentIndex + 1);
final DmSegMobileReply result = await DanmakaHttp.queryDanmaku(
cid: cid, segmentIndex: segmentIndex + 1);
if (result.elems.isNotEmpty) {
for (var element in result.elems) {
int pos = element.progress ~/ 100;//每0.1秒存储一次
int pos = element.progress ~/ 100; //每0.1秒存储一次
if (dmSegMap[pos] == null) {
dmSegMap[pos] = [];
}

View File

@ -1,4 +1,3 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
@ -43,15 +42,13 @@ class _PlDanmakuState extends State<PlDanmaku> {
super.initState();
enableShowDanmaku =
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
_plDanmakuController =
PlDanmakuController(widget.cid);
_plDanmakuController = PlDanmakuController(widget.cid);
if (mounted) {
playerController = widget.playerController;
if (enableShowDanmaku || playerController.isOpenDanmu.value) {
_plDanmakuController.initiate(
playerController.duration.value.inMilliseconds,
playerController.position.value.inMilliseconds
);
playerController.position.value.inMilliseconds);
}
playerController
..addStatusLister(playerListener)
@ -61,8 +58,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
if (p0 && !_plDanmakuController.initiated) {
_plDanmakuController.initiate(
playerController.duration.value.inMilliseconds,
playerController.position.value.inMilliseconds
);
playerController.position.value.inMilliseconds);
}
});
blockTypes = playerController.blockTypes;
@ -87,7 +83,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
return;
}
int currentPosition = position.inMilliseconds;
currentPosition -= currentPosition % 100;//取整百的毫秒数
currentPosition -= currentPosition % 100; //取整百的毫秒数
if (currentPosition == latestAddedPosition) {
return;
@ -98,17 +94,18 @@ class _PlDanmakuState extends State<PlDanmaku> {
_plDanmakuController.getCurrentDanmaku(currentPosition);
if (currentDanmakuList != null) {
Color? defaultColor = playerController.blockTypes.contains(6) ?
DmUtils.decimalToColor(16777215) : null;
Color? defaultColor = playerController.blockTypes.contains(6)
? DmUtils.decimalToColor(16777215)
: null;
_controller!.addItems(
currentDanmakuList.map((e) => DanmakuItem(
e.content,
color: defaultColor ?? DmUtils.decimalToColor(e.color),
time: e.progress,
type: DmUtils.getPosition(e.mode),
)).toList()
);
_controller!.addItems(currentDanmakuList
.map((e) => DanmakuItem(
e.content,
color: defaultColor ?? DmUtils.decimalToColor(e.color),
time: e.progress,
type: DmUtils.getPosition(e.mode),
))
.toList());
}
}
@ -128,7 +125,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
duration: const Duration(milliseconds: 100),
child: DanmakuView(
createdController: (DanmakuController e) async {
widget.playerController.danmakuController = _controller = e;
playerController.danmakuController = _controller = e;
},
option: DanmakuOption(
fontSize: 15 * fontSizeVal,
@ -137,7 +134,8 @@ class _PlDanmakuState extends State<PlDanmaku> {
hideTop: blockTypes.contains(5),
hideScroll: blockTypes.contains(2),
hideBottom: blockTypes.contains(4),
duration: danmakuDurationVal / widget.playerController.playbackSpeed,
duration:
danmakuDurationVal / playerController.playbackSpeed,
// initDuration /
// (danmakuSpeedVal * widget.playerController.playbackSpeed),
),

View File

@ -8,11 +8,11 @@ import 'package:pilipala/common/skeleton/video_reply.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/dynamics/deatil/index.dart';
import 'package:pilipala/pages/dynamics/detail/index.dart';
import 'package:pilipala/pages/dynamics/widgets/author_panel.dart';
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/pages/video/detail/reply_new/index.dart';
import 'package:pilipala/pages/video/detail/reply_reply/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/id_utils.dart';

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/utils/feed_back.dart';

View File

@ -1,5 +1,6 @@
// 内容
import 'package:flutter/material.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/preview/index.dart';
@ -48,6 +49,13 @@ class _ContentState extends State<Content> {
WidgetSpan(
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
double maxWidth = box.maxWidth.truncateToDouble();
double maxHeight = box.maxWidth * 0.6; // 设置最大高度
double height = maxWidth *
0.5 *
(pictureItem.height != null && pictureItem.width != null
? pictureItem.height! / pictureItem.width!
: 1);
return GestureDetector(
onTap: () {
showDialog(
@ -58,18 +66,29 @@ class _ContentState extends State<Content> {
},
);
},
child: Padding(
padding: const EdgeInsets.only(top: 4),
child: NetworkImgLayer(
src: pictureItem.url,
child: Container(
padding: const EdgeInsets.only(top: 4),
constraints: BoxConstraints(maxHeight: maxHeight),
width: box.maxWidth / 2,
height: box.maxWidth *
0.5 *
(pictureItem.height != null && pictureItem.width != null
? pictureItem.height! / pictureItem.width!
: 1),
),
),
height: height,
child: Stack(
children: [
Positioned.fill(
child: NetworkImgLayer(
src: pictureItem.url,
width: maxWidth / 2,
height: height,
),
),
height > maxHeight
? const PBadge(
text: '长图',
right: 8,
bottom: 8,
)
: const SizedBox(),
],
)),
);
},
),
@ -83,6 +102,7 @@ class _ContentState extends State<Content> {
list.add(
LayoutBuilder(
builder: (context, BoxConstraints box) {
double maxWidth = box.maxWidth.truncateToDouble();
return GestureDetector(
onTap: () {
showDialog(
@ -95,8 +115,10 @@ class _ContentState extends State<Content> {
},
child: NetworkImgLayer(
src: pics[i].url,
width: box.maxWidth,
height: box.maxWidth,
width: maxWidth,
height: maxWidth,
origAspectRatio:
pics[i].width!.toInt() / pics[i].height!.toInt(),
),
);
},
@ -107,7 +129,7 @@ class _ContentState extends State<Content> {
WidgetSpan(
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
double maxWidth = box.maxWidth;
double maxWidth = box.maxWidth.truncateToDouble();
double crossCount = len < 3 ? 2 : 3;
double height = maxWidth /
crossCount *

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/http/search.dart';
// 富文本
InlineSpan richNode(item, context) {
@ -191,6 +193,39 @@ InlineSpan richNode(item, context) {
),
);
}
// 投稿
if (i.type == 'RICH_TEXT_NODE_TYPE_BV') {
spanChilds.add(
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Icon(
Icons.play_circle_outline_outlined,
size: 16,
color: Theme.of(context).colorScheme.primary,
),
),
);
spanChilds.add(
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: GestureDetector(
onTap: () async {
try {
int cid = await SearchHttp.ab2c(bvid: i.rid);
Get.toNamed('/video?bvid=${i.rid}&cid=$cid',
arguments: {'pic': null, 'heroTag': i.rid});
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
child: Text(
'${i.text} ',
style: authorStyle,
),
),
),
);
}
}
// if (contentType == 'major' &&
// item.modules.moduleDynamic.major.opus.pics.isNotEmpty) {

View File

@ -139,7 +139,7 @@ class _UpPanelState extends State<UpPanel> {
int liveLen = liveList.length;
int upLen = upList.length;
double itemWidth = contentWidth + itemPadding.horizontal;
double screenWidth = MediaQuery.of(context).size.width;
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) {

View File

@ -7,7 +7,7 @@ import 'package:pilipala/common/skeleton/video_card_h.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/no_data.dart';
import 'package:pilipala/pages/favDetail/index.dart';
import 'package:pilipala/pages/fav_detail/index.dart';
import 'widget/fav_video_card.dart';

View File

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/skeleton/video_card_h.dart';
import 'package:pilipala/common/widgets/no_data.dart';
import 'package:pilipala/pages/favDetail/widget/fav_video_card.dart';
import 'package:pilipala/pages/fav_detail/widget/fav_video_card.dart';
import 'controller.dart';

View File

@ -8,12 +8,13 @@ import 'package:pilipala/utils/storage.dart';
class HomeController extends GetxController with GetTickerProviderStateMixin {
bool flag = false;
late List tabs;
int initialIndex = 1;
late RxList tabs = [].obs;
RxInt initialIndex = 1.obs;
late TabController tabController;
late List tabsCtrList;
late List<Widget> tabsPageList;
Box userInfoCache = GStrorage.userInfo;
Box settingStorage = GStrorage.setting;
RxBool userLogin = false.obs;
RxString userFace = ''.obs;
var userInfo;
@ -21,6 +22,8 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
late final StreamController<bool> searchBarStream =
StreamController<bool>.broadcast();
late bool hideSearchBar;
late List defaultTabs;
late List<String> tabbarSort;
@override
void onInit() {
@ -28,17 +31,8 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null;
userFace.value = userInfo != null ? userInfo.face : '';
// 进行tabs配置
tabs = tabsConfig;
tabsCtrList = tabsConfig.map((e) => e['ctr']).toList();
tabsPageList = tabsConfig.map<Widget>((e) => e['page']).toList();
tabController = TabController(
initialIndex: initialIndex,
length: tabs.length,
vsync: this,
);
setTabConfig();
hideSearchBar =
setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
}
@ -62,4 +56,42 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
if (val) return;
userFace.value = userInfo != null ? userInfo.face : '';
}
void setTabConfig() async {
defaultTabs = tabsConfig;
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
tabs.value = defaultTabs
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
.toList();
if (tabbarSort.contains(TabType.rcmd.id)) {
initialIndex.value = tabbarSort.indexOf(TabType.rcmd.id);
} else {
initialIndex.value = 0;
}
tabsCtrList = tabs.map((e) => e['ctr']).toList();
tabsPageList = tabs.map<Widget>((e) => e['page']).toList();
tabController = TabController(
initialIndex: initialIndex.value,
length: tabs.length,
vsync: this,
);
// 监听 tabController 切换
tabController.animation!.addListener(() {
if (tabController.indexIsChanging) {
if (initialIndex.value != tabController.index) {
initialIndex.value = tabController.index;
}
} else {
final int temp = tabController.animation!.value.round();
if (initialIndex.value != temp) {
initialIndex.value = temp;
tabController.index = initialIndex.value;
}
}
});
}
}

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/mine/index.dart';
@ -30,7 +31,7 @@ class _HomePageState extends State<HomePage>
stream = _homeController.searchBarStream.stream;
}
showUserBottonSheet() {
showUserBottomSheet() {
feedBack();
showModalBottomSheet(
context: context,
@ -46,50 +47,61 @@ class _HomePageState extends State<HomePage>
@override
Widget build(BuildContext context) {
super.build(context);
Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
// 设置状态栏图标的亮度
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarIconBrightness: currentBrightness == Brightness.light
? Brightness.dark
: Brightness.light,
));
return Scaffold(
extendBody: true,
extendBodyBehindAppBar: true,
appBar: AppBar(toolbarHeight: 0, elevation: 0),
body: Column(
body: Stack(
children: [
CustomAppBar(
stream: _homeController.hideSearchBar
? stream
: StreamController<bool>.broadcast().stream,
ctr: _homeController,
callback: showUserBottonSheet,
),
const SizedBox(height: 8),
SizedBox(
width: double.infinity,
height: 42,
child: Align(
alignment: Alignment.center,
child: TabBar(
controller: _homeController.tabController,
tabs: [
for (var i in _homeController.tabs) Tab(text: i['label'])
],
isScrollable: true,
dividerColor: Colors.transparent,
enableFeedback: true,
splashBorderRadius: BorderRadius.circular(10),
tabAlignment: TabAlignment.center,
onTap: (value) {
feedBack();
if (_homeController.initialIndex == value) {
_homeController.tabsCtrList[value]().animateToTop();
}
_homeController.initialIndex = value;
},
// gradient background
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]),
),
),
),
),
Expanded(
child: TabBarView(
controller: _homeController.tabController,
children: _homeController.tabsPageList,
),
Column(
children: [
CustomAppBar(
stream: _homeController.hideSearchBar
? stream
: StreamController<bool>.broadcast().stream,
ctr: _homeController,
callback: showUserBottomSheet,
),
if (_homeController.tabs.length > 1) ...[
const CustomTabs(),
] else ...[
const SizedBox(height: 6),
],
Expanded(
child: TabBarView(
controller: _homeController.tabController,
children: _homeController.tabsPageList,
),
),
],
),
],
),
@ -119,87 +131,22 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
return StreamBuilder(
stream: stream,
initialData: true,
builder: (context, AsyncSnapshot snapshot) {
builder: (BuildContext context, AsyncSnapshot snapshot) {
final RxBool isUserLoggedIn = ctr!.userLogin;
final double top = MediaQuery.of(context).padding.top;
return AnimatedOpacity(
opacity: snapshot.data ? 1 : 0,
duration: const Duration(milliseconds: 300),
child: AnimatedContainer(
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500),
height: snapshot.data
? MediaQuery.of(context).padding.top + 52
: MediaQuery.of(context).padding.top - 10,
child: Container(
padding: EdgeInsets.only(
left: 20,
right: 20,
bottom: 0,
top: MediaQuery.of(context).padding.top + 4,
),
child: Row(
children: [
const Expanded(child: SearchPage()),
if (ctr!.userLogin.value) ...[
const SizedBox(width: 6),
IconButton(
onPressed: () => Get.toNamed('/whisper'),
icon: const Icon(Icons.notifications_none))
],
const SizedBox(width: 6),
Obx(
() => ctr!.userLogin.value
? Stack(
children: [
Obx(
() => NetworkImgLayer(
type: 'avatar',
width: 34,
height: 34,
src: ctr!.userFace.value,
),
),
Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => callback!(),
splashColor: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.3),
borderRadius: const BorderRadius.all(
Radius.circular(50),
),
),
),
)
],
)
: SizedBox(
width: 38,
height: 38,
child: IconButton(
style: ButtonStyle(
padding:
MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith((states) {
return Theme.of(context)
.colorScheme
.onInverseSurface;
}),
),
onPressed: () => callback!(),
icon: Icon(
Icons.person_rounded,
size: 22,
color: Theme.of(context).colorScheme.primary,
),
),
),
),
],
),
height: snapshot.data ? top + 52 : top,
padding: EdgeInsets.fromLTRB(14, top + 6, 14, 0),
child: UserInfoWidget(
top: top,
userLogin: isUserLoggedIn,
userFace: ctr?.userFace.value,
callback: () => callback!(),
),
),
);
@ -207,3 +154,229 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
);
}
}
class UserInfoWidget extends StatelessWidget {
const UserInfoWidget({
Key? key,
required this.top,
required this.userLogin,
required this.userFace,
required this.callback,
}) : super(key: key);
final double top;
final RxBool userLogin;
final String? userFace;
final VoidCallback? callback;
@override
Widget build(BuildContext context) {
return Row(
children: [
const SearchBar(),
if (userLogin.value) ...[
const SizedBox(width: 4),
ClipRect(
child: IconButton(
onPressed: () => Get.toNamed('/whisper'),
icon: const Icon(Icons.notifications_none),
),
)
],
const SizedBox(width: 8),
Obx(
() => userLogin.value
? Stack(
children: [
NetworkImgLayer(
type: 'avatar',
width: 34,
height: 34,
src: userFace,
),
Positioned.fill(
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () => callback?.call(),
splashColor: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.3),
borderRadius: const BorderRadius.all(
Radius.circular(50),
),
),
),
)
],
)
: DefaultUser(callback: () => callback!()),
),
],
);
}
}
class DefaultUser extends StatelessWidget {
const DefaultUser({super.key, this.callback});
final Function? callback;
@override
Widget build(BuildContext context) {
return SizedBox(
width: 38,
height: 38,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor: MaterialStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.onInverseSurface;
}),
),
onPressed: () => callback?.call(),
icon: Icon(
Icons.person_rounded,
size: 22,
color: Theme.of(context).colorScheme.primary,
),
),
);
}
}
class CustomTabs extends StatefulWidget {
const CustomTabs({super.key});
@override
State<CustomTabs> createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabs> {
final HomeController _homeController = Get.put(HomeController());
void onTap(int index) {
feedBack();
if (_homeController.initialIndex.value == index) {
_homeController.tabsCtrList[index]().animateToTop();
}
_homeController.initialIndex.value = index;
_homeController.tabController.index = index;
}
@override
Widget build(BuildContext context) {
return Container(
height: 44,
margin: const EdgeInsets.only(top: 4),
child: Obx(
() => ListView.separated(
padding: const EdgeInsets.symmetric(horizontal: 14.0),
scrollDirection: Axis.horizontal,
itemCount: _homeController.tabs.length,
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(width: 10);
},
itemBuilder: (BuildContext context, int index) {
String label = _homeController.tabs[index]['label'];
return Obx(
() => CustomChip(
onTap: () => onTap(index),
label: label,
selected: index == _homeController.initialIndex.value,
),
);
},
),
),
);
}
}
class CustomChip extends StatelessWidget {
final Function onTap;
final String label;
final bool selected;
const CustomChip({
super.key,
required this.onTap,
required this.label,
required this.selected,
});
@override
Widget build(BuildContext context) {
final ColorScheme colorTheme = Theme.of(context).colorScheme;
final Color secondaryContainer = colorTheme.secondaryContainer;
final TextStyle chipTextStyle = selected
? const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)
: const TextStyle(fontSize: 13);
final ColorScheme colorScheme = Theme.of(context).colorScheme;
const VisualDensity visualDensity =
VisualDensity(horizontal: -4.0, vertical: -2.0);
return InputChip(
side: BorderSide(
color: selected
? colorScheme.onSecondaryContainer.withOpacity(0.2)
: Colors.transparent,
),
backgroundColor: secondaryContainer,
selectedColor: secondaryContainer,
color: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) => secondaryContainer.withAlpha(200)),
padding: const EdgeInsets.fromLTRB(7, 1, 7, 1),
label: Text(label, style: chipTextStyle),
onPressed: () => onTap(),
selected: selected,
showCheckmark: false,
visualDensity: visualDensity,
);
}
}
class SearchBar extends StatelessWidget {
const SearchBar({super.key});
@override
Widget build(BuildContext context) {
final SSearchController searchController = Get.put(SSearchController());
final ColorScheme colorScheme = Theme.of(context).colorScheme;
return Expanded(
child: Container(
width: 250,
height: 44,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25),
),
child: Material(
color: colorScheme.onSecondaryContainer.withOpacity(0.05),
child: InkWell(
splashColor: colorScheme.primaryContainer.withOpacity(0.3),
onTap: () => Get.toNamed('/search'),
child: Row(
children: [
const SizedBox(width: 14),
Icon(
Icons.search_outlined,
color: colorScheme.onSecondaryContainer,
),
const SizedBox(width: 10),
Expanded(
child: Obx(
() => Text(
searchController.defaultSearch.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: colorScheme.outline),
),
),
),
],
),
),
),
),
);
}
}

View File

@ -70,68 +70,66 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
body: RefreshIndicator(
onRefresh: () async {
return await _hotController.onRefresh();
},
child: CustomScrollView(
controller: _hotController.scrollController,
slivers: [
SliverPadding(
// 单列布局 EdgeInsets.zero
padding:
const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0),
sliver: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => SliverList(
delegate:
SliverChildBuilderDelegate((context, index) {
return VideoCardH(
videoItem: _hotController.videoList[index],
showPubdate: true,
longPress: () {
_hotController.popupDialog = _createPopupDialog(
_hotController.videoList[index]);
Overlay.of(context)
.insert(_hotController.popupDialog!);
},
longPressEnd: () {
_hotController.popupDialog?.remove();
},
);
}, childCount: _hotController.videoList.length),
),
);
} else {
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
);
}
return RefreshIndicator(
onRefresh: () async {
return await _hotController.onRefresh();
},
child: CustomScrollView(
controller: _hotController.scrollController,
slivers: [
SliverPadding(
// 单列布局 EdgeInsets.zero
padding:
const EdgeInsets.fromLTRB(0, StyleString.safeSpace - 5, 0, 0),
sliver: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => SliverList(
delegate:
SliverChildBuilderDelegate((context, index) {
return VideoCardH(
videoItem: _hotController.videoList[index],
showPubdate: true,
longPress: () {
_hotController.popupDialog = _createPopupDialog(
_hotController.videoList[index]);
Overlay.of(context)
.insert(_hotController.popupDialog!);
},
longPressEnd: () {
_hotController.popupDialog?.remove();
},
);
}, childCount: _hotController.videoList.length),
),
);
} else {
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton();
}, childCount: 10),
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
);
}
},
),
} else {
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
return const VideoCardHSkeleton();
}, childCount: 10),
);
}
},
),
SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).padding.bottom + 10,
),
)
],
),
),
SliverToBoxAdapter(
child: SizedBox(
height: MediaQuery.of(context).padding.bottom + 10,
),
)
],
),
);
}

View File

@ -10,8 +10,8 @@ import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/pages/video/detail/reply_new/index.dart';
import 'package:pilipala/pages/video/detail/reply_reply/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'controller.dart';

View File

@ -162,8 +162,9 @@ class _LivePageState extends State<LivePage>
crossAxisCount: crossAxisCount,
mainAxisExtent:
Get.size.width / crossAxisCount / StyleString.aspectRatio +
(crossAxisCount == 1 ? 48 : 68) *
MediaQuery.of(context).textScaleFactor,
MediaQuery.textScalerOf(context).scale(
(crossAxisCount == 1 ? 48 : 68),
),
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {

View File

@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/video/play/url.dart';
import 'package:pilipala/pages/liveRoom/index.dart';
import 'package:pilipala/pages/live_room/index.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/utils/storage.dart';

View File

@ -1,9 +1,11 @@
import 'dart:async';
import 'package:flutter/services.dart';
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 'package:pilipala/http/common.dart';
import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/view.dart';
import 'package:pilipala/pages/media/index.dart';
@ -19,14 +21,15 @@ class MainController extends GetxController {
RxList navigationBars = [
{
'icon': const Icon(
Icons.favorite_outline,
Icons.home_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.favorite,
Icons.home,
size: 21,
),
'label': "首页",
'count': 0,
},
{
'icon': const Icon(
@ -38,17 +41,19 @@ class MainController extends GetxController {
size: 21,
),
'label': "动态",
'count': 0,
},
{
'icon': const Icon(
Icons.folder_outlined,
Icons.video_collection_outlined,
size: 20,
),
'selectIcon': const Icon(
Icons.folder,
Icons.video_collection,
size: 21,
),
'label': "媒体库",
'count': 0,
}
].obs;
final StreamController<bool> bottomBarStream =
@ -56,6 +61,10 @@ class MainController extends GetxController {
Box setting = GStrorage.setting;
DateTime? _lastPressedAt;
late bool hideTabBar;
late PageController pageController;
int selectedIndex = 0;
Box userInfoCache = GStrorage.userInfo;
RxBool userLogin = false.obs;
@override
void onInit() {
@ -64,17 +73,47 @@ class MainController extends GetxController {
Utils.checkUpdata();
}
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
var userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null;
getUnreadDynamic();
}
Future<bool> onBackPressed(BuildContext context) {
void onBackPressed(BuildContext context) {
if (_lastPressedAt == null ||
DateTime.now().difference(_lastPressedAt!) >
const Duration(seconds: 2)) {
// 两次点击时间间隔超过2秒重新记录时间戳
_lastPressedAt = DateTime.now();
if (selectedIndex != 0) {
pageController.jumpTo(0);
}
SmartDialog.showToast("再按一次退出Pili");
return Future.value(false); // 不退出应用
return; // 不退出应用
}
return Future.value(true); // 退出应用
SystemNavigator.pop(); // 退出应用
}
void getUnreadDynamic() async {
if (!userLogin.value) {
return;
}
int dynamicItemIndex =
navigationBars.indexWhere((item) => item['label'] == "动态");
var res = await CommonHttp.unReadDynamic();
var data = res['data'];
if (dynamicItemIndex != -1) {
navigationBars[dynamicItemIndex]['count'] =
data == null ? 0 : data.length; // 修改 count 属性为新的值
}
navigationBars.refresh();
}
void clearUnread() async {
int dynamicItemIndex =
navigationBars.indexWhere((item) => item['label'] == "动态");
if (dynamicItemIndex != -1) {
navigationBars[dynamicItemIndex]['count'] = 0; // 修改 count 属性为新的值
}
navigationBars.refresh();
}
}

View File

@ -24,8 +24,6 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
final DynamicsController _dynamicController = Get.put(DynamicsController());
final MediaController _mediaController = Get.put(MediaController());
PageController? _pageController;
int selectedIndex = 0;
int? _lastSelectTime; //上次点击时间
Box setting = GStrorage.setting;
late bool enableMYBar;
@ -34,13 +32,14 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
void initState() {
super.initState();
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
_pageController = PageController(initialPage: selectedIndex);
_mainController.pageController =
PageController(initialPage: _mainController.selectedIndex);
enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
}
void setIndex(int value) async {
feedBack();
_pageController!.jumpToPage(value);
_mainController.pageController.jumpToPage(value);
var currentPage = _mainController.pages[value];
if (currentPage is HomePage) {
if (_homeController.flag) {
@ -68,6 +67,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
}
_dynamicController.flag = true;
_mainController.clearUnread();
} else {
_dynamicController.flag = false;
}
@ -88,20 +88,23 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
Widget build(BuildContext context) {
Box localCache = GStrorage.localCache;
double statusBarHeight = MediaQuery.of(context).padding.top;
double sheetHeight = MediaQuery.of(context).size.height -
double sheetHeight = MediaQuery.sizeOf(context).height -
MediaQuery.of(context).padding.top -
MediaQuery.of(context).size.width * 9 / 16;
MediaQuery.sizeOf(context).width * 9 / 16;
localCache.put('sheetHeight', sheetHeight);
localCache.put('statusBarHeight', statusBarHeight);
return PopScope(
onPopInvoked: (bool status) => _mainController.onBackPressed(context),
canPop: false,
onPopInvoked: (bool didPop) async {
_mainController.onBackPressed(context);
},
child: Scaffold(
extendBody: true,
body: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _pageController,
controller: _mainController.pageController,
onPageChanged: (index) {
selectedIndex = index;
_mainController.selectedIndex = index;
setState(() {});
},
children: _mainController.pages,
@ -116,36 +119,48 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
curve: Curves.easeInOutCubicEmphasized,
duration: const Duration(milliseconds: 500),
offset: Offset(0, snapshot.data ? 0 : 1),
child: enableMYBar
? NavigationBar(
onDestinationSelected: (value) => setIndex(value),
selectedIndex: selectedIndex,
destinations: <Widget>[
..._mainController.navigationBars.map((e) {
return NavigationDestination(
icon: e['icon'],
selectedIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
)
: BottomNavigationBar(
currentIndex: selectedIndex,
onTap: (value) => setIndex(value),
iconSize: 16,
selectedFontSize: 12,
unselectedFontSize: 12,
items: [
..._mainController.navigationBars.map((e) {
return BottomNavigationBarItem(
icon: e['icon'],
activeIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
),
child: Obx(
() => enableMYBar
? NavigationBar(
onDestinationSelected: (value) => setIndex(value),
selectedIndex: _mainController.selectedIndex,
destinations: <Widget>[
..._mainController.navigationBars.map((e) {
return NavigationDestination(
icon: Badge(
label: Text(e['count'].toString()),
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible: e['count'] > 0,
child: e['icon'],
),
selectedIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
)
: BottomNavigationBar(
currentIndex: _mainController.selectedIndex,
onTap: (value) => setIndex(value),
iconSize: 16,
selectedFontSize: 12,
unselectedFontSize: 12,
items: [
..._mainController.navigationBars.map((e) {
return BottomNavigationBarItem(
icon: Badge(
label: Text(e['count'].toString()),
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
isLabelVisible: e['count'] > 0,
child: e['icon'],
),
activeIcon: e['selectIcon'],
label: e['label'],
);
}).toList(),
],
),
),
);
},
),

View File

@ -163,7 +163,7 @@ class _MediaPageState extends State<MediaPage>
// const SizedBox(height: 10),
SizedBox(
width: double.infinity,
height: 200 * MediaQuery.of(context).textScaleFactor,
height: MediaQuery.textScalerOf(context).scale(200),
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {

View File

@ -41,7 +41,7 @@ class _MemberPageState extends State<MemberPage>
_memberCoinsFuture = _memberController.getRecentCoinVideo();
_extendNestCtr.addListener(
() {
double offset = _extendNestCtr.position.pixels;
final double offset = _extendNestCtr.position.pixels;
if (offset > 100) {
appbarStream.add(true);
} else {
@ -67,7 +67,7 @@ class _MemberPageState extends State<MemberPage>
title: StreamBuilder(
stream: appbarStream.stream,
initialData: false,
builder: (context, AsyncSnapshot snapshot) {
builder: (BuildContext context, AsyncSnapshot snapshot) {
return AnimatedOpacity(
opacity: snapshot.data ? 1 : 0,
curve: Curves.easeOut,

View File

@ -87,7 +87,7 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
slivers: [
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data != null) {
Map data = snapshot.data as Map;
@ -97,7 +97,7 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
() => list.isNotEmpty
? SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
(BuildContext context, index) {
return VideoCardH(
videoItem: list[index],
showOwner: false,

View File

@ -25,7 +25,7 @@ class MemberSeasonsItem extends StatelessWidget {
child: InkWell(
onTap: () async {
int cid =
await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid);
await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid);
Get.toNamed('/video?bvid=${seasonItem.bvid}&cid=$cid',
arguments: {'videoItem': seasonItem, 'heroTag': heroTag});
},
@ -46,12 +46,13 @@ class MemberSeasonsItem extends StatelessWidget {
height: maxHeight,
),
),
if (seasonItem.duration != null)
if (seasonItem.pubdate != null)
PBadge(
bottom: 6,
right: 6,
type: 'gray',
text: Utils.timeFormat(seasonItem.duration),
text: Utils.CustomStamp_str(
timestamp: seasonItem.pubdate, date: 'YY-MM-DD'),
)
],
);
@ -78,7 +79,7 @@ class MemberSeasonsItem extends StatelessWidget {
const Spacer(),
Text(
Utils.CustomStamp_str(
timestamp: seasonItem.pubdate, date: 'MM-DD'),
timestamp: seasonItem.pubdate, date: 'YY-MM-DD'),
style: TextStyle(
fontSize: 11,
color: Theme.of(context).colorScheme.outline,

View File

@ -20,7 +20,7 @@ class SSearchController extends GetxController {
final _debouncer =
Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间
String hintText = '搜索';
RxString defaultSearch = '输入关键词搜索'.obs;
RxString defaultSearch = ''.obs;
Box setting = GStrorage.setting;
bool enableHotKey = true;

View File

@ -1,6 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:animations/animations.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'controller.dart';
@ -42,120 +41,62 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
@override
Widget build(BuildContext context) {
return OpenContainer(
closedElevation: 0,
openElevation: 0,
onClosed: (_) => _searchController.onClear(),
openColor: Theme.of(context).colorScheme.background,
middleColor: Theme.of(context).colorScheme.background,
closedColor: Theme.of(context).colorScheme.background,
closedShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30.0))),
openShape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(30.0))),
closedBuilder: (BuildContext context, VoidCallback openContainer) {
return Container(
width: 250,
height: 44,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(25)),
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
shape: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.08),
width: 1,
),
child: Material(
color:
Theme.of(context).colorScheme.secondaryContainer.withAlpha(115),
child: InkWell(
splashColor: Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.3),
onTap: openContainer,
child: Row(
children: [
const SizedBox(width: 14),
Icon(
Icons.search_outlined,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
const SizedBox(width: 10),
Expanded(
child: Obx(
() => Text(
_searchController.defaultSearch.value,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
),
],
),
),
),
titleSpacing: 0,
actions: [
IconButton(
onPressed: () => _searchController.submit(),
icon: const Icon(CupertinoIcons.search, size: 22),
),
);
},
openBuilder: (BuildContext context, VoidCallback _) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
shape: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.08),
width: 1,
),
),
titleSpacing: 0,
actions: [
Hero(
tag: 'searchTag',
child: IconButton(
onPressed: () => _searchController.submit(),
icon: const Icon(CupertinoIcons.search, size: 22)),
),
const SizedBox(width: 10)
],
title: Obx(
() => TextField(
autofocus: true,
focusNode: _searchController.searchFocusNode,
controller: _searchController.controller.value,
textInputAction: TextInputAction.search,
onChanged: (value) => _searchController.onChange(value),
decoration: InputDecoration(
hintText: _searchController.hintText,
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(
Icons.clear,
size: 22,
color: Theme.of(context).colorScheme.outline,
),
onPressed: () => _searchController.onClear(),
),
const SizedBox(width: 10)
],
title: Obx(
() => TextField(
autofocus: true,
focusNode: _searchController.searchFocusNode,
controller: _searchController.controller.value,
textInputAction: TextInputAction.search,
onChanged: (value) => _searchController.onChange(value),
decoration: InputDecoration(
hintText: _searchController.hintText,
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(
Icons.clear,
size: 22,
color: Theme.of(context).colorScheme.outline,
),
onSubmitted: (String value) => _searchController.submit(),
onPressed: () => _searchController.onClear(),
),
),
onSubmitted: (String value) => _searchController.submit(),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 12),
// 搜索建议
_searchSuggest(),
// 热搜
Visibility(
visible: _searchController.enableHotKey,
child: hotSearch(_searchController)),
// 搜索历史
_history()
],
),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 12),
// 搜索建议
_searchSuggest(),
// 热搜
Visibility(
visible: _searchController.enableHotKey,
child: hotSearch(_searchController),
),
),
);
},
// 搜索历史
_history()
],
),
),
);
}
@ -299,25 +240,24 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
],
),
),
// if (_searchController.historyList.isNotEmpty)
Obx(() => Wrap(
spacing: 8,
runSpacing: 8,
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: [
for (int i = 0;
i < _searchController.historyList.length;
i++)
SearchText(
searchText: _searchController.historyList[i],
searchTextIdx: i,
onSelect: (value) => _searchController.onSelect(value),
onLongSelect: (value) =>
_searchController.onLongSelect(value),
)
],
)),
Obx(
() => Wrap(
spacing: 8,
runSpacing: 8,
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: [
for (int i = 0; i < _searchController.historyList.length; i++)
SearchText(
searchText: _searchController.historyList[i],
searchTextIdx: i,
onSelect: (value) => _searchController.onSelect(value),
onLongSelect: (value) =>
_searchController.onLongSelect(value),
)
],
),
),
],
),
),

View File

@ -43,7 +43,7 @@ class _SearchPanelState extends State<SearchPanel>
keyword: widget.keyword,
searchType: widget.searchType,
),
tag: widget.searchType!.type,
tag: widget.searchType!.type + widget.keyword!,
);
scrollController = _searchPanelController.scrollController;
scrollController.addListener(() async {
@ -74,36 +74,48 @@ class _SearchPanelState extends State<SearchPanel>
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data;
var ctr = _searchPanelController;
RxList list = ctr.resultList;
if (data['status']) {
return Obx(() {
switch (widget.searchType) {
case SearchType.video:
return SearchVideoPanel(
ctr: _searchPanelController,
// ignore: invalid_use_of_protected_member
list: list.value,
);
case SearchType.media_bangumi:
return searchMbangumiPanel(context, ctr, list);
case SearchType.bili_user:
return searchUserPanel(context, ctr, list);
case SearchType.live_room:
return searchLivePanel(context, ctr, list);
case SearchType.article:
return searchArticlePanel(context, ctr, list);
default:
return const SizedBox();
}
});
if (snapshot.data != null) {
Map data = snapshot.data;
var ctr = _searchPanelController;
RxList list = ctr.resultList;
if (data['status']) {
return Obx(() {
switch (widget.searchType) {
case SearchType.video:
return SearchVideoPanel(
ctr: _searchPanelController,
// ignore: invalid_use_of_protected_member
list: list.value,
);
case SearchType.media_bangumi:
return searchMbangumiPanel(context, ctr, list);
case SearchType.bili_user:
return searchUserPanel(context, ctr, list);
case SearchType.live_room:
return searchLivePanel(context, ctr, list);
case SearchType.article:
return searchArticlePanel(context, ctr, list);
default:
return const SizedBox();
}
});
} else {
return CustomScrollView(
physics: const NeverScrollableScrollPhysics(),
slivers: [
HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
),
],
);
}
} else {
return CustomScrollView(
physics: const NeverScrollableScrollPhysics(),
slivers: [
HttpError(
errMsg: data['msg'],
errMsg: '没有相关数据',
fn: () => setState(() {}),
),
],

View File

@ -26,10 +26,9 @@ Widget searchArticlePanel(BuildContext context, ctr, list) {
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(builder: (context, boxConstraints) {
double width = (boxConstraints.maxWidth -
StyleString.cardSpace *
6 /
MediaQuery.of(context).textScaleFactor) /
2;
StyleString.cardSpace *
6 /
MediaQuery.textScalerOf(context).scale(2.0));
return Container(
constraints: const BoxConstraints(minHeight: 88),
height: width / StyleString.aspectRatio,

View File

@ -16,8 +16,8 @@ Widget searchLivePanel(BuildContext context, ctr, list) {
crossAxisSpacing: StyleString.cardSpace + 2,
mainAxisSpacing: StyleString.cardSpace + 3,
mainAxisExtent:
MediaQuery.of(context).size.width / 2 / StyleString.aspectRatio +
66 * MediaQuery.of(context).textScaleFactor),
MediaQuery.sizeOf(context).width / 2 / StyleString.aspectRatio +
MediaQuery.textScalerOf(context).scale(66.0)),
itemCount: list.length,
itemBuilder: (context, index) {
return LiveItem(liveItem: list![index]);

View File

@ -67,11 +67,11 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
TextSpan(
text: i['text'],
style: TextStyle(
fontSize: Theme.of(context)
fontSize: MediaQuery.textScalerOf(context)
.scale(Theme.of(context)
.textTheme
.titleSmall!
.fontSize! *
MediaQuery.of(context).textScaleFactor,
.fontSize!),
fontWeight: FontWeight.bold,
color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary

View File

@ -3,7 +3,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/pages/searchPanel/index.dart';
import 'package:pilipala/pages/search_panel/index.dart';
class SearchVideoPanel extends StatelessWidget {
SearchVideoPanel({

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/pages/searchPanel/index.dart';
import 'package:pilipala/pages/search_panel/index.dart';
import 'controller.dart';
class SearchResultPage extends StatefulWidget {

View File

@ -0,0 +1,90 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/common/tab_type.dart';
import 'package:pilipala/utils/storage.dart';
class TabbarSetPage extends StatefulWidget {
const TabbarSetPage({super.key});
@override
State<TabbarSetPage> createState() => _TabbarSetPageState();
}
class _TabbarSetPageState extends State<TabbarSetPage> {
Box settingStorage = GStrorage.setting;
late List defaultTabs;
late List<String> tabbarSort;
@override
void initState() {
super.initState();
defaultTabs = tabsConfig;
tabbarSort = settingStorage.get(SettingBoxKey.tabbarSort,
defaultValue: ['live', 'rcmd', 'hot', 'bangumi']);
}
void saveEdit() {
List<String> sortedTabbar = defaultTabs
.where((i) => tabbarSort.contains((i['type'] as TabType).id))
.map<String>((i) => (i['type'] as TabType).id)
.toList();
if (sortedTabbar.isEmpty) {
SmartDialog.showToast('请至少设置一项!');
return;
}
settingStorage.put(SettingBoxKey.tabbarSort, sortedTabbar);
SmartDialog.showToast('保存成功,下次启动时生效');
}
void onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final tabsItem = defaultTabs.removeAt(oldIndex);
defaultTabs.insert(newIndex, tabsItem);
});
}
@override
Widget build(BuildContext context) {
final listTiles = [
for (int i = 0; i < defaultTabs.length; i++) ...[
CheckboxListTile(
key: Key(defaultTabs[i]['label']),
value: tabbarSort.contains((defaultTabs[i]['type'] as TabType).id),
onChanged: (bool? newValue) {
String tabTypeId = (defaultTabs[i]['type'] as TabType).id;
if (!newValue!) {
tabbarSort.remove(tabTypeId);
} else {
tabbarSort.add(tabTypeId);
}
setState(() {});
},
title: Text(defaultTabs[i]['label']),
secondary: const Icon(Icons.drag_indicator_rounded),
)
]
];
return Scaffold(
appBar: AppBar(
title: const Text('Tabbar编辑'),
actions: [
TextButton(onPressed: () => saveEdit(), child: const Text('保存')),
const SizedBox(width: 12)
],
),
body: ReorderableListView(
onReorder: onReorder,
physics: const NeverScrollableScrollPhysics(),
footer: SizedBox(
height: MediaQuery.of(context).padding.bottom + 30,
),
children: listTiles,
),
);
}
}

View File

@ -254,6 +254,11 @@ class _StyleSettingState extends State<StyleSetting> {
onTap: () => Get.toNamed('/fontSizeSetting'),
title: Text('字体大小', style: titleStyle),
),
ListTile(
dense: false,
onTap: () => Get.toNamed('/tabbarSetting'),
title: Text('首页tabbar', style: titleStyle),
),
if (Platform.isAndroid)
ListTile(
dense: false,

View File

@ -12,7 +12,7 @@ import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/models/video/play/quality.dart';
import 'package:pilipala/models/video/play/url.dart';
import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/pages/video/detail/reply_reply/index.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
@ -92,7 +92,7 @@ class VideoDetailController extends GetxController
@override
void onInit() {
super.onInit();
Map argMap = Get.arguments;
final Map argMap = Get.arguments;
userInfo = userInfoCache.get('userInfoCache');
var keys = argMap.keys.toList();
if (keys.isNotEmpty) {
@ -188,8 +188,8 @@ class VideoDetailController extends GetxController
/// 根据currentAudioQa 重新设置audioUrl
if (currentAudioQa != null) {
AudioItem firstAudio = data.dash!.audio!.firstWhere(
(i) => i.id == currentAudioQa!.code,
final AudioItem firstAudio = data.dash!.audio!.firstWhere(
(AudioItem i) => i.id == currentAudioQa!.code,
orElse: () => data.dash!.audio!.first,
);
audioUrl = firstAudio.baseUrl ?? '';
@ -246,7 +246,7 @@ class VideoDetailController extends GetxController
var result = await VideoHttp.videoUrl(cid: cid.value, bvid: bvid);
if (result['status']) {
data = result['data'];
List<VideoItem> allVideosList = data.dash!.video!;
final List<VideoItem> allVideosList = data.dash!.video!;
try {
// 当前可播放的最高质量视频
int currentHighVideoQa = allVideosList.first.quality!.code;
@ -255,7 +255,7 @@ class VideoDetailController extends GetxController
int resVideoQa = currentHighVideoQa;
if (cacheVideoQa! <= currentHighVideoQa) {
// 如果预设的画质低于当前最高
List<int> numbers = data.acceptQuality!
final List<int> numbers = data.acceptQuality!
.where((e) => e <= currentHighVideoQa)
.toList();
resVideoQa = Utils.findClosestNumber(cacheVideoQa!, numbers);
@ -263,13 +263,13 @@ class VideoDetailController extends GetxController
currentVideoQa = VideoQualityCode.fromCode(resVideoQa)!;
/// 取出符合当前画质的videoList
List<VideoItem> videosList =
final List<VideoItem> videosList =
allVideosList.where((e) => e.quality!.code == resVideoQa).toList();
/// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式
List<FormatItem> supportFormats = data.supportFormats!;
final List<FormatItem> supportFormats = data.supportFormats!;
// 根据画质选编码格式
List supportDecodeFormats =
final List supportDecodeFormats =
supportFormats.firstWhere((e) => e.quality == resVideoQa).codecs!;
// 默认从设置中取AVC
currentDecodeFormats = VideoDecodeFormatsCode.fromString(cacheDecode)!;
@ -304,7 +304,7 @@ class VideoDetailController extends GetxController
/// 优先顺序 设置中指定质量 -> 当前可选的最高质量
late AudioItem? firstAudio;
List<AudioItem> audiosList = data.dash!.audio!;
final List<AudioItem> audiosList = data.dash!.audio!;
try {
if (data.dash!.dolby?.audio?.isNotEmpty == true) {
@ -318,7 +318,7 @@ class VideoDetailController extends GetxController
}
if (audiosList.isNotEmpty) {
List<int> numbers = audiosList.map((map) => map.id!).toList();
final List<int> numbers = audiosList.map((map) => map.id!).toList();
int closestNumber = Utils.findClosestNumber(cacheAudioQa, numbers);
if (!numbers.contains(cacheAudioQa) &&
numbers.any((e) => e > cacheAudioQa)) {

View File

@ -389,7 +389,7 @@ class VideoIntroController extends GetxController {
SmartDialog.showToast('账号未登录');
return;
}
int currentStatus = followStatus['attribute'];
final int currentStatus = followStatus['attribute'];
int actionStatus = 0;
switch (currentStatus) {
case 0:
@ -411,8 +411,12 @@ class VideoIntroController extends GetxController {
content: Text(currentStatus == 0 ? '关注UP主?' : '取消关注UP主?'),
actions: [
TextButton(
onPressed: () => SmartDialog.dismiss(),
child: const Text('点错了')),
onPressed: () => SmartDialog.dismiss(),
child: Text(
'点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
var result = await VideoHttp.relationMod(
@ -463,7 +467,7 @@ class VideoIntroController extends GetxController {
// 修改分P或番剧分集
Future changeSeasonOrbangu(bvid, cid, aid) async {
// 重新获取视频资源
VideoDetailController videoDetailCtr =
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
videoDetailCtr.bvid = bvid;
videoDetailCtr.cid.value = cid;
@ -472,7 +476,7 @@ class VideoIntroController extends GetxController {
// 重新请求评论
try {
/// 未渲染回复组件时可能异常
VideoReplyController videoReplyCtr =
final VideoReplyController videoReplyCtr =
Get.find<VideoReplyController>(tag: heroTag);
videoReplyCtr.aid = aid;
videoReplyCtr.queryReplyList(type: 'init');
@ -513,29 +517,27 @@ class VideoIntroController extends GetxController {
/// 列表循环或者顺序播放时,自动播放下一个
void nextPlay() {
late List episodes;
final List episodes = [];
bool isPages = false;
if (videoDetail.value.ugcSeason != null) {
UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
List<SectionItem> sections = ugcSeason.sections!;
episodes = [];
final UgcSeason ugcSeason = videoDetail.value.ugcSeason!;
final List<SectionItem> sections = ugcSeason.sections!;
for (int i = 0; i < sections.length; i++) {
List<EpisodeItem> episodesList = sections[i].episodes!;
final List<EpisodeItem> episodesList = sections[i].episodes!;
episodes.addAll(episodesList);
}
} else if (videoDetail.value.pages != null) {
isPages = true;
List<Part> pages = videoDetail.value.pages!;
episodes = [];
final List<Part> pages = videoDetail.value.pages!;
episodes.addAll(pages);
}
int currentIndex = episodes.indexWhere((e) => e.cid == lastPlayCid.value);
final int currentIndex =
episodes.indexWhere((e) => e.cid == lastPlayCid.value);
int nextIndex = currentIndex + 1;
VideoDetailController videoDetailCtr =
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
final PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat;
// 列表循环
if (nextIndex >= episodes.length) {
@ -546,9 +548,9 @@ class VideoIntroController extends GetxController {
return;
}
}
int cid = episodes[nextIndex].cid!;
String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
final int cid = episodes[nextIndex].cid!;
final String rBvid = isPages ? bvid : episodes[nextIndex].bvid;
final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!;
changeSeasonOrbangu(rBvid, cid, rAid);
}
@ -563,7 +565,7 @@ class VideoIntroController extends GetxController {
// ai总结
Future aiConclusion() async {
SmartDialog.showLoading(msg: '正在生产ai总结');
var res = await VideoHttp.aiConclusion(
final res = await VideoHttp.aiConclusion(
bvid: bvid,
cid: lastPlayCid.value,
upMid: videoDetail.value.owner!.mid!,

View File

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
@ -26,7 +24,7 @@ import 'widgets/page.dart';
import 'widgets/season.dart';
class VideoIntroPanel extends StatefulWidget {
const VideoIntroPanel({Key? key}) : super(key: key);
const VideoIntroPanel({super.key});
@override
State<VideoIntroPanel> createState() => _VideoIntroPanelState();
@ -124,8 +122,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
late final VideoDetailController videoDetailCtr;
late final Map<dynamic, dynamic> videoItem;
Box localCache = GStrorage.localCache;
Box setting = GStrorage.setting;
final Box<dynamic> localCache = GStrorage.localCache;
final Box<dynamic> setting = GStrorage.setting;
late double sheetHeight;
late final bool loadingStatus; // 加载状态
@ -138,12 +136,15 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
late bool enableAi;
bool isProcessing = false;
void Function()? handleState(Future Function() action) {
return isProcessing ? null : () async {
setState(() => isProcessing = true);
await action();
setState(() => isProcessing = false);
};
return isProcessing
? null
: () async {
setState(() => isProcessing = true);
await action();
setState(() => isProcessing = false);
};
}
@override
void initState() {
super.initState();
@ -168,7 +169,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
SmartDialog.showToast('账号未登录');
return;
}
bool enableDragQuickFav =
final bool enableDragQuickFav =
setting.get(SettingBoxKey.enableQuickFav, defaultValue: false);
// 快速收藏 &
// 点按 收藏至默认文件夹
@ -182,7 +183,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
builder: (BuildContext context) {
return FavPanel(ctr: videoIntroController);
},
);
@ -192,7 +193,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
builder: (BuildContext context) {
return FavPanel(ctr: videoIntroController);
},
);
@ -202,7 +203,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
builder: (BuildContext context) {
return FavPanel(ctr: videoIntroController);
},
);
@ -251,8 +252,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
ThemeData t = Theme.of(context);
Color outline = t.colorScheme.outline;
final ThemeData t = Theme.of(context);
final Color outline = t.colorScheme.outline;
return SliverPadding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10),
@ -333,7 +334,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
top: 6,
child: GestureDetector(
onTap: () async {
var res =
final res =
await videoIntroController.aiConclusion();
if (res['status']) {
showAiBottomSheet();
@ -413,14 +414,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
),
),
const Spacer(),
AnimatedOpacity(
opacity: loadingStatus ? 0 : 1,
duration: const Duration(milliseconds: 150),
child: SizedBox(
height: 32,
child: Obx(
() =>
videoIntroController.followStatus.isNotEmpty
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,
@ -452,9 +457,9 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
.actionRelationMod,
child: const Text('关注'),
),
),
),
),
),
),
)),
],
),
),
@ -472,13 +477,14 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
}
Widget actionGrid(BuildContext context, videoIntroController) {
return LayoutBuilder(builder: (context, constraints) {
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Container(
margin: const EdgeInsets.only(top: 6, bottom: 4),
height: constraints.maxWidth / 5 * 0.8,
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(0),
padding: EdgeInsets.zero,
crossAxisCount: 5,
childAspectRatio: 1.25,
children: <Widget>[
@ -543,7 +549,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
}
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
return Row(children: [
return Row(children: <Widget>[
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),

View File

@ -6,15 +6,15 @@ import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
class FavPanel extends StatefulWidget {
final dynamic ctr;
const FavPanel({super.key, this.ctr});
final dynamic ctr;
@override
State<FavPanel> createState() => _FavPanelState();
}
class _FavPanelState extends State<FavPanel> {
Box localCache = GStrorage.localCache;
final Box<dynamic> localCache = GStrorage.localCache;
late double sheetHeight;
late Future _futureBuilderFuture;
@ -45,7 +45,7 @@ class _FavPanelState extends State<FavPanel> {
child: Material(
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
@ -109,7 +109,7 @@ class _FavPanelState extends State<FavPanel> {
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
children: <Widget>[
TextButton(
onPressed: () => Get.back(),
style: TextButton.styleFrom(

View File

@ -17,7 +17,7 @@ class GroupPanel extends StatefulWidget {
}
class _GroupPanelState extends State<GroupPanel> {
Box localCache = GStrorage.localCache;
final Box<dynamic> localCache = GStrorage.localCache;
late double sheetHeight;
late Future _futureBuilderFuture;
late List<MemberTagItemModel> tagsList;
@ -33,17 +33,20 @@ class _GroupPanelState extends State<GroupPanel> {
void onSave() async {
feedBack();
// 是否有选中的 有选中的带id没选使用默认0
bool anyHasChecked = tagsList.any((e) => e.checked == true);
final bool anyHasChecked =
tagsList.any((MemberTagItemModel e) => e.checked == true);
late String tagids;
if (anyHasChecked) {
List checkedList = tagsList.where((e) => e.checked == true).toList();
List<int> tagidList = checkedList.map<int>((e) => e.tagid).toList();
final List<MemberTagItemModel> checkedList =
tagsList.where((MemberTagItemModel e) => e.checked == true).toList();
final List<int> tagidList =
checkedList.map<int>((e) => e.tagid!).toList();
tagids = tagidList.join(',');
} else {
tagids = '0';
}
// 保存
var res = await MemberHttp.addUsers(widget.mid, tagids);
final res = await MemberHttp.addUsers(widget.mid, tagids);
SmartDialog.showToast(res['msg']);
if (res['status']) {
Get.back();
@ -56,7 +59,7 @@ class _GroupPanelState extends State<GroupPanel> {
height: sheetHeight,
color: Theme.of(context).colorScheme.background,
child: Column(
children: [
children: <Widget>[
AppBar(
centerTitle: false,
elevation: 0,
@ -70,7 +73,7 @@ class _GroupPanelState extends State<GroupPanel> {
child: Material(
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {

View File

@ -12,12 +12,11 @@ Box localCache = GStrorage.localCache;
late double sheetHeight;
class IntroDetail extends StatelessWidget {
final dynamic videoDetail;
const IntroDetail({
Key? key,
super.key,
this.videoDetail,
}) : super(key: key);
});
final dynamic videoDetail;
@override
Widget build(BuildContext context) {
@ -86,13 +85,11 @@ class IntroDetail extends StatelessWidget {
SizedBox(
width: double.infinity,
child: SelectableRegion(
magnifierConfiguration:
const TextMagnifierConfiguration(),
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
children: <Widget>[
Text(
videoDetail!.bvid!,
style: const TextStyle(fontSize: 13),
@ -122,20 +119,21 @@ class IntroDetail extends StatelessWidget {
}
InlineSpan buildContent(BuildContext context, content) {
List descV2 = content.descV2;
final List descV2 = content.descV2;
// type
// 1 普通文本
// 2 @用户
List<TextSpan> spanChilds = List.generate(descV2.length, (index) {
final List<TextSpan> spanChilds = List.generate(descV2.length, (index) {
final currentDesc = descV2[index];
switch (currentDesc.type) {
case 1:
List<InlineSpan> spanChildren = [];
RegExp urlRegExp = RegExp(r'https?://\S+\b');
Iterable<Match> matches = urlRegExp.allMatches(currentDesc.rawText);
final List<InlineSpan> spanChildren = <InlineSpan>[];
final RegExp urlRegExp = RegExp(r'https?://\S+\b');
final Iterable<Match> matches =
urlRegExp.allMatches(currentDesc.rawText);
int previousEndIndex = 0;
for (Match match in matches) {
for (final Match match in matches) {
if (match.start > previousEndIndex) {
spanChildren.add(TextSpan(
text: currentDesc.rawText
@ -172,11 +170,12 @@ class IntroDetail extends StatelessWidget {
text: currentDesc.rawText.substring(previousEndIndex)));
}
TextSpan result = TextSpan(children: spanChildren);
final TextSpan result = TextSpan(children: spanChildren);
return result;
case 2:
final colorSchemePrimary = Theme.of(context).colorScheme.primary;
final heroTag = Utils.makeHeroTag(currentDesc.bizId);
final Color colorSchemePrimary =
Theme.of(context).colorScheme.primary;
final String heroTag = Utils.makeHeroTag(currentDesc.bizId);
return TextSpan(
text: '@${currentDesc.rawText}',
style: TextStyle(color: colorSchemePrimary),

View File

@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:pilipala/utils/feed_back.dart';
class MenuRow extends StatelessWidget {
final bool? loadingStatus;
const MenuRow({
Key? key,
super.key,
this.loadingStatus,
}) : super(key: key);
});
final bool? loadingStatus;
@override
Widget build(BuildContext context) {
@ -50,7 +50,7 @@ class MenuRow extends StatelessWidget {
}
Widget actionRowLineItem(
context, Function? onTap, bool? loadingStatus, String? text,
BuildContext context, Function? onTap, bool? loadingStatus, String? text,
{bool selectStatus = false}) {
return Material(
color: selectStatus
@ -97,18 +97,18 @@ class MenuRow extends StatelessWidget {
}
class ActionRowLineItem extends StatelessWidget {
const ActionRowLineItem({
super.key,
this.selectStatus,
this.onTap,
this.text,
this.loadingStatus = false,
});
final bool? selectStatus;
final Function? onTap;
final bool? loadingStatus;
final String? text;
const ActionRowLineItem(
{super.key,
this.selectStatus,
this.onTap,
this.text,
this.loadingStatus = false});
@override
Widget build(BuildContext context) {
return Material(

View File

@ -4,11 +4,6 @@ import 'package:pilipala/models/video_detail_res.dart';
import 'package:pilipala/pages/video/detail/index.dart';
class PagesPanel extends StatefulWidget {
final List<Part> pages;
final int? cid;
final double? sheetHeight;
final Function? changeFuc;
const PagesPanel({
super.key,
required this.pages,
@ -16,6 +11,10 @@ class PagesPanel extends StatefulWidget {
this.sheetHeight,
this.changeFuc,
});
final List<Part> pages;
final int? cid;
final double? sheetHeight;
final Function? changeFuc;
@override
State<PagesPanel> createState() => _PagesPanelState();
@ -25,7 +24,7 @@ class _PagesPanelState extends State<PagesPanel> {
late List<Part> episodes;
late int cid;
late int currentIndex;
String heroTag = Get.arguments['heroTag'];
final String heroTag = Get.arguments['heroTag'];
late VideoDetailController _videoDetailController;
final ScrollController _scrollController = ScrollController();
@ -35,11 +34,11 @@ class _PagesPanelState extends State<PagesPanel> {
cid = widget.cid!;
episodes = widget.pages;
_videoDetailController = Get.find<VideoDetailController>(tag: heroTag);
currentIndex = episodes.indexWhere((e) => e.cid == cid);
_videoDetailController.cid.listen((p0) {
currentIndex = episodes.indexWhere((Part e) => e.cid == cid);
_videoDetailController.cid.listen((int p0) {
cid = p0;
setState(() {});
currentIndex = episodes.indexWhere((e) => e.cid == cid);
currentIndex = episodes.indexWhere((Part e) => e.cid == cid);
});
}
@ -60,7 +59,7 @@ class _PagesPanelState extends State<PagesPanel> {
@override
Widget build(BuildContext context) {
return Column(
children: [
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 10, bottom: 2),
child: Row(
@ -133,7 +132,8 @@ class _PagesPanelState extends State<PagesPanel> {
child: ListView.builder(
controller: _scrollController,
itemCount: episodes.length,
itemBuilder: (context, index) {
itemBuilder:
(BuildContext context, int index) {
return ListTile(
onTap: () {
changeFucCall(
@ -191,7 +191,7 @@ class _PagesPanelState extends State<PagesPanel> {
scrollDirection: Axis.horizontal,
itemCount: widget.pages.length,
itemExtent: 150,
itemBuilder: ((context, i) {
itemBuilder: (BuildContext context, int i) {
return Container(
width: 150,
margin: const EdgeInsets.only(right: 10),
@ -205,8 +205,8 @@ class _PagesPanelState extends State<PagesPanel> {
padding: const EdgeInsets.symmetric(
vertical: 8, horizontal: 8),
child: Row(
children: [
if (i == currentIndex) ...[
children: <Widget>[
if (i == currentIndex) ...<Widget>[
Image.asset(
'assets/images/live.gif',
color: Theme.of(context).colorScheme.primary,
@ -231,7 +231,7 @@ class _PagesPanelState extends State<PagesPanel> {
),
),
);
}),
},
),
)
],

View File

@ -6,11 +6,6 @@ import 'package:pilipala/utils/id_utils.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class SeasonPanel extends StatefulWidget {
final UgcSeason ugcSeason;
final int? cid;
final double? sheetHeight;
final Function? changeFuc;
const SeasonPanel({
super.key,
required this.ugcSeason,
@ -18,6 +13,10 @@ class SeasonPanel extends StatefulWidget {
this.sheetHeight,
this.changeFuc,
});
final UgcSeason ugcSeason;
final int? cid;
final double? sheetHeight;
final Function? changeFuc;
@override
State<SeasonPanel> createState() => _SeasonPanelState();
@ -27,7 +26,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
late List<EpisodeItem> episodes;
late int cid;
late int currentIndex;
String heroTag = Get.arguments['heroTag'];
final String heroTag = Get.arguments['heroTag'];
late VideoDetailController _videoDetailController;
final ScrollController _scrollController = ScrollController();
final ItemScrollController itemScrollController = ItemScrollController();
@ -41,9 +40,9 @@ class _SeasonPanelState extends State<SeasonPanel> {
/// 根据 cid 找到对应集,找到对应 episodes
/// 有多个episodes时只显示其中一个
/// TODO 同时显示多个合集
List<SectionItem> sections = widget.ugcSeason.sections!;
final List<SectionItem> sections = widget.ugcSeason.sections!;
for (int i = 0; i < sections.length; i++) {
List<EpisodeItem> episodesList = sections[i].episodes!;
final List<EpisodeItem> episodesList = sections[i].episodes!;
for (int j = 0; j < episodesList.length; j++) {
if (episodesList[j].cid == cid) {
episodes = episodesList;
@ -56,22 +55,21 @@ class _SeasonPanelState extends State<SeasonPanel> {
// episodes = widget.ugcSeason.sections!
// .firstWhere((e) => e.seasonId == widget.ugcSeason.id)
// .episodes!;
currentIndex = episodes.indexWhere((e) => e.cid == cid);
_videoDetailController.cid.listen((p0) {
currentIndex = episodes.indexWhere((EpisodeItem e) => e.cid == cid);
_videoDetailController.cid.listen((int p0) {
cid = p0;
setState(() {});
currentIndex = episodes.indexWhere((e) => e.cid == cid);
currentIndex = episodes.indexWhere((EpisodeItem e) => e.cid == cid);
});
}
void changeFucCall(item, i) async {
void changeFucCall(item, int i) async {
await widget.changeFuc!(
IdUtils.av2bv(item.aid),
item.cid,
item.aid,
);
currentIndex = i;
setState(() {});
Get.back();
setState(() {});
}
@ -84,7 +82,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
@override
Widget build(BuildContext context) {
return Builder(builder: (context) {
return Builder(builder: (BuildContext context) {
return Container(
margin: const EdgeInsets.only(
top: 8,
@ -136,7 +134,8 @@ class _SeasonPanelState extends State<SeasonPanel> {
child: Material(
child: ScrollablePositionedList.builder(
itemCount: episodes.length,
itemBuilder: (context, index) => ListTile(
itemBuilder: (BuildContext context, int index) =>
ListTile(
onTap: () =>
changeFucCall(episodes[index], index),
dense: false,
@ -174,7 +173,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
child: Row(
children: [
children: <Widget>[
Expanded(
child: Text(
'合集:${widget.ugcSeason.title!}',

View File

@ -7,21 +7,16 @@ import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
import './controller.dart';
class RelatedVideoPanel extends StatefulWidget {
const RelatedVideoPanel({super.key});
@override
State<RelatedVideoPanel> createState() => _RelatedVideoPanelState();
}
class _RelatedVideoPanelState extends State<RelatedVideoPanel> {
class RelatedVideoPanel extends StatelessWidget {
final ReleatedController _releatedController =
Get.put(ReleatedController(), tag: Get.arguments['heroTag']);
Get.put(ReleatedController(), tag: Get.arguments?['heroTag']);
RelatedVideoPanel({super.key});
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _releatedController.queryRelatedVideo(),
builder: (context, snapshot) {
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SliverToBoxAdapter(child: SizedBox());
@ -72,7 +67,7 @@ class _RelatedVideoPanelState extends State<RelatedVideoPanel> {
OverlayEntry _createPopupDialog(videoItem) {
return OverlayEntry(
builder: (context) => AnimatedDialog(
builder: (BuildContext context) => AnimatedDialog(
closeFn: _releatedController.popupDialog?.remove,
child: OverlayPop(
videoItem: videoItem,

View File

@ -41,8 +41,8 @@ class VideoReplyController extends GetxController {
@override
void onInit() {
super.onInit();
int deaultReplySortIndex =
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
final int deaultReplySortIndex =
setting.get(SettingBoxKey.replySortType, defaultValue: 0) as int;
_sortType = ReplySortType.values[deaultReplySortIndex];
sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels;
@ -56,7 +56,7 @@ class VideoReplyController extends GetxController {
if (noMore.value == '没有更多了') {
return;
}
var res = await ReplyHttp.replyList(
final res = await ReplyHttp.replyList(
oid: aid!,
pageNum: currentPage + 1,
ps: ps,
@ -64,7 +64,7 @@ class VideoReplyController extends GetxController {
sort: _sortType.index,
);
if (res['status']) {
List<ReplyItemModel> replies = res['data'].replies;
final List<ReplyItemModel> replies = res['data'].replies;
if (replies.isNotEmpty) {
noMore.value = '加载中...';
@ -84,9 +84,8 @@ class VideoReplyController extends GetxController {
if (type == 'init') {
// 添加置顶回复
if (res['data'].upper.top != null) {
bool flag = res['data']
.topReplies
.any((reply) => reply.rpid == res['data'].upper.top.rpid);
final bool flag = res['data'].topReplies.any((ReplyItemModel reply) =>
reply.rpid == res['data'].upper.top.rpid) as bool;
if (!flag) {
replies.insert(0, res['data'].upper.top);
}

View File

@ -8,7 +8,7 @@ import 'package:pilipala/common/skeleton/video_reply.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/video/detail/replyNew/index.dart';
import 'package:pilipala/pages/video/detail/reply_new/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/id_utils.dart';
import 'controller.dart';
@ -107,7 +107,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
// 展示二级回复
void replyReply(replyItem) {
VideoDetailController videoDetailCtr =
final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag);
if (replyItem != null) {
videoDetailCtr.oid = replyItem.oid;
@ -193,7 +193,7 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
if (data['status']) {
@ -203,13 +203,13 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
_videoReplyController.replyList.isEmpty
? SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
(BuildContext context, index) {
return const VideoReplySkeleton();
}, childCount: 5),
)
: SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
(BuildContext context, index) {
double bottom =
MediaQuery.of(context).padding.bottom;
if (index ==
@ -262,7 +262,8 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
} else {
// 骨架屏
return SliverList(
delegate: SliverChildBuilderDelegate((context, index) {
delegate: SliverChildBuilderDelegate(
(BuildContext context, index) {
return const VideoReplySkeleton();
}, childCount: 5),
);
@ -318,12 +319,11 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
}
class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
_MySliverPersistentHeaderDelegate({required this.child});
final double _minExtent = 45;
final double _maxExtent = 45;
final Widget child;
_MySliverPersistentHeaderDelegate({required this.child});
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {

Some files were not shown because too many files have changed in this diff Show More