Compare commits

..

387 Commits

Author SHA1 Message Date
c9265897d1 mod: 评论头部样式 2024-03-24 11:30:50 +08:00
48b412bdbf Merge branch 'main' into feature-replyItem 2024-03-24 11:10:46 +08:00
72f1a82650 bump: sdk&dependencies 2024-03-23 19:44:37 +08:00
b5a46d1be0 feat: 接收自定义组件传入 2024-03-23 18:03:33 +08:00
7b15f19895 fix: 详情页TabBar布局异常 2024-03-23 18:00:52 +08:00
14338dc33d fix: 详情页TabBar布局异常 2024-03-23 17:23:51 +08:00
c216c9bd65 mod: tabbar样式&增加评论数 2024-03-21 23:51:43 +08:00
3701fdef97 Merge branch 'fix-scrollSetVolume' 2024-03-21 23:40:55 +08:00
59641f0216 Merge branch 'fix' 2024-03-21 23:40:30 +08:00
4dbcd2e0ec fix: 尝试修复音量调节抖动 issues #647 #498 #104 #198 2024-03-21 23:39:56 +08:00
6d276fce4c fix: 媒体库无法滑动 2024-03-21 00:03:20 +08:00
710361caea fix: 历史记录相关错误 2024-03-20 23:21:41 +08:00
00b81b194f Merge branch 'fix-issues#620' 2024-03-20 00:13:51 +08:00
734db176bf Merge branch 'main' into fix 2024-03-20 00:08:37 +08:00
c23fe9c2bf Merge branch 'main' into feature-updateVideoDetailStructure 2024-03-20 00:07:35 +08:00
79fcd017ab mod: tabbar样式&增加评论数 2024-03-20 00:07:00 +08:00
64292d523f fix: player fit rebuild 2024-03-18 22:13:15 +08:00
af96d16062 feat: 电池优化 2024-03-17 14:45:40 +08:00
12c299685b Merge branch 'feature-removeLockIcon' 2024-03-17 14:35:14 +08:00
1182a58cb4 Merge branch 'fix' 2024-03-17 14:30:26 +08:00
e04a7e5702 fix: 图片保存命名、质量、权限问题 2024-03-17 14:29:39 +08:00
e9dc6f7fdb Merge branch 'main' into fix 2024-03-17 01:13:54 +08:00
25d1ccc87a Merge branch 'feature-liveRoomRender' 2024-03-17 00:38:19 +08:00
33d28f51d1 feat: 未登录状态切换直播画质提示 2024-03-17 00:36:00 +08:00
a37f3b8b5b Merge branch 'main' into feature-liveRoomRender 2024-03-16 23:35:18 +08:00
a57f5e8b2f Merge branch 'main' of github.com:guozhigq/pilipala 2024-03-16 23:12:52 +08:00
beb640ac83 Merge branch 'fix-bangumi' 2024-03-16 23:12:31 +08:00
1b54f07bc3 merge main 2024-03-16 23:11:47 +08:00
fca0588377 Merge pull request #628 from guozhigq/feature-customBottomControl
Feature custom bottom control
2024-03-16 23:08:22 +08:00
4865948609 Merge branch 'fix-whisperRenderError' 2024-03-16 23:07:01 +08:00
09eb180fc7 Merge branch 'main' of github.com:guozhigq/pilipala 2024-03-16 23:06:18 +08:00
3fab47780b Merge pull request #635 from guozhigq/feature-videoDetailCodeCleanUp
feat: 视频番剧详情页代码整理
2024-03-16 23:05:58 +08:00
453cbd7b1c Merge branch 'main' of github.com:guozhigq/pilipala 2024-03-16 23:03:09 +08:00
0bf2326c73 Merge branch 'feature-updateVideoDetailStructure' 2024-03-16 23:02:43 +08:00
2985c624ab mod: stream close 2024-03-16 23:02:21 +08:00
32cbc2759e Merge branch 'main' into feature-updateVideoDetailStructure 2024-03-16 22:52:15 +08:00
13c77957fe fix: 番剧badge 2024-03-16 22:48:47 +08:00
f382c8f377 Merge pull request #632 from kalac2232/main
feature: 动态页跳转登录
2024-03-16 21:56:18 +08:00
491bc87251 fix: 私信列表渲染异常 issues #295 2024-03-15 23:26:43 +08:00
b13d7b475b feat: 非全屏状态下隐藏锁定按钮 2024-03-14 23:49:36 +08:00
5e8d9b524b Merge branch 'fix-favoritesCalc' 2024-03-14 23:31:00 +08:00
606f1b5c64 fix: 收藏夹详情计数错误 issues #637 2024-03-14 23:30:18 +08:00
c4fec14517 Merge branch 'main' into fix-favoritesCalc 2024-03-14 23:23:41 +08:00
f368ef83ee Merge branch 'fix' 2024-03-14 23:23:27 +08:00
357133fa97 fix:去除无用的setState 2024-03-14 10:00:32 +08:00
337cdafef3 typo: vdCtr 2024-03-14 00:18:03 +08:00
bc74e32c10 Merge branch 'main' into fix 2024-03-14 00:17:09 +08:00
a5558de872 Merge branch 'feature-hiddenRelateVideo' 2024-03-14 00:15:24 +08:00
73693c5bbb Merge branch 'feature-updateVideoDetailStructure' 2024-03-14 00:11:34 +08:00
2ad9c3c993 merge main 2024-03-14 00:11:04 +08:00
cab74cff17 Merge branch 'main' of github.com:guozhigq/pilipala 2024-03-14 00:08:52 +08:00
a161df2c7b Merge pull request #627 from guozhigq/feature-updateVideoDetailStructure
Feature update video detail structure
2024-03-14 00:08:32 +08:00
4def3ffb80 Merge branch 'fix' 2024-03-14 00:06:20 +08:00
da2bbeedff mod: 默认直播画质设置 2024-03-13 23:12:34 +08:00
70317f92e2 mod: 视频详情页结构优化 2024-03-13 22:42:34 +08:00
bc9ea43cd2 feat: 视频番剧详情页代码整理 2024-03-12 23:45:22 +08:00
882957e2f8 feature:动态页的错误按钮在未登录状态下的按钮为直接跳转登录页 2024-03-12 14:34:32 +08:00
d3766ae31b typo: dynamic type 2024-03-11 23:35:07 +08:00
641cf4ebb3 Merge branch 'main' of github.com:guozhigq/pilipala 2024-03-11 23:32:39 +08:00
99e6abdad9 feat: 相关视频推荐开关 issues #585 2024-03-11 23:31:20 +08:00
98aaca286b feat: 直播画质切换 2024-03-11 00:02:11 +08:00
dc1edf7e73 Merge pull request #624 from yeqiling/feature-rank
feat:支持排行榜功能
2024-03-10 22:58:47 +08:00
31405750e6 mod: 视频详情页布局 2024-03-10 22:44:07 +08:00
6fdfcb888d feat: 播放器底部控制栏自定义 2024-03-10 19:41:23 +08:00
3ece2bb173 feat: 系统倍速可编辑 2024-03-10 17:57:24 +08:00
b7b75e956f fix: 删除单个搜索历史无效 2024-03-10 16:13:11 +08:00
bf37c33291 feat:支持排行榜功能 2024-03-09 19:39:21 +08:00
06fb3e8d2f fix: 请求github异常 2024-03-09 01:25:54 +08:00
504be6fbda fix: 搜索结果类型为课堂时渲染异常 2024-03-09 01:18:26 +08:00
df4539a035 fix: github链接 issues #618 2024-03-08 23:15:48 +08:00
a3e1fd4e91 fix: 清除缓存提示 issues #619 2024-03-08 23:09:52 +08:00
f41bb02bae fix: 合集最后一p不显示 issues #620 2024-03-08 23:05:09 +08:00
105a29f311 feat: disable Battery Optimization 2024-03-08 22:56:16 +08:00
3bf6136bc6 fix: 楼中楼评论请求重复 #284 2024-03-08 00:03:34 +08:00
ab24da5f55 fix: 媒体通知进度条未按预期停止 2024-03-07 23:35:39 +08:00
ed0b43eff1 v1.0.21 更新日志 2024-03-06 23:29:18 +08:00
ab9ae3a481 fix: setState() called after dispose() 导致全屏失效 2024-03-06 00:04:52 +08:00
d728b1fb6d mod: 评论区非正常地址判断 2024-03-05 23:39:05 +08:00
12e947ef84 fix: reply callback null error issues #615 2024-03-05 23:21:51 +08:00
3fad86e7e3 fix: 视频简介被遮挡 issues #613 2024-03-05 23:04:59 +08:00
fea70011cb fix: navBars unmodifiable issues #612 2024-03-05 23:01:23 +08:00
32cdb27f7c fix: enableGradientBg未定义 2024-03-05 22:37:52 +08:00
eb4435045b fix: 番剧全屏时title取值异常 2024-03-04 23:48:01 +08:00
f1b829cec1 fix: 首页tarbar指示器跳动 2024-03-04 08:29:01 +08:00
b248158e62 v1.0.20 更新日志 2024-03-03 19:48:10 +08:00
83b0ff02e4 fix: 图片预览放大、取消下滑关闭图片预览 2024-03-03 18:32:13 +08:00
8109314aaf opt: url scheme优化 issues #581 2024-03-03 15:20:59 +08:00
c4b3446956 Merge branch 'fix-replyPanelScroll' 2024-03-03 12:54:26 +08:00
d804d95d78 Merge branch 'fix-pip' 2024-03-03 12:53:48 +08:00
234dfe9d64 Merge branch 'fix' 2024-03-03 12:53:40 +08:00
c20df8fd81 fix: enable pip 2024-03-03 12:22:10 +08:00
19f0b1b28f fix: 动态专栏重复 2024-03-03 11:53:15 +08:00
481fa0d934 feat: 默认启动页设置 issues #483 2024-03-03 11:09:52 +08:00
caca16a957 fix: 动态页面upPanel不刷新 2024-03-03 09:57:41 +08:00
602d795909 Merge branch 'main' into fix 2024-03-03 09:37:28 +08:00
800f714f4a mod: 视频详情页appBar 2024-03-03 00:52:47 +08:00
75f569cb79 mod: 合集布局 2024-03-03 00:15:16 +08:00
0e888537e8 mod: yml rename 2024-03-02 22:37:56 +08:00
a3ce15bd9e mod: CI format 2024-03-02 22:27:02 +08:00
40f94e7ace Merge pull request #587 from VillagerTom/sending-beta-to-tg-channel
推送至main分支时编译为beta版本,发送到Telegram频道
2024-03-02 22:24:19 +08:00
370dcaf419 mod: 用户登录状态msg取值 2024-03-02 16:05:15 +08:00
f81f348a3e fix: 视频详情页评论下拉刷新 issues #486 2024-03-02 15:51:24 +08:00
4191cafe78 fix: 推荐卡片单列布局 2024-03-02 14:47:07 +08:00
ae33cbf7ca fix: 搜索框默认搜索词溢出 2024-03-02 13:02:43 +08:00
fca7c36203 mod: 动态页面upPanel 2024-03-02 12:56:16 +08:00
5fc783ebc2 Merge branch 'design' 2024-03-02 11:46:20 +08:00
98122aeaae fix: audioHandler null 2024-03-02 11:45:36 +08:00
f0d8e2a122 feat: 播放器控制栏动画开关 2024-03-02 11:19:18 +08:00
f815affff9 opt: 播放器控制栏动画 2024-03-02 00:40:53 +08:00
fce701090a Merge branch 'main' into design 2024-03-02 00:39:01 +08:00
d6da2a8a47 fix: headerControl bvid丢失 2024-03-01 23:55:19 +08:00
b3e162c8d3 Merge branch 'main' into fix 2024-03-01 23:45:12 +08:00
e5eae93a78 fix: 私信页面表情面板 issues #588 2024-03-01 23:01:10 +08:00
962dcca6d4 Merge branch 'fix' 2024-03-01 00:15:14 +08:00
be56fb721f fix: 私信页面表情面板 issues #588 2024-03-01 00:14:42 +08:00
ce1c80fd86 fix: 动态最新关注横行拉伸 issuse #580 2024-02-29 23:42:09 +08:00
33ef18ef1d fix: 评论jumpUrl正则转义 2024-02-29 00:30:55 +08:00
ba61e38c9b Merge branch 'fix-dynamicReplySeekTime' 2024-02-29 00:00:09 +08:00
0b0db1a2b1 mod: videoPage path判断 2024-02-28 23:59:47 +08:00
a9d73a9f1b fix: 动态标题未显示 2024-02-28 23:51:30 +08:00
0b5397ec00 fix: 动态评论区seek error 2024-02-28 23:29:02 +08:00
466214b26a fix: statusBarIcon color 2024-02-28 23:17:03 +08:00
699be4125b 将版本号中的alpha改为beta; 加回之前删去的“v” 2024-02-28 16:22:14 +08:00
45cc46d6d6 重命名:.github/workflows/alpha.yml -> .github/workflows/CI.yml 2024-02-28 15:38:17 +08:00
3f9fcabc2d Revert "将alpha.yml的workflow name改为alpha, 避免混淆"
This reverts commit 04186cdd5b.
2024-02-28 15:36:30 +08:00
65d2bfd844 升级至channel-post@v1.0.7, 支持传输大文件 2024-02-28 15:35:28 +08:00
4642c2a847 将git log pretty format中raw body替换为subject, 避免revert commit多行输出 2024-02-28 15:35:28 +08:00
04186cdd5b 将alpha.yml的workflow name改为alpha, 避免混淆 2024-02-28 15:35:28 +08:00
40cc4e0dd1 channel-post@v1.0.5重复发送文件,改为v1.0.4 2024-02-28 15:35:28 +08:00
95bc4a9f46 在Telegram消息中显示最后一次提交信息 2024-02-28 15:35:28 +08:00
3bf3fd9a46 使用参数fetch-depth: 0取得所有分支和tags, 末端提交改回HEAD 2024-02-28 15:35:28 +08:00
83ad11402f 😅注释符被识别为文件名的一部分 2024-02-28 15:35:28 +08:00
cfeb0588c1 取消发送其他架构APK, 减少发送文件大小 2024-02-28 15:35:28 +08:00
381e832f3c 修正架构名称拼写错误 2024-02-28 15:35:28 +08:00
6c20a434ed 列出文件 2024-02-28 15:35:28 +08:00
fc2da3ce57 使checkout action克隆指定分支; 统一代码缩进 2024-02-28 15:35:28 +08:00
a3abed0a03 新增alpha.yml, 用于编译推送至alpha分支的代码并发送至Telegram频道 2024-02-28 15:35:28 +08:00
db03cdd442 fix: List dataType 2024-02-28 00:45:13 +08:00
542975d0ec feat: 全屏手势设置 issues #517 2024-02-28 00:34:46 +08:00
835ea0a9ff Merge branch 'design' 2024-02-26 00:03:00 +08:00
89501d3daa Merge branch 'main' of github.com:guozhigq/pilipala 2024-02-26 00:02:06 +08:00
90c0256766 opt: 图片加载&设置 2024-02-26 00:00:14 +08:00
c2767486f5 Merge branch 'main' into design 2024-02-25 23:24:33 +08:00
e2489ef0e3 feat: 私信页面表情面板 2024-02-25 22:48:02 +08:00
b2a4c54565 merge main 2024-02-25 20:32:02 +08:00
bf071ea9e1 feat: 消息未读标记 2024-02-25 19:34:24 +08:00
f8a8c0967a feat: 评论增加表情 2024-02-25 19:09:12 +08:00
078e4716b4 feat: 我的订阅 2024-02-25 12:12:54 +08:00
4da6667b81 mod: 直播间背景图片 2024-02-24 17:37:14 +08:00
e2befb11ff feat: 横屏全屏时展示视频标题 2024-02-24 02:37:16 +08:00
cb0ff334b3 Merge pull request #569 from My-Responsitories/fix-dynamic-risk-challenge
fix: up主页未登录状态风控校验
2024-02-24 02:01:08 +08:00
e536f58ff4 Merge branch 'feature-replyJumpUrl' 2024-02-24 01:49:06 +08:00
d9992663d8 Merge branch 'fix-issues#568' 2024-02-24 01:47:51 +08:00
b1a05c5c27 mod: 修改关于页面 2024-02-24 01:38:27 +08:00
02cc164635 feat: 首页tabbar样式设置 issues #564 2024-02-23 22:44:10 +08:00
35dc94014c mod: 直播mcdn链接替换 issues #568 2024-02-23 00:30:26 +08:00
5746b85b27 fix: 视频全屏遮挡 issues #347 2024-02-22 00:17:38 +08:00
740d5f1ddd fix: 视频详情页点击主页按钮卡死 issues #562 2024-02-21 23:27:32 +08:00
a0f92df5b5 fix dynamic risk challenge 2024-02-21 13:16:38 +08:00
fce96d4976 feat: 评论话题匹配 2024-02-20 23:54:45 +08:00
dd6c537135 Merge branch 'feature-chargeVideo' 2024-02-19 23:24:12 +08:00
bcf94e287a mod: 修改收藏视频响应判断 2024-02-18 23:44:21 +08:00
841d0f25f5 fix: 评论区jumpUrl BV跳转 2024-02-18 08:20:48 +08:00
4811dc5ba5 fix: changeSeasonOrbangu aid null 2024-02-18 08:11:11 +08:00
41af6c799b Merge branch 'main' into fix 2024-02-18 08:10:20 +08:00
e8f63f6114 fix: up投稿动态页增加未登录风控提示 2024-02-17 17:32:13 +08:00
d1e8068e51 Merge pull request #514 from orz12/fix-audio-fucus-interrupt
fix: 修复焦点恢复时错误播放的问题
2024-02-17 16:47:13 +08:00
6de9b1977c Merge pull request #548 from orz12/mod-imagepreview-hide-statusbar-in-android
mod: 图片预览页,安卓也隐藏状态栏
2024-02-17 15:09:52 +08:00
3c0f54bfd7 fix: app端model bvid null issues #546 2024-02-16 21:46:48 +08:00
3d2c6a122a feat: 充电视频试看 2024-02-16 21:30:29 +08:00
8950658f08 mod: 图片预览页,安卓也隐藏状态栏 2024-02-16 20:20:37 +08:00
7a78729a44 fix: 合集切换推荐视频未刷新 2024-02-16 18:23:34 +08:00
03e5e22fef Merge pull request #458 from orz12/mod-add-time-in-rcmd-and-search
mod: 搜索和推荐页增加时间
2024-02-16 11:42:29 +08:00
aa93ce0b89 Merge branch 'main' into mod-add-time-in-rcmd-and-search 2024-02-16 11:42:01 +08:00
0c365ad049 Merge branch 'design' 2024-02-16 11:00:48 +08:00
3d5c578fef mod: 动态页面upPanel 2024-02-16 11:00:23 +08:00
0a22f0f543 Merge branch 'design' 2024-02-16 09:49:55 +08:00
5bf7b69d79 feat: 收藏搜索结果删除 2024-02-16 09:33:59 +08:00
d57f84a1d7 fix: 路由跳转传参丢失 2024-02-15 21:59:28 +08:00
32b2f0ceff Merge pull request #539 from orz12/fix-speed-dialog-cannot-dismiss
fix: 播放速度dialog无法关闭
2024-02-15 21:10:52 +08:00
bae871cfa1 Merge branch 'feature-replyItem' 2024-02-15 21:07:55 +08:00
d95fe9fe14 mod: MorePanel样式 2024-02-15 21:07:23 +08:00
eb006e4c55 Merge branch 'feature-replyItem' 2024-02-14 20:09:48 +08:00
cb88d0c9ae Merge branch 'feature-liveRoomRender' 2024-02-14 20:09:40 +08:00
3efad736ae fix: 直播闪退 issues #540 2024-02-14 19:38:55 +08:00
42ad959155 fix: 速度设置无法取消 2024-02-14 08:44:00 +08:00
cdf800c49f mod: 评论复制逻辑 issues #420 #331 #297 #152 2024-02-13 23:33:51 +08:00
569277572a Merge pull request #536 from KoolShow/fix_seekto_regexp
fix: 含有小时的时间无法跳转
2024-02-12 18:11:26 +08:00
19b84571c1 Merge branch 'main' into fix_seekto_regexp 2024-02-12 18:11:15 +08:00
0812b8339e Merge branch 'feature-replyItem' 2024-02-12 17:55:08 +08:00
b817a0c807 修正正则表达式以匹配含小时的时间 2024-02-12 17:20:18 +08:00
3da70d7e27 Merge branch 'fix-replyRepeat' 2024-02-12 16:55:56 +08:00
5e59db85be fix: 评论笔记跳转 issues #472 2024-02-12 16:51:05 +08:00
77477ff4dd mod: merge main 2024-02-12 10:30:18 +08:00
89026e671c mod: 收藏视频相关 issues #51 #534 2024-02-12 10:07:29 +08:00
1c8e7e53a5 Merge branch 'fix-replyRepeat' 2024-02-11 23:20:11 +08:00
b264427be6 fix: 切换合集评论不刷新 issues #326 #525 2024-02-11 23:07:44 +08:00
d5134f972d Merge branch 'feature-liveRoomRender' 2024-02-11 18:48:24 +08:00
e2fd01a6d5 fix: video Storage初始化 2024-02-11 18:47:51 +08:00
289cc99bc2 mod 2024-02-11 09:10:45 +08:00
3d5ebe7e99 fix: 视频详情页评论重复请求 2024-02-10 19:57:10 +08:00
d9964d37a4 Merge branch 'fix-favBangumiPushError' 2024-02-10 19:24:54 +08:00
5da39a9c52 Merge branch 'feature-cacheManage' 2024-02-10 19:22:49 +08:00
44a162762c fix: 评论页面路由跳转 issues #405 2024-02-09 23:24:26 +08:00
d0f036ec35 fix: 评论回复多张图片拉伸 2024-02-09 09:32:28 +08:00
10b928474b mod 2024-02-08 22:46:39 +08:00
94f3b7c1e4 fix: minePage 路由跳转 2024-02-08 21:33:02 +08:00
fb8b2de115 feat: up搜索 2024-02-08 21:27:22 +08:00
0d5d33a365 feat: up投稿排序 2024-02-08 10:29:26 +08:00
c39e91073b feat: 应用内缓存清理 2024-02-07 22:57:30 +08:00
d258474a5a mod: 直播页面内容更新 2024-02-07 22:23:29 +08:00
b0c56feef5 mod: 首页网络异常请求重试 2024-02-07 02:47:11 +08:00
191472d0c4 mod: 网络请求异常样式修改 2024-02-07 01:17:35 +08:00
40c666e3d1 mod: 网络异常组件样式修改 2024-02-07 00:52:25 +08:00
63d600070b fix: 收藏详情页跳转搜索mediaId取值异常 2024-02-06 15:27:39 +08:00
ebdeec6730 fix: up主页跳转搜索mid取值异常 2024-02-06 12:23:07 +08:00
ee2a273d8b Merge branch 'main' into fix 2024-02-06 11:15:09 +08:00
083739e562 mod: 收藏卡片内容修改 2024-02-06 11:13:33 +08:00
71ccb9c0e5 fix: 收藏国创跳转异常 2024-02-06 11:01:36 +08:00
4a5f4ca2ca fix: 限时免费无法播放 issues #457 2024-02-06 00:14:46 +08:00
78ade4a193 mod: 移除评论按【最多回复】排序 issues #298 2024-02-05 23:41:40 +08:00
ae14653e72 Merge pull request #434 from orz12/mod-not-login-recommend2
mod: 推荐功能增强,新增模拟未登录和过滤器
2024-02-05 00:35:46 +08:00
01ac2c13e1 Merge branch 'main' into mod-not-login-recommend2 2024-02-05 00:35:11 +08:00
9e471b83d9 mod: cancel Get.snackbar 2024-02-05 00:19:03 +08:00
a560d66567 mod: rcmd FutureBuilder 2024-02-04 23:03:24 +08:00
80b39daaff mod: jumpUrl增加icon显示 issues #471 2024-02-04 22:06:45 +08:00
fb32388536 fix: 尝试修复焦点恢复时错误播放的问题 2024-02-04 18:44:48 +08:00
5f92a0c293 mod: 用户投稿显示弹幕数 2024-02-04 00:32:01 +08:00
3de009ac43 Merge branch 'fix-audioAutoReplay' 2024-02-03 23:46:34 +08:00
b29256f598 Merge branch 'fix-floating' 2024-02-03 23:42:54 +08:00
e7cf472a0f Merge branch 'fix-videoIntroError' 2024-02-03 23:39:17 +08:00
03c59d23b8 Merge branch 'fix-githubModelError' 2024-02-03 23:38:53 +08:00
b6f805f0e4 Merge branch 'main' of github.com:guozhigq/pilipala 2024-02-03 23:38:21 +08:00
e23c2469ed Merge pull request #509 from orz12/feat-auto_reply_push-msgtype
feat: 私信支持显示自动推送回复
2024-02-03 20:04:05 +08:00
387c799de1 feat: 动态未读标记 issues #459 2024-02-03 16:59:54 +08:00
230dd81342 fix: List 越界 2024-02-03 01:13:36 +08:00
47bdfec8c2 fix: github assets null error 2024-02-03 01:07:12 +08:00
6a844da259 mod: 点赞接口登录拦截 2024-02-03 00:53:18 +08:00
18bb58d293 mod: 投币状态响应status 2024-02-03 00:43:38 +08:00
045186b3c8 mod: 视频详情页响应status 2024-02-03 00:33:29 +08:00
b531599893 mod: floating依赖 2024-02-03 00:29:47 +08:00
1da84508d8 feat: 自动推送回复私信显示支持 2024-02-03 00:23:32 +08:00
4c44fab217 Merge pull request #502 from orz12/fix-query-onlineTotal-status-false
fix: 查询在线人数错误时没有返回status
2024-02-02 23:39:09 +08:00
5c3d438a7e Merge pull request #508 from guozhigq/fix-minePanelPush
fix: 个人面板无法跳转设置页面
2024-02-02 23:30:57 +08:00
92a8efdee1 Merge pull request #470 from orz12/fix-reply-reply-parse2
fix: 评论区识别逻辑重构,修复含有关键词的评论重复出现的问题
2024-02-02 23:27:59 +08:00
eb1e2ca5f4 fix: 个人面板无法跳转设置页面 2024-02-02 23:24:36 +08:00
5b1022628c fix: 九图部分位置无法点击 2024-02-02 02:34:59 +08:00
33f61ac0fa fix: 查询在线人数错误时没有返回status 2024-02-02 01:18:06 +08:00
0b349e102e fix:评论区HTML实体转义;逻辑错误短路 2024-02-02 00:56:41 +08:00
81371c5a31 fix: 只有时间的评论区不高亮 2024-02-02 00:56:41 +08:00
85a59e11b9 fix: 修复没有关键词时无法匹配时间、修复不显示关键词时不替换超链接、时间添加中文冒号匹配并提升分支判定严格程度 2024-02-02 00:56:41 +08:00
e24ccc16fa mod: av2bv方法修改 2024-02-01 00:32:52 +08:00
89a43b1285 v1.0.19 更新日志 2024-01-31 23:28:51 +08:00
ea8af28828 fix: 专栏封面图尺寸异常 2024-01-31 23:11:03 +08:00
8a2c023343 fix: magType value 2024-01-31 23:03:45 +08:00
a86fe76e59 Merge branch 'fix-replyReqError' 2024-01-31 22:44:14 +08:00
d703e38c3f fix: avbv转换 2024-01-31 22:43:40 +08:00
9e93b50860 mod: 还原aid 2024-01-31 22:33:04 +08:00
9907967a0a Merge pull request #454 from orz12/feat-whisper-detail-type-and-emoji
feat: 私信显示分享视频内容、富文本表情,补充信息类型枚举
2024-01-31 08:08:29 +08:00
331969cc8d Merge pull request #443 from orz12/opt-video-detail-page
fix: 播放页数个问题
2024-01-31 08:06:26 +08:00
7157f89245 Update main.yml 2024-01-30 23:23:12 +08:00
163bb3c8da v1.0.18 更新 2024-01-30 23:17:00 +08:00
33f71c62df Merge branch 'fix-replyReqError' 2024-01-30 23:15:22 +08:00
365c367cc7 Merge branch 'main' of github.com:guozhigq/pilipala 2024-01-30 23:14:49 +08:00
7ea95e550b Merge branch 'feature-ciActionIpa' 2024-01-30 23:14:24 +08:00
699361e04c fix: aid 2024-01-30 23:11:54 +08:00
e835821f3c Merge pull request #452 from orz12/mod-ai-conclusion-not-support-tips
mod: AI视频总结提示
2024-01-29 23:47:16 +08:00
76d5f6333e Merge branch 'feature-logger' 2024-01-29 23:28:07 +08:00
91899a9537 Merge branch 'fix-replyLoadError' 2024-01-29 23:02:30 +08:00
5591bb3dbf Merge branch 'opt-requestError' 2024-01-29 23:01:30 +08:00
1adbdf127f Merge branch 'main' of github.com:guozhigq/pilipala 2024-01-29 22:58:34 +08:00
8169f5739c fix: 首页tabbar排序无效 2024-01-28 23:44:41 +08:00
127c6734f8 feat: 自动打包ipa文件 2024-01-28 23:15:34 +08:00
a78ce4f6d4 feat: 错误日志记录 2024-01-28 15:52:30 +08:00
22a2628513 fix: 评论区加载超时导致视图崩溃 issues #389 2024-01-27 22:42:04 +08:00
e972d17f1c mod: 优化请求异常信息 2024-01-27 22:37:16 +08:00
e603942b5f fix: 评论区时间正则拼接顺序 2024-01-27 15:49:53 +08:00
0c4bad406e fix: 评论区识别逻辑重构,修复含有关键词的评论重复出现的问题 2024-01-27 14:30:04 +08:00
791047753d Merge pull request #445 from orz12/feat-danmaku-strokewidth
feat: 新增弹幕描边粗细设置,默认值降低
2024-01-27 13:25:28 +08:00
3784f1f87b Merge pull request #468 from guozhigq/feature-modLongImgLabel
mod: 长图标签显示判断
2024-01-27 13:06:40 +08:00
c0162892ef mod: 长图标签显示判断 2024-01-27 13:05:59 +08:00
10d2995429 mod: 对齐搜索栏调整 2024-01-27 12:06:45 +08:00
b0d8f5d0b6 Merge branch 'main' into pr/434 2024-01-27 10:28:55 +08:00
a64a49df22 Merge pull request #466 from guozhigq/feature-removeRcmdCache
mod: 移除首页推荐视频默认缓存
2024-01-27 10:13:02 +08:00
349de75dfd mod: 移除首页推荐视频默认缓存 2024-01-27 10:12:21 +08:00
1014c26d29 mod: make headers eid issues #435 2024-01-27 01:11:29 +08:00
d1bacf8950 fix: 退出搜索页面控制器未销毁 2024-01-27 00:24:54 +08:00
0595648f4c fix: 搜索页面时长筛选、快速回顶 2024-01-26 23:25:06 +08:00
23c8b34189 fix: app端推荐屏蔽时间显示,播放量与弹幕组件改为动态类型 2024-01-26 16:40:29 +08:00
932be48125 mod: 推荐、搜索页添加时间,修复视频搜索页无法筛选和回顶 2024-01-26 16:38:56 +08:00
791eed8a01 Merge pull request #446 from orz12/fix-typo-in-http
fix: typo
2024-01-26 00:16:45 +08:00
e791210039 v1.0.17 更新 2024-01-25 23:16:10 +08:00
01fa3c1cb3 mod: 关于页面增加官网链接 2024-01-25 23:13:16 +08:00
9663278916 feat: 私信显示分享视频内容、富文本表情,补充信息类型枚举 2024-01-25 21:22:39 +08:00
1b03f3f31f mod: AI视频总结未支持提醒 2024-01-25 21:15:22 +08:00
a73f2974e1 fix: typo 2024-01-25 20:56:59 +08:00
bf8ae0f317 feat: 新增弹幕描边粗细设置,默认值降低 2024-01-25 20:55:35 +08:00
545def36e6 mod: 自动播放按钮改为官方版 2024-01-25 20:51:01 +08:00
aaeecc9e53 fix: 重力旋转后划出下方的详情页 2024-01-25 20:51:01 +08:00
16895b5c32 fix: 点击视频评论区用户头像后返回详情页灰屏 2024-01-25 20:51:01 +08:00
a68c04001b fix: 竖屏全屏异常 2024-01-25 20:51:01 +08:00
1dd70f482f fix: 旋转横屏仍有状态栏 2024-01-25 20:51:01 +08:00
103423abf7 fix: 修复部分手机横屏两侧不等宽 2024-01-25 20:51:01 +08:00
569184a507 opt: 切换页面时销毁播放器组件提升性能 2024-01-25 20:51:01 +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
9122dd7f3a mod: 新增推荐过滤器,回退model转换修改,移除不必要的futureBuilder 2024-01-20 17:07:10 +08:00
41ddeab41a 新增模拟未登录推荐,独立推荐设置,新增accesskey风控警告,统一推荐逻辑 2024-01-20 15:14:52 +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
244 changed files with 12482 additions and 6033 deletions

