Compare commits

...

243 Commits

Author SHA1 Message Date
955d8f5401 feat: 简单实现字幕功能 2024-03-24 23:25:45 +08:00
1f75a7e781 fix: appbar滑动距离移除 2024-03-24 16:54:19 +08:00
2cd8ab7d27 mod: 视频详情页简介查看&操作栏 2024-03-24 16:54:19 +08:00
862ccea879 Merge branch 'main' into feature-playerSubtitle 2024-03-24 13:48:15 +08:00
031d57e1fd opt: 排行榜切换tab数据缓存 2024-03-24 13:47:54 +08:00
4db5a950f3 mod: 弹幕开关状态 2024-03-24 11:43:44 +08:00
c9327c97e5 fix: 评论投票message重复 2024-03-24 11:43:44 +08:00
8ff387d54a mod: 评论头部样式 2024-03-24 11:43:44 +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
42ed67e03f merge main 2024-03-17 22:42:46 +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
1eb2d23fb9 merge main 2024-03-11 22:36:36 +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
ee368d348d feat: 字幕展示 2024-02-27 22:50:02 +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
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
23c8b34189 fix: app端推荐屏蔽时间显示,播放量与弹幕组件改为动态类型 2024-01-26 16:40:29 +08:00
932be48125 mod: 推荐、搜索页添加时间,修复视频搜索页无法筛选和回顶 2024-01-26 16:38: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
180 changed files with 8463 additions and 3333 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

@ -223,6 +223,10 @@
android:pathPattern="/mobile/video/.*" /> android:pathPattern="/mobile/video/.*" />
<data android:scheme="https" android:host="www.bilibili.com" <data android:scheme="https" android:host="www.bilibili.com"
android:pathPattern="/mobile/video/.*" /> 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> </intent-filter>
</activity> </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

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

@ -22,20 +22,27 @@ class HttpError extends StatelessWidget {
"assets/images/error.svg", "assets/images/error.svg",
height: 200, height: 200,
), ),
const SizedBox(height: 20), const SizedBox(height: 30),
Text( Text(
errMsg ?? '请求异常', errMsg ?? '请求异常',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
const SizedBox(height: 30), const SizedBox(height: 20),
OutlinedButton.icon( FilledButton.tonal(
onPressed: () { onPressed: () {
fn!(); fn!();
}, },
icon: const Icon(Icons.arrow_forward_outlined, size: 20), style: ButtonStyle(
label: Text(btnText ?? '点击重试'), 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

@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/utils/extension.dart'; import 'package:pilipala/utils/extension.dart';
import 'package:pilipala/utils/global_data.dart';
import '../../utils/storage.dart'; import '../../utils/storage.dart';
import '../constants.dart'; import '../constants.dart';
@ -32,8 +33,10 @@ class NetworkImgLayer extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final int defaultImgQuality = GlobalData().imgQuality;
final String imageUrl = final String imageUrl =
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? 100}q.webp'; '${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? defaultImgQuality}q.webp';
print(imageUrl);
int? memCacheWidth, memCacheHeight; int? memCacheWidth, memCacheHeight;
double aspectRatio = (width / height).toDouble(); double aspectRatio = (width / height).toDouble();
@ -81,7 +84,7 @@ class NetworkImgLayer extends StatelessWidget {
fadeOutDuration ?? const Duration(milliseconds: 120), fadeOutDuration ?? const Duration(milliseconds: 120),
fadeInDuration: fadeInDuration:
fadeInDuration ?? const Duration(milliseconds: 120), fadeInDuration ?? const Duration(milliseconds: 120),
filterQuality: FilterQuality.high, filterQuality: FilterQuality.low,
errorWidget: (BuildContext context, String url, Object error) => errorWidget: (BuildContext context, String url, Object error) =>
placeholder(context), placeholder(context),
placeholder: (BuildContext context, String url) => placeholder: (BuildContext context, String url) =>
@ -104,17 +107,19 @@ class NetworkImgLayer extends StatelessWidget {
? 0 ? 0
: StyleString.imgRadius.x), : StyleString.imgRadius.x),
), ),
child: Center( child: type == 'bg'
child: Image.asset( ? const SizedBox()
type == 'avatar' : Center(
? 'assets/images/noface.jpeg' child: Image.asset(
: 'assets/images/loading.png', type == 'avatar'
width: width, ? 'assets/images/noface.jpeg'
height: height, : 'assets/images/loading.png',
cacheWidth: width.cacheSize(context), width: width,
cacheHeight: height.cacheSize(context), height: height,
), cacheWidth: width.cacheSize(context),
), cacheHeight: height.cacheSize(context),
),
),
); );
} }
} }

View File

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

View File

@ -38,6 +38,10 @@ class VideoCardH extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final int aid = videoItem.aid; final int aid = videoItem.aid;
final String bvid = videoItem.bvid; final String bvid = videoItem.bvid;
String type = 'video';
try {
type = videoItem.type;
} catch (_) {}
final String heroTag = Utils.makeHeroTag(aid); final String heroTag = Utils.makeHeroTag(aid);
return GestureDetector( return GestureDetector(
onLongPress: () { onLongPress: () {
@ -53,6 +57,10 @@ class VideoCardH extends StatelessWidget {
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
try { try {
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');
return;
}
final int cid = final int cid =
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid); videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
Get.toNamed('/video?bvid=$bvid&cid=$cid', Get.toNamed('/video?bvid=$bvid&cid=$cid',
@ -95,12 +103,20 @@ class VideoCardH extends StatelessWidget {
height: maxHeight, height: maxHeight,
), ),
), ),
PBadge( if (videoItem.duration != 0)
text: Utils.timeFormat(videoItem.duration!), PBadge(
right: 6.0, text: Utils.timeFormat(videoItem.duration!),
bottom: 6.0, right: 6.0,
type: 'gray', bottom: 6.0,
), type: 'gray',
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: 'primary',
),
// if (videoItem.rcmdReason != null && // if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '') // videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context, // pBadge(videoItem.rcmdReason.content, context,
@ -324,8 +340,9 @@ class VideoContent extends StatelessWidget {
reSrc: 11, reSrc: 11,
); );
SmartDialog.dismiss(); SmartDialog.dismiss();
SmartDialog.showToast( SmartDialog.showToast(res['code'] == 0
res['msg'] ?? '成功'); ? '成功'
: res['msg']);
}, },
child: const Text('确认'), child: const Text('确认'),
) )

View File