208
.github/workflows/beta_ci.yml vendored Normal file
View File

@ -0,0 +1,208 @@
name: Pilipala Beta
on:
workflow_dispatch:
push:
branches:
- "main"
paths-ignore:
- "**.md"
- "**.txt"
- ".github/**"
- ".idea/**"
- "!.github/workflows/**"
jobs:
update_version:
name: Read and update version
runs-on: ubuntu-latest
outputs:
# 定义输出变量 version以便在其他job中引用
new_version: ${{ steps.version.outputs.new_version }}
last_commit: ${{ steps.get-last-commit.outputs.last_commit }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
fetch-depth: 0
- name: 获取first parent commit次数
id: get-first-parent-commit-count
run: |
version=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
recent_release_tag=$(git tag -l | grep $version | egrep -v "[-|+]" || true)
if [[ "x$recent_release_tag" == "x" ]]; then
echo "当前版本tag不存在请手动生成tag."
exit 1
fi
git log --oneline --first-parent $recent_release_tag..HEAD
first_parent_commit_count=$(git rev-list --first-parent --count $recent_release_tag..HEAD)
echo "count=$first_parent_commit_count" >> $GITHUB_OUTPUT
- name: 获取最后一次提交
id: get-last-commit
run: |
last_commit=$(git log -1 --pretty="%h %s" --first-parent)
echo "last_commit=$last_commit" >> $GITHUB_OUTPUT
- name: 更新版本号
id: version
run: |
# 读取版本号
VERSION=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
# 获取GitHub Actions的run_number
#RUN_NUMBER=${{ github.run_number }}
# 构建新版本号
NEW_VERSION=$VERSION-beta.${{ steps.get-first-parent-commit-count.outputs.count }}
# 输出新版本号
echo "New version: $NEW_VERSION"
# 设置新版本号为输出变量
echo "new_version=$NEW_VERSION" >>$GITHUB_OUTPUT
android:
name: Build CI (Android)
needs: update_version
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
- name: 构建Java环境
uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "17"
token: ${{secrets.GIT_TOKEN}}
- name: 检查缓存
uses: actions/cache@v2
id: cache-flutter
with:
path: /root/flutter-sdk
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2
with:
flutter-version: 3.16.5
channel: any
- name: 下载项目依赖
run: flutter pub get
- name: 解码生成 jks
run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
- name: 更新版本号
id: version
run: |
# 更新pubspec.yaml文件中的版本号
sed -i "s/version: .*+/version: ${{ needs.update_version.outputs.new_version }}+/g" pubspec.yaml
- name: flutter build apk
run: flutter build apk --release --split-per-abi
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
- name: 重命名应用
run: |
for file in build/app/outputs/flutter-apk/app-*.apk; do
if [[ $file =~ app-(.?*)release.apk ]]; then
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}v${{ needs.update_version.outputs.new_version }}.apk"
mv "$file" "$new_file_name"
fi
done
- name: 上传
uses: actions/upload-artifact@v3
with:
name: Pilipala-Beta
path: |
build/app/outputs/flutter-apk/Pili-*.apk
iOS:
name: Build CI (iOS)
needs: update_version
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
ref: ${{ github.ref_name }}
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2.10.0
with:
cache: true
flutter-version: 3.16.5
- name: 更新版本号
id: version
run: |
# 更新pubspec.yaml文件中的版本号
sed -i "" "s/version: .*+/version: ${{ needs.update_version.outputs.new_version }}+/g" pubspec.yaml
- name: flutter build ipa
run: |
flutter build ios --release --no-codesign
ln -sf ./build/ios/iphoneos Payload
zip -r9 app.ipa Payload/runner.app
- name: 重命名应用
run: |
DATE=${{ steps.date.outputs.date }}
for file in app.ipa; do
new_file_name="build/Pili-v${{ needs.update_version.outputs.new_version }}.ipa"
mv "$file" "$new_file_name"
done
- name: 上传
uses: actions/upload-artifact@v3
with:
if-no-files-found: error
name: Pilipala-Beta
path: |
build/Pili-*.ipa
upload:
runs-on: ubuntu-latest
needs:
- update_version
- android
- iOS
steps:
- uses: actions/download-artifact@v3
with:
name: Pilipala-Beta
path: ./Pilipala-Beta
- name: 发送到Telegram频道
uses: xireiki/channel-post@v1.0.7
with:
bot_token: ${{ secrets.BOT_TOKEN }}
chat_id: ${{ secrets.CHAT_ID }}
large_file: true
api_id: ${{ secrets.TELEGRAM_API_ID }}
api_hash: ${{ secrets.TELEGRAM_API_HASH }}
method: sendFile
path: Pilipala-Beta/*
parse_mode: Markdown
context: "*Beta版本: v${{ needs.update_version.outputs.new_version }}*\n更新内容: [${{ needs.update_version.outputs.last_commit }}](${{ github.event.head_commit.url }})"

View File

@ -1,84 +0,0 @@
name: build_apk
# action事件触发
on:
push:
# push tag时触发
tags:
- 'v*.*.*'
# 可以有多个jobs
jobs:
build_apk:
# 运行环境 ubuntu-latest window-latest mac-latest
runs-on: ubuntu-latest
# 每个jobs中可以有多个steps
steps:
- name: 代码迁出
uses: actions/checkout@v3
- name: 构建Java环境
uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "17"
token: ${{secrets.GIT_TOKEN}}
- name: 检查缓存
uses: actions/cache@v2
id: cache-flutter
with:
path: /root/flutter-sdk # Flutter SDK 的路径
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2
with:
flutter-version: 3.16.4
channel: any
- name: 下载项目依赖
run: flutter pub get
- name: 解码生成 jks
run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
- name: flutter build apk
# 对应 android/app/build.gradle signingConfigs中的配置项
run: flutter build apk --release --split-per-abi
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
- name: 获取版本号
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
# - name: 获取当前日期
# id: date
# run: echo "date=$(date +'%m%d')" >>$GITHUB_OUTPUT
- name: 重命名应用 Pili-arm64-v8a-*.*.*.0101.apk
run: |
# DATE=${{ steps.date.outputs.date }}
for file in build/app/outputs/flutter-apk/app-*-release.apk; do
if [[ $file =~ app-(.*)-release.apk ]]; then
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}-${{ steps.version.outputs.version }}.apk"
mv "$file" "$new_file_name"
fi
done
- name: 构建和发布release
uses: ncipollo/release-action@v1
with:
# release title
name: v${{ steps.version.outputs.version }}
artifacts: "build/app/outputs/flutter-apk/Pili-*.apk"
bodyFile: "change_log/${{steps.version.outputs.version}}.md"
token: ${{ secrets.GIT_TOKEN }}
allowUpdates: true

157
.github/workflows/release_ci.yml vendored Normal file
View File

@ -0,0 +1,157 @@
name: Pilipala Release
# action事件触发
on:
push:
# push tag时触发
tags:
- "v*.*.*"
# 可以有多个jobs
jobs:
android:
# 运行环境 ubuntu-latest window-latest mac-latest
runs-on: ubuntu-latest
# 每个jobs中可以有多个steps
steps:
- name: 代码迁出
uses: actions/checkout@v3
- name: 构建Java环境
uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "17"
token: ${{secrets.GIT_TOKEN}}
- name: 检查缓存
uses: actions/cache@v2
id: cache-flutter
with:
path: /root/flutter-sdk # Flutter SDK 的路径
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2
with:
flutter-version: 3.16.5
channel: any
- name: 下载项目依赖
run: flutter pub get
- name: 解码生成 jks
run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks
env:
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
- name: flutter build apk
run: flutter build apk --release --split-per-abi
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
- name: flutter build apk
run: flutter build apk --release
env:
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
- name: 获取版本号
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
# - name: 获取当前日期
# id: date
# run: echo "date=$(date +'%m%d')" >>$GITHUB_OUTPUT
- name: 重命名应用
run: |
# DATE=${{ steps.date.outputs.date }}
for file in build/app/outputs/flutter-apk/app-*.apk; do
if [[ $file =~ app-(.?*)release.apk ]]; then
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}${{ steps.version.outputs.version }}.apk"
mv "$file" "$new_file_name"
fi
done
- name: 上传
uses: actions/upload-artifact@v3
with:
name: Pilipala-Release
path: |
build/app/outputs/flutter-apk/Pili-*.apk
iOS:
runs-on: macos-latest
steps:
- name: 代码迁出
uses: actions/checkout@v4
- name: 安装Flutter
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2.10.0
with:
cache: true
flutter-version: 3.16.5
- name: flutter build ipa
run: |
flutter build ios --release --no-codesign
ln -sf ./build/ios/iphoneos Payload
zip -r9 app.ipa Payload/runner.app
- name: 获取版本号
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
- name: 重命名应用
run: |
DATE=${{ steps.date.outputs.date }}
for file in app.ipa; do
new_file_name="build/Pili-${{ steps.version.outputs.version }}.ipa"
mv "$file" "$new_file_name"
done
- name: 上传
uses: actions/upload-artifact@v3
with:
if-no-files-found: error
name: Pilipala-Release
path: |
build/Pili-*.ipa
upload:
runs-on: ubuntu-latest
needs:
- android
- iOS
steps:
- uses: actions/download-artifact@v3
with:
name: Pilipala-Release
path: ./Pilipala-Release
- name: Install dependencies
run: sudo apt-get install tree -y
- name: Get version
id: version
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
- name: Upload Release
uses: ncipollo/release-action@v1
with:
name: v${{ steps.version.outputs.version }}
token: ${{ secrets.GIT_TOKEN }}
omitBodyDuringUpdate: true
omitNameDuringUpdate: true
omitPrereleaseDuringUpdate: true
allowUpdates: true
artifacts: Pilipala-Release/*

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

@ -58,11 +58,10 @@ android {
applicationId "com.guozhigq.pilipala"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
// minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
minSdkVersion 19
minSdkVersion 21
multiDexEnabled true
}
@ -95,3 +94,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" />
@ -223,6 +223,10 @@
android:pathPattern="/mobile/video/.*" />
<data android:scheme="https" android:host="www.bilibili.com"
android:pathPattern="/mobile/video/.*" />
<data android:scheme="https" android:host="b23.tv"
android:pathPattern="/*" />
<data android:scheme="https" android:host="space.bilibili.com"
android:pathPattern="/*" />
</intent-filter>
</activity>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-pointer="none" viewBox="0 0 24 24"><path fill-rule="evenodd" d="m8.085 4.891-.999-1.499a1.008 1.008 0 0 1 1.679-1.118l1.709 2.566c.54-.008 1.045-.012 1.515-.012h.13c.345 0 .707.003 1.088.007l1.862-2.59a1.008 1.008 0 0 1 1.637 1.177l-1.049 1.46c.788.02 1.631.046 2.53.078 1.958.069 3.468 1.6 3.74 3.507.088.613.13 2.158.16 3.276l.001.027c.01.333.017.63.025.856a.987.987 0 0 1-1.974.069c-.008-.23-.016-.539-.025-.881v-.002c-.028-1.103-.066-2.541-.142-3.065-.143-1.004-.895-1.78-1.854-1.813-2.444-.087-4.466-.13-6.064-.131-1.598 0-3.619.044-6.063.13a2.037 2.037 0 0 0-1.945 1.748c-.15 1.04-.225 2.341-.225 3.904 0 1.874.11 3.474.325 4.798.154.949.95 1.66 1.91 1.708a97.58 97.58 0 0 0 5.416.139.988.988 0 0 1 0 1.975c-2.196 0-3.61-.047-5.513-.141A4.012 4.012 0 0 1 2.197 17.7c-.236-1.446-.351-3.151-.351-5.116 0-1.64.08-3.035.245-4.184A4.013 4.013 0 0 1 5.92 4.96c.761-.027 1.483-.05 2.164-.069Zm4.436 4.707h-1.32v4.63h2.222v.848h-2.618v1.078h2.431a5.01 5.01 0 0 1 3.575-3.115V9.598h-1.276a8.59 8.59 0 0 0 .748-1.42l-1.089-.384a14.232 14.232 0 0 1-.814 1.804h-1.518l.693-.308a8.862 8.862 0 0 0-.814-1.408l-1.045.352c.297.396.572.847.825 1.364Zm-4.18 3.564.154-1.485h1.98V8.289h-3.2v.979h2.067v1.43H7.483l-.308 3.454h2.277c0 1.166-.044 1.925-.12 2.277-.078.352-.386.528-.936.528-.308 0-.616-.022-.902-.055l.297 1.067.062.004c.285.02.551.04.818.04 1.001-.066 1.562-.418 1.694-1.056.11-.638.176-1.903.176-3.795h-2.2Zm7.458.11v-.858h-1.254v.858H15.8Zm-2.376-.858v.858h-1.199v-.858h1.2Zm-1.199-.946h1.2v-.902h-1.2v.902Zm2.321 0v-.902H15.8v.902h-1.254Zm3.517 10.594a4 4 0 1 0 0-8 4 4 0 0 0 0 8Zm-.002-1.502a2.5 2.5 0 0 1-2.217-3.657l3.326 3.398a2.49 2.49 0 0 1-1.109.259Zm2.5-2.5c0 .42-.103.815-.286 1.162l-3.328-3.401a2.5 2.5 0 0 1 3.614 2.239Z" clip-rule="evenodd"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-pointer="none" viewBox="0 0 24 24" id="svgcontent" overflow="visible" width="24" height="24" x="597" y="286"><g class="layer" style="pointer-events:all"><title style="pointer-events:inherit">Layer 1</title><path fill-rule="evenodd" d="M11.99,4.83C11.52,4.83 11.01,4.83 10.47,4.84L8.76,2.27A1.01,1.01 0 0 0 7.09,3.39L8.08,4.89C7.4,4.91 6.68,4.93 5.92,4.96A4.01,4.01 0 0 0 2.09,8.4C1.93,9.55 1.85,10.94 1.85,12.58C1.85,14.55 1.96,16.25 2.2,17.7A4.01,4.01 0 0 0 5.96,21.06L6.86,21.11C8.07,21.17 8.67,21.2 10.47,21.2A0.99,0.99 0 0 0 10.47,19.23C8.71,19.23 8.13,19.2 6.97,19.14L6.06,19.09A2.04,2.04 0 0 1 4.15,17.38C3.93,16.06 3.82,14.46 3.82,12.58C3.82,11.02 3.9,9.72 4.05,8.68C4.19,7.7 5.01,6.97 5.99,6.93C8.43,6.85 10.46,6.8 12.05,6.8C13.65,6.8 15.67,6.85 18.12,6.93C19.08,6.97 19.83,7.74 19.97,8.75C20.05,9.27 20.09,10.71 20.11,11.81L20.11,11.81C20.12,12.16 20.13,12.46 20.14,12.69A0.99,0.99 0 1 0 22.11,12.63C22.1,12.4 22.1,12.1 22.09,11.77L22.09,11.74C22.06,10.62 22.01,9.08 21.93,8.47C21.65,6.56 20.14,5.03 18.19,4.96C17.29,4.93 16.44,4.9 15.66,4.88L16.71,3.42A1.01,1.01 0 0 0 15.07,2.24L13.21,4.83C12.83,4.83 12.46,4.83 12.12,4.83L11.99,4.83zM12.51,9.6L11.19,9.6L11.19,14.23L13.41,14.23L13.41,15.08L10.79,15.08L10.79,16.16L13.41,16.16L13.42,16.84C13.78,16.86 14.13,17 14.43,17.24L14.54,17.24L14.54,16.16L17.23,16.16L17.23,15.08L14.53,15.08L14.53,14.23L16.8,14.23L16.8,9.6L15.52,9.6A8.59,8.59 0 0 0 16.27,8.18L15.18,7.8A14.23,14.23 0 0 1 14.37,9.6L12.85,9.6L13.54,9.3A8.86,8.86 0 0 0 12.73,7.89L11.68,8.24C11.98,8.64 12.26,9.09 12.51,9.6zM8.33,13.17L8.48,11.68L10.46,11.68L10.46,8.29L7.26,8.29L7.26,9.27L9.33,9.27L9.33,10.7L7.47,10.7L7.16,14.16L9.44,14.16C9.44,15.32 9.4,16.08 9.32,16.43C9.24,16.79 8.94,16.96 8.39,16.96C8.08,16.96 7.77,16.94 7.48,16.91L7.78,17.97L7.84,17.98C8.13,18 8.39,18.02 8.66,18.02C9.66,17.95 10.22,17.6 10.35,16.96C10.46,16.32 10.53,15.06 10.53,13.17L8.33,13.17zM15.79,13.28L15.79,12.42L14.53,12.42L14.53,13.28L15.79,13.28zM13.41,12.42L13.41,13.28L12.21,13.28L12.21,12.42L13.41,12.42zM12.21,11.47L13.41,11.47L13.41,10.57L12.21,10.57L12.21,11.47zM14.53,11.47L14.53,10.57L15.79,10.57L15.79,11.47L14.53,11.47z" clip-rule="evenodd" id="svg_1"></path><path fill="#000000" fill-rule="evenodd" d="M22.85,14.63A1,1 0 0 0 21.43,14.7L16.34,20.41L14.13,18.13L14.03,18.04L14.02,18.04A1,1 0 0 0 12.7,19.53L15.66,22.57L15.76,22.66L15.76,22.66C16.17,22.98 16.76,22.93 17.12,22.54L22.93,16.03L23.01,15.93L23.01,15.92A1,1 0 0 0 22.85,14.63z" clip-rule="evenodd" id="svg_2"></path></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

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上查看
问题反馈、功能建议请查看「关于」页面。

16
change_log/1.0.18.0130.md Normal file
View File

@ -0,0 +1,16 @@
## 1.0.18
### 功能
### 修复
### 优化
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

15
change_log/1.0.19.0131.md Normal file
View File

@ -0,0 +1,15 @@
## 1.0.19
### 修复
+ 视频404、评论加载错误
+ bvav转换
### 优化
+ 视频详情页内存占用
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

31
change_log/1.0.20.0303.md Normal file
View File

@ -0,0 +1,31 @@
## 1.0.20
### 功能
+ 评论区增加表情
+ 首页渐变背景开关
+ 媒体库显示「我的订阅」
+ 评论区链接解析
+ 默认启动页设置
### 修复
+ 评论区内容重复
+ pip相关问题
+ 播放多p视频评论不刷新
+ 视频评论翻页重复
### 优化
+ url scheme优化
+ 图片预览放大
+ 图片加载速度
+ 视频评论区复制
+ 全屏显示视频标题
+ 网络异常处理
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -0,0 +1,9 @@
## 1.0.21
### 修复
+ 推荐视频全屏问题
+ 番剧全屏播放时灰屏问题
+ 评论回调导致页面卡死问题
更多更新日志可在Github上查看
问题反馈、功能建议请查看「关于」页面。

View File

@ -13,8 +13,13 @@ PODS:
- device_info_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_mailer (0.0.1):
- Flutter
- flutter_volume_controller (0.0.1):
- Flutter
- fluttertoast (0.0.2):
- Flutter
- Toast
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
@ -49,6 +54,7 @@ PODS:
- Flutter
- system_proxy (0.0.1):
- Flutter
- Toast (4.1.0)
- url_launcher_ios (0.0.1):
- Flutter
- volume_controller (0.0.1):
@ -68,7 +74,9 @@ DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`)
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
@ -93,6 +101,7 @@ SPEC REPOS:
- FMDB
- GT3Captcha-iOS
- ReachabilitySwift
- Toast
EXTERNAL SOURCES:
appscheme:
@ -109,8 +118,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/device_info_plus/ios"
Flutter:
:path: Flutter
flutter_mailer:
:path: ".symlinks/plugins/flutter_mailer/ios"
flutter_volume_controller:
:path: ".symlinks/plugins/flutter_volume_controller/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
gt3_flutter_plugin:
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
media_kit_libs_ios_video:
@ -156,7 +169,9 @@ SPEC CHECKSUMS:
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
@ -173,6 +188,7 @@ SPEC CHECKSUMS:
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
status_bar_control: 7c84146799e6a076315cc1550f78ef53aae3e446
system_proxy: bec1a5c5af67dd3e3ebf43979400a8756c04cc44
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47

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

@ -22,20 +22,27 @@ class HttpError extends StatelessWidget {
"assets/images/error.svg",
height: 200,
),
const SizedBox(height: 20),
const SizedBox(height: 30),
Text(
errMsg ?? '请求异常',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall,
),
const SizedBox(height: 30),
OutlinedButton.icon(
const SizedBox(height: 20),
FilledButton.tonal(
onPressed: () {
fn!();
},
icon: const Icon(Icons.arrow_forward_outlined, size: 20),
label: Text(btnText ?? '点击重试'),
)
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.primary.withAlpha(20);
}),
),
child: Text(
btnText ?? '点击重试',
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
),
],
),
),

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,104 @@
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 'package:pilipala/utils/global_data.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 int defaultImgQuality = GlobalData().imgQuality;
final String imageUrl =
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? defaultImgQuality}q.webp';
print(imageUrl);
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.low,
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'
@ -81,14 +107,19 @@ class NetworkImgLayer extends StatelessWidget {
? 0
: StyleString.imgRadius.x),
),
child: Center(
child: Image.asset(
type == 'avatar'
? 'assets/images/noface.jpeg'
: 'assets/images/loading.png',
width: 300,
height: 300,
)),
child: type == 'bg'
? const SizedBox()
: Center(
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

@ -3,7 +3,7 @@ import 'package:pilipala/utils/utils.dart';
class StatDanMu extends StatelessWidget {
final String? theme;
final int? danmu;
final dynamic danmu;
final String? size;
const StatDanMu({Key? key, this.theme, this.danmu, this.size})

View File

@ -3,7 +3,7 @@ import 'package:pilipala/utils/utils.dart';
class StatView extends StatelessWidget {
final String? theme;
final int? view;
final dynamic view;
final String? size;
const StatView({Key? key, this.theme, this.view, this.size})

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,15 @@ 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;
String type = 'video';
try {
type = videoItem.type;
} catch (_) {}
final String heroTag = Utils.makeHeroTag(aid);
return GestureDetector(
onLongPress: () {
if (longPress != null) {
@ -53,7 +57,11 @@ class VideoCardH extends StatelessWidget {
child: InkWell(
onTap: () async {
try {
int cid =
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');
return;
}
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 +73,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,31 +85,38 @@ 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.duration != 0)
PBadge(
text: Utils.timeFormat(videoItem.duration!),
right: 6.0,
bottom: 6.0,
type: 'gray',
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
@ -159,7 +174,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 +187,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 +231,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 +245,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 +282,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 +295,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 +308,60 @@ 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['code'] == 0
? '成功'
: 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,19 @@
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 '../../models/model_rec_video_item.dart';
import 'stat/danmu.dart';
import 'stat/view.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 {
@ -159,12 +161,12 @@ class VideoCardV extends StatelessWidget {
height: maxHeight,
),
),
if (videoItem.duration != null)
if (videoItem.duration > 0)
if (crossAxisCount == 1) ...[
PBadge(
bottom: 10,
right: 10,
text: videoItem.duration,
text: Utils.timeFormat(videoItem.duration),
)
] else ...[
PBadge(
@ -172,7 +174,7 @@ class VideoCardV extends StatelessWidget {
right: 7,
size: 'small',
type: 'gray',
text: videoItem.duration,
text: Utils.timeFormat(videoItem.duration),
)
],
],
@ -217,15 +219,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,
),
],
],
@ -234,6 +231,7 @@ class VideoContent extends StatelessWidget {
const SizedBox(height: 2),
VideoStat(
videoItem: videoItem,
crossAxisCount: crossAxisCount,
),
],
if (crossAxisCount == 1) const SizedBox(height: 4),
@ -297,19 +295,15 @@ class VideoContent extends StatelessWidget {
),
VideoStat(
videoItem: videoItem,
crossAxisCount: crossAxisCount,
),
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)
@ -325,42 +319,55 @@ class VideoContent extends StatelessWidget {
class VideoStat extends StatelessWidget {
final dynamic videoItem;
final int crossAxisCount;
const VideoStat({
Key? key,
required this.videoItem,
required this.crossAxisCount,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return RichText(
maxLines: 1,
text: TextSpan(
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline,
return Row(
children: [
StatView(
theme: 'gray',
view: videoItem.stat.view,
),
children: [
if (videoItem.stat.view != '-')
TextSpan(text: '${videoItem.stat.view}观看'),
if (videoItem.stat.danmu != '-')
TextSpan(text: '${videoItem.stat.danmu}弹幕'),
],
),
const SizedBox(width: 8),
StatDanMu(
theme: 'gray',
danmu: videoItem.stat.danmu,
),
if (videoItem is RecVideoItemModel) ...<Widget>[
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
RichText(
maxLines: 1,
text: TextSpan(
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),
),
const SizedBox(width: 4),
]
],
);
}
}
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 +377,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 +387,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 +402,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

@ -185,7 +185,7 @@ class Api {
static const String searchDefault = '/x/web-interface/wbi/search/default';
// 搜索关键词
static const String serachSuggest =
static const String searchSuggest =
'https://s.search.bilibili.com/main/suggest';
// 分类搜索
@ -214,6 +214,9 @@ class Api {
// https://api.bilibili.com/x/relation/tags
static const String followingsClass = '/x/relation/tags';
// 搜索follow
static const followSearch = '/x/relation/followings/search';
// 粉丝
// vmid 用户id pn 页码 ps 每页个数最大50 order: desc
// order_type 排序规则 最近访问传空,最常访问传 attention
@ -230,6 +233,10 @@ class Api {
static const String liveRoomInfo =
'${HttpString.liveBaseUrl}/xlive/web-room/v2/index/getRoomPlayInfo';
// 直播间详情 H5
static const String liveRoomInfoH5 =
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getH5InfoByRoom';
// 用户信息 需要Wbi签名
// https://api.bilibili.com/x/space/wbi/acc/info?mid=503427686&token=&platform=web&web_location=1550101&w_rid=d709892496ce93e3d94d6d37c95bde91&wts=1689301482
static const String memberInfo = '/x/space/wbi/acc/info';
@ -467,4 +474,33 @@ class Api {
/// page_size
static const getSeasonDetailApi =
'/x/polymer/web-space/seasons_archives_list';
/// 获取未读动态数
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
/// 用户动态主页
static const dynamicSpmPrefix = 'https://space.bilibili.com/1/dynamic';
/// 激活buvid3
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
/// 我的订阅
static const userSubFolder = '/x/v3/fav/folder/collected/list';
/// 我的订阅详情
static const userSubFolderDetail = '/x/space/fav/season/list';
/// 表情
static const emojiList = '/x/emote/user/panel/web';
/// 已读标记
static const String ackSessionMsg =
'${HttpString.tUrl}/session_svr/v1/session_svr/update_ack';
/// 发送私信
static const String sendMsg = '${HttpString.tUrl}/web_im/v1/web_im/send_msg';
/// 排行榜
static const String getRankApi = "/x/web-interface/ranking/v2";
}

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,21 @@
// ignore_for_file: avoid_print
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'dart:async';
import 'package:dio/dio.dart';
import 'dart:math' show Random;
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 'package:pilipala/utils/id_utils.dart';
import '../utils/storage.dart';
import '../utils/utils.dart';
import 'api.dart';
import 'constants.dart';
import 'interceptor.dart';
class Request {
static final Request _instance = Request._internal();
@ -20,25 +24,26 @@ 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;
static final RegExp spmPrefixExp = RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
/// 设置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 {
@ -50,21 +55,21 @@ class Request {
}
setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null);
if (cookie.isEmpty) {
try {
await Request().get(HttpString.baseUrl);
} catch (e) {
log("setCookie, ${e.toString()}");
}
try {
await buvidActivate();
} catch (e) {
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,17 +78,45 @@ class Request {
return token;
}
static setOptionsHeaders(userInfo, status) {
static setOptionsHeaders(userInfo, bool status) {
if (status) {
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
dio.options.headers['x-bili-aurora-eid'] =
IdUtils.genAuroraEid(userInfo.mid);
}
dio.options.headers['env'] = 'prod';
dio.options.headers['app-key'] = 'android64';
dio.options.headers['x-bili-aurora-eid'] = 'UlMFQVcABlAH';
dio.options.headers['x-bili-aurora-zone'] = 'sh001';
dio.options.headers['referer'] = 'https://www.bilibili.com/';
}
static Future buvidActivate() async {
var html = await Request().get(Api.dynamicSpmPrefix);
String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
Random rand = Random();
String rand_png_end = base64.encode(
List<int>.generate(32, (_) => rand.nextInt(256)) +
List<int>.filled(4, 0) +
[73, 69, 78, 68] +
List<int>.generate(4, (_) => rand.nextInt(256))
);
String jsonData = json.encode({
'3064': 1,
'39c8': '${spmPrefix}.fp.risk',
'3c43': {
'adca': 'Linux',
'bfe9': rand_png_end.substring(rand_png_end.length - 50),
},
});
await Request().post(
Api.activateBuvidApi,
data: {'payload': jsonData},
options: Options(contentType: 'application/json')
);
}
/*
* config it and create
*/
@ -100,30 +133,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 +179,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 +190,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;
@ -175,8 +209,14 @@ class Request {
);
return response;
} on DioException catch (e) {
print('get error: $e');
return Future.error(await ApiInterceptor.dioError(e));
Response errResponse = Response(
data: {
'message': await ApiInterceptor.dioError(e)
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
statusCode: 200,
requestOptions: RequestOptions(),
);
return errResponse;
}
}
@ -197,8 +237,14 @@ class Request {
// print('post success: ${response.data}');
return response;
} on DioException catch (e) {
print('post error: $e');
return Future.error(await ApiInterceptor.dioError(e));
Response errResponse = Response(
data: {
'message': await ApiInterceptor.dioError(e)
}, // 将自定义 Map 数据赋值给 Response 的 data 属性
statusCode: 200,
requestOptions: RequestOptions(),
);
return errResponse;
}
}