@ -1,6 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import '../../models/model_rec_video_item.dart';
import 'stat/danmu.dart';
import 'stat/view.dart';
import '../../http/dynamics.dart'; import '../../http/dynamics.dart';
import '../../http/search.dart'; import '../../http/search.dart';
import '../../http/user.dart'; import '../../http/user.dart';
@ -158,12 +161,12 @@ class VideoCardV extends StatelessWidget {
height: maxHeight, height: maxHeight,
), ),
), ),
if (videoItem.duration != null) if (videoItem.duration > 0)
if (crossAxisCount == 1) ...[ if (crossAxisCount == 1) ...[
PBadge( PBadge(
bottom: 10, bottom: 10,
right: 10, right: 10,
text: videoItem.duration, text: Utils.timeFormat(videoItem.duration),
) )
] else ...[ ] else ...[
PBadge( PBadge(
@ -171,7 +174,7 @@ class VideoCardV extends StatelessWidget {
right: 7, right: 7,
size: 'small', size: 'small',
type: 'gray', type: 'gray',
text: videoItem.duration, text: Utils.timeFormat(videoItem.duration),
) )
], ],
], ],
@ -228,6 +231,7 @@ class VideoContent extends StatelessWidget {
const SizedBox(height: 2), const SizedBox(height: 2),
VideoStat( VideoStat(
videoItem: videoItem, videoItem: videoItem,
crossAxisCount: crossAxisCount,
), ),
], ],
if (crossAxisCount == 1) const SizedBox(height: 4), if (crossAxisCount == 1) const SizedBox(height: 4),
@ -291,6 +295,7 @@ class VideoContent extends StatelessWidget {
), ),
VideoStat( VideoStat(
videoItem: videoItem, videoItem: videoItem,
crossAxisCount: crossAxisCount,
), ),
const Spacer(), const Spacer(),
], ],
@ -314,29 +319,41 @@ class VideoContent extends StatelessWidget {
class VideoStat extends StatelessWidget { class VideoStat extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final int crossAxisCount;
const VideoStat({ const VideoStat({
Key? key, Key? key,
required this.videoItem, required this.videoItem,
required this.crossAxisCount,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return RichText( return Row(
maxLines: 1, children: [
text: TextSpan( StatView(
style: TextStyle( theme: 'gray',
fontSize: MediaQuery.textScalerOf(context) view: videoItem.stat.view,
.scale(Theme.of(context).textTheme.labelSmall!.fontSize!),
color: Theme.of(context).colorScheme.outline,
), ),
children: [ const SizedBox(width: 8),
if (videoItem.stat.view != '-') StatDanMu(
TextSpan(text: '${videoItem.stat.view}观看'), theme: 'gray',
if (videoItem.stat.danmu != '-') danmu: videoItem.stat.danmu,
TextSpan(text: '${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),
]
],
); );
} }
} }

View File

@ -214,6 +214,9 @@ class Api {
// https://api.bilibili.com/x/relation/tags // https://api.bilibili.com/x/relation/tags
static const String followingsClass = '/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 // vmid 用户id pn 页码 ps 每页个数最大50 order: desc
// order_type 排序规则 最近访问传空,最常访问传 attention // order_type 排序规则 最近访问传空,最常访问传 attention
@ -230,6 +233,10 @@ class Api {
static const String liveRoomInfo = static const String liveRoomInfo =
'${HttpString.liveBaseUrl}/xlive/web-room/v2/index/getRoomPlayInfo'; '${HttpString.liveBaseUrl}/xlive/web-room/v2/index/getRoomPlayInfo';
// 直播间详情 H5
static const String liveRoomInfoH5 =
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getH5InfoByRoom';
// 用户信息 需要Wbi签名 // 用户信息 需要Wbi签名
// https://api.bilibili.com/x/space/wbi/acc/info?mid=503427686&token=&platform=web&web_location=1550101&w_rid=d709892496ce93e3d94d6d37c95bde91&wts=1689301482 // 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'; static const String memberInfo = '/x/space/wbi/acc/info';
@ -470,4 +477,33 @@ class Api {
/// 获取未读动态数 /// 获取未读动态数
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance'; 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 getSubtitleConfig = '/x/player/v2';
/// 我的订阅
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,7 +1,9 @@
// ignore_for_file: avoid_print // ignore_for_file: avoid_print
import 'dart:async'; import 'dart:async';
import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'dart:io'; import 'dart:io';
import 'dart:math' show Random;
import 'package:cookie_jar/cookie_jar.dart'; import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio/io.dart'; import 'package:dio/io.dart';
@ -11,6 +13,7 @@ import 'package:hive/hive.dart';
import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/id_utils.dart';
import '../utils/storage.dart'; import '../utils/storage.dart';
import '../utils/utils.dart'; import '../utils/utils.dart';
import 'api.dart';
import 'constants.dart'; import 'constants.dart';
import 'interceptor.dart'; import 'interceptor.dart';
@ -24,6 +27,7 @@ class Request {
late bool enableSystemProxy; late bool enableSystemProxy;
late String systemProxyHost; late String systemProxyHost;
late String systemProxyPort; late String systemProxyPort;
static final RegExp spmPrefixExp = RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
/// 设置cookie /// 设置cookie
static setCookie() async { static setCookie() async {
@ -51,13 +55,12 @@ class Request {
} }
setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null); setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null);
if (cookie.isEmpty) { try {
try { await buvidActivate();
await Request().get(HttpString.baseUrl); } catch (e) {
} catch (e) { log("setCookie, ${e.toString()}");
log("setCookie, ${e.toString()}");
}
} }
final String cookieString = cookie final String cookieString = cookie
.map((Cookie cookie) => '${cookie.name}=${cookie.value}') .map((Cookie cookie) => '${cookie.name}=${cookie.value}')
.join('; '); .join('; ');
@ -87,6 +90,33 @@ class Request {
dio.options.headers['referer'] = 'https://www.bilibili.com/'; 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 * config it and create
*/ */

View File

@ -45,10 +45,13 @@ class ApiInterceptor extends Interceptor {
void onError(DioException err, ErrorInterceptorHandler handler) async { void onError(DioException err, ErrorInterceptorHandler handler) async {
// 处理网络请求错误 // 处理网络请求错误
// handler.next(err); // handler.next(err);
SmartDialog.showToast( String url = err.requestOptions.uri.toString();
await dioError(err), if (!url.contains('heartBeat')) {
displayType: SmartToastType.onlyRefresh, SmartDialog.showToast(
); await dioError(err),
displayType: SmartToastType.onlyRefresh,
);
}
super.onError(err, handler); super.onError(err, handler);
} }
@ -75,23 +78,24 @@ class ApiInterceptor extends Interceptor {
} }
static Future<String> checkConnect() async { static Future<String> checkConnect() async {
final ConnectivityResult connectivityResult = final List<ConnectivityResult> connectivityResult =
await Connectivity().checkConnectivity(); await Connectivity().checkConnectivity();
switch (connectivityResult) { if (connectivityResult.contains(ConnectivityResult.mobile)) {
case ConnectivityResult.mobile: return '正在使用移动流量';
return '正在使用移动流量'; } else if (connectivityResult.contains(ConnectivityResult.wifi)) {
case ConnectivityResult.wifi: return '正在使用wifi';
return '正在使用wifi'; } else if (connectivityResult.contains(ConnectivityResult.ethernet)) {
case ConnectivityResult.ethernet: return '正在使用局域网';
return '正在使用局域网'; } else if (connectivityResult.contains(ConnectivityResult.vpn)) {
case ConnectivityResult.vpn: return '正在使用代理网络';
return '正在使用代理网络'; } else if (connectivityResult.contains(ConnectivityResult.bluetooth)) {
case ConnectivityResult.other: return '正在使用蓝牙网络';
return '正在使用其他网络'; } else if (connectivityResult.contains(ConnectivityResult.other)) {
case ConnectivityResult.none: return '正在使用其他网络';
return '未连接到任何网络'; } else if (connectivityResult.contains(ConnectivityResult.none)) {
default: return '未连接到任何网络';
return ''; } else {
return '';
} }
} }
} }

View File

@ -1,5 +1,6 @@
import '../models/live/item.dart'; import '../models/live/item.dart';
import '../models/live/room_info.dart'; import '../models/live/room_info.dart';
import '../models/live/room_info_h5.dart';
import 'api.dart'; import 'api.dart';
import 'init.dart'; import 'init.dart';
@ -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

@ -79,6 +79,8 @@ class MemberHttp {
String order = 'pubdate', String order = 'pubdate',
bool orderAvoided = true, bool orderAvoided = true,
}) async { }) async {
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
Map params = await WbiSign().makSign({ Map params = await WbiSign().makSign({
'mid': mid, 'mid': mid,
'ps': ps, 'ps': ps,
@ -88,7 +90,11 @@ class MemberHttp {
'order': order, 'order': order,
'platform': 'web', 'platform': 'web',
'web_location': 1550101, '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( var res = await Request().get(
Api.memberArchive, Api.memberArchive,
@ -101,10 +107,13 @@ class MemberHttp {
'data': MemberArchiveDataModel.fromJson(res.data['data']) 'data': MemberArchiveDataModel.fromJson(res.data['data'])
}; };
} else { } else {
Map errMap = {
-352: '风控校验失败,请检查登录状态',
};
return { return {
'status': false, 'status': false,
'data': [], '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']), 'data': DynamicsDataModel.fromJson(res.data['data']),
}; };
} else { } else {
Map errMap = {
-352: '风控校验失败,请检查登录状态',
};
return { return {
'status': false, 'status': false,
'data': [], '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,3 +1,4 @@
import 'dart:math';
import '../models/msg/account.dart'; import '../models/msg/account.dart';
import '../models/msg/session.dart'; import '../models/msg/session.dart';
import '../utils/wbi_sign.dart'; import '../utils/wbi_sign.dart';
@ -22,14 +23,22 @@ class MsgHttp {
Map signParams = await WbiSign().makSign(params); Map signParams = await WbiSign().makSign(params);
var res = await Request().get(Api.sessionList, data: signParams); var res = await Request().get(Api.sessionList, data: signParams);
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return { try {
'status': true, return {
'data': SessionDataModel.fromJson(res.data['data']), 'status': true,
}; 'data': SessionDataModel.fromJson(res.data['data']),
};
} catch (err) {
return {
'status': false,
'data': [],
'msg': err.toString(),
};
}
} else { } else {
return { return {
'status': false, 'status': false,
'date': [], 'data': [],
'msg': res.data['message'], 'msg': res.data['message'],
}; };
} }
@ -42,12 +51,16 @@ class MsgHttp {
'mobi_app': 'web', 'mobi_app': 'web',
}); });
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return { try {
'status': true, return {
'data': res.data['data'] 'status': true,
.map<AccountListModel>((e) => AccountListModel.fromJson(e)) 'data': res.data['data']
.toList(), .map<AccountListModel>((e) => AccountListModel.fromJson(e))
}; .toList(),
};
} catch (err) {
print('err🔟: $err');
}
} else { } else {
return { return {
'status': false, '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,4 +1,5 @@
import '../models/video/reply/data.dart'; import '../models/video/reply/data.dart';
import '../models/video/reply/emote.dart';
import 'api.dart'; import 'api.dart';
import 'init.dart'; import 'init.dart';
@ -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

@ -6,6 +6,8 @@ import '../models/user/fav_folder.dart';
import '../models/user/history.dart'; import '../models/user/history.dart';
import '../models/user/info.dart'; import '../models/user/info.dart';
import '../models/user/stat.dart'; import '../models/user/stat.dart';
import '../models/user/sub_detail.dart';
import '../models/user/sub_folder.dart';
import 'api.dart'; import 'api.dart';
import 'init.dart'; import 'init.dart';
@ -305,4 +307,46 @@ class UserHttp {
return {'status': false, 'msg': res.data['message']}; 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

@ -8,8 +8,11 @@ import '../models/model_rec_video_item.dart';
import '../models/user/fav_folder.dart'; import '../models/user/fav_folder.dart';
import '../models/video/ai.dart'; import '../models/video/ai.dart';
import '../models/video/play/url.dart'; import '../models/video/play/url.dart';
import '../models/video/subTitile/result.dart';
import '../models/video_detail_res.dart'; import '../models/video_detail_res.dart';
import '../utils/recommend_filter.dart';
import '../utils/storage.dart'; import '../utils/storage.dart';
import '../utils/subtitle.dart';
import '../utils/wbi_sign.dart'; import '../utils/wbi_sign.dart';
import 'api.dart'; import 'api.dart';
import 'init.dart'; import 'init.dart';
@ -46,8 +49,13 @@ class VideoHttp {
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]); setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
for (var i in res.data['data']['item']) { for (var i in res.data['data']['item']) {
//过滤掉live与ad以及拉黑用户 //过滤掉live与ad以及拉黑用户
if (i['goto'] == 'av' && !blackMidsList.contains(i['owner']['mid'])) { if (i['goto'] == 'av' &&
list.add(RecVideoItemModel.fromJson(i)); (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}; return {'status': true, 'data': list};
@ -59,7 +67,9 @@ class VideoHttp {
} }
} }
static Future rcmdVideoListApp({int? ps, required int freshIdx}) async { // 添加额外的loginState变量模拟未登录状态
static Future rcmdVideoListApp(
{bool loginStatus = true, required int freshIdx}) async {
try { try {
var res = await Request().get( var res = await Request().get(
Api.recommendListApp, Api.recommendListApp,
@ -72,9 +82,11 @@ class VideoHttp {
'device_name': 'vivo', 'device_name': 'vivo',
'pull': freshIdx == 0 ? 'true' : 'false', 'pull': freshIdx == 0 ? 'true' : 'false',
'appkey': Constants.appKey, 'appkey': Constants.appKey,
'access_key': localCache 'access_key': loginStatus
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ?? ? (localCache.get(LocalCacheKey.accessKey,
'' defaultValue: {})['value'] ??
'')
: ''
}, },
); );
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
@ -87,12 +99,15 @@ class VideoHttp {
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) && (!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
(i['args'] != null && (i['args'] != null &&
!blackMidsList.contains(i['args']['up_mid']))) { !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}; return {'status': true, 'data': list};
} else { } else {
return {'status': false, 'data': [], 'msg': ''}; return {'status': false, 'data': [], 'msg': res.data['message']};
} }
} catch (err) { } catch (err) {
return {'status': false, 'data': [], 'msg': err.toString()}; return {'status': false, 'data': [], 'msg': err.toString()};
@ -117,7 +132,7 @@ class VideoHttp {
} }
return {'status': true, 'data': list}; return {'status': true, 'data': list};
} else { } else {
return {'status': false, 'data': []}; return {'status': false, 'data': [], 'msg': res.data['message']};
} }
} catch (err) { } catch (err) {
return {'status': false, 'data': [], 'msg': err}; return {'status': false, 'data': [], 'msg': err};
@ -203,7 +218,10 @@ class VideoHttp {
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
List<HotVideoItemModel> list = []; List<HotVideoItemModel> list = [];
for (var i in res.data['data']) { 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}; return {'status': true, 'data': list};
} else { } else {
@ -306,7 +324,7 @@ class VideoHttp {
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']}; return {'status': true, 'data': res.data['data']};
} else { } else {
return {'status': false, 'data': []}; return {'status': false, 'data': [], 'msg': res.data['message']};
} }
} }
@ -459,4 +477,54 @@ class VideoHttp {
return {'status': false, 'data': []}; return {'status': false, 'data': []};
} }
} }
static Future getSubtitle({int? cid, String? bvid}) async {
var res = await Request().get(Api.getSubtitleConfig, data: {
'cid': cid,
'bvid': bvid,
});
try {
if (res.data['code'] == 0) {
return {
'status': true,
'data': SubTitlteModel.fromJson(res.data['data']),
};
} else {
return {'status': false, 'data': [], 'msg': res.data['msg']};
}
} catch (err) {
print(err);
}
}
// 视频排行
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};
}
}
// 获取字幕内容
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
var res = await Request().get('https:$url');
final String content = SubTitleUtils.convertToWebVTT(res.data['body']);
final List body = res.data['body'];
return {'content': content, 'body': body};
}
} }

View File

@ -16,11 +16,13 @@ import 'package:pilipala/pages/search/index.dart';
import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/router/app_pages.dart'; import 'package:pilipala/router/app_pages.dart';
import 'package:pilipala/pages/main/view.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/services/service_locator.dart';
import 'package:pilipala/utils/app_scheme.dart'; import 'package:pilipala/utils/app_scheme.dart';
import 'package:pilipala/utils/data.dart'; import 'package:pilipala/utils/data.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:media_kit/media_kit.dart'; // Provides [Player], [Media], [Playlist] etc. 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 'package:catcher_2/catcher_2.dart';
import './services/loggeer.dart'; import './services/loggeer.dart';
@ -34,6 +36,7 @@ void main() async {
await setupServiceLocator(); await setupServiceLocator();
Request(); Request();
await Request.setCookie(); await Request.setCookie();
RecommendFilter();
// 异常捕获 logo记录 // 异常捕获 logo记录
final Catcher2Options debugConfig = Catcher2Options( final Catcher2Options debugConfig = Catcher2Options(
@ -68,8 +71,8 @@ void main() async {
statusBarColor: Colors.transparent, statusBarColor: Colors.transparent,
)); ));
Data.init(); Data.init();
GStrorage.lazyInit();
PiliSchame.init(); PiliSchame.init();
DisableBatteryOpt();
}); });
} }

View File

@ -7,5 +7,5 @@ enum DynamicsType {
extension BusinessTypeExtension on DynamicsType { extension BusinessTypeExtension on DynamicsType {
String get values => ['all', 'video', 'pgc', 'article'][index]; 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 { extension RcmdTypeExtension on RcmdType {
String get values => ['web', 'app'][index]; String get values => ['web', 'app', 'notLogin'][index];
String get labels => ['web端', 'app端'][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 { extension ReplySortTypeExtension on ReplySortType {
String get titles => ['最新评论', '最热评论', '回复最多'][index]; String get titles => ['最新评论', '最热评论'][index];
String get labels => ['最新', '最热', '最多回复'][index]; String get labels => ['最新', '最热'][index];
} }

View File

@ -0,0 +1,47 @@
enum SubtitleType {
// 中文(中国)
zhCN,
// 中文(自动翻译)
aizh,
// 英语(自动生成)
aien,
}
extension SubtitleTypeExtension on SubtitleType {
String get description {
switch (this) {
case SubtitleType.zhCN:
return '中文(中国)';
case SubtitleType.aizh:
return '中文(自动翻译)';
case SubtitleType.aien:
return '英语(自动生成)';
}
}
}
extension SubtitleIdExtension on SubtitleType {
String get id {
switch (this) {
case SubtitleType.zhCN:
return 'zh-CN';
case SubtitleType.aizh:
return 'ai-zh';
case SubtitleType.aien:
return 'ai-en';
}
}
}
extension SubtitleCodeExtension on SubtitleType {
int get code {
switch (this) {
case SubtitleType.zhCN:
return 1;
case SubtitleType.aizh:
return 2;
case SubtitleType.aien:
return 3;
}
}
}

View File

@ -2,18 +2,28 @@ class FollowUpModel {
FollowUpModel({ FollowUpModel({
this.liveUsers, this.liveUsers,
this.upList, this.upList,
this.liveList,
this.myInfo,
}); });
LiveUsers? liveUsers; LiveUsers? liveUsers;
List<UpItem>? upList; List<UpItem>? upList;
List<LiveUserItem>? liveList;
MyInfo? myInfo;
FollowUpModel.fromJson(Map<String, dynamic> json) { FollowUpModel.fromJson(Map<String, dynamic> json) {
liveUsers = json['live_users'] != null liveUsers = json['live_users'] != null
? LiveUsers.fromJson(json['live_users']) ? LiveUsers.fromJson(json['live_users'])
: null; : null;
liveList = json['live_users'] != null
? json['live_users']['items']
.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
.toList()
: [];
upList = json['up_list'] != null upList = json['up_list'] != null
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList() ? 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']; 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

@ -1,3 +1,5 @@
import 'package:pilipala/utils/id_utils.dart';
class RecVideoItemAppModel { class RecVideoItemAppModel {
RecVideoItemAppModel({ RecVideoItemAppModel({
this.id, this.id,
@ -28,7 +30,7 @@ class RecVideoItemAppModel {
int? cid; int? cid;
String? pic; String? pic;
RcmdStat? stat; RcmdStat? stat;
String? duration; int? duration;
String? title; String? title;
int? isFollowed; int? isFollowed;
RcmdOwner? owner; RcmdOwner? owner;
@ -50,17 +52,32 @@ class RecVideoItemAppModel {
? json['player_args']['aid'] ? json['player_args']['aid']
: int.parse(json['param'] ?? '-1'); : int.parse(json['param'] ?? '-1');
aid = json['player_args'] != null ? json['player_args']['aid'] : -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; cid = json['player_args'] != null ? json['player_args']['cid'] : -1;
pic = json['cover']; pic = json['cover'];
stat = RcmdStat.fromJson(json); 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']; title = json['title'];
isFollowed = 0;
owner = RcmdOwner.fromJson(json); owner = RcmdOwner.fromJson(json);
rcmdReason = json['rcmd_reason_style'] != null rcmdReason = json['rcmd_reason_style'] != null
? RcmdReason.fromJson(json['rcmd_reason_style']) ? RcmdReason.fromJson(json['rcmd_reason_style'])
: null; : 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']; goto = json['goto'];
param = int.parse(json['param']); param = int.parse(json['param']);
uri = json['uri']; uri = json['uri'];

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) { Stat.fromJson(Map<String, dynamic> json) {
view = json["play"]; 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 './model_owner.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -38,7 +36,7 @@ class RecVideoItemModel {
@HiveField(6) @HiveField(6)
String? title = ''; String? title = '';
@HiveField(7) @HiveField(7)
String? duration = ''; int? duration = -1;
@HiveField(8) @HiveField(8)
int? pubdate = -1; int? pubdate = -1;
@HiveField(9) @HiveField(9)
@ -58,7 +56,7 @@ class RecVideoItemModel {
uri = json["uri"]; uri = json["uri"];
pic = json["pic"]; pic = json["pic"];
title = json["title"]; title = json["title"];
duration = Utils.tampToSeektime(json["duration"]); duration = json["duration"];
pubdate = json["pubdate"]; pubdate = json["pubdate"];
owner = Owner.fromJson(json["owner"]); owner = Owner.fromJson(json["owner"]);
stat = Stat.fromJson(json["stat"]); stat = Stat.fromJson(json["stat"]);
@ -77,14 +75,15 @@ class Stat {
this.danmu, this.danmu,
}); });
@HiveField(0) @HiveField(0)
String? view; int? view;
@HiveField(1) @HiveField(1)
int? like; int? like;
@HiveField(2) @HiveField(2)
int? danmu; int? danmu;
Stat.fromJson(Map<String, dynamic> json) { Stat.fromJson(Map<String, dynamic> json) {
view = Utils.numFormat(json["view"]); // 无需在model中转换以保留原始数据在view层处理即可
view = json["view"];
like = json["like"]; like = json["like"];
danmu = json['danmaku']; danmu = json['danmaku'];
} }

View File

@ -24,7 +24,7 @@ class RecVideoItemModelAdapter extends TypeAdapter<RecVideoItemModel> {
uri: fields[4] as String?, uri: fields[4] as String?,
pic: fields[5] as String?, pic: fields[5] as String?,
title: fields[6] as String?, title: fields[6] as String?,
duration: fields[7] as String?, duration: fields[7] as int?,
pubdate: fields[8] as int?, pubdate: fields[8] as int?,
owner: fields[9] as Owner?, owner: fields[9] as Owner?,
stat: fields[10] as Stat?, stat: fields[10] as Stat?,
@ -87,7 +87,7 @@ class StatAdapter extends TypeAdapter<Stat> {
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
}; };
return Stat( return Stat(
view: fields[0] as String?, view: fields[0] as int?,
like: fields[1] as int?, like: fields[1] as int?,
danmu: fields[2] as int?, danmu: fields[2] as int?,
); );

View File

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

View File

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

View File

@ -15,7 +15,7 @@ class FavFolderData {
? json['list'] ? json['list']
.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e)) .map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
.toList() .toList()
: [FavFolderItemData()]; : <FavFolderItemData>[];
hasMore = json['has_more']; 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? seekParam;
String? seekType; String? seekType;
Dash? dash; Dash? dash;
List<Durl>? durl;
List<FormatItem>? supportFormats; List<FormatItem>? supportFormats;
// String? highFormat; // String? highFormat;
int? lastPlayTime; int? lastPlayTime;
@ -52,7 +53,8 @@ class PlayUrlModel {
videoCodecid = json['video_codecid']; videoCodecid = json['video_codecid'];
seekParam = json['seek_param']; seekParam = json['seek_param'];
seekType = json['seek_type']; 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 supportFormats = json['support_formats'] != null
? json['support_formats'] ? json['support_formats']
.map<FormatItem>((e) => FormatItem.fromJson(e)) .map<FormatItem>((e) => FormatItem.fromJson(e))
@ -250,3 +252,30 @@ class Flac {
audio = json['audio'] != null ? AudioItem.fromJson(json['audio']) : null; 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.vote,
this.richText, this.richText,
this.isText, this.isText,
this.topicsMeta,
}); });
String? message; String? message;
@ -20,6 +21,7 @@ class ReplyContent {
Map? vote; Map? vote;
Map? richText; Map? richText;
bool? isText; bool? isText;
Map? topicsMeta;
ReplyContent.fromJson(Map<String, dynamic> json) { ReplyContent.fromJson(Map<String, dynamic> json) {
message = json['message'] message = json['message']
@ -39,6 +41,7 @@ class ReplyContent {
richText = json['rich_text'] ?? {}; richText = json['rich_text'] ?? {};
// 不包含@ 笔记 图片的时候,文字可折叠 // 不包含@ 笔记 图片的时候,文字可折叠
isText = atNameToMid!.isEmpty && vote!.isEmpty && pictures!.isEmpty; 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

@ -0,0 +1,20 @@
class SubTitileContentModel {
double? from;
double? to;
int? location;
String? content;
SubTitileContentModel({
this.from,
this.to,
this.location,
this.content,
});
SubTitileContentModel.fromJson(Map<String, dynamic> json) {
from = json['from'];
to = json['to'];
location = json['location'];
content = json['content'];
}
}

View File

@ -0,0 +1,89 @@
import 'package:get/get.dart';
import '../../common/subtitle_type.dart';
class SubTitlteModel {
SubTitlteModel({
this.aid,
this.bvid,
this.cid,
this.loginMid,
this.loginMidHash,
this.isOwner,
this.name,
this.subtitles,
});
int? aid;
String? bvid;
int? cid;
int? loginMid;
String? loginMidHash;
bool? isOwner;
String? name;
List<SubTitlteItemModel>? subtitles;
factory SubTitlteModel.fromJson(Map<String, dynamic> json) => SubTitlteModel(
aid: json["aid"],
bvid: json["bvid"],
cid: json["cid"],
loginMid: json["login_mid"],
loginMidHash: json["login_mid_hash"],
isOwner: json["is_owner"],
name: json["name"],
subtitles: json["subtitle"] != null
? json["subtitle"]["subtitles"]
.map<SubTitlteItemModel>((x) => SubTitlteItemModel.fromJson(x))
.toList()
: [],
);
}
class SubTitlteItemModel {
SubTitlteItemModel({
this.id,
this.lan,
this.lanDoc,
this.isLock,
this.subtitleUrl,
this.type,
this.aiType,
this.aiStatus,
this.title,
this.code,
this.content,
this.body,
});
int? id;
String? lan;
String? lanDoc;
bool? isLock;
String? subtitleUrl;
int? type;
int? aiType;
int? aiStatus;
String? title;
int? code;
String? content;
List? body;
factory SubTitlteItemModel.fromJson(Map<String, dynamic> json) =>
SubTitlteItemModel(
id: json["id"],
lan: json["lan"].replaceAll('-', ''),
lanDoc: json["lan_doc"],
isLock: json["is_lock"],
subtitleUrl: json["subtitle_url"],
type: json["type"],
aiType: json["ai_type"],
aiStatus: json["ai_status"],
title: json["lan_doc"],
code: SubtitleType.values
.firstWhereOrNull(
(element) => element.id.toString() == json["lan"])
?.index ??
-1,
content: '',
body: [],
);
}

View File

@ -7,6 +7,7 @@ import 'package:pilipala/http/index.dart';
import 'package:pilipala/models/github/latest.dart'; import 'package:pilipala/models/github/latest.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../../utils/cache_manage.dart';
class AboutPage extends StatefulWidget { class AboutPage extends StatefulWidget {
const AboutPage({super.key}); const AboutPage({super.key});
@ -17,6 +18,19 @@ class AboutPage extends StatefulWidget {
class _AboutPageState extends State<AboutPage> { class _AboutPageState extends State<AboutPage> {
final AboutController _aboutController = Get.put(AboutController()); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -39,29 +53,54 @@ class _AboutPageState extends State<AboutPage> {
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
Text(
'使用Flutter开发的哔哩哔哩第三方客户端',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
const SizedBox(height: 20),
Obx( Obx(
() => ListTile( () => Badge(
title: const Text('当前版本'), isLabelVisible: _aboutController.isLoading.value
trailing: Text(_aboutController.currentVersion.value, ? false
style: subTitleStyle), : _aboutController.isUpdate.value,
), label: const Text('New'),
), child: Padding(
Obx( padding: const EdgeInsets.fromLTRB(0, 0, 0, 30),
() => ListTile( child: FilledButton.tonal(
onTap: () => _aboutController.onUpdate(), onPressed: () {
title: const Text('最新版本'), showModalBottomSheet(
trailing: Text( context: context,
_aboutController.isLoading.value builder: (context) {
? '正在获取' return Column(
: _aboutController.isUpdate.value mainAxisSize: MainAxisSize.min,
? '有新版本 ❤️${_aboutController.remoteVersion.value}' children: [
: '当前已是最新版', ListTile(
style: subTitleStyle, 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,
),
),
),
), ),
), ),
), ),
@ -73,14 +112,9 @@ class _AboutPageState extends State<AboutPage> {
// size: 16, // size: 16,
// ), // ),
// ), // ),
Divider(
thickness: 1,
height: 30,
color: Theme.of(context).colorScheme.outlineVariant,
),
ListTile( ListTile(
onTap: () => _aboutController.githubUrl(), onTap: () => _aboutController.githubUrl(),
title: const Text('Github'), title: const Text('开源地址'),
trailing: Text( trailing: Text(
'github.com/guozhigq/pilipala', 'github.com/guozhigq/pilipala',
style: subTitleStyle, style: subTitleStyle,
@ -115,19 +149,43 @@ class _AboutPageState extends State<AboutPage> {
), ),
), ),
ListTile( ListTile(
onTap: () => _aboutController.qqChanel(), onTap: () {
title: const Text('QQ群'), 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( trailing: Icon(
Icons.arrow_forward_ios, Icons.arrow_forward_ios,
size: 16, size: 16,
color: outline, color: outline,
), ),
), ),
ListTile(
onTap: () => _aboutController.tgChanel(),
title: const Text('TG频道'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline),
),
ListTile( ListTile(
onTap: () => _aboutController.aPay(), onTap: () => _aboutController.aPay(),
title: const Text('赞助'), title: const Text('赞助'),
@ -138,6 +196,17 @@ class _AboutPageState extends State<AboutPage> {
title: const Text('错误日志'), title: const Text('错误日志'),
trailing: Icon(Icons.arrow_forward_ios, size: 16, color: outline), 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)
], ],
), ),
), ),
@ -184,12 +253,16 @@ class AboutController extends GetxController {
// 获取远程版本 // 获取远程版本
Future getRemoteApp() async { Future getRemoteApp() async {
var result = await Request().get(Api.latestApp, extra: {'ua': 'pc'}); 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); data = LatestDataModel.fromJson(result.data);
remoteAppInfo = data; remoteAppInfo = data;
remoteVersion.value = data.tagName!; remoteVersion.value = data.tagName!;
isUpdate.value = isUpdate.value =
Utils.needUpdate(currentVersion.value, remoteVersion.value); Utils.needUpdate(currentVersion.value, remoteVersion.value);
isLoading.value = false;
} }
// 跳转下载/本地更新 // 跳转下载/本地更新
@ -205,11 +278,26 @@ class AboutController extends GetxController {
); );
} }
githubRelease() {
launchUrl(
Uri.parse('https://github.com/guozhigq/pilipala/releases'),
mode: LaunchMode.externalApplication,
);
}
// 从网盘下载 // 从网盘下载
panDownload() { panDownload() {
launchUrl( Clipboard.setData(
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'), const ClipboardData(text: 'pili'),
mode: LaunchMode.externalApplication, );
SmartDialog.showToast(
'已复制提取码pili',
displayTime: const Duration(milliseconds: 500),
).then(
(value) => launchUrl(
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
mode: LaunchMode.externalApplication,
),
); );
} }
@ -225,7 +313,7 @@ class AboutController extends GetxController {
// qq频道 // qq频道
qqChanel() { qqChanel() {
Clipboard.setData( Clipboard.setData(
const ClipboardData(text: '489981949'), const ClipboardData(text: '616150809'),
); );
SmartDialog.showToast('已复制QQ群号'); SmartDialog.showToast('已复制QQ群号');
} }
@ -266,6 +354,13 @@ class AboutController extends GetxController {
); );
} }
qimiao() {
launchUrl(
Uri.parse('https://www.magicalapk.com/home'),
mode: LaunchMode.externalApplication,
);
}
// 日志 // 日志
logs() { logs() {
Get.toNamed('/logs'); Get.toNamed('/logs');

View File

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

View File

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

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/bangumi/widgets/bangumi_panel.dart';
import 'package:pilipala/pages/video/detail/index.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_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/pages/video/detail/introduction/widgets/fav_panel.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import '../../../common/widgets/http_error.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/intro_detail.dart'; import 'widgets/intro_detail.dart';
@ -51,9 +50,6 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
cid = widget.cid!; cid = widget.cid!;
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag); bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag); videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
bangumiIntroController.bangumiDetail.listen((BangumiInfoModel value) {
bangumiDetail = value;
});
_futureBuilderFuture = bangumiIntroController.queryBangumiIntro(); _futureBuilderFuture = bangumiIntroController.queryBangumiIntro();
videoDetailCtr.cid.listen((int p0) { videoDetailCtr.cid.listen((int p0) {
cid = p0; cid = p0;
@ -68,27 +64,32 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SliverToBoxAdapter(child: SizedBox());
}
if (snapshot.data['status']) { if (snapshot.data['status']) {
// 请求成功 // 请求成功
return Obx(
return BangumiInfo( () => BangumiInfo(
loadingStatus: false, bangumiDetail: bangumiIntroController.bangumiDetail.value,
bangumiDetail: bangumiDetail, cid: cid,
cid: cid, ),
); );
} else { } else {
// 请求错误 // 请求错误
// return HttpError( return HttpError(
// errMsg: snapshot.data['msg'], errMsg: snapshot.data['msg'],
// fn: () => Get.back(), fn: () => Get.back(),
// ); );
return const SizedBox();
} }
} else { } else {
return BangumiInfo( return const SliverToBoxAdapter(
loadingStatus: true, child: SizedBox(
bangumiDetail: bangumiDetail, height: 100,
cid: cid, child: Center(
child: CircularProgressIndicator(),
),
),
); );
} }
}, },
@ -99,12 +100,10 @@ class _BangumiIntroPanelState extends State<BangumiIntroPanel>
class BangumiInfo extends StatefulWidget { class BangumiInfo extends StatefulWidget {
const BangumiInfo({ const BangumiInfo({
super.key, super.key,
this.loadingStatus = false,
this.bangumiDetail, this.bangumiDetail,
this.cid, this.cid,
}); });
final bool loadingStatus;
final BangumiInfoModel? bangumiDetail; final BangumiInfoModel? bangumiDetail;
final int? cid; final int? cid;
@ -117,7 +116,6 @@ class _BangumiInfoState extends State<BangumiInfo> {
late final BangumiIntroController bangumiIntroController; late final BangumiIntroController bangumiIntroController;
late final VideoDetailController videoDetailCtr; late final VideoDetailController videoDetailCtr;
Box localCache = GStrorage.localCache; Box localCache = GStrorage.localCache;
late final BangumiInfoModel? bangumiItem;
late double sheetHeight; late double sheetHeight;
int? cid; int? cid;
bool isProcessing = false; bool isProcessing = false;
@ -136,13 +134,10 @@ class _BangumiInfoState extends State<BangumiInfo> {
super.initState(); super.initState();
bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag); bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag);
videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag); videoDetailCtr = Get.find<VideoDetailController>(tag: heroTag);
bangumiItem = bangumiIntroController.bangumiItem;
sheetHeight = localCache.get('sheetHeight'); sheetHeight = localCache.get('sheetHeight');
cid = widget.cid!; cid = widget.cid!;
print('cid: $cid');
videoDetailCtr.cid.listen((p0) { videoDetailCtr.cid.listen((p0) {
cid = p0; cid = p0;
print('cid: $cid');
setState(() {}); setState(() {});
}); });
} }
@ -182,207 +177,155 @@ class _BangumiInfoState extends State<BangumiInfo> {
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 20), left: StyleString.safeSpace, right: StyleString.safeSpace, top: 20),
sliver: SliverToBoxAdapter( sliver: SliverToBoxAdapter(
child: !widget.loadingStatus || bangumiItem != null child: Column(
? Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [ children: [
Row( NetworkImgLayer(
crossAxisAlignment: CrossAxisAlignment.start, width: 105,
children: [ height: 160,
Stack( src: widget.bangumiDetail!.cover!,
children: [ ),
NetworkImgLayer( PBadge(
width: 105, text: '评分 ${widget.bangumiDetail!.rating!['score']!}',
height: 160, top: null,
src: !widget.loadingStatus right: 6,
? widget.bangumiDetail!.cover! bottom: 6,
: bangumiItem!.cover!, left: null,
), ),
if (bangumiItem != null && ],
bangumiItem!.rating != null) ),
PBadge( const SizedBox(width: 10),
text: Expanded(
'评分 ${!widget.loadingStatus ? widget.bangumiDetail!.rating!['score']! : bangumiItem!.rating!['score']!}', child: InkWell(
top: null, onTap: () => showIntroDetail(),
right: 6, child: SizedBox(
bottom: 6, height: 158,
left: null, 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: 20),
), SizedBox(
const SizedBox(width: 10), width: 34,
Expanded( height: 34,
child: InkWell( child: IconButton(
onTap: () => showIntroDetail(), style: ButtonStyle(
child: SizedBox( padding: MaterialStateProperty.all(
height: 158, EdgeInsets.zero),
child: Column( backgroundColor:
crossAxisAlignment: CrossAxisAlignment.start, MaterialStateProperty.resolveWith(
mainAxisSize: MainAxisSize.min, (Set<MaterialState> states) {
children: [ return t.colorScheme.primaryContainer
Row( .withOpacity(0.7);
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(
(Set<MaterialState> states) {
return t
.colorScheme.primaryContainer
.withOpacity(0.7);
}),
),
onPressed: () =>
bangumiIntroController.bangumiAdd(),
icon: Icon(
Icons.favorite_border_rounded,
color: t.colorScheme.primary,
size: 22,
),
),
),
],
), ),
Row( onPressed: () =>
children: [ bangumiIntroController.bangumiAdd(),
StatView( icon: Icon(
theme: 'gray', Icons.favorite_border_rounded,
view: !widget.loadingStatus color: t.colorScheme.primary,
? widget.bangumiDetail!.stat!['views'] size: 22,
: bangumiItem!.stat!['views'],
size: 'medium',
),
const SizedBox(width: 6),
StatDanMu(
theme: 'gray',
danmu: !widget.loadingStatus
? widget
.bangumiDetail!.stat!['danmakus']
: bangumiItem!.stat!['danmakus'],
size: 'medium',
),
],
), ),
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,
)
],
],
)),
); );
} }
@ -402,57 +345,44 @@ class _BangumiInfoState extends State<BangumiInfo> {
children: <Widget>[ children: <Widget>[
Obx( Obx(
() => ActionItem( () => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsUp), icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp), selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: onTap: handleState(bangumiIntroController.actionLikeVideo),
handleState(bangumiIntroController.actionLikeVideo), selectStatus: bangumiIntroController.hasLike.value,
selectStatus: bangumiIntroController.hasLike.value, text: widget.bangumiDetail!.stat!['likes']!.toString(),
loadingStatus: false, ),
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['likes']!.toString()
: bangumiItem!.stat!['likes']!.toString()),
), ),
Obx( Obx(
() => ActionItem( () => ActionItem(
icon: const Icon(FontAwesomeIcons.b), icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b), selectIcon: const Icon(FontAwesomeIcons.b),
onTap: onTap: handleState(bangumiIntroController.actionCoinVideo),
handleState(bangumiIntroController.actionCoinVideo), selectStatus: bangumiIntroController.hasCoin.value,
selectStatus: bangumiIntroController.hasCoin.value, text: widget.bangumiDetail!.stat!['coins']!.toString(),
loadingStatus: false, ),
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['coins']!.toString()
: bangumiItem!.stat!['coins']!.toString()),
), ),
Obx( Obx(
() => ActionItem( () => ActionItem(
icon: const Icon(FontAwesomeIcons.star), icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar), selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => showFavBottomSheet(), onTap: () => showFavBottomSheet(),
selectStatus: bangumiIntroController.hasFav.value, selectStatus: bangumiIntroController.hasFav.value,
loadingStatus: false, text: widget.bangumiDetail!.stat!['favorite']!.toString(),
text: !widget.loadingStatus ),
? widget.bangumiDetail!.stat!['favorite']!.toString()
: bangumiItem!.stat!['favorite']!.toString()),
), ),
ActionItem( ActionItem(
icon: const Icon(FontAwesomeIcons.comment), icon: const Icon(FontAwesomeIcons.comment),
selectIcon: const Icon(FontAwesomeIcons.reply), selectIcon: const Icon(FontAwesomeIcons.reply),
onTap: () => videoDetailCtr.tabCtr.animateTo(1), onTap: () => videoDetailCtr.tabCtr.animateTo(1),
selectStatus: false, selectStatus: false,
loadingStatus: false, text: widget.bangumiDetail!.stat!['reply']!.toString(),
text: !widget.loadingStatus
? widget.bangumiDetail!.stat!['reply']!.toString()
: bangumiItem!.stat!['reply']!.toString(),
), ),
ActionItem( ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare), icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => bangumiIntroController.actionShareVideo(), onTap: () => bangumiIntroController.actionShareVideo(),
selectStatus: false, selectStatus: false,
loadingStatus: false, text: widget.bangumiDetail!.stat!['share']!.toString(),
text: !widget.loadingStatus ),
? widget.bangumiDetail!.stat!['share']!.toString()
: bangumiItem!.stat!['share']!.toString()),
], ],
), ),
), ),
@ -460,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

@ -9,7 +9,6 @@ import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart'; import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/rcmd/view.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/bangumu_card_v.dart'; import 'widgets/bangumu_card_v.dart';
@ -199,7 +198,10 @@ class _BangumiPageState extends State<BangumiPage>
} else { } else {
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => {}, fn: () {
_futureBuilderFuture =
_bangumidController.queryBangumiListFeed();
},
); );
} }
} else { } else {
@ -208,7 +210,6 @@ class _BangumiPageState extends State<BangumiPage>
}, },
), ),
), ),
const LoadingMore()
], ],
), ),
); );