View File

@ -1,11 +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 'package:get/get.dart' hide Response;
import '../utils/storage.dart';
class ApiInterceptor extends Interceptor {
@override
@ -21,16 +20,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 (_) {}
}
}
@ -46,54 +45,57 @@ class ApiInterceptor extends Interceptor {
void onError(DioException err, ErrorInterceptorHandler handler) async {
// 处理网络请求错误
// handler.next(err);
SmartDialog.showToast(
await dioError(err),
displayType: SmartToastType.onlyRefresh,
);
String url = err.requestOptions.uri.toString();
if (!url.contains('heartBeat')) {
SmartDialog.showToast(
await dioError(err),
displayType: SmartToastType.onlyRefresh,
);
}
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 checkConnect();
return '$res,网络异常!';
}
}
static Future<dynamic> checkConect() async {
final 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.
} 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)
} else if (connectivityResult == ConnectivityResult.other) {
// I am connected to a network which is not in the above mentioned networks.
} else if (connectivityResult == ConnectivityResult.none) {
return 'not connected to any network';
static Future<String> checkConnect() async {
final List<ConnectivityResult> connectivityResult =
await Connectivity().checkConnectivity();
if (connectivityResult.contains(ConnectivityResult.mobile)) {
return '正在使用移动流量';
} else if (connectivityResult.contains(ConnectivityResult.wifi)) {
return '正在使用wifi';
} else if (connectivityResult.contains(ConnectivityResult.ethernet)) {
return '正在使用局域网';
} else if (connectivityResult.contains(ConnectivityResult.vpn)) {
return '正在使用代理网络';
} else if (connectivityResult.contains(ConnectivityResult.bluetooth)) {
return '正在使用蓝牙网络';
} else if (connectivityResult.contains(ConnectivityResult.other)) {
return '正在使用其他网络';
} else if (connectivityResult.contains(ConnectivityResult.none)) {
return '未连接到任何网络';
} else {
return '';
}
}
}