View File

@ -14,12 +14,14 @@ class BangumiPanel extends StatefulWidget {
this.cid, this.cid,
this.sheetHeight, this.sheetHeight,
this.changeFuc, this.changeFuc,
this.bangumiDetail,
}); });
final List<EpisodeItem> pages; final List<EpisodeItem> pages;
final int? cid; final int? cid;
final double? sheetHeight; final double? sheetHeight;
final Function? changeFuc; final Function? changeFuc;
final BangumiInfoModel? bangumiDetail;
@override @override
State<BangumiPanel> createState() => _BangumiPanelState(); State<BangumiPanel> createState() => _BangumiPanelState();
@ -65,6 +67,47 @@ class _BangumiPanelState extends State<BangumiPanel> {
super.dispose(); 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() { void showBangumiPanel() {
showBottomSheet( showBottomSheet(
context: context, context: context,
@ -105,38 +148,22 @@ class _BangumiPanelState extends State<BangumiPanel> {
Expanded( Expanded(
child: Material( child: Material(
child: ScrollablePositionedList.builder( child: ScrollablePositionedList.builder(
itemCount: widget.pages.length, itemCount: widget.pages.length + 1,
itemBuilder: (BuildContext context, int index) => itemBuilder: (BuildContext context, int index) {
ListTile( bool isLastItem = index == widget.pages.length;
onTap: () { bool isCurrentIndex = currentIndex == index;
setState(() { return isLastItem
changeFucCall(widget.pages[index], index); ? SizedBox(
}); height:
}, MediaQuery.of(context).padding.bottom +
dense: false, 20,
leading: index == currentIndex
? Image.asset(
'assets/images/live.gif',
color: Theme.of(context).colorScheme.primary,
height: 12,
) )
: null, : buildPageListItem(
title: Text( widget.pages[index],
'${index + 1}${widget.pages[index].longTitle!}', index,
style: TextStyle( isCurrentIndex,
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(),
),
itemScrollController: itemScrollController, itemScrollController: itemScrollController,
), ),
), ),
@ -151,7 +178,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
} }
void changeFucCall(item, i) async { void changeFucCall(item, i) async {
if (item.badge != null && vipStatus != 1) { if (item.badge != null && item.badge == '会员' && vipStatus != 1) {
SmartDialog.showToast('需要大会员'); SmartDialog.showToast('需要大会员');
return; return;
} }
@ -178,11 +205,11 @@ class _BangumiPanelState extends State<BangumiPanel> {
return Column( return Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(top: 10, bottom: 6), padding: const EdgeInsets.only(top: 10, bottom: 10),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
const Text(''), const Text(''),
Expanded( Expanded(
child: Text( child: Text(
' 正在播放:${widget.pages[currentIndex].longTitle}', ' 正在播放:${widget.pages[currentIndex].longTitle}',
@ -202,7 +229,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
), ),
onPressed: () => showBangumiPanel(), onPressed: () => showBangumiPanel(),
child: Text( child: Text(
'${widget.pages.length}', '${widget.bangumiDetail!.newEp!['desc']}',
style: const TextStyle(fontSize: 13), style: const TextStyle(fontSize: 13),
), ),
), ),
@ -255,11 +282,16 @@ class _BangumiPanelState extends State<BangumiPanel> {
), ),
const SizedBox(width: 2), const SizedBox(width: 2),
if (widget.pages[i].badge != null) ...[ if (widget.pages[i].badge != null) ...[
Image.asset( const Spacer(),
'assets/images/big-vip.png', Text(
height: 16, widget.pages[i].badge!,
style: TextStyle(
fontSize: 12,
color:
Theme.of(context).colorScheme.primary,
),
), ),
], ]
], ],
), ),
const SizedBox(height: 3), const SizedBox(height: 3),

View File

@ -139,7 +139,7 @@ class BlackListController extends GetxController {
int currentPage = 1; int currentPage = 1;
int pageSize = 50; int pageSize = 50;
RxInt total = 0.obs; RxInt total = 0.obs;
RxList<BlackListItem> blackList = [BlackListItem()].obs; RxList<BlackListItem> blackList = <BlackListItem>[].obs;
Future queryBlacklist({type = 'init'}) async { Future queryBlacklist({type = 'init'}) async {
if (type == 'init') { if (type == 'init') {

View File

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

View File

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

View File

@ -14,6 +14,7 @@ import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import '../mine/controller.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/dynamic_panel.dart'; import 'widgets/dynamic_panel.dart';
import 'widgets/up_panel.dart'; import 'widgets/up_panel.dart';
@ -28,6 +29,7 @@ class DynamicsPage extends StatefulWidget {
class _DynamicsPageState extends State<DynamicsPage> class _DynamicsPageState extends State<DynamicsPage>
with AutomaticKeepAliveClientMixin { with AutomaticKeepAliveClientMixin {
final DynamicsController _dynamicsController = Get.put(DynamicsController()); final DynamicsController _dynamicsController = Get.put(DynamicsController());
final MineController mineController = Get.put(MineController());
late Future _futureBuilderFuture; late Future _futureBuilderFuture;
late Future _futureBuilderFutureUp; late Future _futureBuilderFutureUp;
Box userInfoCache = GStrorage.userInfo; 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)); return Obx(() => UpPanel(_dynamicsController.upData.value));
} else { } else {
return const SliverToBoxAdapter( return const SliverToBoxAdapter(
child: SizedBox(height: 80)); child: SizedBox(height: 80),
);
} }
} else { } else {
return const SliverToBoxAdapter( 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( FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { 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 { } else {
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data['msg'],

View File

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

View File

@ -45,7 +45,9 @@ class _ContentState extends State<Content> {
if (len == 1) { if (len == 1) {
OpusPicsModel pictureItem = pics.first; OpusPicsModel pictureItem = pics.first;
picList.add(pictureItem.url!); picList.add(pictureItem.url!);
spanChilds.add(const TextSpan(text: '\n'));
/// 图片上方的空白间隔
// spanChilds.add(const TextSpan(text: '\n'));
spanChilds.add( spanChilds.add(
WidgetSpan( WidgetSpan(
child: LayoutBuilder( child: LayoutBuilder(

View File

@ -19,6 +19,17 @@ InlineSpan richNode(item, context) {
// 动态页面 richTextNodes 层级可能与主页动态层级不同 // 动态页面 richTextNodes 层级可能与主页动态层级不同
richTextNodes = richTextNodes =
item.modules.moduleDynamic.major.opus.summary.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) { if (richTextNodes == null || richTextNodes.isEmpty) {
return spacer; return spacer;

View File

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

View File

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

View File

@ -16,7 +16,7 @@ class FavDetailController extends GetxController {
RxMap favInfo = {}.obs; RxMap favInfo = {}.obs;
RxList favList = [].obs; RxList favList = [].obs;
RxString loadingText = '加载中...'.obs; RxString loadingText = '加载中...'.obs;
int mediaCount = 0; RxInt mediaCount = 0.obs;
@override @override
void onInit() { void onInit() {
@ -29,12 +29,12 @@ class FavDetailController extends GetxController {
} }
Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async { Future<dynamic> queryUserFavFolderDetail({type = 'init'}) async {
if (type == 'onLoad' && favList.length >= mediaCount) { if (type == 'onLoad' && favList.length >= mediaCount.value) {
loadingText.value = '没有更多了'; loadingText.value = '没有更多了';
return; return;
} }
isLoadingMore = true; isLoadingMore = true;
var res = await await UserHttp.userFavFolderDetail( var res = await UserHttp.userFavFolderDetail(
pn: currentPage, pn: currentPage,
ps: 20, ps: 20,
mediaId: mediaId!, mediaId: mediaId!,
@ -43,11 +43,11 @@ class FavDetailController extends GetxController {
favInfo.value = res['data'].info; favInfo.value = res['data'].info;
if (currentPage == 1 && type == 'init') { if (currentPage == 1 && type == 'init') {
favList.value = res['data'].medias; favList.value = res['data'].medias;
mediaCount = res['data'].info['media_count']; mediaCount.value = res['data'].info['media_count'];
} else if (type == 'onLoad') { } else if (type == 'onLoad') {
favList.addAll(res['data'].medias); favList.addAll(res['data'].medias);
} }
if (favList.length >= mediaCount) { if (favList.length >= mediaCount.value) {
loadingText.value = '没有更多了'; loadingText.value = '没有更多了';
} }
} }
@ -60,16 +60,14 @@ class FavDetailController extends GetxController {
var result = await VideoHttp.favVideo( var result = await VideoHttp.favVideo(
aid: id, addIds: '', delIds: mediaId.toString()); aid: id, addIds: '', delIds: mediaId.toString());
if (result['status']) { if (result['status']) {
if (result['data']['prompt']) { List dataList = favList;
List dataList = favList; for (var i in dataList) {
for (var i in dataList) { if (i.id == id) {
if (i.id == id) { dataList.remove(i);
dataList.remove(i); break;
break;
}
} }
SmartDialog.showToast('取消收藏');
} }
SmartDialog.showToast('取消收藏');
} }
} }

View File

@ -24,10 +24,12 @@ class _FavDetailPageState extends State<FavDetailPage> {
Get.put(FavDetailController()); Get.put(FavDetailController());
late StreamController<bool> titleStreamC; // a late StreamController<bool> titleStreamC; // a
Future? _futureBuilderFuture; Future? _futureBuilderFuture;
late String mediaId;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
mediaId = Get.parameters['mediaId']!;
_futureBuilderFuture = _favDetailController.queryUserFavFolderDetail(); _futureBuilderFuture = _favDetailController.queryUserFavFolderDetail();
titleStreamC = StreamController<bool>(); titleStreamC = StreamController<bool>();
_controller.addListener( _controller.addListener(
@ -82,7 +84,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
Text( Text(
'${_favDetailController.item!.mediaCount!}条视频', '${_favDetailController.mediaCount}条视频',
style: Theme.of(context).textTheme.labelMedium, style: Theme.of(context).textTheme.labelMedium,
) )
], ],
@ -94,8 +96,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
), ),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => Get.toNamed( onPressed: () =>
'/favSearch?searchType=0&mediaId=${Get.parameters['mediaId']!}'), Get.toNamed('/favSearch?searchType=0&mediaId=$mediaId'),
icon: const Icon(Icons.search_outlined), icon: const Icon(Icons.search_outlined),
), ),
// IconButton( // IconButton(
@ -173,7 +175,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14), padding: const EdgeInsets.only(top: 15, bottom: 8, left: 14),
child: Obx( child: Obx(
() => Text( () => Text(
'${_favDetailController.favList.length}条视频', '${_favDetailController.mediaCount}条视频',
style: TextStyle( style: TextStyle(
fontSize: fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize, Theme.of(context).textTheme.labelMedium!.fontSize,

View File

@ -9,14 +9,20 @@ import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import '../../../common/widgets/badge.dart';
// 收藏视频卡片 - 水平布局 // 收藏视频卡片 - 水平布局
class FavVideoCardH extends StatelessWidget { class FavVideoCardH extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final Function? callFn; final Function? callFn;
final int? searchType;
const FavVideoCardH({Key? key, required this.videoItem, this.callFn}) const FavVideoCardH({
: super(key: key); Key? key,
required this.videoItem,
this.callFn,
this.searchType,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -27,7 +33,9 @@ class FavVideoCardH extends StatelessWidget {
onTap: () async { onTap: () async {
// int? seasonId; // int? seasonId;
String? epId; String? epId;
if (videoItem.ogv != null && videoItem.ogv['type_name'] == '番剧') { if (videoItem.ogv != null &&
(videoItem.ogv['type_name'] == '番剧' ||
videoItem.ogv['type_name'] == '国创')) {
videoItem.cid = await SearchHttp.ab2c(bvid: bvid); videoItem.cid = await SearchHttp.ab2c(bvid: bvid);
// seasonId = videoItem.ogv['season_id']; // seasonId = videoItem.ogv['season_id'];
epId = videoItem.epId; epId = videoItem.epId;
@ -84,28 +92,31 @@ class FavVideoCardH extends StatelessWidget {
height: maxHeight, height: maxHeight,
), ),
), ),
Positioned( PBadge(
right: 4, text: Utils.timeFormat(videoItem.duration!),
bottom: 4, right: 6.0,
child: Container( bottom: 6.0,
padding: const EdgeInsets.symmetric( type: 'gray',
vertical: 1, horizontal: 6), ),
decoration: BoxDecoration( if (videoItem.ogv != null) ...[
borderRadius: BorderRadius.circular(4), PBadge(
color: Colors.black54.withOpacity(0.4)), text: videoItem.ogv['type_name'],
child: Text( top: 6.0,
Utils.timeFormat(videoItem.duration!), right: 6.0,
style: const TextStyle( bottom: null,
fontSize: 11, color: Colors.white), left: null,
),
), ),
) ],
], ],
); );
}, },
), ),
), ),
VideoContent(videoItem: videoItem, callFn: callFn) VideoContent(
videoItem: videoItem,
callFn: callFn,
searchType: searchType,
)
], ],
), ),
); );
@ -121,93 +132,123 @@ class FavVideoCardH extends StatelessWidget {
class VideoContent extends StatelessWidget { class VideoContent extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final Function? callFn; final Function? callFn;
const VideoContent({super.key, required this.videoItem, this.callFn}); final int? searchType;
const VideoContent({
super.key,
required this.videoItem,
this.callFn,
this.searchType,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Expanded( return Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
child: Column( child: Stack(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Column(
videoItem.title, crossAxisAlignment: CrossAxisAlignment.start,
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: [ children: [
StatView( Text(
theme: 'gray', videoItem.title,
view: videoItem.cntInfo['play'], textAlign: TextAlign.start,
style: const TextStyle(
fontWeight: FontWeight.w500,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
const SizedBox(width: 8), if (videoItem.ogv != null) ...[
StatDanMu(theme: 'gray', danmu: videoItem.cntInfo['danmaku']), Text(
const Spacer(), videoItem.intro,
SizedBox( style: TextStyle(
width: 26, fontSize:
height: 26, Theme.of(context).textTheme.labelMedium!.fontSize,
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, color: Theme.of(context).colorScheme.outline,
size: 18,
), ),
), ),
],
const Spacer(),
Text(
Utils.dateFormat(videoItem.favTime),
style: TextStyle(
fontSize: 11,
color: Theme.of(context).colorScheme.outline),
),
if (videoItem.owner.name != '') ...[
Text(
videoItem.owner.name,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
],
Padding(
padding: const EdgeInsets.only(top: 2),
child: Row(
children: [
StatView(
theme: 'gray',
view: videoItem.cntInfo['play'],
),
const SizedBox(width: 8),
StatDanMu(
theme: 'gray', danmu: videoItem.cntInfo['danmaku']),
const Spacer(),
],
),
), ),
], ],
), ),
searchType != 1
? Positioned(
right: 0,
bottom: -4,
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,
),
),
)
: const SizedBox(),
], ],
), ),
), ),

View File

@ -1,8 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/user/fav_detail.dart'; import 'package:pilipala/models/user/fav_detail.dart';
import '../../http/video.dart';
class FavSearchController extends GetxController { class FavSearchController extends GetxController {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
Rx<TextEditingController> controller = TextEditingController().obs; Rx<TextEditingController> controller = TextEditingController().obs;
@ -72,4 +75,19 @@ class FavSearchController extends GetxController {
if (!hasMore) return; if (!hasMore) return;
searchFav(type: 'onLoad'); searchFav(type: 'onLoad');
} }
onCancelFav(int id) async {
var result = await VideoHttp.favVideo(
aid: id, addIds: '', delIds: mediaId.toString());
if (result['status']) {
List dataList = favList;
for (var i in dataList) {
if (i.id == id) {
dataList.remove(i);
break;
}
}
SmartDialog.showToast('取消收藏');
}
}
} }

View File

@ -8,9 +8,7 @@ import 'package:pilipala/pages/fav_detail/widget/fav_video_card.dart';
import 'controller.dart'; import 'controller.dart';
class FavSearchPage extends StatefulWidget { class FavSearchPage extends StatefulWidget {
final int? sourceType; const FavSearchPage({super.key});
final int? mediaId;
const FavSearchPage({super.key, this.sourceType, this.mediaId});
@override @override
State<FavSearchPage> createState() => _FavSearchPageState(); State<FavSearchPage> createState() => _FavSearchPageState();
@ -19,11 +17,12 @@ class FavSearchPage extends StatefulWidget {
class _FavSearchPageState extends State<FavSearchPage> { class _FavSearchPageState extends State<FavSearchPage> {
final FavSearchController _favSearchCtr = Get.put(FavSearchController()); final FavSearchController _favSearchCtr = Get.put(FavSearchController());
late ScrollController scrollController; late ScrollController scrollController;
late int searchType;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
searchType = int.parse(Get.parameters['searchType']!);
scrollController = _favSearchCtr.scrollController; scrollController = _favSearchCtr.scrollController;
scrollController.addListener( scrollController.addListener(
() { () {
@ -100,7 +99,11 @@ class _FavSearchPageState extends State<FavSearchPage> {
} else { } else {
return FavVideoCardH( return FavVideoCardH(
videoItem: _favSearchCtr.favList[index], videoItem: _favSearchCtr.favList[index],
callFn: () => null, searchType: searchType,
callFn: () => searchType != 1
? _favSearchCtr
.onCancelFav(_favSearchCtr.favList[index].id!)
: {},
); );
} }
}, },

View File

@ -37,6 +37,29 @@ class _FollowPageState extends State<FollowPage> {
: '${_followController.name}的关注', : '${_followController.name}的关注',
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
actions: [
IconButton(
onPressed: () => Get.toNamed('/followSearch?mid=$mid'),
icon: const Icon(Icons.search_outlined),
),
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
PopupMenuItem(
onTap: () => Get.toNamed('/blackListPage'),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.block, size: 19),
SizedBox(width: 10),
Text('黑名单管理'),
],
),
)
],
),
const SizedBox(width: 6),
],
), ),
body: Obx( body: Obx(
() => !_followController.isOwner.value () => !_followController.isOwner.value
@ -87,3 +110,22 @@ class _FollowPageState extends State<FollowPage> {
); );
} }
} }
class _FakeAPI {
static const List<String> _kOptions = <String>[
'aardvark',
'bobcat',
'chameleon',
];
// Searches the options, but injects a fake "network" delay.
static Future<Iterable<String>> search(String query) async {
await Future<void>.delayed(
const Duration(seconds: 1)); // Fake 1 second delay.
if (query == '') {
return const Iterable<String>.empty();
}
return _kOptions.where((String option) {
return option.contains(query.toLowerCase());
});
}
}

View File

@ -42,7 +42,7 @@ class FollowItem extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
dense: true, dense: true,
trailing: ctr!.isOwner.value trailing: ctr != null && ctr!.isOwner.value
? SizedBox( ? SizedBox(
height: 34, height: 34,
child: TextButton( child: TextButton(

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/member.dart';
import '../../models/follow/result.dart';
class FollowSearchController extends GetxController {
Rx<TextEditingController> controller = TextEditingController().obs;
final FocusNode searchFocusNode = FocusNode();
RxString searchKeyWord = ''.obs;
String hintText = '搜索';
RxString loadingStatus = 'init'.obs;
late int mid = 1;
RxString uname = ''.obs;
int ps = 20;
int pn = 1;
RxList<FollowItemModel> followList = <FollowItemModel>[].obs;
RxInt total = 0.obs;
@override
void onInit() {
super.onInit();
mid = int.parse(Get.parameters['mid']!);
}
// 清空搜索
void onClear() {
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
controller.value.clear();
searchKeyWord.value = '';
} else {
Get.back();
}
}
void onChange(value) {
searchKeyWord.value = value;
}
// 提交搜索内容
void submit() {
loadingStatus.value = 'loading';
searchFollow();
}
Future searchFollow({type = 'init'}) async {
if (controller.value.text == '') {
return {'status': true, 'data': <FollowItemModel>[].obs};
}
if (type == 'init') {
ps = 1;
}
var res = await MemberHttp.getfollowSearch(
mid: mid,
ps: ps,
pn: pn,
name: controller.value.text,
);
if (res['status']) {
if (type == 'init') {
followList.value = res['data'].list;
} else {
followList.addAll(res['data'].list);
}
total.value = res['data'].total;
}
return res;
}
void onLoad() {
searchFollow(type: 'onLoad');
}
}

View File

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

View File

@ -0,0 +1,121 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/follow_search/index.dart';
import '../follow/widgets/follow_item.dart';
class FollowSearchPage extends StatefulWidget {
const FollowSearchPage({super.key});
@override
State<FollowSearchPage> createState() => _FollowSearchPageState();
}
class _FollowSearchPageState extends State<FollowSearchPage> {
final FollowSearchController _followSearchController =
Get.put(FollowSearchController());
late Future? _futureBuilder;
final ScrollController scrollController = ScrollController();
@override
void initState() {
super.initState();
_futureBuilder = _followSearchController.searchFollow();
scrollController.addListener(
() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle(
'my-throttler', const Duration(milliseconds: 500), () {
_followSearchController.onLoad();
});
}
},
);
}
void reRequest() {
setState(() {
_futureBuilder = _followSearchController.searchFollow();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
titleSpacing: 0,
actions: [
IconButton(
onPressed: reRequest,
icon: const Icon(CupertinoIcons.search, size: 22),
),
const SizedBox(width: 6),
],
title: TextField(
autofocus: true,
focusNode: _followSearchController.searchFocusNode,
controller: _followSearchController.controller.value,
textInputAction: TextInputAction.search,
onChanged: (value) => _followSearchController.onChange(value),
decoration: InputDecoration(
hintText: _followSearchController.hintText,
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(
Icons.clear,
size: 22,
color: Theme.of(context).colorScheme.outline,
),
onPressed: () => _followSearchController.onClear(),
),
),
onSubmitted: (String value) => reRequest(),
),
),
body: FutureBuilder(
future: _futureBuilder,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
if (data == null) {
return CustomScrollView(
slivers: [
HttpError(errMsg: snapshot.data['msg'], fn: reRequest)
],
);
}
if (data['status']) {
RxList followList = _followSearchController.followList;
return Obx(
() => followList.isNotEmpty
? ListView.builder(
controller: scrollController,
itemCount: followList.length,
itemBuilder: ((context, index) {
return FollowItem(
item: followList[index],
);
}),
)
: CustomScrollView(
slivers: [HttpError(errMsg: '未搜索到结果', fn: reRequest)],
),
);
} else {
return CustomScrollView(
slivers: [
HttpError(errMsg: snapshot.data['msg'], fn: reRequest)
],
);
}
} else {
return const SizedBox();
}
}),
);
}
}

View File

@ -88,8 +88,10 @@ class HistoryController extends GetxController {
// 观看历史暂停状态 // 观看历史暂停状态
Future historyStatus() async { Future historyStatus() async {
var res = await UserHttp.historyStatus(); var res = await UserHttp.historyStatus();
pauseStatus.value = res.data['data']; if (res.data['code'] == 0) {
localCache.put(LocalCacheKey.historyPause, res.data['data']); pauseStatus.value = res.data['data'];
localCache.put(LocalCacheKey.historyPause, res.data['data']);
}
} }
// 清空观看历史 // 清空观看历史

View File

@ -70,10 +70,6 @@ class _HistoryPageState extends State<HistoryPage> {
child1: AppBar( child1: AppBar(
titleSpacing: 0, titleSpacing: 0,
centerTitle: false, centerTitle: false,
leading: IconButton(
onPressed: () => Get.back(),
icon: const Icon(Icons.arrow_back_outlined),
),
title: Text( title: Text(
'观看记录', '观看记录',
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,

View File

@ -26,6 +26,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
late List defaultTabs; late List defaultTabs;
late List<String> tabbarSort; late List<String> tabbarSort;
RxString defaultSearch = ''.obs; RxString defaultSearch = ''.obs;
late bool enableGradientBg;
@override @override
void onInit() { void onInit() {
@ -33,13 +34,15 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null; userLogin.value = userInfo != null;
userFace.value = userInfo != null ? userInfo.face : ''; userFace.value = userInfo != null ? userInfo.face : '';
// 进行tabs配置
setTabConfig();
hideSearchBar = hideSearchBar =
setting.get(SettingBoxKey.hideSearchBar, defaultValue: true); setting.get(SettingBoxKey.hideSearchBar, defaultValue: true);
if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) { if (setting.get(SettingBoxKey.enableSearchWord, defaultValue: true)) {
searchDefault(); searchDefault();
} }
enableGradientBg =
setting.get(SettingBoxKey.enableGradientBg, defaultValue: true);
// 进行tabs配置
setTabConfig();
} }
void onRefresh() { void onRefresh() {
@ -88,19 +91,21 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
vsync: this, vsync: this,
); );
// 监听 tabController 切换 // 监听 tabController 切换
tabController.animation!.addListener(() { if (enableGradientBg) {
if (tabController.indexIsChanging) { tabController.animation!.addListener(() {
if (initialIndex.value != tabController.index) { if (tabController.indexIsChanging) {
initialIndex.value = tabController.index; if (initialIndex.value != tabController.index) {
initialIndex.value = tabController.index;
}
} else {
final int temp = tabController.animation!.value.round();
if (initialIndex.value != temp) {
initialIndex.value = temp;
tabController.index = initialIndex.value;
}
} }
} else { });
final int temp = tabController.animation!.value.round(); }
if (initialIndex.value != temp) {
initialIndex.value = temp;
tabController.index = initialIndex.value;
}
}
});
} }
void searchDefault() async { void searchDefault() async {

View File

@ -48,38 +48,51 @@ class _HomePageState extends State<HomePage>
super.build(context); super.build(context);
Brightness currentBrightness = MediaQuery.of(context).platformBrightness; Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
// 设置状态栏图标的亮度 // 设置状态栏图标的亮度
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( if (_homeController.enableGradientBg) {
statusBarIconBrightness: currentBrightness == Brightness.light SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
? Brightness.dark statusBarIconBrightness: currentBrightness == Brightness.light
: Brightness.light, ? Brightness.dark
)); : Brightness.light,
));
}
return Scaffold( return Scaffold(
extendBody: true, extendBody: true,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
appBar: _homeController.enableGradientBg
? null
: AppBar(toolbarHeight: 0, elevation: 0),
body: Stack( body: Stack(
children: [ children: [
// gradient background // gradient background
Align( if (_homeController.enableGradientBg) ...[
alignment: Alignment.topLeft, Align(
child: Opacity( alignment: Alignment.topLeft,
opacity: 0.6, child: Opacity(
child: Container( opacity: 0.6,
width: MediaQuery.of(context).size.width, child: Container(
height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width,
decoration: BoxDecoration( height: MediaQuery.of(context).size.height,
gradient: LinearGradient( decoration: BoxDecoration(
colors: [ gradient: LinearGradient(
Theme.of(context).colorScheme.primary.withOpacity(0.9), colors: [
Theme.of(context).colorScheme.primary.withOpacity(0.5), Theme.of(context)
Theme.of(context).colorScheme.surface .colorScheme
], .primary
begin: Alignment.topLeft, .withOpacity(0.9),
end: Alignment.bottomRight, Theme.of(context)
stops: const [0, 0.0034, 0.34]), .colorScheme
.primary
.withOpacity(0.5),
Theme.of(context).colorScheme.surface
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
stops: const [0, 0.0034, 0.34]),
),
), ),
), ),
), ),
), ],
Column( Column(
children: [ children: [
CustomAppBar( CustomAppBar(
@ -90,7 +103,37 @@ class _HomePageState extends State<HomePage>
callback: showUserBottomSheet, callback: showUserBottomSheet,
), ),
if (_homeController.tabs.length > 1) ...[ if (_homeController.tabs.length > 1) ...[
const CustomTabs(), if (_homeController.enableGradientBg) ...[
const CustomTabs(),
] else ...[
const SizedBox(height: 4),
SizedBox(
width: double.infinity,
height: 42,
child: Align(
alignment: Alignment.center,
child: TabBar(
controller: _homeController.tabController,
tabs: [
for (var i in _homeController.tabs)
Tab(text: i['label'])
],
isScrollable: true,
dividerColor: Colors.transparent,
enableFeedback: true,
splashBorderRadius: BorderRadius.circular(10),
tabAlignment: TabAlignment.center,
onTap: (value) {
feedBack();
if (_homeController.initialIndex.value == value) {
_homeController.tabsCtrList[value]().animateToTop();
}
_homeController.initialIndex.value = value;
},
),
),
),
],
] else ...[ ] else ...[
const SizedBox(height: 6), const SizedBox(height: 6),
], ],
@ -372,13 +415,16 @@ class SearchBar extends StatelessWidget {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Obx( Obx(
() => Text( () => Expanded(
ctr!.defaultSearch.value, child: Text(
maxLines: 1, ctr!.defaultSearch.value,
overflow: TextOverflow.ellipsis, maxLines: 1,
style: TextStyle(color: colorScheme.outline), overflow: TextOverflow.ellipsis,
style: TextStyle(color: colorScheme.outline),
),
), ),
), ),
const SizedBox(width: 15),
], ],
), ),
), ),

View File

@ -7,7 +7,7 @@ class HotController extends GetxController {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
final int _count = 20; final int _count = 20;
int _currentPage = 1; int _currentPage = 1;
RxList<HotVideoItemModel> videoList = [HotVideoItemModel()].obs; RxList<HotVideoItemModel> videoList = <HotVideoItemModel>[].obs;
bool isLoadingMore = false; bool isLoadingMore = false;
bool flag = false; bool flag = false;
OverlayEntry? popupDialog; OverlayEntry? popupDialog;

View File

@ -89,8 +89,7 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
if (data['status']) { if (data['status']) {
return Obx( return Obx(
() => SliverList( () => SliverList(
delegate: delegate: SliverChildBuilderDelegate((context, index) {
SliverChildBuilderDelegate((context, index) {
return VideoCardH( return VideoCardH(
videoItem: _hotController.videoList[index], videoItem: _hotController.videoList[index],
showPubdate: true, showPubdate: true,
@ -110,7 +109,12 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
} else { } else {
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => setState(() {}), fn: () {
setState(() {
_futureBuilderFuture =
_hotController.queryHotFeed('init');
});
},
); );
} }
} else { } else {

View File

@ -96,9 +96,6 @@ class HtmlRenderController extends GetxController {
_sortType = ReplySortType.like; _sortType = ReplySortType.like;
break; break;
case ReplySortType.like: case ReplySortType.like:
_sortType = ReplySortType.reply;
break;
case ReplySortType.reply:
_sortType = ReplySortType.time; _sortType = ReplySortType.time;
break; break;
default: default:

View File

@ -10,8 +10,7 @@ class LiveController extends GetxController {
int count = 12; int count = 12;
int _currentPage = 1; int _currentPage = 1;
RxInt crossAxisCount = 2.obs; RxInt crossAxisCount = 2.obs;
RxList<LiveItemModel> liveList = [LiveItemModel()].obs; RxList<LiveItemModel> liveList = <LiveItemModel>[].obs;
bool isLoadingMore = false;
bool flag = false; bool flag = false;
OverlayEntry? popupDialog; OverlayEntry? popupDialog;
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
@ -39,7 +38,6 @@ class LiveController extends GetxController {
} }
_currentPage += 1; _currentPage += 1;
} }
isLoadingMore = false;
return res; return res;
} }

View File

@ -11,7 +11,6 @@ import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart'; import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/rcmd/index.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/live_item.dart'; import 'widgets/live_item.dart';
@ -45,8 +44,8 @@ class _LivePageState extends State<LivePage>
() { () {
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) { scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle('liveList', const Duration(seconds: 1), () { EasyThrottle.throttle('liveList', const Duration(milliseconds: 200),
_liveController.isLoadingMore = true; () {
_liveController.onLoad(); _liveController.onLoad();
}); });
} }
@ -108,24 +107,20 @@ class _LivePageState extends State<LivePage>
} else { } else {
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => {}, fn: () {
setState(() {
_futureBuilderFuture =
_liveController.queryLiveList('init');
});
},
); );
} }
} else { } else {
// 缓存数据 return contentGrid(_liveController, []);
if (_liveController.liveList.length > 1) {
return contentGrid(
_liveController, _liveController.liveList);
}
// 骨架屏
else {
return contentGrid(_liveController, []);
}
} }
}, },
), ),
), ),
LoadingMore(ctr: _liveController)
], ],
), ),
), ),

View File

@ -184,18 +184,32 @@ class VideoStat extends StatelessWidget {
tileMode: TileMode.mirror, tileMode: TileMode.mirror,
), ),
), ),
child: RichText( child: Row(
maxLines: 1, mainAxisAlignment: MainAxisAlignment.spaceBetween,
textAlign: TextAlign.justify, children: [
softWrap: false, Text(
text: TextSpan( liveItem!.areaName!,
style: const TextStyle(fontSize: 11, color: Colors.white), style: const TextStyle(fontSize: 11, color: Colors.white),
children: [ ),
TextSpan(text: liveItem!.areaName!), Text(
TextSpan(text: liveItem!.watchedShow!['text_small']), liveItem!.watchedShow!['text_small'],
], style: const TextStyle(fontSize: 11, color: Colors.white),
), ),
],
), ),
// child: RichText(
// maxLines: 1,
// textAlign: TextAlign.justify,
// softWrap: false,
// text: TextSpan(
// style: const TextStyle(fontSize: 11, color: Colors.white),
// children: [
// TextSpan(text: liveItem!.areaName!),
// TextSpan(text: liveItem!.watchedShow!['text_small']),
// ],
// ),
// ),
); );
} }
} }

View File

@ -1,8 +1,13 @@
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/constants.dart'; import 'package:pilipala/http/constants.dart';
import 'package:pilipala/http/live.dart'; import 'package:pilipala/http/live.dart';
import 'package:pilipala/models/live/quality.dart';
import 'package:pilipala/models/live/room_info.dart'; import 'package:pilipala/models/live/room_info.dart';
import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/plugin/pl_player/index.dart';
import '../../models/live/room_info_h5.dart';
import '../../utils/storage.dart';
import '../../utils/video_utils.dart';
class LiveRoomController extends GetxController { class LiveRoomController extends GetxController {
String cover = ''; String cover = '';
@ -14,17 +19,18 @@ class LiveRoomController extends GetxController {
RxBool volumeOff = false.obs; RxBool volumeOff = false.obs;
PlPlayerController plPlayerController = PlPlayerController plPlayerController =
PlPlayerController.getInstance(videoType: 'live'); PlPlayerController.getInstance(videoType: 'live');
Rx<RoomInfoH5Model> roomInfoH5 = RoomInfoH5Model().obs;
// MeeduPlayerController meeduPlayerController = MeeduPlayerController( late bool enableCDN;
// colorTheme: Theme.of(Get.context!).colorScheme.primary, late int currentQn;
// pipEnabled: true, int? tempCurrentQn;
// controlsStyle: ControlsStyle.live, late List<Map<String, dynamic>> acceptQnList;
// enabledButtons: const EnabledButtons(pip: true), RxString currentQnDesc = ''.obs;
// );
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
currentQn = setting.get(SettingBoxKey.defaultLiveQa,
defaultValue: LiveQuality.values.last.code);
roomId = int.parse(Get.parameters['roomid']!); roomId = int.parse(Get.parameters['roomid']!);
if (Get.arguments != null) { if (Get.arguments != null) {
liveItem = Get.arguments['liveItem']; liveItem = Get.arguments['liveItem'];
@ -36,11 +42,12 @@ class LiveRoomController extends GetxController {
cover = liveItem.cover; cover = liveItem.cover;
} }
} }
queryLiveInfo(); // CDN优化
enableCDN = setting.get(SettingBoxKey.enableCDN, defaultValue: true);
} }
playerInit(source) { playerInit(source) async {
plPlayerController.setDataSource( await plPlayerController.setDataSource(
DataSource( DataSource(
videoSource: source, videoSource: source,
audioSource: null, audioSource: null,
@ -58,15 +65,35 @@ class LiveRoomController extends GetxController {
} }
Future queryLiveInfo() async { Future queryLiveInfo() async {
var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: 10000); var res = await LiveHttp.liveRoomInfo(roomId: roomId, qn: currentQn);
if (res['status']) { if (res['status']) {
List<CodecItem> codec = List<CodecItem> codec =
res['data'].playurlInfo.playurl.stream.first.format.first.codec; res['data'].playurlInfo.playurl.stream.first.format.first.codec;
CodecItem item = codec.first; CodecItem item = codec.first;
String videoUrl = (item.urlInfo?.first.host)! + // 以服务端返回的码率为准
item.baseUrl! + currentQn = item.currentQn!;
item.urlInfo!.first.extra!; if (tempCurrentQn != null && tempCurrentQn == currentQn) {
playerInit(videoUrl); SmartDialog.showToast('画质切换失败,请检查登录状态');
}
List acceptQn = item.acceptQn!;
acceptQnList = acceptQn.map((e) {
return {
'code': e,
'desc': LiveQuality.values
.firstWhere((element) => element.code == e)
.description,
};
}).toList();
currentQnDesc.value = LiveQuality.values
.firstWhere((element) => element.code == currentQn)
.description;
String videoUrl = enableCDN
? VideoUtils.getCdnUrl(item)
: (item.urlInfo?.first.host)! +
item.baseUrl! +
item.urlInfo!.first.extra!;
await playerInit(videoUrl);
return res;
} }
} }
@ -80,4 +107,25 @@ class LiveRoomController extends GetxController {
volumeOff.value = true; volumeOff.value = true;
} }
} }
Future queryLiveInfoH5() async {
var res = await LiveHttp.liveRoomInfoH5(roomId: roomId);
if (res['status']) {
roomInfoH5.value = res['data'];
}
return res;
}
// 修改画质
void changeQn(int qn) async {
tempCurrentQn = currentQn;
if (currentQn == qn) {
return;
}
currentQn = qn;
currentQnDesc.value = LiveQuality.values
.firstWhere((element) => element.code == currentQn)
.description;
await queryLiveInfo();
}
} }

View File

@ -19,6 +19,8 @@ class LiveRoomPage extends StatefulWidget {
class _LiveRoomPageState extends State<LiveRoomPage> { class _LiveRoomPageState extends State<LiveRoomPage> {
final LiveRoomController _liveRoomController = Get.put(LiveRoomController()); final LiveRoomController _liveRoomController = Get.put(LiveRoomController());
PlPlayerController? plPlayerController; PlPlayerController? plPlayerController;
late Future? _futureBuilder;
late Future? _futureBuilderFuture;
bool isShowCover = true; bool isShowCover = true;
bool isPlay = true; bool isPlay = true;
@ -27,18 +29,16 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
plPlayerController = _liveRoomController.plPlayerController;
plPlayerController!.onPlayerStatusChanged.listen(
(PlayerStatus status) {
if (status == PlayerStatus.playing) {
isShowCover = false;
setState(() {});
}
},
);
if (Platform.isAndroid) { if (Platform.isAndroid) {
floating = Floating(); floating = Floating();
} }
videoSourceInit();
_futureBuilderFuture = _liveRoomController.queryLiveInfo();
}
Future<void> videoSourceInit() async {
_futureBuilder = _liveRoomController.queryLiveInfoH5();
plPlayerController = _liveRoomController.plPlayerController;
} }
@override @override
@ -52,57 +52,127 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget videoPlayerPanel = FutureBuilder(
future: _futureBuilderFuture,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData && snapshot.data['status']) {
return PLVideoPlayer(
controller: plPlayerController!,
bottomControl: BottomControl(
controller: plPlayerController,
liveRoomCtr: _liveRoomController,
floating: floating,
),
);
} else {
return const SizedBox();
}
},
);
Widget childWhenDisabled = Scaffold( Widget childWhenDisabled = Scaffold(
primary: true, primary: true,
appBar: PreferredSize( backgroundColor: Colors.black,
preferredSize: Size.fromHeight( body: Stack(
MediaQuery.of(context).orientation == Orientation.portrait ? 56 : 0,
),
child: AppBar(
centerTitle: false,
titleSpacing: 0,
title: _liveRoomController.liveItem != null
? Row(
children: [
NetworkImgLayer(
width: 34,
height: 34,
type: 'avatar',
src: _liveRoomController.liveItem.face,
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_liveRoomController.liveItem.uname,
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 1),
if (_liveRoomController.liveItem.watchedShow != null)
Text(
_liveRoomController
.liveItem.watchedShow['text_large'] ??
'',
style: const TextStyle(fontSize: 12)),
],
),
],
)
: const SizedBox(),
// actions: [
// SizedBox(
// height: 34,
// child: ElevatedButton(onPressed: () {}, child: const Text('关注')),
// ),
// const SizedBox(width: 12),
// ],
),
),
body: Column(
children: [ children: [
Stack( Positioned(
left: 0,
right: 0,
bottom: 0,
child: Opacity(
opacity: 0.8,
child: Image.asset(
'assets/images/live/default_bg.webp',
fit: BoxFit.cover,
// width: Get.width,
// height: Get.height,
),
),
),
Obx(
() => Positioned(
left: 0,
right: 0,
bottom: 0,
child: _liveRoomController
.roomInfoH5.value.roomInfo?.appBackground !=
'' &&
_liveRoomController
.roomInfoH5.value.roomInfo?.appBackground !=
null
? Opacity(
opacity: 0.8,
child: NetworkImgLayer(
width: Get.width,
height: Get.height,
type: 'bg',
src: _liveRoomController
.roomInfoH5.value.roomInfo?.appBackground ??
'',
),
)
: const SizedBox(),
),
),
Column(
children: [ children: [
AppBar(
centerTitle: false,
titleSpacing: 0,
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
toolbarHeight:
MediaQuery.of(context).orientation == Orientation.portrait
? 56
: 0,
title: FutureBuilder(
future: _futureBuilder,
builder: (context, snapshot) {
if (snapshot.data == null) {
return const SizedBox();
}
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => Row(
children: [
NetworkImgLayer(
width: 34,
height: 34,
type: 'avatar',
src: _liveRoomController
.roomInfoH5.value.anchorInfo!.baseInfo!.face,
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_liveRoomController.roomInfoH5.value
.anchorInfo!.baseInfo!.uname!,
style: const TextStyle(fontSize: 14),
),
const SizedBox(height: 1),
if (_liveRoomController
.roomInfoH5.value.watchedShow !=
null)
Text(
_liveRoomController.roomInfoH5.value
.watchedShow!['text_large'] ??
'',
style: const TextStyle(fontSize: 12),
),
],
),
],
),
);
} else {
return const SizedBox();
}
},
),
),
PopScope( PopScope(
canPop: plPlayerController?.isFullScreen.value != true, canPop: plPlayerController?.isFullScreen.value != true,
onPopInvoked: (bool didPop) { onPopInvoked: (bool didPop) {
@ -120,55 +190,19 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
Orientation.landscape Orientation.landscape
? Get.size.height ? Get.size.height
: Get.size.width * 9 / 16, : Get.size.width * 9 / 16,
child: plPlayerController!.videoPlayerController != null child: videoPlayerPanel,
? PLVideoPlayer(
controller: plPlayerController!,
bottomControl: BottomControl(
controller: plPlayerController,
liveRoomCtr: _liveRoomController,
floating: floating,
),
)
: const SizedBox(),
), ),
), ),
// if (_liveRoomController.liveItem != null &&
// _liveRoomController.liveItem.cover != null)
// Visibility(
// visible: isShowCover,
// child: Positioned(
// top: 0,
// left: 0,
// right: 0,
// child: NetworkImgLayer(
// type: 'emote',
// src: _liveRoomController.liveItem.cover,
// width: Get.size.width,
// height: videoHeight,
// ),
// ),
// ),
], ],
), ),
], ],
), ),
); );
Widget childWhenEnabled = AspectRatio(
aspectRatio: 16 / 9,
child: plPlayerController!.videoPlayerController != null
? PLVideoPlayer(
controller: plPlayerController!,
bottomControl: BottomControl(
controller: plPlayerController,
liveRoomCtr: _liveRoomController,
),
)
: const SizedBox(),
);
if (Platform.isAndroid) { if (Platform.isAndroid) {
return PiPSwitcher( return PiPSwitcher(
childWhenDisabled: childWhenDisabled, childWhenDisabled: childWhenDisabled,
childWhenEnabled: childWhenEnabled, childWhenEnabled: videoPlayerPanel,
floating: floating,
); );
} else { } else {
return childWhenDisabled; return childWhenDisabled;

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:floating/floating.dart'; import 'package:floating/floating.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/models/video/play/url.dart'; import 'package:pilipala/models/video/play/url.dart';
import 'package:pilipala/pages/live_room/index.dart'; import 'package:pilipala/pages/live_room/index.dart';
@ -29,7 +30,6 @@ class BottomControl extends StatefulWidget implements PreferredSizeWidget {
class _BottomControlState extends State<BottomControl> { class _BottomControlState extends State<BottomControl> {
late PlayUrlModel videoInfo; late PlayUrlModel videoInfo;
List<PlaySpeed> playSpeed = PlaySpeed.values;
TextStyle subTitleStyle = const TextStyle(fontSize: 12); TextStyle subTitleStyle = const TextStyle(fontSize: 12);
TextStyle titleStyle = const TextStyle(fontSize: 14); TextStyle titleStyle = const TextStyle(fontSize: 14);
Size get preferredSize => const Size(double.infinity, kToolbarHeight); Size get preferredSize => const Size(double.infinity, kToolbarHeight);
@ -84,6 +84,30 @@ class _BottomControlState extends State<BottomControl> {
// ), // ),
// ), // ),
// const SizedBox(width: 4), // const SizedBox(width: 4),
SizedBox(
width: 30,
child: PopupMenuButton<int>(
padding: EdgeInsets.zero,
onSelected: (value) {
widget.liveRoomCtr!.changeQn(value);
},
child: Obx(
() => Text(
widget.liveRoomCtr!.currentQnDesc.value,
style: const TextStyle(color: Colors.white, fontSize: 13),
),
),
itemBuilder: (BuildContext context) {
return widget.liveRoomCtr!.acceptQnList.map((e) {
return PopupMenuItem<int>(
value: e['code'],
child: Text(e['desc']),
);
}).toList();
},
),
),
const SizedBox(width: 10),
if (Platform.isAndroid) ...[ if (Platform.isAndroid) ...[
SizedBox( SizedBox(
width: 34, width: 34,
@ -111,7 +135,7 @@ class _BottomControlState extends State<BottomControl> {
), ),
), ),
), ),
const SizedBox(width: 4), const SizedBox(width: 10),
], ],
ComBtn( ComBtn(
icon: const Icon( icon: const Icon(

View File

@ -9,54 +9,20 @@ import 'package:pilipala/http/common.dart';
import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/view.dart'; import 'package:pilipala/pages/home/view.dart';
import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/rank/index.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import '../../models/common/dynamic_badge_mode.dart'; import '../../models/common/dynamic_badge_mode.dart';
import '../../models/common/nav_bar_config.dart';
class MainController extends GetxController { class MainController extends GetxController {
List<Widget> pages = <Widget>[ List<Widget> pages = <Widget>[
const HomePage(), const HomePage(),
const RankPage(),
const DynamicsPage(), const DynamicsPage(),
const MediaPage(), const MediaPage(),
]; ];
RxList navigationBars = [ RxList navigationBars = defaultNavigationBars.obs;
{
'icon': const Icon(
Icons.home_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.home,
size: 21,
),
'label': "首页",
'count': 0,
},
{
'icon': const Icon(
Icons.motion_photos_on_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.motion_photos_on,
size: 21,
),
'label': "动态",
'count': 0,
},
{
'icon': const Icon(
Icons.video_collection_outlined,
size: 20,
),
'selectIcon': const Icon(
Icons.video_collection,
size: 21,
),
'label': "媒体库",
'count': 0,
}
].obs;
final StreamController<bool> bottomBarStream = final StreamController<bool> bottomBarStream =
StreamController<bool>.broadcast(); StreamController<bool>.broadcast();
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
@ -75,6 +41,10 @@ class MainController extends GetxController {
Utils.checkUpdata(); Utils.checkUpdata();
} }
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true); hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
int defaultHomePage =
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
selectedIndex = defaultNavigationBars
.indexWhere((item) => item['id'] == defaultHomePage);
var userInfo = userInfoCache.get('userInfoCache'); var userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null; userLogin.value = userInfo != null;
dynamicBadgeType.value = DynamicBadgeMode.values[setting.get( dynamicBadgeType.value = DynamicBadgeMode.values[setting.get(

View File

@ -7,6 +7,7 @@ import 'package:pilipala/models/common/dynamic_badge_mode.dart';
import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/rank/index.dart';
import 'package:pilipala/utils/event_bus.dart'; import 'package:pilipala/utils/event_bus.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
@ -22,6 +23,7 @@ class MainApp extends StatefulWidget {
class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin { class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
final MainController _mainController = Get.put(MainController()); final MainController _mainController = Get.put(MainController());
final HomeController _homeController = Get.put(HomeController()); final HomeController _homeController = Get.put(HomeController());
final RankController _rankController = Get.put(RankController());
final DynamicsController _dynamicController = Get.put(DynamicsController()); final DynamicsController _dynamicController = Get.put(DynamicsController());
final MediaController _mediaController = Get.put(MediaController()); final MediaController _mediaController = Get.put(MediaController());
@ -57,6 +59,21 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
_homeController.flag = false; _homeController.flag = false;
} }
if (currentPage is RankPage) {
if (_rankController.flag) {
// 单击返回顶部 双击并刷新
if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) {
_rankController.onRefresh();
} else {
_rankController.animateToTop();
}
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
}
_rankController.flag = true;
} else {
_rankController.flag = false;
}
if (currentPage is DynamicsPage) { if (currentPage is DynamicsPage) {
if (_dynamicController.flag) { if (_dynamicController.flag) {
// 单击返回顶部 双击并刷新 // 单击返回顶部 双击并刷新

View File

@ -28,6 +28,11 @@ class MediaController extends GetxController {
'title': '我的收藏', 'title': '我的收藏',
'onTap': () => Get.toNamed('/fav'), 'onTap': () => Get.toNamed('/fav'),
}, },
{
'icon': Icons.subscriptions_outlined,
'title': '我的订阅',
'onTap': () => Get.toNamed('/subscription'),
},
{ {
'icon': Icons.watch_later_outlined, 'icon': Icons.watch_later_outlined,
'title': '稍后再看', 'title': '稍后再看',

View File

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/user/fav_folder.dart'; import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/pages/main/index.dart'; import 'package:pilipala/pages/main/index.dart';
@ -102,7 +103,11 @@ class _MediaPageState extends State<MediaPage>
], ],
Obx(() => mediaController.userLogin.value Obx(() => mediaController.userLogin.value
? favFolder(mediaController, context) ? favFolder(mediaController, context)
: const SizedBox()) : const SizedBox()),
SizedBox(
height: MediaQuery.of(context).padding.bottom +
kBottomNavigationBarHeight,
)
], ],
), ),
), ),

View File

@ -20,7 +20,7 @@ class MemberController extends GetxController {
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
late int ownerMid; late int ownerMid;
// 投稿列表 // 投稿列表
RxList<VListItemModel>? archiveList = [VListItemModel()].obs; RxList<VListItemModel>? archiveList = <VListItemModel>[].obs;
dynamic userInfo; dynamic userInfo;
RxInt attribute = (-1).obs; RxInt attribute = (-1).obs;
RxString attributeText = '关注'.obs; RxString attributeText = '关注'.obs;

View File

@ -105,7 +105,7 @@ class _MemberPageState extends State<MemberPage>
actions: [ actions: [
IconButton( IconButton(
onPressed: () => Get.toNamed( onPressed: () => Get.toNamed(
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'), '/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'),
icon: const Icon(Icons.search_outlined), icon: const Icon(Icons.search_outlined),
), ),
PopupMenuButton( PopupMenuButton(

View File

@ -25,7 +25,7 @@ class MemberArchiveController extends GetxController {
// 获取用户投稿 // 获取用户投稿
Future getMemberArchive(type) async { Future getMemberArchive(type) async {
if (type == 'onRefresh') { if (type == 'init') {
pn = 1; pn = 1;
} }
var res = await MemberHttp.memberArchive( var res = await MemberHttp.memberArchive(
@ -34,7 +34,12 @@ class MemberArchiveController extends GetxController {
order: currentOrder['type']!, order: currentOrder['type']!,
); );
if (res['status']) { if (res['status']) {
archivesList.addAll(res['data'].list.vlist); if (type == 'init') {
archivesList.value = res['data'].list.vlist;
}
if (type == 'onLoad') {
archivesList.addAll(res['data'].list.vlist);
}
count = res['data'].page['count']; count = res['data'].page['count'];
pn += 1; pn += 1;
} }
@ -42,13 +47,14 @@ class MemberArchiveController extends GetxController {
} }
toggleSort() async { toggleSort() async {
pn = 1; List<String> typeList = orderList.map((e) => e['type']!).toList();
int index = orderList.indexOf(currentOrder); int index = typeList.indexOf(currentOrder['type']!);
if (index == orderList.length - 1) { if (index == orderList.length - 1) {
currentOrder.value = orderList.first; currentOrder.value = orderList.first;
} else { } else {
currentOrder.value = orderList[index + 1]; currentOrder.value = orderList[index + 1];
} }
getMemberArchive('init');
} }
// 上拉加载 // 上拉加载

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/video_card_h.dart'; import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import '../../common/widgets/http_error.dart';
import 'controller.dart'; import 'controller.dart';
class MemberArchivePage extends StatefulWidget { class MemberArchivePage extends StatefulWidget {
@ -25,8 +26,7 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
final String heroTag = Utils.makeHeroTag(mid); final String heroTag = Utils.makeHeroTag(mid);
_memberArchivesController = _memberArchivesController =
Get.put(MemberArchiveController(), tag: heroTag); Get.put(MemberArchiveController(), tag: heroTag);
_futureBuilderFuture = _futureBuilderFuture = _memberArchivesController.getMemberArchive('init');
_memberArchivesController.getMemberArchive('onRefresh');
scrollController = _memberArchivesController.scrollController; scrollController = _memberArchivesController.scrollController;
scrollController.addListener( scrollController.addListener(
() { () {
@ -48,39 +48,16 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
titleSpacing: 0, titleSpacing: 0,
centerTitle: false, centerTitle: false,
title: Text('他的投稿', style: Theme.of(context).textTheme.titleMedium), title: Text('他的投稿', style: Theme.of(context).textTheme.titleMedium),
// actions: [ actions: [
// Obx( Obx(
// () => PopupMenuButton<String>( () => TextButton.icon(
// padding: EdgeInsets.zero, icon: const Icon(Icons.sort, size: 20),
// tooltip: '投稿排序', onPressed: _memberArchivesController.toggleSort,
// icon: Icon( label: Text(_memberArchivesController.currentOrder['label']!),
// Icons.more_vert_outlined, ),
// color: Theme.of(context).colorScheme.outline, ),
// ), const SizedBox(width: 6),
// position: PopupMenuPosition.under, ],
// onSelected: (String type) {},
// itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
// for (var i in _memberArchivesController.orderList) ...[
// PopupMenuItem<String>(
// onTap: () {},
// value: _memberArchivesController.currentOrder['label'],
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Text(i['label']!),
// if (_memberArchivesController.currentOrder['label'] ==
// i['label']) ...[
// const SizedBox(width: 10),
// const Icon(Icons.done, size: 20),
// ],
// ],
// ),
// ),
// ]
// ],
// ),
// ),
// ],
), ),
body: CustomScrollView( body: CustomScrollView(
controller: _memberArchivesController.scrollController, controller: _memberArchivesController.scrollController,
@ -110,10 +87,16 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
: const SliverToBoxAdapter(), : const SliverToBoxAdapter(),
); );
} else { } else {
return const SliverToBoxAdapter(); return HttpError(
errMsg: snapshot.data['msg'],
fn: () {},
);
} }
} else { } else {
return const SliverToBoxAdapter(); return HttpError(
errMsg: snapshot.data['msg'],
fn: () {},
);
} }
} else { } else {
return const SliverToBoxAdapter(); return const SliverToBoxAdapter();

View File

@ -4,6 +4,7 @@ import 'package:get/get.dart';
import 'package:pilipala/pages/member_dynamics/index.dart'; import 'package:pilipala/pages/member_dynamics/index.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import '../../common/widgets/http_error.dart';
import '../dynamics/widgets/dynamic_panel.dart'; import '../dynamics/widgets/dynamic_panel.dart';
class MemberDynamicsPage extends StatefulWidget { class MemberDynamicsPage extends StatefulWidget {
@ -80,10 +81,16 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
: const SliverToBoxAdapter(), : const SliverToBoxAdapter(),
); );
} else { } else {
return const SliverToBoxAdapter(); return HttpError(
errMsg: snapshot.data['msg'],
fn: () {},
);
} }
} else { } else {
return const SliverToBoxAdapter(); return HttpError(
errMsg: snapshot.data['msg'],
fn: () {},
);
} }
} else { } else {
return const SliverToBoxAdapter(); return const SliverToBoxAdapter();

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