View File

@ -1,7 +1,8 @@
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 '../models/live/room_info_h5.dart';
import 'api.dart';
import 'init.dart';
class LiveHttp {
static Future liveList(
@ -46,4 +47,22 @@ class LiveHttp {
};
}
}
static Future liveRoomInfoH5({roomId, qn}) async {
var res = await Request().get(Api.liveRoomInfoH5, data: {
'room_id': roomId,
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': RoomInfoH5Model.fromJson(res.data['data'])
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
}

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({
@ -79,6 +79,8 @@ class MemberHttp {
String order = 'pubdate',
bool orderAvoided = true,
}) async {
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
Map params = await WbiSign().makSign({
'mid': mid,
'ps': ps,
@ -88,7 +90,11 @@ class MemberHttp {
'order': order,
'platform': 'web',
'web_location': 1550101,
'order_avoided': orderAvoided
'order_avoided': orderAvoided,
'dm_img_list': '[]',
'dm_img_str': dmImgStr.substring(0, dmImgStr.length - 2),
'dm_cover_img_str': dmCoverImgStr.substring(0, dmCoverImgStr.length - 2),
'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
});
var res = await Request().get(
Api.memberArchive,
@ -101,10 +107,13 @@ class MemberHttp {
'data': MemberArchiveDataModel.fromJson(res.data['data'])
};
} else {
Map errMap = {
-352: '风控校验失败,请检查登录状态',
};
return {
'status': false,
'data': [],
'msg': res.data['message'],
'msg': errMap[res.data['code']] ?? res.data['message'],
};
}
}
@ -123,10 +132,13 @@ class MemberHttp {
'data': DynamicsDataModel.fromJson(res.data['data']),
};
} else {
Map errMap = {
-352: '风控校验失败,请检查登录状态',
};
return {
'status': false,
'data': [],
'msg': res.data['message'],
'msg': errMap[res.data['code']] ?? res.data['message'],
};
}
}
@ -461,4 +473,41 @@ class MemberHttp {
};
}
}
// 搜索follow
static Future getfollowSearch({
required int mid,
required int ps,
required int pn,
required String name,
}) async {
Map<String, dynamic> data = {
'vmid': mid,
'pn': pn,
'ps': ps,
'order': 'desc',
'order_type': 'attention',
'gaia_source': 'main_web',
'name': name,
'web_location': 333.999,
};
Map params = await WbiSign().makSign(data);
var res = await Request().get(Api.followSearch, data: {
...data,
'w_rid': params['w_rid'],
'wts': params['wts'],
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': FollowDataModel.fromJson(res.data['data'])
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
}

View File

@ -1,8 +1,9 @@
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 'dart:math';
import '../models/msg/account.dart';
import '../models/msg/session.dart';
import '../utils/wbi_sign.dart';
import 'api.dart';
import 'init.dart';
class MsgHttp {
// 会话列表
@ -22,14 +23,22 @@ class MsgHttp {
Map signParams = await WbiSign().makSign(params);
var res = await Request().get(Api.sessionList, data: signParams);
if (res.data['code'] == 0) {
return {
'status': true,
'data': SessionDataModel.fromJson(res.data['data']),
};
try {
return {
'status': true,
'data': SessionDataModel.fromJson(res.data['data']),
};
} catch (err) {
return {
'status': false,
'data': [],
'msg': err.toString(),
};
}
} else {
return {
'status': false,
'date': [],
'data': [],
'msg': res.data['message'],
};
}
@ -42,12 +51,16 @@ class MsgHttp {
'mobi_app': 'web',
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data']
.map<AccountListModel>((e) => AccountListModel.fromJson(e))
.toList(),
};
try {
return {
'status': true,
'data': res.data['data']
.map<AccountListModel>((e) => AccountListModel.fromJson(e))
.toList(),
};
} catch (err) {
print('err🔟: $err');
}
} else {
return {
'status': false,
@ -86,4 +99,125 @@ class MsgHttp {
};
}
}
// 消息标记已读
static Future ackSessionMsg({
int? talkerId,
int? ackSeqno,
}) async {
String csrf = await Request.getCsrf();
Map params = await WbiSign().makSign({
'talker_id': talkerId,
'session_type': 1,
'ack_seqno': ackSeqno,
'build': 0,
'mobi_app': 'web',
'csrf_token': csrf,
'csrf': csrf
});
var res = await Request().get(Api.ackSessionMsg, data: params);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
'date': [],
'msg': "message: ${res.data['message']},"
" msg: ${res.data['msg']},"
" code: ${res.data['code']}",
};
}
}
// 发送私信
static Future sendMsg({
int? senderUid,
int? receiverId,
int? receiverType,
int? msgType,
dynamic content,
}) async {
String csrf = await Request.getCsrf();
Map<String, dynamic> params = await WbiSign().makSign({
'msg[sender_uid]': senderUid,
'msg[receiver_id]': receiverId,
'msg[receiver_type]': receiverType ?? 1,
'msg[msg_type]': msgType ?? 1,
'msg[msg_status]': 0,
'msg[dev_id]': getDevId(),
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
'msg[new_face_version]': 0,
'msg[content]': content,
'from_firework': 0,
'build': 0,
'mobi_app': 'web',
'csrf_token': csrf,
'csrf': csrf,
});
var res =
await Request().post(Api.sendMsg, queryParameters: <String, dynamic>{
...params,
'csrf_token': csrf,
'csrf': csrf,
}, data: {
'w_sender_uid': params['msg[sender_uid]'],
'w_receiver_id': params['msg[receiver_id]'],
'w_dev_id': params['msg[dev_id]'],
'w_rid': params['w_rid'],
'wts': params['wts'],
'csrf_token': csrf,
'csrf': csrf,
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
'date': [],
'msg': "message: ${res.data['message']},"
" msg: ${res.data['msg']},"
" code: ${res.data['code']}",
};
}
}
static String getDevId() {
final List<String> b = [
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'A',
'B',
'C',
'D',
'E',
'F'
];
final List<String> s = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".split('');
for (int i = 0; i < s.length; i++) {
if ('-' == s[i] || '4' == s[i]) {
continue;
}
final int randomInt = Random().nextInt(16);
if ('x' == s[i]) {
s[i] = b[randomInt];
} else {
s[i] = b[3 & randomInt | 8];
}
}
return s.join();
}
}

View File

@ -1,6 +1,7 @@
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 '../models/video/reply/emote.dart';
import 'api.dart';
import 'init.dart';
class ReplyHttp {
static Future replyList({
@ -100,4 +101,23 @@ class ReplyHttp {
};
}
}
static Future getEmoteList({String? business}) async {
var res = await Request().get(Api.emojiList, data: {
'business': business ?? 'reply',
'web_location': '333.1245',
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': EmoteModelData.fromJson(res.data['data']),
};
} else {
return {
'status': false,
'date': [],
'msg': res.data['message'],
};
}
}
}

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;
@ -37,7 +36,7 @@ class SearchHttp {
// 获取搜索建议
static Future searchSuggest({required term}) async {
var res = await Request().get(Api.serachSuggest,
var res = await Request().get(Api.searchSuggest,
data: {'term': term, 'main_ver': 'v1', 'highlight': term});
if (res.data is String) {
Map<String, dynamic> resultMap = json.decode(res.data);
@ -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,15 @@
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 '../models/user/sub_detail.dart';
import '../models/user/sub_folder.dart';
import 'api.dart';
import 'init.dart';
class UserHttp {
static Future<dynamic> userStat({required int mid}) async {
@ -306,4 +307,46 @@ class UserHttp {
return {'status': false, 'msg': res.data['message']};
}
}
// 我的订阅
static Future userSubFolder({
required int mid,
required int pn,
required int ps,
}) async {
var res = await Request().get(Api.userSubFolder, data: {
'up_mid': mid,
'ps': ps,
'pn': pn,
'platform': 'web',
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': SubFolderModelData.fromJson(res.data['data'])
};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
static Future userSubFolderDetail({
required int seasonId,
required int pn,
required int ps,
}) async {
var res = await Request().get(Api.userSubFolderDetail, data: {
'season_id': seasonId,
'ps': ps,
'pn': pn,
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': SubDetailModelData.fromJson(res.data['data'])
};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
}

View File

@ -1,19 +1,19 @@
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/recommend_filter.dart';
import '../utils/storage.dart';
import '../utils/wbi_sign.dart';
import 'api.dart';
import 'init.dart';
/// res.data['code'] == 0 请求正常返回结果
/// res.data['data'] 为结果
@ -33,27 +33,41 @@ 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' &&
(i['owner'] != null &&
!blackMidsList.contains(i['owner']['mid']))) {
RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i);
if (!RecommendFilter.filter(videoItem)) {
list.add(videoItem);
}
}
}
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()};
}
}
static Future rcmdVideoListApp({int? ps, required int freshIdx}) async {
// 添加额外的loginState变量模拟未登录状态
static Future rcmdVideoListApp(
{bool loginStatus = true, required int freshIdx}) async {
try {
var res = await Request().get(
Api.recommendListApp,
@ -66,9 +80,11 @@ class VideoHttp {
'device_name': 'vivo',
'pull': freshIdx == 0 ? 'true' : 'false',
'appkey': Constants.appKey,
'access_key': localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
''
'access_key': loginStatus
? (localCache.get(LocalCacheKey.accessKey,
defaultValue: {})['value'] ??
'')
: ''
},
);
if (res.data['code'] == 0) {
@ -81,12 +97,15 @@ class VideoHttp {
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
(i['args'] != null &&
!blackMidsList.contains(i['args']['up_mid']))) {
list.add(RecVideoItemAppModel.fromJson(i));
RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i);
if (!RecommendFilter.filter(videoItem)) {
list.add(videoItem);
}
}
}
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()};
@ -111,7 +130,7 @@ class VideoHttp {
}
return {'status': true, 'data': list};
} else {
return {'status': false, 'data': []};
return {'status': false, 'data': [], 'msg': res.data['message']};
}
} catch (err) {
return {'status': false, 'data': [], 'msg': err};
@ -134,6 +153,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 +167,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) {
@ -196,7 +216,10 @@ class VideoHttp {
if (res.data['code'] == 0) {
List<HotVideoItemModel> list = [];
for (var i in res.data['data']) {
list.add(HotVideoItemModel.fromJson(i));
HotVideoItemModel videoItem = HotVideoItemModel.fromJson(i);
if (!RecommendFilter.filter(videoItem, relatedVideos: true)) {
list.add(videoItem);
}
}
return {'status': true, 'data': list};
} else {
@ -217,10 +240,11 @@ class VideoHttp {
// 获取投币状态
static Future hasCoinVideo({required String bvid}) async {
var res = await Request().get(Api.hasCoinVideo, data: {'bvid': bvid});
print('res: $res');
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': true, 'data': []};
return {'status': false, 'data': []};
}
}
@ -298,7 +322,7 @@ class VideoHttp {
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': false, 'data': []};
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
@ -354,7 +378,7 @@ class VideoHttp {
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': true, 'data': []};
return {'status': false, 'data': []};
}
}
@ -370,7 +394,7 @@ class VideoHttp {
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': true, 'data': []};
return {'status': false, 'data': []};
}
}
@ -426,6 +450,8 @@ class VideoHttp {
});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': false, 'data': null, 'msg': res.data['message']};
}
}
@ -440,11 +466,36 @@ class VideoHttp {
'up_mid': upMid,
});
var res = await Request().get(Api.aiConclusion, data: params);
if (res.data['code'] == 0) {
if (res.data['code'] == 0 && res.data['data']['code'] == 0) {
return {
'status': true,
'data': AiConclusionModel.fromJson(res.data['data']),
};
} else {
return {'status': false, 'data': []};
}
}
// 视频排行
static Future getRankVideoList(int rid) async {
try {
var rankApi = "${Api.getRankApi}?rid=$rid&type=all";
var res = await Request().get(rankApi);
if (res.data['code'] == 0) {
List<HotVideoItemModel> list = [];
List<int> blackMidsList =
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
for (var i in res.data['data']['list']) {
if (!blackMidsList.contains(i['owner']['mid'])) {
list.add(HotVideoItemModel.fromJson(i));
}
}
return {'status': true, 'data': list};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
} catch (err) {
return {'status': false, 'data': [], 'msg': err};
}
}
}

View File

@ -16,11 +16,15 @@ import 'package:pilipala/pages/search/index.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/router/app_pages.dart';
import 'package:pilipala/pages/main/view.dart';
import 'package:pilipala/services/disable_battery_opt.dart';
import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/app_scheme.dart';
import 'package:pilipala/utils/data.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc.
import 'package:pilipala/utils/recommend_filter.dart';
import 'package:catcher_2/catcher_2.dart';
import './services/loggeer.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -30,7 +34,35 @@ void main() async {
.then((_) async {
await GStrorage.init();
await setupServiceLocator();
runApp(const MyApp());
Request();
await Request.setCookie();
RecommendFilter();
// 异常捕获 logo记录
final Catcher2Options debugConfig = Catcher2Options(
SilentReportMode(),
[
FileHandler(await getLogsPath()),
ConsoleHandler(
enableDeviceParameters: false,
enableApplicationParameters: false,
)
],
);
final Catcher2Options releaseConfig = Catcher2Options(
SilentReportMode(),
[FileHandler(await getLogsPath())],
);
Catcher2(
debugConfig: debugConfig,
releaseConfig: releaseConfig,
runAppFunction: () {
runApp(const MyApp());
},
);
// 小白条、导航栏沉浸
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
@ -38,10 +70,9 @@ void main() async {
systemNavigationBarDividerColor: Colors.transparent,
statusBarColor: Colors.transparent,
));
await Request.setCookie();
Data.init();
GStrorage.lazyInit();
PiliSchame.init();
DisableBatteryOpt();
});
}
@ -112,6 +143,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 +164,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 +186,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

@ -0,0 +1,9 @@
enum DynamicBadgeMode { hidden, point, number }
extension DynamicBadgeModeDesc on DynamicBadgeMode {
String get description => ['隐藏', '红点', '数字'][index];
}
extension DynamicBadgeModeCode on DynamicBadgeMode {
int get code => [0, 1, 2][index];
}

View File

@ -7,5 +7,5 @@ enum DynamicsType {
extension BusinessTypeExtension on DynamicsType {
String get values => ['all', 'video', 'pgc', 'article'][index];
String get labels => ['全部', '视频', '', '专栏'][index];
String get labels => ['全部', '投稿', '', '专栏'][index];
}

View File

@ -0,0 +1,12 @@
enum FullScreenGestureMode {
/// 从上滑到下
fromToptoBottom,
/// 从下滑到上
fromBottomtoTop,
}
extension FullScreenGestureModeExtension on FullScreenGestureMode {
String get values => ['fromToptoBottom', 'fromBottomtoTop'][index];
String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏'][index];
}

View File

@ -0,0 +1,4 @@
library commonn_model;
export './business_type.dart';
export './gesture_mode.dart';

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
List defaultNavigationBars = [
{
'id': 0,
'icon': const Icon(
Icons.home_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.home,
size: 21,
),
'label': "首页",
'count': 0,
},
{
'id': 1,
'icon': const Icon(
Icons.trending_up,
size: 21,
),
'selectIcon': const Icon(
Icons.trending_up_outlined,
size: 21,
),
'label': "排行榜",
'count': 0,
},
{
'id': 2,
'icon': const Icon(
Icons.motion_photos_on_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.motion_photos_on,
size: 21,
),
'label': "动态",
'count': 0,
},
{
'id': 3,
'icon': const Icon(
Icons.video_collection_outlined,
size: 20,
),
'selectIcon': const Icon(
Icons.video_collection,
size: 21,
),
'label': "媒体库",
'count': 0,
}
];

View File

@ -0,0 +1,240 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/pages/rank/zone/index.dart';
enum RandType {
all,
creation,
animation,
music,
dance,
game,
knowledge,
technology,
sport,
car,
life,
food,
animal,
madness,
fashion,
entertainment,
film,
origin,
rookie
}
extension RankTypeDesc on RandType {
String get description => [
'全站',
'国创相关',
'动画',
'音乐',
'舞蹈',
'游戏',
'知识',
'科技',
'运动',
'汽车',
'生活',
'美食',
'动物圈',
'鬼畜',
'时尚',
'娱乐',
'影视'
][index];
String get id => [
'all',
'creation',
'animation',
'music',
'dance',
'game',
'knowledge',
'technology',
'sport',
'car',
'life',
'food',
'animal',
'madness',
'fashion',
'entertainment',
'film'
][index];
}
List tabsConfig = [
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '全站',
'type': RandType.all,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 0),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '国创相关',
'type': RandType.creation,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 168),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '动画',
'type': RandType.animation,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 1),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '音乐',
'type': RandType.music,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 3),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '舞蹈',
'type': RandType.dance,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 129),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '游戏',
'type': RandType.game,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 4),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '知识',
'type': RandType.knowledge,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 36),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '科技',
'type': RandType.technology,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 188),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '运动',
'type': RandType.sport,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 234),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '汽车',
'type': RandType.car,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 223),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '生活',
'type': RandType.life,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 160),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '美食',
'type': RandType.food,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 211),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '动物圈',
'type': RandType.animal,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 217),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '鬼畜',
'type': RandType.madness,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 119),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '时尚',
'type': RandType.fashion,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 155),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '娱乐',
'type': RandType.entertainment,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 5),
},
{
'icon': const Icon(
Icons.live_tv_outlined,
size: 15,
),
'label': '影视',
'type': RandType.film,
'ctr': Get.put<ZoneController>,
'page': const ZonePage(rid: 181),
}
];

View File

@ -1,7 +1,7 @@
// 首页推荐类型
enum RcmdType { web, app }
enum RcmdType { web, app, notLogin }
extension RcmdTypeExtension on RcmdType {
String get values => ['web', 'app'][index];
String get labels => ['web端', 'app端'][index];
String get values => ['web', 'app', 'notLogin'][index];
String get labels => ['web端', 'app端', '游客模式'][index];
}

View File

@ -1,6 +1,6 @@
enum ReplySortType { time, like, reply }
enum ReplySortType { time, like }
extension ReplySortTypeExtension on ReplySortType {
String get titles => ['最新评论', '最热评论', '回复最多'][index];
String get labels => ['最新', '最热', '最多回复'][index];
String get titles => ['最新评论', '最热评论'][index];
String get labels => ['最新', '最热'][index];
}

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

@ -2,18 +2,28 @@ class FollowUpModel {
FollowUpModel({
this.liveUsers,
this.upList,
this.liveList,
this.myInfo,
});
LiveUsers? liveUsers;
List<UpItem>? upList;
List<LiveUserItem>? liveList;
MyInfo? myInfo;
FollowUpModel.fromJson(Map<String, dynamic> json) {
liveUsers = json['live_users'] != null
? LiveUsers.fromJson(json['live_users'])
: null;
liveList = json['live_users'] != null
? json['live_users']['items']
.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
.toList()
: [];
upList = json['up_list'] != null
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList()
: [];
myInfo = json['my_info'] != null ? MyInfo.fromJson(json['my_info']) : null;
}
}
@ -93,3 +103,21 @@ class UpItem {
uname = json['uname'];
}
}
class MyInfo {
MyInfo({
this.face,
this.mid,
this.name,
});
String? face;
int? mid;
String? name;
MyInfo.fromJson(Map<String, dynamic> json) {
face = json['face'];
mid = json['mid'];
name = json['name'];
}
}

View File

@ -17,8 +17,9 @@ class LatestDataModel {
url = json['url'];
tagName = json['tag_name'];
createdAt = json['created_at'];
assets =
json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList();
assets = json['assets'] != null
? json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList()
: [];
body = json['body'];
}
}

View File

@ -1,8 +1,5 @@
import 'package:hive/hive.dart';
import 'package:pilipala/utils/id_utils.dart';
part 'result.g.dart';
@HiveType(typeId: 0)
class RecVideoItemAppModel {
RecVideoItemAppModel({
this.id,
@ -27,47 +24,27 @@ class RecVideoItemAppModel {
this.adInfo,
});
@HiveField(0)
int? id;
@HiveField(1)
int? aid;
@HiveField(2)
String? bvid;
@HiveField(3)
int? cid;
@HiveField(4)
String? pic;
@HiveField(5)
RcmdStat? stat;
@HiveField(6)
String? duration;
@HiveField(7)
int? duration;
String? title;
@HiveField(8)
int? isFollowed;
@HiveField(9)
RcmdOwner? owner;
@HiveField(10)
RcmdReason? rcmdReason;
@HiveField(11)
String? goto;
@HiveField(12)
int? param;
@HiveField(13)
String? uri;
@HiveField(14)
String? talkBack;
// 番剧
@HiveField(15)
String? bangumiView;
@HiveField(16)
String? bangumiFollow;
@HiveField(17)
String? bangumiBadge;
@HiveField(18)
String? cardType;
@HiveField(19)
Map? adInfo;
RecVideoItemAppModel.fromJson(Map<String, dynamic> json) {
@ -75,17 +52,32 @@ class RecVideoItemAppModel {
? json['player_args']['aid']
: int.parse(json['param'] ?? '-1');
aid = json['player_args'] != null ? json['player_args']['aid'] : -1;
bvid = null;
bvid = json['player_args'] != null
? IdUtils.av2bv(json['player_args']['aid'])
: '';
cid = json['player_args'] != null ? json['player_args']['cid'] : -1;
pic = json['cover'];
stat = RcmdStat.fromJson(json);
duration = json['cover_right_text'];
// 改用player_args中的duration作为原始数据秒数
duration =
json['player_args'] != null ? json['player_args']['duration'] : -1;
//duration = json['cover_right_text'];
title = json['title'];
isFollowed = 0;
owner = RcmdOwner.fromJson(json);
rcmdReason = json['rcmd_reason_style'] != null
? RcmdReason.fromJson(json['rcmd_reason_style'])
: null;
// 由于app端api并不会直接返回与owner的关注状态
// 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态从而与web端接口等效
isFollowed = rcmdReason != null &&
rcmdReason!.content != null &&
rcmdReason!.content!.contains('关注')
? 1
: 0;
// 如果是就无需再显示推荐原因交由view统一处理即可
if (isFollowed == 1) {
rcmdReason = null;
}
goto = json['goto'];
param = int.parse(json['param']);
uri = json['uri'];
@ -102,18 +94,14 @@ class RecVideoItemAppModel {
}
}
@HiveType(typeId: 1)
class RcmdStat {
RcmdStat({
this.view,
this.like,
this.danmu,
});
@HiveField(0)
String? view;
@HiveField(1)
String? like;
@HiveField(2)
String? danmu;
RcmdStat.fromJson(Map<String, dynamic> json) {
@ -122,13 +110,10 @@ class RcmdStat {
}
}
@HiveType(typeId: 2)
class RcmdOwner {
RcmdOwner({this.name, this.mid});
@HiveField(0)
String? name;
@HiveField(1)
int? mid;
RcmdOwner.fromJson(Map<String, dynamic> json) {
@ -141,13 +126,11 @@ class RcmdOwner {
}
}
@HiveType(typeId: 8)
class RcmdReason {
RcmdReason({
this.content,
});
@HiveField(0)
String? content;
RcmdReason.fromJson(Map<String, dynamic> json) {

View File

@ -1,209 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'result.dart';
// **************************************************************************
// TypeAdapterGenerator
// **************************************************************************
class RecVideoItemAppModelAdapter extends TypeAdapter<RecVideoItemAppModel> {
@override
final int typeId = 0;
@override
RecVideoItemAppModel read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RecVideoItemAppModel(
id: fields[0] as int?,
aid: fields[1] as int?,
bvid: fields[2] as String?,
cid: fields[3] as int?,
pic: fields[4] as String?,
stat: fields[5] as RcmdStat?,
duration: fields[6] as String?,
title: fields[7] as String?,
isFollowed: fields[8] as int?,
owner: fields[9] as RcmdOwner?,
rcmdReason: fields[10] as RcmdReason?,
goto: fields[11] as String?,
param: fields[12] as int?,
uri: fields[13] as String?,
talkBack: fields[14] as String?,
bangumiView: fields[15] as String?,
bangumiFollow: fields[16] as String?,
bangumiBadge: fields[17] as String?,
cardType: fields[18] as String?,
adInfo: (fields[19] as Map?)?.cast<dynamic, dynamic>(),
);
}
@override
void write(BinaryWriter writer, RecVideoItemAppModel obj) {
writer
..writeByte(20)
..writeByte(0)
..write(obj.id)
..writeByte(1)
..write(obj.aid)
..writeByte(2)
..write(obj.bvid)
..writeByte(3)
..write(obj.cid)
..writeByte(4)
..write(obj.pic)
..writeByte(5)
..write(obj.stat)
..writeByte(6)
..write(obj.duration)
..writeByte(7)
..write(obj.title)
..writeByte(8)
..write(obj.isFollowed)
..writeByte(9)
..write(obj.owner)
..writeByte(10)
..write(obj.rcmdReason)
..writeByte(11)
..write(obj.goto)
..writeByte(12)
..write(obj.param)
..writeByte(13)
..write(obj.uri)
..writeByte(14)
..write(obj.talkBack)
..writeByte(15)
..write(obj.bangumiView)
..writeByte(16)
..write(obj.bangumiFollow)
..writeByte(17)
..write(obj.bangumiBadge)
..writeByte(18)
..write(obj.cardType)
..writeByte(19)
..write(obj.adInfo);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RecVideoItemAppModelAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class RcmdStatAdapter extends TypeAdapter<RcmdStat> {
@override
final int typeId = 1;
@override
RcmdStat read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RcmdStat(
view: fields[0] as String?,
like: fields[1] as String?,
danmu: fields[2] as String?,
);
}
@override
void write(BinaryWriter writer, RcmdStat obj) {
writer
..writeByte(3)
..writeByte(0)
..write(obj.view)
..writeByte(1)
..write(obj.like)
..writeByte(2)
..write(obj.danmu);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RcmdStatAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class RcmdOwnerAdapter extends TypeAdapter<RcmdOwner> {
@override
final int typeId = 2;
@override
RcmdOwner read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RcmdOwner(
name: fields[0] as String?,
mid: fields[1] as int?,
);
}
@override
void write(BinaryWriter writer, RcmdOwner obj) {
writer
..writeByte(2)
..writeByte(0)
..write(obj.name)
..writeByte(1)
..write(obj.mid);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RcmdOwnerAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}
class RcmdReasonAdapter extends TypeAdapter<RcmdReason> {
@override
final int typeId = 8;
@override
RcmdReason read(BinaryReader reader) {
final numOfFields = reader.readByte();
final fields = <int, dynamic>{
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
};
return RcmdReason(
content: fields[0] as String?,
);
}
@override
void write(BinaryWriter writer, RcmdReason obj) {
writer
..writeByte(1)
..writeByte(0)
..write(obj.content);
}
@override
int get hashCode => typeId.hashCode;
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is RcmdReasonAdapter &&
runtimeType == other.runtimeType &&
typeId == other.typeId;
}

View File

@ -0,0 +1,43 @@
enum LiveQuality {
dolby,
super4K,
origin,
bluRay,
superHD,
smooth,
flunt,
}
extension LiveQualityCode on LiveQuality {
static final List<int> _codeList = [
30000,
20000,
10000,
400,
250,
150,
80,
];
int get code => _codeList[index];
static LiveQuality? fromCode(int code) {
final index = _codeList.indexOf(code);
if (index != -1) {
return LiveQuality.values[index];
}
return null;
}
}
extension VideoQualityDesc on LiveQuality {
static final List<String> _descList = [
'杜比',
'4K',
'原画',
'蓝光',
'超清',
'高清',
'流畅',
];
get description => _descList[index];
}

View File

@ -0,0 +1,130 @@
class RoomInfoH5Model {
RoomInfoH5Model({
this.roomInfo,
this.anchorInfo,
this.isRoomFeed,
this.watchedShow,
this.likeInfoV3,
this.blockInfo,
});
RoomInfo? roomInfo;
AnchorInfo? anchorInfo;
int? isRoomFeed;
Map? watchedShow;
LikeInfoV3? likeInfoV3;
Map? blockInfo;
RoomInfoH5Model.fromJson(Map<String, dynamic> json) {
roomInfo = RoomInfo.fromJson(json['room_info']);
anchorInfo = AnchorInfo.fromJson(json['anchor_info']);
isRoomFeed = json['is_room_feed'];
watchedShow = json['watched_show'];
likeInfoV3 = LikeInfoV3.fromJson(json['like_info_v3']);
blockInfo = json['block_info'];
}
}
class RoomInfo {
RoomInfo({
this.uid,
this.roomId,
this.title,
this.cover,
this.description,
this.liveStatus,
this.liveStartTime,
this.areaId,
this.areaName,
this.parentAreaId,
this.parentAreaName,
this.online,
this.background,
this.appBackground,
this.liveId,
});
int? uid;
int? roomId;
String? title;
String? cover;
String? description;
int? liveStatus;
int? liveStartTime;
int? areaId;
String? areaName;
int? parentAreaId;
String? parentAreaName;
int? online;
String? background;
String? appBackground;
String? liveId;
RoomInfo.fromJson(Map<String, dynamic> json) {
uid = json['uid'];
roomId = json['room_id'];
title = json['title'];
cover = json['cover'];
description = json['description'];
liveStatus = json['liveS_satus'];
liveStartTime = json['live_start_time'];
areaId = json['area_id'];
areaName = json['area_name'];
parentAreaId = json['parent_area_id'];
parentAreaName = json['parent_area_name'];
online = json['online'];
background = json['background'];
appBackground = json['app_background'];
liveId = json['live_id'];
}
}
class AnchorInfo {
AnchorInfo({
this.baseInfo,
this.relationInfo,
});
BaseInfo? baseInfo;
RelationInfo? relationInfo;
AnchorInfo.fromJson(Map<String, dynamic> json) {
baseInfo = BaseInfo.fromJson(json['base_info']);
relationInfo = RelationInfo.fromJson(json['relation_info']);
}
}
class BaseInfo {
BaseInfo({
this.uname,
this.face,
});
String? uname;
String? face;
BaseInfo.fromJson(Map<String, dynamic> json) {
uname = json['uname'];
face = json['face'];
}
}
class RelationInfo {
RelationInfo({this.attention});
int? attention;
RelationInfo.fromJson(Map<String, dynamic> json) {
attention = json['attention'];
}
}
class LikeInfoV3 {
LikeInfoV3({this.totalLikes});
int? totalLikes;
LikeInfoV3.fromJson(Map<String, dynamic> json) {
totalLikes = json['total_likes'];
}
}

View File

@ -142,7 +142,7 @@ class Stat {
Stat.fromJson(Map<String, dynamic> json) {
view = json["play"];
danmaku = json['comment'];
danmaku = json['video_review'];
}
}

View File

@ -1,5 +1,3 @@
import 'package:pilipala/utils/utils.dart';
import './model_owner.dart';
import 'package:hive/hive.dart';
@ -38,7 +36,7 @@ class RecVideoItemModel {
@HiveField(6)
String? title = '';
@HiveField(7)
String? duration = '';
int? duration = -1;
@HiveField(8)
int? pubdate = -1;
@HiveField(9)
@ -58,7 +56,7 @@ class RecVideoItemModel {
uri = json["uri"];
pic = json["pic"];
title = json["title"];
duration = Utils.tampToSeektime(json["duration"]);
duration = json["duration"];
pubdate = json["pubdate"];
owner = Owner.fromJson(json["owner"]);
stat = Stat.fromJson(json["stat"]);
@ -84,6 +82,7 @@ class Stat {
int? danmu;
Stat.fromJson(Map<String, dynamic> json) {
// 无需在model中转换以保留原始数据在view层处理即可
view = json["view"];
like = json["like"];
danmu = json['danmaku'];

View File

@ -24,7 +24,7 @@ class RecVideoItemModelAdapter extends TypeAdapter<RecVideoItemModel> {
uri: fields[4] as String?,
pic: fields[5] as String?,
title: fields[6] as String?,
duration: fields[7] as String?,
duration: fields[7] as int?,
pubdate: fields[8] as int?,
owner: fields[9] as Owner?,
stat: fields[10] as Stat?,

View File

@ -8,7 +8,7 @@ class SessionDataModel {
this.hasMore,
});
List? sessionList;
List<SessionList>? sessionList;
int? hasMore;
SessionDataModel.fromJson(Map<String, dynamic> json) {
@ -121,35 +121,37 @@ class LastMsg {
this.msgKey,
this.msgStatus,
this.notifyCode,
this.newFaceVersion,
// this.newFaceVersion,
});
int? senderIid;
int? receiverType;
int? receiverId;
int? msgType;
Map? content;
dynamic content;
int? msgSeqno;
int? timestamp;
String? atUids;
int? msgKey;
int? msgStatus;
String? notifyCode;
int? newFaceVersion;
// int? newFaceVersion;
LastMsg.fromJson(Map<String, dynamic> json) {
senderIid = json['sender_uid'];
receiverType = json['receiver_type'];
receiverId = json['receiver_id'];
msgType = json['msg_type'];
content = jsonDecode(json['content']);
content = json['content'] != null && json['content'] != ''
? jsonDecode(json['content'])
: '';
msgSeqno = json['msg_seqno'];
timestamp = json['timestamp'];
atUids = json['at_uids'];
msgKey = json['msg_key'];
msgStatus = json['msg_status'];
notifyCode = json['notify_code'];
newFaceVersion = json['new_face_version'];
// newFaceVersion = json['new_face_version'];
}
}
@ -166,7 +168,7 @@ class SessionMsgDataModel {
int? hasMore;
int? minSeqno;
int? maxSeqno;
List? eInfos;
List<dynamic>? eInfos;
SessionMsgDataModel.fromJson(Map<String, dynamic> json) {
messages = json['messages']
@ -214,7 +216,9 @@ class MessageItem {
receiverId = json['receiver_id'];
// 1 文本 2 图片 18 系统提示 10 系统通知 5 撤回的消息
msgType = json['msg_type'];
content = jsonDecode(json['content']);
content = json['content'] != null && json['content'] != ''
? jsonDecode(json['content'])
: '';
msgSeqno = json['msg_seqno'];
timestamp = json['timestamp'];
atUids = json['at_uids'];

View File

@ -85,7 +85,9 @@ class SearchVideoItemModel {
// title = json['title'].replaceAll(RegExp(r'<.*?>'), '');
title = Em.regTitle(json['title']);
description = json['description'];
pic = 'https:${json['pic']}';
pic = json['pic'] != null && json['pic'].startsWith('//')
? 'https:${json['pic']}'
: json['pic'] ?? '';
videoReview = json['video_review'];
pubdate = json['pubdate'];
senddate = json['senddate'];

View File

@ -15,7 +15,7 @@ class FavFolderData {
? json['list']
.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
.toList()
: [FavFolderItemData()];
: <FavFolderItemData>[];
hasMore = json['has_more'];
}
}

View File

@ -0,0 +1,123 @@
class SubDetailModelData {
DetailInfo? info;
List<SubDetailMediaItem>? medias;
SubDetailModelData({this.info, this.medias});
SubDetailModelData.fromJson(Map<String, dynamic> json) {
info = DetailInfo.fromJson(json['info']);
if (json['medias'] != null) {
medias = <SubDetailMediaItem>[];
json['medias'].forEach((v) {
medias!.add(SubDetailMediaItem.fromJson(v));
});
}
}
}
class SubDetailMediaItem {
int? id;
String? title;
String? cover;
String? pic;
int? duration;
int? pubtime;
String? bvid;
Map? upper;
Map? cntInfo;
int? enableVt;
String? vtDisplay;
SubDetailMediaItem({
this.id,
this.title,
this.cover,
this.pic,
this.duration,
this.pubtime,
this.bvid,
this.upper,
this.cntInfo,
this.enableVt,
this.vtDisplay,
});
SubDetailMediaItem.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
cover = json['cover'];
pic = json['cover'];
duration = json['duration'];
pubtime = json['pubtime'];
bvid = json['bvid'];
upper = json['upper'];
cntInfo = json['cnt_info'];
enableVt = json['enable_vt'];
vtDisplay = json['vt_display'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['id'] = id;
data['title'] = title;
data['cover'] = cover;
data['duration'] = duration;
data['pubtime'] = pubtime;
data['bvid'] = bvid;
data['upper'] = upper;
data['cnt_info'] = cntInfo;
data['enable_vt'] = enableVt;
data['vt_display'] = vtDisplay;
return data;
}
}
class DetailInfo {
int? id;
int? seasonType;
String? title;
String? cover;
Map? upper;
Map? cntInfo;
int? mediaCount;
String? intro;
int? enableVt;
DetailInfo({
this.id,
this.seasonType,
this.title,
this.cover,
this.upper,
this.cntInfo,
this.mediaCount,
this.intro,
this.enableVt,
});
DetailInfo.fromJson(Map<String, dynamic> json) {
id = json['id'];
seasonType = json['season_type'];
title = json['title'];
cover = json['cover'];
upper = json['upper'];
cntInfo = json['cnt_info'];
mediaCount = json['media_count'];
intro = json['intro'];
enableVt = json['enable_vt'];
}
Map<String, dynamic> toJson() {
final data = <String, dynamic>{};
data['id'] = id;
data['season_type'] = seasonType;
data['title'] = title;
data['cover'] = cover;
data['upper'] = upper;
data['cnt_info'] = cntInfo;
data['media_count'] = mediaCount;
data['intro'] = intro;
data['enable_vt'] = enableVt;
return data;
}
}

View File

@ -0,0 +1,111 @@
class SubFolderModelData {
final int? count;
final List<SubFolderItemData>? list;
SubFolderModelData({
this.count,
this.list,
});
factory SubFolderModelData.fromJson(Map<String, dynamic> json) {
return SubFolderModelData(
count: json['count'],
list: json['list'] != null
? (json['list'] as List)
.map<SubFolderItemData>((i) => SubFolderItemData.fromJson(i))
.toList()
: null,
);
}
}
class SubFolderItemData {
final int? id;
final int? fid;
final int? mid;
final int? attr;
final String? title;
final String? cover;
final Upper? upper;
final int? coverType;
final String? intro;
final int? ctime;
final int? mtime;
final int? state;
final int? favState;
final int? mediaCount;
final int? viewCount;
final int? vt;
final int? playSwitch;
final int? type;
final String? link;
final String? bvid;
SubFolderItemData({
this.id,
this.fid,
this.mid,
this.attr,
this.title,
this.cover,
this.upper,
this.coverType,
this.intro,
this.ctime,
this.mtime,
this.state,
this.favState,
this.mediaCount,
this.viewCount,
this.vt,
this.playSwitch,
this.type,
this.link,
this.bvid,
});
factory SubFolderItemData.fromJson(Map<String, dynamic> json) {
return SubFolderItemData(
id: json['id'],
fid: json['fid'],
mid: json['mid'],
attr: json['attr'],
title: json['title'],
cover: json['cover'],
upper: json['upper'] != null ? Upper.fromJson(json['upper']) : null,
coverType: json['cover_type'],
intro: json['intro'],
ctime: json['ctime'],
mtime: json['mtime'],
state: json['state'],
favState: json['fav_state'],
mediaCount: json['media_count'],
viewCount: json['view_count'],
vt: json['vt'],
playSwitch: json['play_switch'],
type: json['type'],
link: json['link'],
bvid: json['bvid'],
);
}
}
class Upper {
final int? mid;
final String? name;
final String? face;
Upper({
this.mid,
this.name,
this.face,
});
factory Upper.fromJson(Map<String, dynamic> json) {
return Upper(
mid: json['mid'],
name: json['name'],
face: json['face'],
);
}
}

View File

@ -34,6 +34,7 @@ class PlayUrlModel {
String? seekParam;
String? seekType;
Dash? dash;
List<Durl>? durl;
List<FormatItem>? supportFormats;
// String? highFormat;
int? lastPlayTime;
@ -52,7 +53,8 @@ class PlayUrlModel {
videoCodecid = json['video_codecid'];
seekParam = json['seek_param'];
seekType = json['seek_type'];
dash = Dash.fromJson(json['dash']);
dash = json['dash'] != null ? Dash.fromJson(json['dash']) : null;
durl = json['durl']?.map<Durl>((e) => Durl.fromJson(e)).toList();
supportFormats = json['support_formats'] != null
? json['support_formats']
.map<FormatItem>((e) => FormatItem.fromJson(e))
@ -250,3 +252,30 @@ class Flac {
audio = json['audio'] != null ? AudioItem.fromJson(json['audio']) : null;
}
}
class Durl {
Durl({
this.order,
this.length,
this.size,
this.ahead,
this.vhead,
this.url,
});
int? order;
int? length;
int? size;
String? ahead;
String? vhead;
String? url;
Durl.fromJson(Map<String, dynamic> json) {
order = json['order'];
length = json['length'];
size = json['size'];
ahead = json['ahead'];
vhead = json['vhead'];
url = json['url'];
}
}

View File

@ -9,6 +9,7 @@ class ReplyContent {
this.vote,
this.richText,
this.isText,
this.topicsMeta,
});
String? message;
@ -20,6 +21,7 @@ class ReplyContent {
Map? vote;
Map? richText;
bool? isText;
Map? topicsMeta;
ReplyContent.fromJson(Map<String, dynamic> json) {
message = json['message']
@ -39,6 +41,7 @@ class ReplyContent {
richText = json['rich_text'] ?? {};
// 不包含@ 笔记 图片的时候,文字可折叠
isText = atNameToMid!.isEmpty && vote!.isEmpty && pictures!.isEmpty;
topicsMeta = json['topics_meta'] ?? {};
}
}

View File

@ -0,0 +1,120 @@
class EmoteModelData {
final List<PackageItem>? packages;
EmoteModelData({
required this.packages,
});
factory EmoteModelData.fromJson(Map<String, dynamic> jsonRes) {
final List<PackageItem>? packages =
jsonRes['packages'] is List ? <PackageItem>[] : null;
if (packages != null) {
for (final dynamic item in jsonRes['packages']!) {
if (item != null) {
try {
packages.add(PackageItem.fromJson(item));
} catch (_) {}
}
}
}
return EmoteModelData(
packages: packages,
);
}
}
class PackageItem {
final int? id;
final String? text;
final String? url;
final int? mtime;
final int? type;
final int? attr;
final Meta? meta;
final List<Emote>? emote;
PackageItem({
required this.id,
required this.text,
required this.url,
required this.mtime,
required this.type,
required this.attr,
required this.meta,
required this.emote,
});
factory PackageItem.fromJson(Map<String, dynamic> jsonRes) {
final List<Emote>? emote = jsonRes['emote'] is List ? <Emote>[] : null;
if (emote != null) {
for (final dynamic item in jsonRes['emote']!) {
if (item != null) {
try {
emote.add(Emote.fromJson(item));
} catch (_) {}
}
}
}
return PackageItem(
id: jsonRes['id'],
text: jsonRes['text'],
url: jsonRes['url'],
mtime: jsonRes['mtime'],
type: jsonRes['type'],
attr: jsonRes['attr'],
meta: Meta.fromJson(jsonRes['meta']),
emote: emote,
);
}
}
class Meta {
final int? size;
final List<String>? suggest;
Meta({
required this.size,
required this.suggest,
});
factory Meta.fromJson(Map<String, dynamic> jsonRes) => Meta(
size: jsonRes['size'],
suggest: jsonRes['suggest'] is List ? <String>[] : null,
);
}
class Emote {
final int? id;
final int? packageId;
final String? text;
final String? url;
final int? mtime;
final int? type;
final int? attr;
final Meta? meta;
final dynamic activity;
Emote({
required this.id,
required this.packageId,
required this.text,
required this.url,
required this.mtime,
required this.type,
required this.attr,
required this.meta,
required this.activity,
});
factory Emote.fromJson(Map<String, dynamic> jsonRes) => Emote(
id: jsonRes['id'],
packageId: jsonRes['package_id'],
text: jsonRes['text'],
url: jsonRes['url'],
mtime: jsonRes['mtime'],
type: jsonRes['type'],
attr: jsonRes['attr'],
meta: Meta.fromJson(jsonRes['meta']),
activity: jsonRes['activity'],
);
}

View File

@ -7,6 +7,7 @@ import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/github/latest.dart';
import 'package:pilipala/utils/utils.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../utils/cache_manage.dart';
class AboutPage extends StatefulWidget {
const AboutPage({super.key});
@ -17,10 +18,23 @@ class AboutPage extends StatefulWidget {
class _AboutPageState extends State<AboutPage> {
final AboutController _aboutController = Get.put(AboutController());
String cacheSize = '';
@override
void initState() {
super.initState();
// 读取缓存占用
getCacheSize();
}
Future<void> getCacheSize() async {
final res = await CacheManage().loadApplicationCache();
setState(() => cacheSize = res);
}
@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 +43,6 @@ class _AboutPageState extends State<AboutPage> {
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'assets/images/logo/logo_android_2.png',
@ -40,29 +53,54 @@ class _AboutPageState extends State<AboutPage> {
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 6),
Text(
'使用Flutter开发的哔哩哔哩第三方客户端',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
const SizedBox(height: 20),
Obx(
() => ListTile(
title: const Text("当前版本"),
trailing: Text(_aboutController.currentVersion.value,
style: subTitleStyle),
),
),
Obx(
() => ListTile(
onTap: () => _aboutController.onUpdate(),
title: const Text('最新版本'),
trailing: Text(
_aboutController.isLoading.value
? '正在获取'
: _aboutController.isUpdate.value
? '有新版本 ❤️${_aboutController.remoteVersion.value}'
: '当前已是最新版',
style: subTitleStyle,
() => Badge(
isLabelVisible: _aboutController.isLoading.value
? false
: _aboutController.isUpdate.value,
label: const Text('New'),
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 30),
child: FilledButton.tonal(
onPressed: () {
showModalBottomSheet(
context: context,
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
onTap: () => _aboutController.githubRelease(),
title: const Text('Github下载'),
),
ListTile(
onTap: () => _aboutController.panDownload(),
title: const Text('网盘下载'),
),
ListTile(
onTap: () => _aboutController.webSiteUrl(),
title: const Text('官网下载'),
),
ListTile(
onTap: () => _aboutController.qimiao(),
title: const Text('奇妙应用'),
),
SizedBox(
height:
MediaQuery.of(context).padding.bottom +
20)
],
);
},
);
},
child: Text(
'V${_aboutController.currentVersion.value}',
style: subTitleStyle.copyWith(
color: Theme.of(context).primaryColor,
),
),
),
),
),
),
@ -74,19 +112,22 @@ class _AboutPageState extends State<AboutPage> {
// size: 16,
// ),
// ),
Divider(
thickness: 1,
height: 30,
color: Theme.of(context).colorScheme.outlineVariant,
),
ListTile(
onTap: () => _aboutController.githubUrl(),
title: const Text('Github'),
title: const Text('开源地址'),
trailing: Text(
'github.com/guozhigq/pilipala',
style: subTitleStyle,
),
),
ListTile(
onTap: () => _aboutController.webSiteUrl(),
title: const Text('访问官网'),
trailing: Text(
'https://pilipalanet.mysxl.cn',
style: subTitleStyle,
),
),
ListTile(
onTap: () => _aboutController.panDownload(),
title: const Text('网盘下载'),
@ -108,24 +149,64 @@ class _AboutPageState extends State<AboutPage> {
),
),
ListTile(
onTap: () => _aboutController.qqChanel(),
title: const Text('QQ群'),
onTap: () {
showModalBottomSheet(
context: context,
builder: (context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
onTap: () => _aboutController.qqChanel(),
title: const Text('QQ群'),
trailing: Text(
'616150809',
style: subTitleStyle,
),
),
ListTile(
onTap: () => _aboutController.tgChanel(),
title: const Text('TG频道'),
trailing: Text(
'https://t.me/+lm_oOVmF0RJiODk1',
style: subTitleStyle,
),
),
SizedBox(
height: MediaQuery.of(context).padding.bottom + 20)
],
);
},
);
},
title: const Text('交流社区'),
trailing: Icon(
Icons.arrow_forward_ios,
size: 16,
color: outline,
),
),
ListTile(
onTap: () => _aboutController.tgChanel(),
title: const Text('TG频道'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
),
ListTile(
onTap: () => _aboutController.aPay(),
title: const Text('赞助'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
),
ListTile(
onTap: () => _aboutController.logs(),
title: const Text('错误日志'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
),
ListTile(
onTap: () async {
var cleanStatus = await CacheManage().clearCacheAll();
if (cleanStatus) {
getCacheSize();
}
},
title: const Text('清除缓存'),
subtitle: Text('图片及网络缓存 $cacheSize', style: subTitleStyle),
),
SizedBox(height: MediaQuery.of(context).padding.bottom + 20)
],
),
),
@ -172,12 +253,16 @@ class AboutController extends GetxController {
// 获取远程版本
Future getRemoteApp() async {
var result = await Request().get(Api.latestApp, extra: {'ua': 'pc'});
isLoading.value = false;
if (result.data == null || result.data.isEmpty) {
SmartDialog.showToast('获取远程版本失败,请检查网络');
return;
}
data = LatestDataModel.fromJson(result.data);
remoteAppInfo = data;
remoteVersion.value = data.tagName!;
isUpdate.value =
Utils.needUpdate(currentVersion.value, remoteVersion.value);
isLoading.value = false;
}
// 跳转下载/本地更新
@ -193,11 +278,26 @@ class AboutController extends GetxController {
);
}
githubRelease() {
launchUrl(
Uri.parse('https://github.com/guozhigq/pilipala/releases'),
mode: LaunchMode.externalApplication,
);
}
// 从网盘下载
panDownload() {
launchUrl(
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
mode: LaunchMode.externalApplication,
Clipboard.setData(
const ClipboardData(text: 'pili'),
);
SmartDialog.showToast(
'已复制提取码pili',
displayTime: const Duration(milliseconds: 500),
).then(
(value) => launchUrl(
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
mode: LaunchMode.externalApplication,
),
);
}
@ -213,7 +313,7 @@ class AboutController extends GetxController {
// qq频道
qqChanel() {
Clipboard.setData(
const ClipboardData(text: '489981949'),
const ClipboardData(text: '616150809'),
);
SmartDialog.showToast('已复制QQ群号');
}
@ -245,4 +345,24 @@ class AboutController extends GetxController {
print(e);
}
}
// 官网
webSiteUrl() {
launchUrl(
Uri.parse('https://pilipalanet.mysxl.cn'),
mode: LaunchMode.externalApplication,
);
}
qimiao() {
launchUrl(
Uri.parse('https://www.magicalapk.com/home'),
mode: LaunchMode.externalApplication,
);
}
// 日志
logs() {
Get.toNamed('/logs');
}
}

View File

@ -7,8 +7,8 @@ import 'package:pilipala/utils/storage.dart';
class BangumiController extends GetxController {
final ScrollController scrollController = ScrollController();
RxList<BangumiListItemModel> bangumiList = [BangumiListItemModel()].obs;
RxList<BangumiListItemModel> bangumiFollowList = [BangumiListItemModel()].obs;
RxList<BangumiListItemModel> bangumiList = <BangumiListItemModel>[].obs;
RxList<BangumiListItemModel> bangumiFollowList = <BangumiListItemModel>[].obs;
int _currentPage = 1;
bool isLoadingMore = true;
Box userInfoCache = GStrorage.userInfo;

View File

@ -25,13 +25,6 @@ class BangumiIntroController extends GetxController {
? int.tryParse(Get.parameters['epId']!)
: null;
// 是否预渲染 骨架屏
bool preRender = false;
// 视频详情 上个页面传入
Map? videoItem = {};
BangumiInfoModel? bangumiItem;
// 请求状态
RxBool isLoading = false.obs;
@ -63,27 +56,6 @@ class BangumiIntroController extends GetxController {
@override
void onInit() {
super.onInit();
if (Get.arguments.isNotEmpty) {
if (Get.arguments.containsKey('bangumiItem')) {
preRender = true;
bangumiItem = Get.arguments['bangumiItem'];
// bangumiItem!['pic'] = args.pic;
// if (args.title is String) {
// videoItem!['title'] = args.title;
// } else {
// String str = '';
// for (Map map in args.title) {
// str += map['text'];
// }
// videoItem!['title'] = str;
// }
// if (args.stat != null) {
// videoItem!['stat'] = args.stat;
// }
// videoItem!['pubdate'] = args.pubdate;
// videoItem!['owner'] = args.owner;
}
}
userInfo = userInfoCache.get('userInfoCache');
userLogin = userInfo != null;
}
@ -183,20 +155,21 @@ class BangumiIntroController extends GetxController {
actions: [
TextButton(onPressed: () => Get.back(), child: const Text('取消')),
TextButton(
onPressed: () async {
var res = await VideoHttp.coinVideo(
bvid: bvid, multiply: _tempThemeValue);
if (res['status']) {
SmartDialog.showToast('投币成功 👏');
hasCoin.value = true;
bangumiDetail.value.stat!['coins'] =
bangumiDetail.value.stat!['coins'] + _tempThemeValue;
} else {
SmartDialog.showToast(res['msg']);
}
Get.back();
},
child: const Text('确定'))
onPressed: () async {
var res = await VideoHttp.coinVideo(
bvid: bvid, multiply: _tempThemeValue);
if (res['status']) {
SmartDialog.showToast('投币成功 👏');
hasCoin.value = true;
bangumiDetail.value.stat!['coins'] =
bangumiDetail.value.stat!['coins'] + _tempThemeValue;
} else {
SmartDialog.showToast(res['msg']);
}
Get.back();
},
child: const Text('确定'),
)
],
);
});
@ -218,14 +191,12 @@ class BangumiIntroController extends GetxController {
addIds: addMediaIdsNew.join(','),
delIds: delMediaIdsNew.join(','));
if (result['status']) {
if (result['data']['prompt']) {
addMediaIdsNew = [];
delMediaIdsNew = [];
Get.back();
// 重新获取收藏状态
queryHasFavVideo();
SmartDialog.showToast('✅ 操作成功');
}
addMediaIdsNew = [];
delMediaIdsNew = [];
// 重新获取收藏状态
queryHasFavVideo();
SmartDialog.showToast('✅ 操作成功');
Get.back();
}
}

View File

@ -12,11 +12,10 @@ import 'package:pilipala/models/bangumi/info.dart';
import 'package:pilipala/pages/bangumi/widgets/bangumi_panel.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/video/detail/introduction/widgets/action_item.dart';
import 'package:pilipala/pages/video/detail/introduction/widgets/action_row_item.dart';
import 'package:pilipala/pages/video/detail/introduction/widgets/fav_panel.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
import '../../../common/widgets/http_error.dart';
import 'controller.dart';
import 'widgets/intro_detail.dart';
@ -51,12 +50,8 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
cid = widget.cid!;
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
bangumiIntroController.bangumiDetail.listen((value) {
bangumiDetail = value;
});
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
videoDetailCtr.cid.listen((p0) {
print('🐶🐶$p0');
videoDetailCtr.cid.listen((int p0) {
cid = p0;
setState(() {});
});
@ -67,29 +62,34 @@ 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 == null) {
return const SliverToBoxAdapter(child: SizedBox());
}
if (snapshot.data['status']) {
// 请求成功
return BangumiInfo(
loadingStatus: false,
bangumiDetail: bangumiDetail,
cid: cid,
return Obx(
() => BangumiInfo(
bangumiDetail: bangumiIntroController.bangumiDetail.value,
cid: cid,
),
);
} else {
// 请求错误
// return HttpError(
// errMsg: snapshot.data['msg'],
// fn: () => Get.back(),
// );
return SizedBox();
return HttpError(
errMsg: snapshot.data['msg'],
fn: () => Get.back(),
);
}
} else {
return BangumiInfo(
loadingStatus: true,
bangumiDetail: bangumiDetail,
cid: cid,
return const SliverToBoxAdapter(
child: SizedBox(
height: 100,
child: Center(
child: CircularProgressIndicator(),
),
),
);
}
},
@ -98,16 +98,14 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
}
class BangumiInfo extends StatefulWidget {
final bool loadingStatus;
final BangumiInfoModel? bangumiDetail;
final int? cid;
const BangumiInfo({
Key? key,
this.loadingStatus = false,
super.key,
this.bangumiDetail,
this.cid,
}) : super(key: key);
});
final BangumiInfoModel? bangumiDetail;
final int? cid;
@override
State<BangumiInfo> createState() => _BangumiInfoState();
@ -118,29 +116,28 @@ class _BangumiInfoState extends State<BangumiInfo> {
late final BangumiIntroController bangumiIntroController;
late final VideoDetailController videoDetailCtr;
Box localCache = GStrorage.localCache;
late final BangumiInfoModel? bangumiItem;
late double sheetHeight;
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();
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
bangumiItem = bangumiIntroController.bangumiItem;
sheetHeight = localCache.get('sheetHeight');
cid = widget.cid!;
print('cid: $cid');
videoDetailCtr.cid.listen((p0) {
cid = p0;
print('cid: $cid');
setState(() {});
});
}
@ -155,7 +152,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
builder: (BuildContext context) {
return FavPanel(ctr: bangumiIntroController);
},
);
@ -175,218 +172,166 @@ 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),
sliver: SliverToBoxAdapter(
child: !widget.loadingStatus || bangumiItem != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
NetworkImgLayer(
width: 105,
height: 160,
src: !widget.loadingStatus
? widget.bangumiDetail!.cover!
: bangumiItem!.cover!,
),
if (bangumiItem != null &&
bangumiItem!.rating != null)
PBadge(
text:
'评分 ${!widget.loadingStatus ? widget.bangumiDetail!.rating!['score']! : bangumiItem!.rating!['score']!}',
top: null,
right: 6,
bottom: 6,
left: null,
NetworkImgLayer(
width: 105,
height: 160,
src: widget.bangumiDetail!.cover!,
),
PBadge(
text: '评分 ${widget.bangumiDetail!.rating!['score']!}',
top: null,
right: 6,
bottom: 6,
left: null,
),
],
),
const SizedBox(width: 10),
Expanded(
child: InkWell(
onTap: () => showIntroDetail(),
child: SizedBox(
height: 158,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Text(
widget.bangumiDetail!.title!,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(width: 10),
Expanded(
child: InkWell(
onTap: () => showIntroDetail(),
child: SizedBox(
height: 158,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
Expanded(
child: Text(
!widget.loadingStatus
? widget.bangumiDetail!.title!
: bangumiItem!.title!,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 20),
SizedBox(
width: 34,
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith(
(states) {
return t
.colorScheme.primaryContainer
.withOpacity(0.7);
}),
),
onPressed: () =>
bangumiIntroController.bangumiAdd(),
icon: Icon(
Icons.favorite_border_rounded,
color: t.colorScheme.primary,
size: 22,
),
),
),
],
const SizedBox(width: 20),
SizedBox(
width: 34,
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith(
(Set<MaterialState> states) {
return t.colorScheme.primaryContainer
.withOpacity(0.7);
}),
),
Row(
children: [
StatView(
theme: 'gray',
view: !widget.loadingStatus
? widget.bangumiDetail!.stat!['views']
: bangumiItem!.stat!['views'],
size: 'medium',
),
const SizedBox(width: 6),
StatDanMu(
theme: 'gray',
danmu: !widget.loadingStatus
? widget
.bangumiDetail!.stat!['danmakus']
: bangumiItem!.stat!['danmakus'],
size: 'medium',
),
],
onPressed: () =>
bangumiIntroController.bangumiAdd(),
icon: Icon(
Icons.favorite_border_rounded,
color: t.colorScheme.primary,
size: 22,
),
const SizedBox(height: 6),
Row(
children: [
Text(
!widget.loadingStatus
? (widget.bangumiDetail!.areas!
.isNotEmpty
? widget.bangumiDetail!.areas!
.first['name']
: '')
: (bangumiItem!.areas!.isNotEmpty
? bangumiItem!
.areas!.first['name']
: ''),
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
const SizedBox(width: 6),
Text(
!widget.loadingStatus
? widget.bangumiDetail!
.publish!['pub_time_show']
: bangumiItem!
.publish!['pub_time_show'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
],
),
// const SizedBox(height: 4),
Text(
!widget.loadingStatus
? widget.bangumiDetail!.newEp!['desc']
: bangumiItem!.newEp!['desc'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
// const SizedBox(height: 10),
const Spacer(),
Text(
'简介:${!widget.loadingStatus ? widget.bangumiDetail!.evaluate! : bangumiItem!.evaluate!}',
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: t.colorScheme.outline,
),
),
],
),
),
],
),
Row(
children: [
StatView(
theme: 'gray',
view: widget.bangumiDetail!.stat!['views'],
size: 'medium',
),
const SizedBox(width: 6),
StatDanMu(
theme: 'gray',
danmu: widget.bangumiDetail!.stat!['danmakus'],
size: 'medium',
),
],
),
const SizedBox(height: 6),
Row(
children: [
Text(
(widget.bangumiDetail!.areas!.isNotEmpty
? widget.bangumiDetail!.areas!.first['name']
: ''),
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
const SizedBox(width: 6),
Text(
widget.bangumiDetail!.publish!['pub_time_show'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
],
),
Text(
widget.bangumiDetail!.newEp!['desc'],
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
),
),
],
const Spacer(),
Text(
'简介:${widget.bangumiDetail!.evaluate!}',
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: t.colorScheme.outline,
),
),
],
),
),
const SizedBox(height: 6),
// 点赞收藏转发 布局样式1
// SingleChildScrollView(
// padding: const EdgeInsets.only(top: 7, bottom: 7),
// scrollDirection: Axis.horizontal,
// child: actionRow(
// context,
// bangumiIntroController,
// videoDetailCtr,
// ),
// ),
// 点赞收藏转发 布局样式2
actionGrid(context, bangumiIntroController),
// 番剧分p
if ((!widget.loadingStatus &&
widget.bangumiDetail!.episodes!.isNotEmpty) ||
bangumiItem != null &&
bangumiItem!.episodes!.isNotEmpty) ...[
BangumiPanel(
pages: bangumiItem != null
? bangumiItem!.episodes!
: widget.bangumiDetail!.episodes!,
cid: cid ??
(bangumiItem != null
? bangumiItem!.episodes!.first.cid
: widget.bangumiDetail!.episodes!.first.cid),
sheetHeight: sheetHeight,
changeFuc: (bvid, cid, aid) => bangumiIntroController
.changeSeasonOrbangu(bvid, cid, aid),
)
],
],
)
: const SizedBox(
height: 100,
child: Center(
child: CircularProgressIndicator(),
),
),
),
],
),
const SizedBox(height: 6),
/// 点赞收藏转发
actionGrid(context, bangumiIntroController),
// 番剧分p
if (widget.bangumiDetail!.episodes!.isNotEmpty) ...[
BangumiPanel(
pages: widget.bangumiDetail!.episodes!,
cid: cid ?? widget.bangumiDetail!.episodes!.first.cid,
sheetHeight: sheetHeight,
changeFuc: (bvid, cid, aid) =>
bangumiIntroController.changeSeasonOrbangu(bvid, cid, aid),
bangumiDetail: bangumiIntroController.bangumiDetail.value,
)
],
],
)),
);
}
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,61 +339,50 @@ 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>[
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: handleState(bangumiIntroController.actionLikeVideo),
selectStatus: bangumiIntroController.hasLike.value,
loadingStatus: false,
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['likes']!.toString()
: bangumiItem!.stat!['likes']!.toString()),
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: handleState(bangumiIntroController.actionLikeVideo),
selectStatus: bangumiIntroController.hasLike.value,
text: widget.bangumiDetail!.stat!['likes']!.toString(),
),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: handleState(bangumiIntroController.actionCoinVideo),
selectStatus: bangumiIntroController.hasCoin.value,
loadingStatus: false,
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['coins']!.toString()
: bangumiItem!.stat!['coins']!.toString()),
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: handleState(bangumiIntroController.actionCoinVideo),
selectStatus: bangumiIntroController.hasCoin.value,
text: widget.bangumiDetail!.stat!['coins']!.toString(),
),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => showFavBottomSheet(),
selectStatus: bangumiIntroController.hasFav.value,
loadingStatus: false,
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['favorite']!.toString()
: bangumiItem!.stat!['favorite']!.toString()),
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => showFavBottomSheet(),
selectStatus: bangumiIntroController.hasFav.value,
text: widget.bangumiDetail!.stat!['favorite']!.toString(),
),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.comment),
selectIcon: const Icon(FontAwesomeIcons.reply),
onTap: () => videoDetailCtr.tabCtr.animateTo(1),
selectStatus: false,
loadingStatus: false,
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['reply']!.toString()
: bangumiItem!.stat!['reply']!.toString(),
text: widget.bangumiDetail!.stat!['reply']!.toString(),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => bangumiIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: false,
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['share']!.toString()
: bangumiItem!.stat!['share']!.toString()),
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => bangumiIntroController.actionShareVideo(),
selectStatus: false,
text: widget.bangumiDetail!.stat!['share']!.toString(),
),
],
),
),
@ -456,63 +390,4 @@ class _BangumiInfoState extends State<BangumiInfo> {
);
});
}
Widget actionRow(BuildContext context, videoIntroController, videoDetailCtr) {
return Row(children: [
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
onTap: handleState(videoIntroController.actionLikeVideo),
selectStatus: videoIntroController.hasLike.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['likes']!.toString()
: '-',
),
),
const SizedBox(width: 8),
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.b),
onTap: handleState(videoIntroController.actionCoinVideo),
selectStatus: videoIntroController.hasCoin.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['coins']!.toString()
: '-',
),
),
const SizedBox(width: 8),
Obx(
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.heart),
onTap: () => showFavBottomSheet(),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['favorite']!.toString()
: '-',
),
),
const SizedBox(width: 8),
ActionRowItem(
icon: const Icon(FontAwesomeIcons.comment),
onTap: () {
videoDetailCtr.tabCtr.animateTo(1);
},
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['reply']!.toString()
: '-',
),
const SizedBox(width: 8),
ActionRowItem(
icon: const Icon(FontAwesomeIcons.share),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
loadingStatus: widget.loadingStatus,
text: '转发'),
]);
}
}

View File

@ -4,11 +4,11 @@ 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';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/rcmd/view.dart';
import 'controller.dart';
import 'widgets/bangumu_card_v.dart';
@ -74,7 +74,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 +112,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 +157,10 @@ class _BangumiPageState extends State<BangumiPage>
),
);
} else {
return const SizedBox();
return nil;
}
} else {
return const SizedBox();
return nil;
}
},
),
@ -188,7 +189,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']) {
@ -197,7 +198,10 @@ class _BangumiPageState extends State<BangumiPage>
} else {
return HttpError(
errMsg: data['msg'],
fn: () => {},
fn: () {
_futureBuilderFuture =
_bangumidController.queryBangumiListFeed();
},
);
}
} else {
@ -206,7 +210,6 @@ class _BangumiPageState extends State<BangumiPage>
},
),
),
LoadingMore()
],
),
);
@ -222,13 +225,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,19 +8,21 @@ 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,
this.cid,
this.sheetHeight,
this.changeFuc,
this.bangumiDetail,
});
final List<EpisodeItem> pages;
final int? cid;
final double? sheetHeight;
final Function? changeFuc;
final BangumiInfoModel? bangumiDetail;
@override
State<BangumiPanel> createState() => _BangumiPanelState();
}
@ -50,10 +52,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();
});
}
@ -65,6 +67,47 @@ class _BangumiPanelState extends State<BangumiPanel> {
super.dispose();
}
Widget buildPageListItem(
EpisodeItem page,
int index,
bool isCurrentIndex,
) {
Color primary = Theme.of(context).colorScheme.primary;
return ListTile(
onTap: () {
Get.back();
setState(() {
changeFucCall(page, index);
});
},
dense: false,
leading: isCurrentIndex
? Image.asset(
'assets/images/live.gif',
color: primary,
height: 12,
)
: null,
title: Text(
'${page.title}${page.longTitle!}',
style: TextStyle(
fontSize: 14,
color: isCurrentIndex
? primary
: Theme.of(context).colorScheme.onSurface,
),
),
trailing: page.badge != null
? Text(
page.badge!,
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
)
: const SizedBox(),
);
}
void showBangumiPanel() {
showBottomSheet(
context: context,
@ -105,37 +148,22 @@ class _BangumiPanelState extends State<BangumiPanel> {
Expanded(
child: Material(
child: ScrollablePositionedList.builder(
itemCount: widget.pages.length,
itemBuilder: (context, index) => ListTile(
onTap: () {
setState(() {
changeFucCall(widget.pages[index], index);
});
},
dense: false,
leading: index == currentIndex
? Image.asset(
'assets/images/live.gif',
color: Theme.of(context).colorScheme.primary,
height: 12,
itemCount: widget.pages.length + 1,
itemBuilder: (BuildContext context, int index) {
bool isLastItem = index == widget.pages.length;
bool isCurrentIndex = currentIndex == index;
return isLastItem
? SizedBox(
height:
MediaQuery.of(context).padding.bottom +
20,
)
: null,
title: Text(
'${index + 1}${widget.pages[index].longTitle!}',
style: TextStyle(
fontSize: 14,
color: index == currentIndex
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
),
trailing: widget.pages[index].badge != null
? Image.asset(
'assets/images/big-vip.png',
height: 20,
)
: const SizedBox(),
),
: buildPageListItem(
widget.pages[index],
index,
isCurrentIndex,
);
},
itemScrollController: itemScrollController,
),
),
@ -150,7 +178,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
}
void changeFucCall(item, i) async {
if (item.badge != null && vipStatus != 1) {
if (item.badge != null && item.badge == '会员' && vipStatus != 1) {
SmartDialog.showToast('需要大会员');
return;
}
@ -177,11 +205,11 @@ class _BangumiPanelState extends State<BangumiPanel> {
return Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 10, bottom: 6),
padding: const EdgeInsets.only(top: 10, bottom: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(''),
const Text(''),
Expanded(
child: Text(
' 正在播放:${widget.pages[currentIndex].longTitle}',
@ -201,7 +229,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
),
onPressed: () => showBangumiPanel(),
child: Text(
'${widget.pages.length}',
'${widget.bangumiDetail!.newEp!['desc']}',
style: const TextStyle(fontSize: 13),
),
),
@ -212,78 +240,79 @@ 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',
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: 6)
],
Text(
'${i + 1}',
style: TextStyle(
fontSize: 13,
color: i == currentIndex
? Theme.of(context).colorScheme.primary
: Theme.of(context)
.colorScheme
.onSurface),
),
const SizedBox(width: 2),
if (widget.pages[i].badge != null) ...[
const Spacer(),
Text(
widget.pages[i].badge!,
style: TextStyle(
fontSize: 12,
color:
Theme.of(context).colorScheme.primary,
height: 12,
),
const SizedBox(width: 6)
],
Text(
'${i + 1}',
style: TextStyle(
fontSize: 13,
color: i == currentIndex
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.onSurface),
),
const SizedBox(width: 2),
if (widget.pages[i].badge != null) ...[
Image.asset(
'assets/images/big-vip.png',
height: 16,
),
],
],
),
const SizedBox(height: 3),
Text(
widget.pages[i].longTitle!,
maxLines: 1,
style: TextStyle(
fontSize: 13,
color: i == currentIndex
? Theme.of(context).colorScheme.primary
: Theme.of(context)
.colorScheme
.onSurface),
overflow: TextOverflow.ellipsis,
)
],
),
]
],
),
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']) {
@ -139,7 +139,7 @@ class BlackListController extends GetxController {
int currentPage = 1;
int pageSize = 50;
RxInt total = 0.obs;
RxList<BlackListItem> blackList = [BlackListItem()].obs;
RxList<BlackListItem> blackList = <BlackListItem>[].obs;
Future queryBlacklist({type = 'init'}) async {
if (type == 'init') {

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';
@ -36,6 +35,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
late double opacityVal;
late double fontSizeVal;
late double danmakuDurationVal;
late double strokeWidth;
int latestAddedPosition = -1;
@override
@ -43,15 +43,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,14 +59,14 @@ 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;
showArea = playerController.showArea;
opacityVal = playerController.opacityVal;
fontSizeVal = playerController.fontSizeVal;
strokeWidth = playerController.strokeWidth;
danmakuDurationVal = playerController.danmakuDurationVal;
}
@ -87,7 +85,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
return;
}
int currentPosition = position.inMilliseconds;
currentPosition -= currentPosition % 100;//取整百的毫秒数
currentPosition -= currentPosition % 100; //取整百的毫秒数
if (currentPosition == latestAddedPosition) {
return;
@ -98,17 +96,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 +127,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 +136,9 @@ 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,
strokeWidth: strokeWidth,
// initDuration /
// (danmakuSpeedVal * widget.playerController.playbackSpeed),
),

View File

@ -20,7 +20,7 @@ import 'package:pilipala/utils/utils.dart';
class DynamicsController extends GetxController {
int page = 1;
String? offset = '';
RxList<DynamicItemModel> dynamicsList = [DynamicItemModel()].obs;
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs;
Rx<DynamicsType> dynamicsType = DynamicsType.values[0].obs;
RxString dynamicsTypeLabel = '全部'.obs;
final ScrollController scrollController = ScrollController();
@ -105,7 +105,7 @@ class DynamicsController extends GetxController {
onSelectType(value) async {
dynamicsType.value = filterTypeList[value]['value'];
dynamicsList.value = [DynamicItemModel()];
dynamicsList.value = <DynamicItemModel>[];
page = 1;
initialValue.value = value;
await queryFollowDynamic();
@ -249,8 +249,8 @@ class DynamicsController extends GetxController {
return {'status': false, 'msg': '账号未登录'};
}
if (type == 'init') {
upData.value.upList = [];
upData.value.liveUsers = LiveUsers();
upData.value.upList = <UpItem>[];
upData.value.liveList = <LiveUserItem>[];
}
var res = await DynamicsHttp.followUp();
if (res['status']) {
@ -258,20 +258,23 @@ class DynamicsController extends GetxController {
if (upData.value.upList!.isEmpty) {
mid.value = -1;
}
upData.value.upList!.insertAll(0, [
UpItem(face: '', uname: '全部动态', mid: -1),
UpItem(face: userInfo.face, uname: '', mid: userInfo.mid),
]);
}
return res;
}
onSelectUp(mid) async {
dynamicsType.value = DynamicsType.values[0];
dynamicsList.value = [DynamicItemModel()];
dynamicsList.value = <DynamicItemModel>[];
page = 1;
queryFollowDynamic();
}
onRefresh() async {
page = 1;
print('onRefresh');
await queryFollowUp();
await queryFollowDynamic();
}
@ -293,7 +296,7 @@ class DynamicsController extends GetxController {
dynamicsType.value = DynamicsType.values[0];
initialValue.value = 0;
SmartDialog.showToast('还原默认加载');
dynamicsList.value = [DynamicItemModel()];
dynamicsList.value = <DynamicItemModel>[];
queryFollowDynamic();
}
}

View File

@ -17,7 +17,7 @@ class DynamicDetailController extends GetxController {
int currentPage = 0;
bool isLoadingMore = false;
RxString noMore = ''.obs;
RxList<ReplyItemModel> replyList = [ReplyItemModel()].obs;
RxList<ReplyItemModel> replyList = <ReplyItemModel>[].obs;
RxInt acount = 0.obs;
final ScrollController scrollController = ScrollController();
@ -37,6 +37,10 @@ class DynamicDetailController extends GetxController {
}
int deaultReplySortIndex =
setting.get(SettingBoxKey.replySortType, defaultValue: 0);
if (deaultReplySortIndex == 2) {
setting.put(SettingBoxKey.replySortType, 0);
deaultReplySortIndex = 0;
}
_sortType = ReplySortType.values[deaultReplySortIndex];
sortTypeTitle.value = _sortType.titles;
sortTypeLabel.value = _sortType.labels;
@ -92,9 +96,6 @@ class DynamicDetailController extends GetxController {
_sortType = ReplySortType.like;
break;
case ReplySortType.like:
_sortType = ReplySortType.reply;
break;
case ReplySortType.reply:
_sortType = ReplySortType.time;
break;
default:

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

@ -14,6 +14,7 @@ import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
import '../mine/controller.dart';
import 'controller.dart';
import 'widgets/dynamic_panel.dart';
import 'widgets/up_panel.dart';
@ -28,6 +29,7 @@ class DynamicsPage extends StatefulWidget {
class _DynamicsPageState extends State<DynamicsPage>
with AutomaticKeepAliveClientMixin {
final DynamicsController _dynamicsController = Get.put(DynamicsController());
final MineController mineController = Get.put(MineController());
late Future _futureBuilderFuture;
late Future _futureBuilderFutureUp;
Box userInfoCache = GStrorage.userInfo;
@ -192,22 +194,6 @@ class _DynamicsPageState extends State<DynamicsPage>
)
],
),
// Obx(
// () => Visibility(
// visible: _dynamicsController.userLogin.value,
// child: Positioned(
// right: 4,
// top: 0,
// bottom: 0,
// child: IconButton(
// padding: EdgeInsets.zero,
// onPressed: () =>
// {feedBack(), _dynamicsController.resetSearch()},
// icon: const Icon(Icons.history, size: 21),
// ),
// ),
// ),
// ),
],
),
),
@ -229,7 +215,8 @@ class _DynamicsPageState extends State<DynamicsPage>
return Obx(() => UpPanel(_dynamicsController.upData.value));
} else {
return const SliverToBoxAdapter(
child: SizedBox(height: 80));
child: SizedBox(height: 80),
);
}
} else {
return const SliverToBoxAdapter(
@ -240,15 +227,6 @@ class _DynamicsPageState extends State<DynamicsPage>
}
},
),
SliverToBoxAdapter(
child: Container(
height: 6,
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.5),
),
),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
@ -280,6 +258,14 @@ class _DynamicsPageState extends State<DynamicsPage>
}
},
);
} else if (data['msg'] == "账号未登录") {
return HttpError(
errMsg: data['msg'],
btnText: "去登录",
fn: () {
mineController.onLogin();
},
);
} else {
return HttpError(
errMsg: data['msg'],

View File

@ -34,25 +34,25 @@ Widget articlePanel(item, context, {floor = 1}) {
),
const SizedBox(height: 8),
],
Text(
item.modules.moduleDynamic.major.opus.title,
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 2),
if (item.modules.moduleDynamic.major.opus.summary.text !=
'undefined') ...[
Text(
item.modules.moduleDynamic.major.opus.summary.richTextNodes.first
.text,
maxLines: 4,
style: const TextStyle(height: 1.55),
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
],
// Text(
// item.modules.moduleDynamic.major.opus.title,
// style: Theme.of(context)
// .textTheme
// .titleMedium!
// .copyWith(fontWeight: FontWeight.bold),
// ),
// const SizedBox(height: 2),
// if (item.modules.moduleDynamic.major.opus.summary.text !=
// 'undefined') ...[
// Text(
// item.modules.moduleDynamic.major.opus.summary.richTextNodes.first
// .text,
// maxLines: 4,
// style: const TextStyle(height: 1.55),
// overflow: TextOverflow.ellipsis,
// ),
// const SizedBox(height: 2),
// ],
picWidget(item, context)
],
),

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,7 @@
// 内容
import 'package:flutter/material.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/models/dynamics/result.dart';
import 'package:pilipala/pages/preview/index.dart';
@ -43,11 +45,20 @@ class _ContentState extends State<Content> {
if (len == 1) {
OpusPicsModel pictureItem = pics.first;
picList.add(pictureItem.url!);
spanChilds.add(const TextSpan(text: '\n'));
/// 图片上方的空白间隔
// spanChilds.add(const TextSpan(text: '\n'));
spanChilds.add(
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 +69,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 > Get.size.height * 0.9
? const PBadge(
text: '长图',
right: 8,
bottom: 8,
)
: const SizedBox(),
],
)),
);
},
),
@ -83,6 +105,7 @@ class _ContentState extends State<Content> {
list.add(
LayoutBuilder(
builder: (context, BoxConstraints box) {
double maxWidth = box.maxWidth.truncateToDouble();
return GestureDetector(
onTap: () {
showDialog(
@ -95,8 +118,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 +132,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,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
@ -87,7 +88,7 @@ Widget picWidget(item, context) {
childAspectRatio: aspectRatio,
children: list,
),
if (len == 1 && origAspectRatio < 0.4)
if (len == 1 && height > Get.size.height * 0.9)
const PBadge(
text: '长图',
top: null,

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) {
@ -17,6 +19,17 @@ InlineSpan richNode(item, context) {
// 动态页面 richTextNodes 层级可能与主页动态层级不同
richTextNodes =
item.modules.moduleDynamic.major.opus.summary.richTextNodes;
if (item.modules.moduleDynamic.major.opus.title != null) {
spanChilds.add(
TextSpan(
text: item.modules.moduleDynamic.major.opus.title + '\n',
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
),
);
}
}
if (richTextNodes == null || richTextNodes.isEmpty) {
return spacer;
@ -191,6 +204,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

@ -1,16 +1,14 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/dynamics/up.dart';
import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/pages/dynamics/controller.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
class UpPanel extends StatefulWidget {
final FollowUpModel? upData;
final FollowUpModel upData;
const UpPanel(this.upData, {Key? key}) : super(key: key);
@override
@ -24,39 +22,22 @@ class _UpPanelState extends State<UpPanel> {
List<UpItem> upList = [];
List<LiveUserItem> liveList = [];
static const itemPadding = EdgeInsets.symmetric(horizontal: 5, vertical: 0);
Box userInfoCache = GStrorage.userInfo;
var userInfo;
late MyInfo userInfo;
@override
void initState() {
super.initState();
upList = widget.upData!.upList!;
if (widget.upData!.liveUsers != null) {
liveList = widget.upData!.liveUsers!.items!;
}
upList.insert(
0,
UpItem(
face: 'https://files.catbox.moe/8uc48f.png', uname: '全部动态', mid: -1),
);
userInfo = userInfoCache.get('userInfoCache');
upList.insert(
1,
UpItem(
face: userInfo.face,
uname: '',
mid: userInfo.mid,
),
);
void listFormat() {
userInfo = widget.upData.myInfo!;
upList = widget.upData.upList!;
liveList = widget.upData.liveList!;
}
@override
Widget build(BuildContext context) {
listFormat();
return SliverPersistentHeader(
floating: true,
pinned: false,
delegate: _SliverHeaderDelegate(
height: 124,
height: liveList.isNotEmpty || upList.isNotEmpty ? 126 : 0,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@ -91,7 +72,7 @@ class _UpPanelState extends State<UpPanel> {
color: Theme.of(context).colorScheme.background,
child: Row(
children: [
Expanded(
Flexible(
child: ListView(
scrollDirection: Axis.horizontal,
controller: scrollController,
@ -121,6 +102,13 @@ class _UpPanelState extends State<UpPanel> {
],
),
),
Container(
height: 6,
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.5),
),
],
)),
);
@ -139,7 +127,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) {
@ -171,6 +159,9 @@ class _UpPanelState extends State<UpPanel> {
},
onLongPress: () {
feedBack();
if (data.mid == -1) {
return;
}
String heroTag = Utils.makeHeroTag(data.mid);
Get.toNamed('/member?mid=${data.mid}',
arguments: {'face': data.face, 'heroTag': heroTag});
@ -198,12 +189,19 @@ class _UpPanelState extends State<UpPanel> {
backgroundColor: data.type == 'live'
? Theme.of(context).colorScheme.secondaryContainer
: Theme.of(context).colorScheme.primary,
child: NetworkImgLayer(
width: 49,
height: 49,
src: data.face,
type: 'avatar',
),
child: data.face != ''
? NetworkImgLayer(
width: 50,
height: 50,
src: data.face,
type: 'avatar',
)
: const CircleAvatar(
radius: 25,
backgroundImage: AssetImage(
'assets/images/noface.jpeg',
),
),
),
Padding(
padding: const EdgeInsets.only(top: 4),
@ -271,13 +269,11 @@ class UpPanelSkeleton extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 49,
height: 49,
width: 50,
height: 50,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: const BorderRadius.all(
Radius.circular(24),
),
borderRadius: BorderRadius.circular(50),
),
),
Container(

View File

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../http/reply.dart';
import '../../models/video/reply/emote.dart';
class EmotePanelController extends GetxController
with GetTickerProviderStateMixin {
late List<PackageItem> emotePackage;
late TabController tabController;
Future getEmote() async {
var res = await ReplyHttp.getEmoteList(business: 'reply');
if (res['status']) {
emotePackage = res['data'].packages;
tabController = TabController(length: emotePackage.length, vsync: this);
}
return res;
}
}

View File

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

116
lib/pages/emote/view.dart Normal file
View File

@ -0,0 +1,116 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../models/video/reply/emote.dart';
import 'controller.dart';
class EmotePanel extends StatefulWidget {
final Function onChoose;
const EmotePanel({super.key, required this.onChoose});
@override
State<EmotePanel> createState() => _EmotePanelState();
}
class _EmotePanelState extends State<EmotePanel>
with AutomaticKeepAliveClientMixin {
final EmotePanelController _emotePanelController =
Get.put(EmotePanelController());
late Future _futureBuilderFuture;
@override
bool get wantKeepAlive => true;
@override
void initState() {
_futureBuilderFuture = _emotePanelController.getEmote();
super.initState();
}
@override
Widget build(BuildContext context) {
super.build(context);
return FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
List<PackageItem> emotePackage =
_emotePanelController.emotePackage;
return Column(
children: [
Expanded(
child: TabBarView(
controller: _emotePanelController.tabController,
children: emotePackage.map(
(e) {
int size = e.emote!.first.meta!.size!;
int type = e.type!;
return Padding(
padding: const EdgeInsets.fromLTRB(12, 6, 12, 0),
child: GridView.builder(
gridDelegate:
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: size == 1 ? 40 : 60,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
),
itemCount: e.emote!.length,
itemBuilder: (context, index) {
return Material(
color: Colors.transparent,
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
child: InkWell(
onTap: () {
widget.onChoose(e, e.emote![index]);
},
child: Padding(
padding: const EdgeInsets.all(3),
child: type == 4
? Text(
e.emote![index].text!,
overflow: TextOverflow.clip,
maxLines: 1,
)
: Image.network(
e.emote![index].url!,
width: size * 38,
height: size * 38,
),
),
),
);
},
),
);
},
).toList(),
)),
Divider(
height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
TabBar(
controller: _emotePanelController.tabController,
dividerColor: Colors.transparent,
isScrollable: true,
tabs: _emotePanelController.emotePackage
.map((e) => Tab(text: e.text))
.toList(),
),
SizedBox(height: MediaQuery.of(context).padding.bottom + 20),
],
);
} else {
return Center(child: Text(data['msg']));
}
} else {
return const Center(child: Text('加载中...'));
}
});
}
}

View File

@ -10,7 +10,7 @@ class FansController extends GetxController {
int pn = 1;
int ps = 20;
int total = 0;
RxList<FansItemModel> fansList = [FansItemModel()].obs;
RxList<FansItemModel> fansList = <FansItemModel>[].obs;
late int mid;
late String name;
var userInfo;

View File

@ -24,7 +24,7 @@ class FavController extends GetxController {
if (!hasMore.value) {
return;
}
var res = await await UserHttp.userfavFolder(
var res = await UserHttp.userfavFolder(
pn: currentPage,
ps: pageSize,
mid: userInfo!.mid!,

View File

@ -1,216 +0,0 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:pilipala/common/constants.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/video.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';
// 收藏视频卡片 - 水平布局
class FavVideoCardH extends StatelessWidget {
final dynamic videoItem;
final Function? callFn;
const FavVideoCardH({Key? key, required this.videoItem, this.callFn})
: super(key: key);
@override
Widget build(BuildContext context) {
int id = videoItem.id;
String bvid = videoItem.bvid ?? IdUtils.av2bv(id);
String heroTag = Utils.makeHeroTag(id);
return InkWell(
onTap: () async {
// int? seasonId;
String? epId;
if (videoItem.ogv != null && videoItem.ogv['type_name'] == '番剧') {
videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
// seasonId = videoItem.ogv['season_id'];
epId = videoItem.epId;
} else if (videoItem.page == 0 || videoItem.page > 1) {
var result = await VideoHttp.videoIntro(bvid: bvid);
if (result['status']) {
epId = result['data'].epId;
}
}
Map<String, String> parameters = {
'bvid': bvid,
'cid': videoItem.cid.toString(),
'epId': epId ?? '',
};
// if (seasonId != null) {
// parameters['seasonId'] = seasonId.toString();
// }
Get.toNamed('/video', parameters: parameters, arguments: {
'videoItem': videoItem,
'heroTag': heroTag,
'videoType':
epId != null ? SearchType.media_bangumi : SearchType.video,
});
},
child: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace, 5, StyleString.safeSpace, 5),
child: LayoutBuilder(
builder: (context, boxConstraints) {
double width =
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
return SizedBox(
height: width / StyleString.aspectRatio,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
double maxWidth = boxConstraints.maxWidth;
double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
Hero(
tag: heroTag,
child: NetworkImgLayer(
src: videoItem.pic,
width: maxWidth,
height: maxHeight,
),
),
Positioned(
right: 4,
bottom: 4,
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 1, horizontal: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: Colors.black54.withOpacity(0.4)),
child: Text(
Utils.timeFormat(videoItem.duration!),
style: const TextStyle(
fontSize: 11, color: Colors.white),
),
),
)
],
);
},
),
),
VideoContent(videoItem: videoItem, callFn: callFn)
],
),
);
},
),
),
],
),
);
}
}
class VideoContent extends StatelessWidget {
final dynamic videoItem;
final Function? callFn;
const VideoContent({super.key, required this.videoItem, this.callFn});
@override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
videoItem.title,
textAlign: TextAlign.start,
style: const TextStyle(
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Text(
Utils.dateFormat(videoItem.ctime!),
style: TextStyle(
fontSize: 11, color: Theme.of(context).colorScheme.outline),
),
Text(
videoItem.owner.name,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
Row(
children: [
StatView(
theme: 'gray',
view: videoItem.cntInfo['play'],
),
const SizedBox(width: 8),
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku']),
const Spacer(),
SizedBox(
width: 26,
height: 26,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('要取消收藏吗?'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.outline),
)),
TextButton(
onPressed: () async {
await callFn!();
Get.back();
},
child: const Text('确定取消'),
)
],
);
},
);
},
icon: Icon(
Icons.clear_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
),
),
],
),
],
),
),
);
}
}

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