Compare commits

...

55 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
a581945c9e feat: interactive video
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-07 15:38:33 +08:00
bggRGjQaUbCoE
331fd0d619 mod: intro panel
opt: pgc page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-07 15:24:03 +08:00
bggRGjQaUbCoE
c6e229d571 fix: replay
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-06 12:09:29 +08:00
bggRGjQaUbCoE
b2c3b1ff95 fix: #199
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-06 12:09:08 +08:00
bggRGjQaUbCoE
3fc12fcc09 mod: widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 16:55:10 +08:00
bggRGjQaUbCoE
e098631553 mod: dyn square type
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 13:49:37 +08:00
bggRGjQaUbCoE
0fcd55755e mod: handleWebview
Closes #194

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 13:36:44 +08:00
bggRGjQaUbCoE
65e7c0c4f4 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-05 12:19:45 +08:00
bggRGjQaUbCoE
70aecd1e38 mod: view point
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-04 14:38:06 +08:00
bggRGjQaUbCoE
a40c773491 fix: interceptor
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-04 13:09:52 +08:00
bggRGjQaUbCoE
b4abb58a41 mod: seg bar, dyn decorate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-04 11:33:23 +08:00
bggRGjQaUbCoE
e368436bc6 feat: reply: sync to dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-03 11:43:42 +08:00
bggRGjQaUbCoE
6c96b3a7f5 fix: check reply url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-03 10:49:25 +08:00
bggRGjQaUbCoE
149f0c082d fix: reply2reply mode
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-03 10:20:30 +08:00
bggRGjQaUbCoE
994199b5a2 fix: check reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 23:44:47 +08:00
bggRGjQaUbCoE
8db3d80151 fix: onVideoDetailChange
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 22:29:25 +08:00
bggRGjQaUbCoE
93af1e7c44 opt: reply check
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 22:29:10 +08:00
dom
54e90bd986 feat: comment antifraud (#193)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 21:24:07 +08:00
bggRGjQaUbCoE
ca16551917 mod: dm chart height
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-02 17:18:21 +08:00
bggRGjQaUbCoE
f4977d2855 mod: def hardwareDecoding
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-01 18:04:16 +08:00
bggRGjQaUbCoE
bd91fb7c6d mod: show volume when hiding sysui for ios
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-02-01 17:59:22 +08:00
bggRGjQaUbCoE
e1805896f4 Revert "opt: dm chart"
This reverts commit 31a639400e.
2025-01-31 20:40:18 +08:00
bggRGjQaUbCoE
31a639400e opt: dm chart
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-31 16:20:59 +08:00
bggRGjQaUbCoE
d6b24561fa fix: dm chart x
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-31 13:32:07 +08:00
dom
7ba9646d38 feat: danmaku chart (#192)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-31 11:36:05 +08:00
bggRGjQaUbCoE
58a7cf1e75 fix: image preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 18:03:15 +08:00
bggRGjQaUbCoE
1a327198f7 opt: video: onreset
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 15:52:59 +08:00
bggRGjQaUbCoE
e4fe91ef92 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 14:22:20 +08:00
bggRGjQaUbCoE
afcf817c4f fix: video duration
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 13:57:21 +08:00
bggRGjQaUbCoE
21550815db fix: seek preview image
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 12:11:44 +08:00
bggRGjQaUbCoE
02af3a18ff opt: video seek preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-30 10:08:15 +08:00
bggRGjQaUbCoE
a5a13b45cf fix: seek preview index
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-29 23:45:10 +08:00
bggRGjQaUbCoE
0fd232ab3a feat: video seek preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-29 21:20:58 +08:00
bggRGjQaUbCoE
8d83143ca6 opt: fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-29 15:29:20 +08:00
bggRGjQaUbCoE
74452cd622 mod: save as livephoto for ios
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-29 12:07:00 +08:00
bggRGjQaUbCoE
cf2e8cec54 fix: horizontal preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 23:27:46 +08:00
bggRGjQaUbCoE
5231faf254 opt: pages
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 21:57:57 +08:00
bggRGjQaUbCoE
959d4de78a opt: image preview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 18:33:40 +08:00
bggRGjQaUbCoE
f5d7dc6b6a feat: live photo
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 15:40:19 +08:00
bggRGjQaUbCoE
b761c35d10 mod: show pendant/decorate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-28 10:18:57 +08:00
bggRGjQaUbCoE
7f3f7f6bdd mod: dyn author panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 21:50:35 +08:00
bggRGjQaUbCoE
c5877b7c5e feat: custom show dyn decorate
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 21:21:28 +08:00
bggRGjQaUbCoE
9e4187ef17 mod: fetch only-fans dyn
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 18:01:32 +08:00
bggRGjQaUbCoE
bf7ce3e5a2 mod: delay reloading fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 17:04:25 +08:00
bggRGjQaUbCoE
2c55314491 fix: dialog title
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 16:21:00 +08:00
bggRGjQaUbCoE
d28efef672 feat: copy/move toview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 15:17:11 +08:00
bggRGjQaUbCoE
49b631d560 feat: copy/move fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 14:59:00 +08:00
bggRGjQaUbCoE
896510f852 mod: fav sheet
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 13:39:33 +08:00
bggRGjQaUbCoE
1d8e469a46 feat: clean fav
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 13:26:31 +08:00
bggRGjQaUbCoE
caee40a5d9 mod: sr desc
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-27 11:52:45 +08:00
bggRGjQaUbCoE
7de051e6bb fix: skip listener
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-26 20:55:29 +08:00
bggRGjQaUbCoE
18cec3c752 mod: update android icon
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-26 15:22:31 +08:00
bggRGjQaUbCoE
3b46655051 fix: cdn test
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-26 13:06:45 +08:00
bggRGjQaUbCoE
f72ad572fb fix: cdn test
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-26 11:50:08 +08:00
bggRGjQaUbCoE
a57ea2adb6 mod: remove androidNotificationIcon tmply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-25 22:00:26 +08:00
154 changed files with 3749 additions and 2316 deletions

View File

@@ -47,6 +47,17 @@
## feat
- [x] 互动视频
- [x] 发评反诈
- [x] 高能进度条
- [x] 滑动跳转预览视频缩略图
- [x] Live Photo
- [x] 复制/移动收藏夹/稍后再看视频
- [x] 超分辨率
- [x] 合并弹幕
- [x] 会员彩色弹幕
- [x] 播放全部/继续播放/倒序播放
- [x] Cookie登录
- [x] 显示视频分段信息
- [x] 调节字幕大小
- [x] 调节全屏弹幕大小
@@ -74,7 +85,7 @@
- [x] 转发动态
- [x] 合集图片
- [x] 删除/置顶私信
- [x] 举报用户/评论/视频
- [x] 举报用户/评论/视频/动态
- [x] 删除/发布文本/图片动态
- [x] 其他

View File

@@ -1,132 +0,0 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.piliplus">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission
android:name="android.permission.INTERNET"
/>
<application
android:label="PiliPlus Debug"
tools:replace="android:label">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
>
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter android:label="PiliPlus Debug">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="*.bilibili.com"/>
<data android:host="*.bilibili.cn"/>
<data android:host="*.bilibili.tv"/>
<data android:host="bilibili.com"/>
<data android:host="bilibili.cn"/>
<data android:host="bilibili.tv"/>
<data android:host="b23.tv" />
<!--<data android:host="live.bilibili.com"/>-->
<!--<data android:host="www.bilibili.com"/>-->
<!--<data android:host="www.bilibili.tv"/>-->
<!--<data android:host="www.bilibili.cn"/>-->
<!--<data android:host="m.bilibili.cn"/>-->
<!--<data android:host="m.bilibili.com"/>-->
<!--<data android:host="bilibili.cn"/>-->
<!--<data android:host="bilibili.com"/>-->
<!--<data android:host="bangumi.bilibili.com"/>-->
<!--<data android:host="space.bilibili.com"/>-->
</intent-filter>
<intent-filter android:label="PiliPlus Debug">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bilibili"/>
<data android:host="forward" />
<data android:host="comment"
android:pathPattern="/detail/.*/.*/.*" />
<data android:host="uper" />
<data android:host="article"
android:pathPattern="/readlist" />
<data android:host="advertise" android:path="/home" />
<data android:host="clip" />
<data android:host="search" />
<data android:host="stardust-search" />
<data android:host="music" />
<data android:host="bangumi"
android:pathPattern="/season.*" />
<data android:host="bangumi" android:pathPattern="/.*" />
<data android:host="pictureshow"
android:pathPrefix="/creative_center" />
<data android:host="cliparea" />
<data android:host="im" />
<data android:host="im" android:path="/notifications" />
<data android:host="following" />
<data android:host="following"
android:pathPattern="/detail/.*" />
<data android:host="following"
android:path="/publishInfo/" />
<data android:host="laser" android:pathPattern="/.*" />
<data android:host="livearea" />
<data android:host="live" />
<data android:host="catalog" />
<data android:host="browser" />
<data android:host="user_center" />
<data android:host="login" />
<data android:host="space" />
<data android:host="author" />
<data android:host="tag" />
<data android:host="rank" />
<data android:host="external" />
<data android:host="blank" />
<data android:host="home" />
<data android:host="root" />
<data android:host="video" />
<data android:host="story" />
<data android:host="podcast" />
<data android:host="search" />
<data android:host="main" android:path="/favorite" />
<data android:host="pgc" android:path="/theater/match" />
<data android:host="pgc" android:path="/theater/square" />
<data android:host="m.bilibili.com"
android:path="/topic-detail" />
<data android:host="article" />
<data android:host="pegasus"
android:pathPattern="/channel/v2/.*" />
<data android:host="feed" android:pathPattern="/channel" />
<data android:host="vip" />
<data android:host="user_center" android:path="/vip" />
<data android:host="history" />
<data android:host="charge" android:path="/rank" />
<data android:host="assistant" />
<data android:host="assistant" />
<data android:host="feedback" />
<data android:host="auth" android:path="/launch" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="512dp"
android:width="512dp"
android:viewportWidth="512.0"
android:viewportHeight="512.0">
<path
android:fillColor="#FF5CB67B"
android:pathData="M456.65,256C456.65,366.81 366.81,456.65 256,456.65 145.19,456.65 55.35,366.81 55.35,256 55.35,145.18 145.19,55.35 256,55.35 366.81,55.35 456.65,145.18 456.65,256Z"
android:strokeWidth="0.783784"
android:fillType="evenOdd" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M270.04,256L156.1,256l15.61,-76.8h98.32c21.21,0 38.4,17.19 38.4,38.4 0,21.21 -17.19,38.4 -38.4,38.4zM270.04,128L202.46,128l-50.1,256h52.76l15.18,-76.8h49.73c49.49,0 89.6,-40.12 89.6,-89.6 0,-49.49 -40.11,-89.6 -89.6,-89.6z"
android:fillType="evenOdd" />
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FF5CB67B</color>
</resources>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

View File

@@ -1,16 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="512dp"
android:width="512dp"
android:viewportWidth="512.0"
android:viewportHeight="512.0">
android:height="108dp"
android:width="108dp"
android:viewportWidth="108.0"
android:viewportHeight="108.0">
<path
android:fillColor="#FF5CB67B"
android:pathData="M456.65,256C456.65,366.81 366.81,456.65 256,456.65 145.19,456.65 55.35,366.81 55.35,256 55.35,145.18 145.19,55.35 256,55.35 366.81,55.35 456.65,145.18 456.65,256Z"
android:strokeWidth="0.783784"
android:fillColor="@color/ic_launcher_foreground"
android:pathData="M56,54L39.78,54l2.22,-10.94h14c3.02,0 5.47,2.45 5.47,5.47 0,3.02 -2.45,5.47 -5.47,5.47zM56,35.77h-9.62l-7.13,36.45h7.51L48.92,61.29h7.08c7.05,0 12.76,-5.71 12.76,-12.76 0,-7.05 -5.71,-12.76 -12.76,-12.76z"
android:fillType="evenOdd" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M270.04,256L156.1,256l15.61,-76.8h98.32c21.21,0 38.4,17.19 38.4,38.4 0,21.21 -17.19,38.4 -38.4,38.4zM270.04,128L202.46,128l-50.1,256h52.76l15.18,-76.8h49.73c49.49,0 89.6,-40.12 89.6,-89.6 0,-49.49 -40.11,-89.6 -89.6,-89.6z"
android:fillType="evenOdd" />
</vector>
</vector>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="512dp"
android:width="512dp"
android:viewportWidth="512.0"
android:viewportHeight="512.0">
<path
android:fillColor="#FF5CB67B"
android:pathData="M456.65,256C456.65,366.81 366.81,456.65 256,456.65 145.19,456.65 55.35,366.81 55.35,256 55.35,145.18 145.19,55.35 256,55.35 366.81,55.35 456.65,145.18 456.65,256Z"
android:strokeWidth="0.783784"
android:fillType="evenOdd" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M270.04,256L156.1,256l15.61,-76.8h98.32c21.21,0 38.4,17.19 38.4,38.4 0,21.21 -17.19,38.4 -38.4,38.4zM270.04,128L202.46,128l-50.1,256h52.76l15.18,-76.8h49.73c49.49,0 89.6,-40.12 89.6,-89.6 0,-49.49 -40.11,-89.6 -89.6,-89.6z"
android:fillType="evenOdd" />
</vector>

View File

@@ -1,16 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="512.0"
android:viewportHeight="512.0">
android:height="108dp"
android:width="108dp"
android:viewportWidth="108.0"
android:viewportHeight="108.0">
<path
android:fillColor="#FF5CB67B"
android:pathData="M456.65,256C456.65,366.81 366.81,456.65 256,456.65 145.19,456.65 55.35,366.81 55.35,256 55.35,145.18 145.19,55.35 256,55.35 366.81,55.35 456.65,145.18 456.65,256Z"
android:strokeWidth="0.783784"
android:fillType="evenOdd" />
<path
android:fillColor="#FFFFFFFF"
android:pathData="M270.04,256L156.1,256l15.61,-76.8h98.32c21.21,0 38.4,17.19 38.4,38.4 0,21.21 -17.19,38.4 -38.4,38.4zM270.04,128L202.46,128l-50.1,256h52.76l15.18,-76.8h49.73c49.49,0 89.6,-40.12 89.6,-89.6 0,-49.49 -40.11,-89.6 -89.6,-89.6z"
android:pathData="M56,54L39.78,54l2.22,-10.94h14c3.02,0 5.47,2.45 5.47,5.47 0,3.02 -2.45,5.47 -5.47,5.47zM56,35.77h-9.62l-7.13,36.45h7.51L48.92,61.29h7.08c7.05,0 12.76,-5.71 12.76,-12.76 0,-7.05 -5.71,-12.76 -12.76,-12.76z"
android:fillType="evenOdd" />
</vector>

View File

@@ -1,14 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
<monochrome>
<inset
android:drawable="@drawable/ic_launcher_monochrome"
android:inset="16%" />
</monochrome>
</adaptive-icon>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 914 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_foreground">@android:color/system_accent1_100</color>
<color name="ic_launcher_background">@android:color/system_neutral1_800</color>
</resources>

View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_foreground">@android:color/system_neutral2_700</color>
<color name="ic_launcher_background">@android:color/system_accent1_100</color>
</resources>

View File

@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FF5CB67B</color>
<color name="ic_launcher_foreground">#FF5CB67B</color>
<color name="ic_launcher_background">#FFFFFFFF</color>
</resources>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@@ -39,51 +39,55 @@ class VideoCardHSkeleton extends StatelessWidget {
),
// VideoContent(videoItem: videoItem)
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 150,
height: 13,
),
const Spacer(),
Container(
color: Theme.of(context).colorScheme.onInverseSurface,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Row(
children: [
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
margin: const EdgeInsets.only(right: 8),
),
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
),
],
)
],
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
color:
Theme.of(context).colorScheme.onInverseSurface,
width: 200,
height: 11,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
color:
Theme.of(context).colorScheme.onInverseSurface,
width: 150,
height: 13,
),
const Spacer(),
Container(
color:
Theme.of(context).colorScheme.onInverseSurface,
width: 100,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Row(
children: [
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
margin: const EdgeInsets.only(right: 8),
),
Container(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
width: 40,
height: 13,
),
],
)
],
),
),
)),
),
],
),
);

View File

@@ -1,3 +1,5 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
import 'package:PiliPlus/utils/extension.dart';
@@ -68,7 +70,8 @@ Widget articleContent({
} else {
context.imageView(
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
imgList: imgList,
imgList:
imgList.map((url) => SourceModel(url: url)).toList(),
);
}
},

View File

@@ -12,6 +12,7 @@ class PBadge extends StatelessWidget {
final double? fs;
final String? semanticsLabel;
final bool bold;
final double? textScaleFactor;
const PBadge({
super.key,
@@ -26,6 +27,7 @@ class PBadge extends StatelessWidget {
this.fs = 11,
this.semanticsLabel,
this.bold = true,
this.textScaleFactor,
});
@override
@@ -71,6 +73,9 @@ class PBadge extends StatelessWidget {
),
child: Text(
text ?? "",
textScaler: textScaleFactor != null
? TextScaler.linear(textScaleFactor!)
: null,
style: TextStyle(
height: 1,
fontSize: fs ?? fontSize,

View File

@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void showConfirmDialog({
required BuildContext context,
required String title,
String? content,
required VoidCallback onConfirm,
}) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(title),
content: content == null ? null : Text(content),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: onConfirm,
child: Text('确认'),
),
],
);
},
);
}

View File

@@ -1,3 +1,5 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart';
@@ -54,7 +56,7 @@ Widget htmlRender({
callback([imgUrl], 0);
} else {
context.imageView(
imgList: [imgUrl],
imgList: [SourceModel(url: imgUrl)],
);
}
},

View File

@@ -2,9 +2,12 @@ import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel, SourceType;
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/nine_grid_view.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
class ImageModel {
@@ -12,16 +15,20 @@ class ImageModel {
required this.width,
required this.height,
required this.url,
this.liveUrl,
});
dynamic width;
dynamic height;
String url;
String? liveUrl;
bool? _isLongPic;
bool? _isLivePhoto;
dynamic get safeWidth => width ?? 1;
dynamic get safeHeight => height ?? 1;
bool get isLongPic => _isLongPic ??= (safeHeight / safeWidth) > (22 / 9);
bool get isLivePhoto => _isLivePhoto ??= liveUrl?.isNotEmpty == true;
}
Widget imageview(
@@ -83,6 +90,17 @@ Widget imageview(
);
}
late final enableLivePhoto = GStorage.enableLivePhoto;
int parseSize(size) {
return switch (size) {
int() => size,
double() => size.round(),
String() => int.tryParse(size) ?? 1,
_ => 1,
};
}
return NineGridView(
type: NineGridType.weiBo,
margin: const EdgeInsets.only(top: 6),
@@ -102,7 +120,19 @@ Widget imageview(
onViewImage?.call();
context.imageView(
initialPage: index,
imgList: picArr.map((item) => item.url).toList(),
imgList: picArr.map(
(item) {
bool isLive = item.isLivePhoto && enableLivePhoto;
return SourceModel(
sourceType:
isLive ? SourceType.livePhoto : SourceType.networkImage,
url: item.url,
liveUrl: isLive ? item.liveUrl : null,
width: isLive ? parseSize(item.width) : null,
height: isLive ? parseSize(item.height) : null,
);
},
).toList(),
onDismissed: onDismissed,
);
}
@@ -143,7 +173,14 @@ Widget imageview(
},
),
),
if (picArr[index].isLongPic)
if (picArr[index].isLivePhoto)
const PBadge(
text: 'Live',
right: 8,
bottom: 8,
type: 'gray',
)
else if (picArr[index].isLongPic)
const PBadge(
text: '长图',
right: 8,

View File

@@ -10,6 +10,8 @@ import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
import 'package:status_bar_control/status_bar_control.dart';
@@ -33,6 +35,24 @@ typedef IndexedFocusedWidgetBuilder = Widget Function(
typedef IndexedTagStringBuilder = String Function(int index);
enum SourceType { fileImage, networkImage, livePhoto }
class SourceModel {
final SourceType sourceType;
final String url;
final String? liveUrl;
final int? width;
final int? height;
const SourceModel({
this.sourceType = SourceType.networkImage,
required this.url,
this.liveUrl,
this.width,
this.height,
});
}
class InteractiveviewerGallery<T> extends StatefulWidget {
const InteractiveviewerGallery({
super.key,
@@ -45,17 +65,14 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
this.onDismissed,
this.setStatusBar,
this.onClose,
this.isFile,
});
final bool? isFile;
final VoidCallback? onClose;
final bool? setStatusBar;
/// The sources to show.
final List<String> sources;
final List<SourceModel> sources;
/// The index of the first source in [sources] to show.
final int initIndex;
@@ -92,7 +109,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
late Offset _doubleTapLocalPosition;
int? currentIndex;
late final RxInt currentIndex = widget.initIndex.obs;
late List<bool> _thumbList;
late final int _quality = GStorage.previewQ;
@@ -115,10 +132,13 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_animation?.value ?? Matrix4.identity();
});
currentIndex = widget.initIndex;
if (widget.setStatusBar != false) {
setStatusBar();
}
if (widget.sources[currentIndex.value].sourceType == SourceType.livePhoto) {
_onPlay(currentIndex.value);
}
}
setStatusBar() async {
@@ -132,6 +152,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
@override
void dispose() async {
_player?.dispose();
_pageController?.dispose();
_animationController.removeListener(() {});
_animationController.dispose();
@@ -140,8 +161,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
StatusBarControl.setHidden(false, animation: StatusBarAnimation.FADE);
}
}
if (widget.isFile != true) {
for (int index = 0; index < widget.sources.length; index++) {
for (int index = 0; index < widget.sources.length; index++) {
if (widget.sources[index].sourceType == SourceType.networkImage) {
CachedNetworkImageProvider(_getActualUrl(index)).evict();
}
}
@@ -201,14 +222,22 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
}
void _onPlay(int index) {
_player ??= Player();
_videoController ??= VideoController(_player!);
_player!.open(Media(widget.sources[index].liveUrl!));
}
/// When the page view changed its page, the source will animate back into the
/// original scale if it was scaled up.
///
/// Additionally the swipe up / down to dismiss gets enabled.
void _onPageChanged(int page) {
setState(() {
currentIndex = page;
});
_player?.pause();
currentIndex.value = page;
if (widget.sources[page].sourceType == SourceType.livePhoto) {
_onPlay(page);
}
widget.onPageChanged?.call(page);
if (_transformationController!.value != Matrix4.identity()) {
// animate the reset for the transformation of the interactive viewer
@@ -225,8 +254,8 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
String _getActualUrl(int index) => _thumbList[index] && _quality != 100
? '${widget.sources[index]}@${_quality}q.webp'.http2https
: widget.sources[index].http2https;
? '${widget.sources[index].url}@${_quality}q.webp'.http2https
: widget.sources[index].url.http2https;
void onClose() {
if (widget.onClose != null) {
@@ -237,6 +266,9 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
}
}
Player? _player;
VideoController? _videoController;
@override
Widget build(BuildContext context) {
return Stack(
@@ -272,12 +304,15 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_doubleTapLocalPosition = details.localPosition;
},
onDoubleTap: onDoubleTap,
onLongPress: widget.isFile == true ? null : onLongPress,
onLongPress:
widget.sources[index].sourceType == SourceType.fileImage
? null
: onLongPress,
child: widget.itemBuilder != null
? widget.itemBuilder!(
context,
index,
index == currentIndex,
index == currentIndex.value,
_enablePageView,
)
: _itemBuilder(index),
@@ -321,51 +356,70 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
if (widget.sources.length > 1)
Align(
alignment: Alignment.center,
child: Text(
"${currentIndex! + 1}/${widget.sources.length}",
style: const TextStyle(color: Colors.white),
child: Obx(
() => Text(
"${currentIndex.value + 1}/${widget.sources.length}",
style: const TextStyle(color: Colors.white),
),
),
),
if (widget.isFile != true)
if (widget.sources[currentIndex.value].sourceType !=
SourceType.fileImage)
Align(
alignment: Alignment.centerRight,
child: PopupMenuButton(
itemBuilder: (context) {
return [
PopupMenuItem(
value: 0,
onTap: () =>
onShareImg(widget.sources[currentIndex!]),
onTap: () => onShareImg(
widget.sources[currentIndex.value].url),
child: const Text("分享图片"),
),
PopupMenuItem(
value: 1,
onTap: () {
Utils.copyText(widget.sources[currentIndex!]);
Utils.copyText(
widget.sources[currentIndex.value].url);
},
child: const Text("复制链接"),
),
PopupMenuItem(
value: 2,
onTap: () {
DownloadUtils.downloadImg(
context,
[widget.sources[currentIndex!]],
[widget.sources[currentIndex.value].url],
);
},
child: const Text("保存图片"),
),
if (widget.sources.length > 1)
PopupMenuItem(
value: 3,
onTap: () {
DownloadUtils.downloadImg(
context,
widget.sources,
widget.sources
.map((item) => item.url)
.toList(),
);
},
child: const Text("保存全部图片"),
),
if (widget.sources[currentIndex.value].sourceType ==
SourceType.livePhoto)
PopupMenuItem(
onTap: () {
DownloadUtils.downloadLivePhoto(
context: context,
url: widget.sources[currentIndex.value].url,
liveUrl: widget
.sources[currentIndex.value].liveUrl!,
width:
widget.sources[currentIndex.value].width!,
height: widget
.sources[currentIndex.value].height!,
);
},
child: const Text("保存 Live Photo"),
),
];
},
child: const Icon(Icons.more_horiz, color: Colors.white),
@@ -396,34 +450,43 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
Widget _itemBuilder(index) {
return Center(
child: Hero(
tag: widget.sources[index],
child: widget.isFile == true
? Image(
filterQuality: FilterQuality.low,
image: FileImage(File(widget.sources[index])),
)
: CachedNetworkImage(
fadeInDuration: const Duration(milliseconds: 0),
fadeOutDuration: const Duration(milliseconds: 0),
imageUrl: _getActualUrl(index),
// fit: BoxFit.contain,
progressIndicatorBuilder: (context, url, progress) {
return Center(
child: SizedBox(
width: 150.0,
child: LinearProgressIndicator(
value: progress.progress ?? 0),
),
);
},
// errorListener: (value) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// setState(() {
// _thumbList[index] = false;
// });
// });
// },
),
tag: widget.sources[index].url,
child: switch (widget.sources[index].sourceType) {
SourceType.fileImage => Image(
filterQuality: FilterQuality.low,
image: FileImage(File(widget.sources[index].url)),
),
SourceType.networkImage => CachedNetworkImage(
fadeInDuration: const Duration(milliseconds: 0),
fadeOutDuration: const Duration(milliseconds: 0),
imageUrl: _getActualUrl(index),
// fit: BoxFit.contain,
progressIndicatorBuilder: (context, url, progress) {
return Center(
child: SizedBox(
width: 150.0,
child:
LinearProgressIndicator(value: progress.progress ?? 0),
),
);
},
// errorListener: (value) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// setState(() {
// _thumbList[index] = false;
// });
// });
// },
),
SourceType.livePhoto => Obx(() => currentIndex.value == index
? IgnorePointer(
child: Video(
controller: _videoController!,
fill: Colors.transparent,
),
)
: const SizedBox.shrink()),
},
),
);
}
@@ -487,7 +550,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
children: [
ListTile(
onTap: () {
onShareImg(widget.sources[currentIndex!]);
onShareImg(widget.sources[currentIndex.value].url);
Get.back();
},
dense: true,
@@ -496,7 +559,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
ListTile(
onTap: () {
Get.back();
Utils.copyText(widget.sources[currentIndex!]);
Utils.copyText(widget.sources[currentIndex.value].url);
},
dense: true,
title: const Text('复制链接', style: TextStyle(fontSize: 14)),
@@ -506,7 +569,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
Get.back();
DownloadUtils.downloadImg(
context,
[widget.sources[currentIndex!]],
[widget.sources[currentIndex.value].url],
);
},
dense: true,
@@ -518,12 +581,31 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
Get.back();
DownloadUtils.downloadImg(
context,
widget.sources,
widget.sources.map((item) => item.url).toList(),
);
},
dense: true,
title: const Text('保存全部图片', style: TextStyle(fontSize: 14)),
),
if (widget.sources[currentIndex.value].sourceType ==
SourceType.livePhoto)
ListTile(
onTap: () {
Get.back();
DownloadUtils.downloadLivePhoto(
context: context,
url: widget.sources[currentIndex.value].url,
liveUrl: widget.sources[currentIndex.value].liveUrl!,
width: widget.sources[currentIndex.value].width!,
height: widget.sources[currentIndex.value].height!,
);
},
dense: true,
title: const Text(
'保存 Live Photo',
style: TextStyle(fontSize: 14),
),
),
],
),
);

View File

@@ -410,7 +410,7 @@ class _NineGridViewState extends State<NineGridView> {
@override
Widget build(BuildContext context) {
Widget? child = Container();
Widget? child;
double? realWidth = widget.width;
double? realHeight = widget.height;
switch (widget.type) {

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
Widget radioWidget<T>({
required T value,
T? groupValue,
required ValueChanged onChanged,
required String title,
double? paddingStart,
}) {
return InkWell(
onTap: () => onChanged(value),
child: Row(
children: [
if (paddingStart != null) SizedBox(width: paddingStart),
Radio(
value: value,
groupValue: groupValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
),
);
}

View File

@@ -22,7 +22,7 @@ class Segment {
class SegmentProgressBar extends CustomPainter {
final List<Segment> segmentColors;
late double _defHeight;
double? _defHeight;
SegmentProgressBar({
required this.segmentColors,
@@ -42,6 +42,18 @@ class SegmentProgressBar extends CustomPainter {
if (segmentColors[i].title != null) {
double fontSize = 10;
_defHeight ??= (TextPainter(
text: TextSpan(
text: segmentColors[i].title,
style: TextStyle(
fontSize: fontSize,
),
),
textDirection: TextDirection.ltr,
)..layout())
.height +
2;
TextPainter getTextPainter() => TextPainter(
text: TextSpan(
text: segmentColors[i].title,
@@ -51,14 +63,12 @@ class SegmentProgressBar extends CustomPainter {
height: 1,
),
),
strutStyle: StrutStyle(height: 1, leading: 0),
strutStyle:
StrutStyle(leading: 0, height: 1, fontSize: fontSize),
textDirection: TextDirection.ltr,
)..layout();
TextPainter textPainter = getTextPainter();
if (i == 0) {
_defHeight = textPainter.height;
}
late double prevStart;
if (i != 0) {
@@ -75,7 +85,7 @@ class SegmentProgressBar extends CustomPainter {
canvas.drawRect(
Rect.fromLTRB(
0,
-_defHeight - 2,
-_defHeight!,
size.width,
0,
),
@@ -86,9 +96,9 @@ class SegmentProgressBar extends CustomPainter {
canvas.drawRect(
Rect.fromLTWH(
segmentStart,
-_defHeight - 2,
-_defHeight!,
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
size.height + _defHeight + 2,
size.height + _defHeight!,
),
paint,
);
@@ -98,7 +108,7 @@ class SegmentProgressBar extends CustomPainter {
: (segmentStart - prevStart - textPainter.width) / 2 +
prevStart +
1;
double textY = (-_defHeight - textPainter.height) / 2 - 1;
double textY = (-_defHeight! - textPainter.height) / 2;
textPainter.paint(canvas, Offset(textX, textY));
} else {
canvas.drawRect(

View File

@@ -151,7 +151,8 @@ class VideoCardH extends StatelessWidget {
},
),
),
videoContent(context)
const SizedBox(width: 10),
videoContent(context),
],
),
),
@@ -176,107 +177,102 @@ class VideoCardH extends StatelessWidget {
: '';
if (pubdate != '') pubdate += ' ';
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (videoItem.title is String)
Expanded(
child: Text(
videoItem.title as String,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (videoItem.title is String)
Expanded(
child: Text(
videoItem.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
)
else
Expanded(
child: RichText(
overflow: TextOverflow.ellipsis,
maxLines: 2,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan(
children: [
for (final i in videoItem.title) ...[
TextSpan(
text: i['text'] as String,
style: TextStyle(
fontSize: Theme.of(context)
.textTheme
.bodyMedium!
.fontSize,
letterSpacing: 0.3,
color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
)
else
Expanded(
child: Text.rich(
overflow: TextOverflow.ellipsis,
maxLines: 2,
TextSpan(
children: [
for (final i in videoItem.title) ...[
TextSpan(
text: i['text'] as String,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: i['type'] == 'em'
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
),
]
],
),
),
]
],
),
),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Expanded(
flex: 0,
child: Text(
"$pubdate ${showOwner ? videoItem.owner.name : ''}",
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
),
const SizedBox(height: 3),
Row(
children: [
if (showView) ...[
statView(
context: context,
theme: 'gray',
view: videoItem.stat.view as int,
),
const SizedBox(width: 8),
],
if (showDanmaku)
statDanMu(
context: context,
theme: 'gray',
danmu: videoItem.stat.danmu as int,
),
const Spacer(),
if (source == 'normal') const SizedBox(width: 24),
],
),
],
),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Expanded(
flex: 0,
child: Text(
"$pubdate ${showOwner ? videoItem.owner.name : ''}",
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
),
const SizedBox(height: 3),
Row(
children: [
if (showView) ...[
statView(
context: context,
theme: 'gray',
view: videoItem.stat.view as int,
),
const SizedBox(width: 8),
],
if (showDanmaku)
statDanMu(
context: context,
theme: 'gray',
danmu: videoItem.stat.danmu as int,
),
const Spacer(),
if (source == 'normal') const SizedBox(width: 24),
],
),
],
),
);
}

View File

@@ -111,7 +111,8 @@ class VideoCardHGrpc extends StatelessWidget {
},
),
),
videoContent(context)
const SizedBox(width: 10),
videoContent(context),
],
);
},
@@ -133,58 +134,43 @@ class VideoCardHGrpc extends StatelessWidget {
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...[
Expanded(
child: Text(
videoItem.smallCoverV5.base.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
videoItem.smallCoverV5.base.title,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
],
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Text(
videoItem.smallCoverV5.rightDesc1,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
// const Spacer(),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// Container(
// padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 5),
// decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(4),
// border: Border.all(
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// child: Text(
// videoItem.rcmdReason.content,
// style: TextStyle(
// fontSize: 9,
// color: Theme.of(context).colorScheme.surfaceTint),
// ),
// ),
// const SizedBox(height: 4),
if (showOwner || showPubdate)
Text(
videoItem.smallCoverV5.rightDesc2,
videoItem.smallCoverV5.rightDesc1,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
@@ -193,26 +179,36 @@ class VideoCardHGrpc extends StatelessWidget {
overflow: TextOverflow.clip,
),
),
// Row(
// children: [
// if (showView) ...[
// StatView(
// theme: 'gray',
// view: videoItem.stat.view as int,
// ),
// const SizedBox(width: 8),
// ],
// if (showDanmaku)
// StatDanMu(
// theme: 'gray',
// danmu: videoItem.stat.danmu as int,
// ),
// const Spacer(),
// if (source == 'normal') const SizedBox(width: 24),
// ],
// ),
],
),
const SizedBox(height: 3),
Text(
videoItem.smallCoverV5.rightDesc2,
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
// Row(
// children: [
// if (showView) ...[
// StatView(
// theme: 'gray',
// view: videoItem.stat.view as int,
// ),
// const SizedBox(width: 8),
// ],
// if (showDanmaku)
// StatDanMu(
// theme: 'gray',
// danmu: videoItem.stat.danmu as int,
// ),
// const Spacer(),
// if (source == 'normal') const SizedBox(width: 24),
// ],
// ),
],
),
);
}

View File

@@ -103,7 +103,8 @@ class VideoCardHMemberVideo extends StatelessWidget {
},
),
),
videoContent(context)
const SizedBox(width: 10),
videoContent(context),
],
);
},
@@ -125,66 +126,61 @@ class VideoCardHMemberVideo extends StatelessWidget {
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...[
Expanded(
child: Text(
// videoItem.season?['title'] ?? videoItem.title ?? '',
videoItem.title ?? '',
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: videoItem.bvid != null && videoItem.bvid == bvid
? FontWeight.bold
: null,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: videoItem.bvid != null && videoItem.bvid == bvid
? Theme.of(context).colorScheme.primary
: null,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Text(
// videoItem.season?['title'] ?? videoItem.title ?? '',
videoItem.title ?? '',
textAlign: TextAlign.start,
style: TextStyle(
fontWeight: videoItem.bvid != null && videoItem.bvid == bvid
? FontWeight.bold
: null,
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
color: videoItem.bvid != null && videoItem.bvid == bvid
? Theme.of(context).colorScheme.primary
: null,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Text(
videoItem.season != null
? Utils.dateFormat(videoItem.season?['mtime'])
: videoItem.publishTimeText ?? '',
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
Row(
children: [
statView(
context: context,
theme: 'gray',
// view: videoItem.season?['view_content'] ??
// videoItem.viewContent,
view: videoItem.viewContent,
),
const SizedBox(width: 8),
statDanMu(
context: context,
theme: 'gray',
// danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku,
danmu: videoItem.danmaku,
),
],
Text(
videoItem.season != null
? Utils.dateFormat(videoItem.season?['mtime'])
: videoItem.publishTimeText ?? '',
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const SizedBox(height: 3),
Row(
children: [
statView(
context: context,
theme: 'gray',
// view: videoItem.season?['view_content'] ??
// videoItem.viewContent,
view: videoItem.viewContent,
),
const SizedBox(width: 8),
statDanMu(
context: context,
theme: 'gray',
// danmu: videoItem.season?['danmaku'] ?? videoItem.danmaku,
danmu: videoItem.danmaku,
),
],
),
],
),
),
],
),
);
}

View File

@@ -314,10 +314,9 @@ class VideoCardV extends StatelessWidget {
const Spacer(),
Expanded(
flex: 0,
child: RichText(
child: Text.rich(
maxLines: 1,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan(
TextSpan(
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
@@ -337,10 +336,9 @@ class VideoCardV extends StatelessWidget {
const Spacer(),
Expanded(
flex: 0,
child: RichText(
child: Text.rich(
maxLines: 1,
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan(
TextSpan(
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,

View File

@@ -121,7 +121,15 @@ class Api {
// up_mid num 目标用户mid 必要
// type num 目标内容属性 非必要 默认为全部 0全部 2视频稿件
// rid num 目标 视频稿件avid
static const String videoInFolder = '/x/v3/fav/folder/created/list-all';
static const String favFolder = '/x/v3/fav/folder/created/list-all';
static const String copyFav = '/x/v3/fav/resource/copy';
static const String moveFav = '/x/v3/fav/resource/move';
static const String copyToview = '/x/v2/history/toview/copy';
static const String moveToview = '/x/v2/history/toview/move';
// 视频详情页 相关视频
static const String relatedList = '/x/web-interface/archive/related';
@@ -182,6 +190,8 @@ class Api {
static const String deleteFolder = '/x/v3/fav/folder/del';
static const String cleanFav = '/x/v3/fav/resource/clean';
/// 收藏夹 详情
/// media_id 当前收藏夹id 搜索全部时为默认收藏夹id
/// pn int 当前页

View File

@@ -14,7 +14,7 @@ class DynamicsHttp {
'type': type ?? 'all',
'timezone_offset': '-480',
'offset': offset,
'features': 'itemOpusStyle'
'features': 'itemOpusStyle,listOnlyfans'
};
if (mid != -1) {
data['host_mid'] = mid;

View File

@@ -14,7 +14,6 @@ import '../utils/utils.dart';
import 'api.dart';
import 'constants.dart';
import 'interceptor.dart';
import 'interceptor_anonymity.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
class Request {
@@ -37,7 +36,7 @@ class Request {
);
cookieManager = CookieManager(cookieJar);
dio.interceptors.add(cookieManager);
dio.interceptors.add(AnonymityInterceptor());
dio.interceptors.add(ApiInterceptor());
final List<Cookie> cookies = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.baseUrl));
for (Cookie item in cookies) {
@@ -175,15 +174,12 @@ class Request {
);
}
//添加拦截器
dio.interceptors.add(ApiInterceptor());
// 日志拦截器 输出请求、响应内容
dio.interceptors.add(LogInterceptor(
request: false,
requestHeader: false,
responseHeader: false,
));
// dio.interceptors.add(LogInterceptor(
// request: false,
// requestHeader: false,
// responseHeader: false,
// ));
dio.transformer = BackgroundTransformer();
dio.options.validateStatus = (int? status) {

View File

@@ -1,17 +1,67 @@
import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class ApiInterceptor extends Interceptor {
// @override
// void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
// debugPrint("请求之前");
// // 在请求之前添加头部或认证信息
// options.headers['Authorization'] = 'Bearer token';
// options.headers['Content-Type'] = 'application/json';
// handler.next(options);
// }
static const List<String> anonymityList = [
Api.videoUrl,
Api.videoIntro,
Api.relatedList,
Api.replyList,
Api.replyReplyList,
Api.searchSuggest,
Api.searchByType,
Api.heartBeat,
Api.ab2c,
Api.bangumiInfo,
Api.liveRoomInfo,
Api.onlineTotal,
Api.webDanmaku,
Api.dynamicDetail,
Api.aiConclusion,
Api.getSeasonDetailApi,
Api.liveRoomDmToken,
Api.liveRoomDmPrefetch,
];
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
void onRemoveCookie() {
options.headers.remove('x-bili-mid');
options.headers.remove('x-bili-aurora-eid');
options.headers.remove('x-bili-aurora-zone');
options.headers['cookie'] = '';
options.queryParameters.remove('access_key');
options.queryParameters.remove('csrf');
options.queryParameters.remove('csrf_token');
if (options.data is Map) {
options.data.remove('access_key');
options.data.remove('csrf');
options.data.remove('csrf_token');
}
}
if (options.extra['clearCookie'] == true) {
onRemoveCookie();
} else if (MineController.anonymity.value) {
String uri = options.uri.toString();
for (var i in anonymityList) {
// 如果请求的url包含无痕列表中的url则清空cookie
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
int index = uri.indexOf(i);
if (index == -1) continue;
if (uri.lastIndexOf('/') >= index + i.length) continue;
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
onRemoveCookie();
break;
}
}
handler.next(options);
}
// @override
// void onResponse(Response response, ResponseInterceptorHandler handler) {

View File

@@ -1,51 +0,0 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:dio/dio.dart';
// import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../pages/mine/controller.dart';
import 'api.dart';
class AnonymityInterceptor extends Interceptor {
static const List<String> anonymityList = [
Api.videoUrl,
Api.videoIntro,
Api.relatedList,
Api.replyList,
Api.replyReplyList,
Api.searchSuggest,
Api.searchByType,
Api.heartBeat,
Api.ab2c,
Api.bangumiInfo,
Api.liveRoomInfo,
Api.onlineTotal,
Api.webDanmaku,
Api.dynamicDetail,
Api.aiConclusion,
Api.getSeasonDetailApi,
Api.liveRoomDmToken,
Api.liveRoomDmPrefetch,
];
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
if (MineController.anonymity.value) {
String uri = options.uri.toString();
for (var i in anonymityList) {
// 如果请求的url包含无痕列表中的url则清空cookie
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
int index = uri.indexOf(i);
if (index == -1) continue;
if (uri.lastIndexOf('/') >= index + i.length) continue;
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
options.headers[HttpHeaders.cookieHeader] = "";
if (options.data != null && options.data.csrf != null) {
options.data.csrf = "";
}
break;
}
}
handler.next(options);
}
}

View File

@@ -404,7 +404,7 @@ class MemberHttp {
'offset': offset ?? '',
'host_mid': mid,
'timezone_offset': '-480',
'features': 'itemOpusStyle',
'features': 'itemOpusStyle,listOnlyfans',
'platform': 'web',
'web_location': '333.999',
'dm_img_list': '[]',

View File

@@ -1,19 +1,17 @@
import 'dart:io';
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPlus/grpc/grpc_repo.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:dio/dio.dart';
import '../models/video/reply/data.dart';
import '../models/video/reply/emote.dart';
import 'api.dart';
import 'constants.dart';
import 'init.dart';
class ReplyHttp {
static Options get _options => Options(extra: {'clearCookie': true});
static Future<LoadingState> replyList({
required bool isLogin,
required int oid,
@@ -23,13 +21,9 @@ class ReplyHttp {
int sort = 1,
required String banWordForReply,
}) async {
Options? options = !isLogin
? Options(
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
: null;
var res = !isLogin
? await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}/main',
'${Api.replyList}/main',
queryParameters: {
'oid': oid,
'type': type,
@@ -37,10 +31,10 @@ class ReplyHttp {
'{"offset":"${nextOffset.replaceAll('"', '\\"')}"}',
'mode': sort + 2, //2:按时间排序3按热度排序
},
options: options,
options: isLogin.not ? _options : null,
)
: await Request().get(
'${HttpString.apiBaseUrl}${Api.replyList}',
Api.replyList,
queryParameters: {
'oid': oid,
'type': type,
@@ -48,7 +42,7 @@ class ReplyHttp {
'pn': page,
'ps': 20,
},
options: options,
options: isLogin.not ? _options : null,
);
if (res.data['code'] == 0) {
ReplyData replyData = ReplyData.fromJson(res.data['data']);
@@ -134,28 +128,25 @@ class ReplyHttp {
}
static Future<LoadingState> replyReplyList({
required bool isLogin,
required int oid,
required int root,
required int pageNum,
required int type,
int sort = 1,
required String banWordForReply,
bool? isCheck,
}) async {
Options? options = GStorage.userInfo.get('userInfoCache') == null
? Options(
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
: null;
var res = await Request().get(
'${HttpString.apiBaseUrl}${Api.replyReplyList}',
Api.replyReplyList,
queryParameters: {
'oid': oid,
'root': root,
'pn': pageNum,
'type': type,
'sort': 1,
'csrf': await Request.getCsrf(),
if (isLogin) 'csrf': await Request.getCsrf(),
},
options: options,
options: isLogin.not ? _options : null,
);
if (res.data['code'] == 0) {
ReplyReplyData replyData = ReplyReplyData.fromJson(res.data['data']);
@@ -168,7 +159,11 @@ class ReplyHttp {
}
return LoadingState.success(replyData);
} else {
return LoadingState.error(res.data['message']);
return LoadingState.error(
isCheck == true
? '${res.data['code']}${res.data['message']}'
: res.data['message'],
);
}
}

View File

@@ -62,6 +62,27 @@ class UserHttp {
}
}
static Future cleanFav({
required dynamic mediaId,
}) async {
var res = await Request().post(
Api.cleanFav,
data: {
'media_id': mediaId,
'platform': 'web',
'csrf': await Request.getCsrf(),
},
options: Options(
contentType: Headers.formUrlEncodedContentType,
),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
static Future deleteFolder({
required List<dynamic> mediaIds,
}) async {

View File

@@ -676,6 +676,52 @@ class VideoHttp {
// }
// }
static Future copyOrMoveFav({
required bool isCopy,
required bool isFav,
required dynamic srcMediaId,
required dynamic tarMediaId,
dynamic mid,
required List resources,
}) async {
var res = await Request().post(
isFav
? isCopy
? Api.copyFav
: Api.moveFav
: isCopy
? Api.copyToview
: Api.moveToview,
data: {
if (srcMediaId != null) 'src_media_id': srcMediaId,
'tar_media_id': tarMediaId,
if (mid != null) 'mid': mid,
'resources': resources.join(','),
'platform': 'web',
'csrf': await Request.getCsrf(),
},
options: Options(contentType: Headers.formUrlEncodedContentType),
);
if (res.data['code'] == 0) {
return {'status': true};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
static Future allFavFolders(mid) async {
var res = await Request().get(
Api.favFolder,
queryParameters: {'up_mid': mid},
);
if (res.data['code'] == 0) {
FavFolderData data = FavFolderData.fromJson(res.data['data']);
return {'status': true, 'data': data};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
// 查看视频被收藏在哪个文件夹
static Future videoInFolder({
dynamic mid,
@@ -683,7 +729,7 @@ class VideoHttp {
dynamic type,
}) async {
var res = await Request().get(
Api.videoInFolder,
Api.favFolder,
queryParameters: {
'up_mid': mid,
'rid': rid,
@@ -713,6 +759,7 @@ class VideoHttp {
int? root,
int? parent,
List? pictures,
bool? syncToDynamic,
}) async {
if (message == '') {
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
@@ -720,10 +767,11 @@ class VideoHttp {
Map<String, dynamic> data = {
'type': type.index,
'oid': oid,
'root': root == null || root == 0 ? '' : root,
'parent': parent == null || parent == 0 ? '' : parent,
if (root != null && root != 0) 'root': root,
if (parent != null && parent != 0) 'parent': parent,
'message': message,
if (pictures != null) 'pictures': jsonEncode(pictures),
if (syncToDynamic == true) 'sync_to_dynamic': 1,
'csrf': await Request.getCsrf(),
};
var res = await Request().post(
@@ -929,7 +977,6 @@ class VideoHttp {
);
if (res.data['code'] == 0) {
dynamic data = res.data['data'];
List subtitlesJson = data['subtitle']['subtitles'];
/*
[
{
@@ -947,10 +994,11 @@ class VideoHttp {
*/
return {
'status': true,
'data': subtitlesJson,
'data': data['subtitle']['subtitles'],
'view_points': data['view_points'],
// 'last_play_time': data['last_play_time'],
'last_play_cid': data['last_play_cid'],
'interaction': data['interaction'],
};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};

View File

@@ -275,6 +275,12 @@ class MyApp extends StatelessWidget {
progressIndicatorTheme: ProgressIndicatorThemeData(
refreshBackgroundColor: colorScheme.onSecondary,
),
dialogTheme: DialogTheme(
titleTextStyle: TextStyle(
fontSize: 18,
color: colorScheme.onSurface,
),
),
);
}
}

View File

@@ -1,5 +1,7 @@
import 'dart:convert';
import 'package:PiliPlus/utils/storage.dart';
class DynamicsDataModel {
DynamicsDataModel({
this.hasMore,
@@ -126,6 +128,8 @@ class ModuleAuthorModel {
this.pubTs,
this.type,
this.vip,
this.decorate,
this.pendant,
});
String? face;
@@ -139,6 +143,8 @@ class ModuleAuthorModel {
int? pubTs;
String? type;
Map? vip;
Map? decorate;
Map? pendant;
ModuleAuthorModel.fromJson(Map<String, dynamic> json) {
face = json['face'];
@@ -152,7 +158,13 @@ class ModuleAuthorModel {
pubTs = json['pub_ts'] == 0 ? null : json['pub_ts'];
type = json['type'];
vip = json['vip'];
if (showDynDecorate) {
decorate = json['decorate'];
pendant = json['pendant'];
}
}
static bool showDynDecorate = GStorage.showDynDecorate;
}
// 单个动态详情 - 动态信息
@@ -421,6 +433,7 @@ class DynamicMajorModel {
this.courses,
this.common,
this.music,
this.blocked,
});
DynamicArchiveModel? archive;
@@ -438,6 +451,7 @@ class DynamicMajorModel {
Map? courses;
Map? common;
Map? music;
Map? blocked;
DynamicMajorModel.fromJson(Map<String, dynamic> json) {
archive = json['archive'] != null
@@ -463,6 +477,7 @@ class DynamicMajorModel {
courses = json['courses'] ?? {};
common = json['common'] ?? {};
music = json['music'] ?? {};
blocked = json['blocked'];
}
}
@@ -658,6 +673,7 @@ class OpusPicsModel {
int? size;
String? src;
String? url;
String? liveUrl;
OpusPicsModel.fromJson(Map<String, dynamic> json) {
width = json['width'];
@@ -665,6 +681,7 @@ class OpusPicsModel {
size = json['size'] != null ? json['size'].toInt() : 0;
src = json['src'];
url = json['url'];
liveUrl = json['live_url'];
}
}

View File

@@ -53,7 +53,8 @@ class _AboutPageState extends State<AboutPage> {
TextStyle subTitleStyle =
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
return Scaffold(
appBar: widget.showAppBar == false ? null : AppBar(title: Text('关于')),
appBar:
widget.showAppBar == false ? null : AppBar(title: const Text('关于')),
body: ListView(
children: [
GestureDetector(

View File

@@ -6,6 +6,7 @@ import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/video/detail/introduction/controller.dart';
import 'package:PiliPlus/pages/video/detail/introduction/pay_coins_page.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -59,8 +60,7 @@ class BangumiIntroController extends CommonController {
dynamic videoTags;
bool isLogin = false;
Rx<FavFolderData> favFolderData = FavFolderData().obs;
List addMediaIdsNew = [];
List delMediaIdsNew = [];
List? favIds;
dynamic userInfo;
late final enableQuickFav =
@@ -314,12 +314,20 @@ class BangumiIntroController extends CommonController {
});
return;
}
List addMediaIdsNew = [];
List delMediaIdsNew = [];
try {
for (var i in favFolderData.value.list!.toList()) {
bool isFaved = favIds?.contains(i.id) == true;
if (i.favState == 1) {
addMediaIdsNew.add(i.id);
if (isFaved.not) {
addMediaIdsNew.add(i.id);
}
} else {
delMediaIdsNew.add(i.id);
if (isFaved) {
delMediaIdsNew.add(i.id);
}
}
}
} catch (_) {}
@@ -330,8 +338,6 @@ class BangumiIntroController extends CommonController {
delIds: delMediaIdsNew.join(','),
);
if (result['status']) {
addMediaIdsNew = [];
delMediaIdsNew = [];
SmartDialog.showToast('操作成功');
Get.back();
Future.delayed(const Duration(milliseconds: 255), () {
@@ -394,14 +400,10 @@ class BangumiIntroController extends CommonController {
onChoose(bool checkValue, int index) {
feedBack();
List<FavFolderItemData> datalist = favFolderData.value.list!;
for (var i = 0; i < datalist.length; i++) {
if (i == index) {
datalist[i].favState = checkValue == true ? 1 : 0;
datalist[i].mediaCount = checkValue == true
? datalist[i].mediaCount! + 1
: datalist[i].mediaCount! - 1;
}
}
datalist[index].favState = checkValue ? 1 : 0;
datalist[index].mediaCount = checkValue
? datalist[index].mediaCount! + 1
: datalist[index].mediaCount! - 1;
favFolderData.value.list = datalist;
favFolderData.refresh();
}
@@ -416,11 +418,7 @@ class BangumiIntroController extends CommonController {
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
..plPlayerController.pause()
..makeHeartBeat()
..playedTime = null
..videoUrl = null
..audioUrl = null
..vttSubtitlesIndex = null
..savedDanmaku = null
..onReset()
..epId = epId
..bvid = bvid
..cid.value = cid
@@ -487,6 +485,7 @@ class BangumiIntroController extends CommonController {
}
Future queryVideoInFolder() async {
favIds = null;
var result = await VideoHttp.videoInFolder(
mid: userInfo.mid,
rid: epId, // bangumi
@@ -494,6 +493,10 @@ class BangumiIntroController extends CommonController {
);
if (result['status']) {
favFolderData.value = result['data'];
favIds = favFolderData.value.list
?.where((item) => item.favState == 1)
.map((item) => item.id)
.toList();
}
return result;
}

View File

@@ -1,6 +1,8 @@
import 'dart:async';
import 'package:PiliPlus/common/widgets/http_error.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
@@ -17,7 +19,6 @@ import 'package:PiliPlus/pages/bangumi/widgets/bangumi_panel.dart';
import 'package:PiliPlus/pages/video/detail/index.dart';
import 'package:PiliPlus/pages/video/detail/introduction/widgets/action_item.dart';
import 'package:PiliPlus/pages/video/detail/introduction/widgets/action_row_item.dart';
import 'package:PiliPlus/pages/video/detail/introduction/widgets/fav_panel.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import '../../../utils/utils.dart';
@@ -185,42 +186,13 @@ class _BangumiInfoState extends State<BangumiInfo>
if (type == 'tap') {
bangumiIntroController.actionFavVideo(type: 'default');
} else {
_showFavBottomSheet();
Utils.showFavBottomSheet(context: context, ctr: bangumiIntroController);
}
} else if (type != 'longPress') {
_showFavBottomSheet();
Utils.showFavBottomSheet(context: context, ctr: bangumiIntroController);
}
}
_showFavBottomSheet() {
showModalBottomSheet(
context: context,
useSafeArea: true,
isScrollControlled: true,
transitionAnimationController: AnimationController(
duration: const Duration(milliseconds: 200),
vsync: this,
),
sheetAnimationStyle: AnimationStyle(curve: Curves.ease),
builder: (BuildContext context) {
return DraggableScrollableSheet(
minChildSize: 0,
maxChildSize: 1,
initialChildSize: 0.7,
snap: true,
expand: false,
snapSizes: const [0.7],
builder: (BuildContext context, ScrollController scrollController) {
return FavPanel(
ctr: bangumiIntroController,
scrollController: scrollController,
);
},
);
},
);
}
// 视频介绍
showIntroDetail() {
feedBack();
@@ -254,9 +226,11 @@ class _BangumiInfoState extends State<BangumiInfo>
videoDetailCtr.onViewImage();
context.imageView(
imgList: [
!widget.isLoading
? widget.bangumiDetail!.cover!
: bangumiItem!.cover!
SourceModel(
url: !widget.isLoading
? widget.bangumiDetail!.cover!
: bangumiItem!.cover!,
)
],
onDismissed: videoDetailCtr.onDismissed,
);
@@ -291,7 +265,7 @@ class _BangumiInfoState extends State<BangumiInfo>
Expanded(
child: GestureDetector(
onTap: showIntroDetail,
behavior: HitTestBehavior.translucent,
behavior: HitTestBehavior.opaque,
child: SizedBox(
height: isLandscape ? 115 : 115 / 0.75,
child: Column(

View File

@@ -82,48 +82,48 @@ class _BangumiPageState extends State<BangumiPage>
slivers: [
SliverToBoxAdapter(
child: Obx(
() => Visibility(
visible: _bangumiController.isLogin.value,
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Obx(
() => Text(
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${_bangumiController.followCount.value == -1 ? '' : ' ${_bangumiController.followCount.value}'}',
style: Theme.of(context).textTheme.titleMedium,
),
() => _bangumiController.isLogin.value
? Column(
children: [
Padding(
padding: const EdgeInsets.only(left: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Obx(
() => Text(
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${_bangumiController.followCount.value == -1 ? '' : ' ${_bangumiController.followCount.value}'}',
style:
Theme.of(context).textTheme.titleMedium,
),
),
IconButton(
tooltip: '刷新',
onPressed: () {
_bangumiController
..followPage = 1
..followEnd = false
..queryBangumiFollow();
},
icon: const Icon(
Icons.refresh,
size: 20,
),
),
],
),
IconButton(
tooltip: '刷新',
onPressed: () {
_bangumiController
..followPage = 1
..followEnd = false
..queryBangumiFollow();
},
icon: const Icon(
Icons.refresh,
size: 20,
),
),
SizedBox(
height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(50),
child: Obx(
() => _buildFollowBody(
_bangumiController.followState.value),
),
],
),
),
SizedBox(
height: Grid.smallCardWidth / 2 / 0.75 +
MediaQuery.textScalerOf(context).scale(50),
child: Obx(
() => _buildFollowBody(
_bangumiController.followState.value),
),
),
],
),
),
),
],
)
: const SizedBox.shrink(),
),
),
SliverToBoxAdapter(

View File

@@ -4,6 +4,8 @@ import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel, SourceType;
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:chat_bottom_container/chat_bottom_container.dart';
@@ -274,8 +276,12 @@ abstract class CommonPublishPageState<T extends CommonPublishPage>
onTap: () {
controller.keepChatPanel();
context.imageView(
isFile: true,
imgList: pathList,
imgList: pathList
.map((path) => SourceModel(
url: path,
sourceType: SourceType.fileImage,
))
.toList(),
initialPage: index,
);
},

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/reply.dart';
import 'package:PiliPlus/models/common/reply_type.dart';
import 'package:PiliPlus/models/video/reply/data.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
@@ -9,6 +10,7 @@ import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/models/common/reply_sort_type.dart';
import 'package:PiliPlus/models/video/reply/item.dart';
@@ -27,10 +29,11 @@ abstract class ReplyController extends CommonController {
late final bool isLogin = GStorage.userInfo.get('userInfoCache') != null;
CursorReply? cursor;
late Mode mode = Mode.MAIN_LIST_HOT;
late Rx<Mode> mode = Mode.MAIN_LIST_HOT.obs;
late bool hasUpTop = false;
late final banWordForReply = GStorage.banWordForReply;
late final enableCommAntifraud = GStorage.enableCommAntifraud;
@override
void onInit() {
@@ -43,7 +46,7 @@ abstract class ReplyController extends CommonController {
}
sortType.value = ReplySortType.values[defaultReplySortIndex];
if (sortType.value == ReplySortType.time) {
mode = Mode.MAIN_LIST_TIME;
mode.value = Mode.MAIN_LIST_TIME;
}
}
@@ -95,7 +98,7 @@ abstract class ReplyController extends CommonController {
hasUpTop = true;
}
}
if (response.response.topReplies != null) {
if ((response.response.topReplies as List?)?.isNotEmpty == true) {
replies.insertAll(0, response.response.topReplies);
hasUpTop = true;
}
@@ -117,11 +120,11 @@ abstract class ReplyController extends CommonController {
switch (sortType.value) {
case ReplySortType.time:
sortType.value = ReplySortType.like;
mode = Mode.MAIN_LIST_HOT;
mode.value = Mode.MAIN_LIST_HOT;
break;
case ReplySortType.like:
sortType.value = ReplySortType.time;
mode = Mode.MAIN_LIST_TIME;
mode.value = Mode.MAIN_LIST_TIME;
break;
}
nextOffset = '';
@@ -204,21 +207,44 @@ abstract class ReplyController extends CommonController {
}
count.value += 1;
loadingState.value = LoadingState.success(response);
if (enableCommAntifraud && context.mounted) {
checkReply(
context,
oid ?? replyItem.oid.toInt(),
replyItem?.id.toInt(),
replyItem?.type.toInt() ??
replyType?.index ??
ReplyType.video.index,
replyInfo.id.toInt(),
replyInfo.content.message,
);
}
} else {
ReplyData response = loadingState.value is Success
? (loadingState.value as Success).response
: ReplyData();
response.replies ??= <ReplyItemModel>[];
ReplyItemModel replyInfo = ReplyItemModel.fromJson(res, '');
if (oid != null) {
response.replies
?.insert(hasUpTop ? 1 : 0, ReplyItemModel.fromJson(res, ''));
response.replies?.insert(hasUpTop ? 1 : 0, replyInfo);
} else {
response.replies?[index].replies ??= <ReplyItemModel>[];
response.replies?[index].replies
?.add(ReplyItemModel.fromJson(res, ''));
response.replies?[index].replies?.add(replyInfo);
}
count.value += 1;
loadingState.value = LoadingState.success(response);
if (enableCommAntifraud && context.mounted) {
checkReply(
context,
oid ?? replyItem.oid,
replyItem?.rpid,
replyItem?.type.toInt() ??
replyType?.index ??
ReplyType.video.index,
replyInfo.rpid ?? 0,
replyInfo.content?.message ?? '',
);
}
}
}
},
@@ -262,4 +288,186 @@ abstract class ReplyController extends CommonController {
loadingState.value = LoadingState.success(response);
}
}
// ref https://github.com/freedom-introvert/biliSendCommAntifraud
void checkReply(
BuildContext context,
dynamic oid,
dynamic rpid,
int replyType,
int replyId,
String message,
) async {
void showReplyCheckResult(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('评论检查结果'),
content: SelectableText(message),
),
);
}
await Future.delayed(const Duration(seconds: 5));
if (context.mounted.not) return;
// root reply
if (rpid == null) {
// no cookie check
dynamic res = await ReplyHttp.replyList(
isLogin: false,
oid: oid,
nextOffset: '',
type: replyType,
sort: ReplySortType.time.index,
page: 1,
banWordForReply: '',
);
if (context.mounted.not) return;
if (res is Error) {
SmartDialog.showToast('获取评论主列表时发生错误:${res.errMsg}');
return;
} else if (res is Success) {
ReplyData replies = res.response;
int index =
replies.replies?.indexWhere((item) => item.rpid == replyId) ?? -1;
if (index != -1) {
// found
if (context.mounted) {
showReplyCheckResult(
'无账号状态下找到了你的评论,评论正常!\n\n你的评论:$message',
);
}
} else {
// not found
if (context.mounted.not) return;
// cookie check
dynamic res1 = await ReplyHttp.replyReplyList(
isLogin: isLogin,
oid: oid,
root: rpid ?? replyId,
pageNum: 1,
type: replyType,
banWordForReply: '',
);
if (context.mounted.not) return;
if (res1 is Error) {
// not found
if (context.mounted) {
showReplyCheckResult(
'无法找到你的评论。\n\n你的评论:$message',
);
}
} else if (res1 is Success) {
// found
if (context.mounted.not) return;
// no cookie check
dynamic res2 = await ReplyHttp.replyReplyList(
isLogin: false,
oid: oid,
root: rpid ?? replyId,
pageNum: 1,
type: replyType,
banWordForReply: '',
isCheck: true,
);
if (context.mounted.not) return;
if (res2 is Error) {
// not found
if (context.mounted) {
showReplyCheckResult(
res2.errMsg.startsWith('12022')
? '你的评论被shadow ban仅自己可见\n\n你的评论: $message'
: '评论不可见(${res2.errMsg}): $message',
);
}
} else if (res2 is Success) {
// found
if (context.mounted) {
showReplyCheckResult('''
你评论状态有点可疑,虽然无账号翻找评论区获取不到你的评论,但是无账号可通过
https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=${rpid ?? replyId}&type=$replyType
获取你的评论,疑似评论区被戒严或者这是你的视频。
你的评论:$message''');
}
}
}
}
}
} else {
for (int i = 1; true; i++) {
if (context.mounted.not) return;
dynamic res3 = await ReplyHttp.replyReplyList(
isLogin: false,
oid: oid,
root: rpid ?? replyId,
pageNum: i,
type: replyType,
banWordForReply: '',
isCheck: true,
);
if (res3 is Error) {
break;
} else if (res3 is Success) {
ReplyReplyData data = res3.response;
if (data.replies.isNullOrEmpty) {
break;
}
int index =
data.replies?.indexWhere((item) => item.rpid == replyId) ?? -1;
if (index == -1) {
// not found
} else {
// found
if (context.mounted) {
showReplyCheckResult(
'无账号状态下找到了你的评论,评论正常!\n\n你的评论:$message',
);
}
return;
}
}
}
for (int i = 1; true; i++) {
if (context.mounted.not) return;
dynamic res4 = await ReplyHttp.replyReplyList(
isLogin: true,
oid: oid,
root: rpid ?? replyId,
pageNum: i,
type: replyType,
banWordForReply: '',
isCheck: true,
);
if (res4 is Error) {
break;
} else if (res4 is Success) {
ReplyReplyData data = res4.response;
if (data.replies.isNullOrEmpty) {
break;
}
int index =
data.replies?.indexWhere((item) => item.rpid == replyId) ?? -1;
if (index == -1) {
// not found
} else {
// found
if (context.mounted) {
showReplyCheckResult(
'你的评论被shadow ban仅自己可见\n\n你的评论: $message',
);
}
return;
}
}
}
if (context.mounted) {
showReplyCheckResult(
'评论不可见: $message',
);
}
}
}
}

View File

@@ -45,7 +45,7 @@ class DynamicDetailController extends ReplyController {
oid: oid!,
cursor: CursorReq(
next: cursor?.next ?? $fixnum.Int64(0),
mode: mode,
mode: mode.value,
),
banWordForReply: banWordForReply,
)

View File

@@ -43,7 +43,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
AnimationController? _fabAnimationCtr;
late StreamController<bool> _titleStreamC; // appBar title
bool _visibleTitle = false;
String? action;
// String? action;
// 回复类型
late int replyType;
bool _isFabVisible = true;
@@ -89,10 +89,10 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
// floor 1原创 2转发
init();
_titleStreamC = StreamController<bool>();
if (action == 'comment') {
_visibleTitle = true;
_titleStreamC.add(true);
}
// if (action == 'comment') {
// _visibleTitle = true;
// _titleStreamC.add(true);
// }
_fabAnimationCtr = AnimationController(
vsync: this,
@@ -109,7 +109,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
// 楼层
int floor = args['floor'];
// 从action栏点击进入
action = args.containsKey('action') ? args['action'] : null;
// action = args.containsKey('action') ? args['action'] : null;
// 评论类型
int commentType = args['item'].basic!['comment_type'] ?? 11;
replyType = (commentType == 0) ? 11 : commentType;
@@ -156,7 +156,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text('评论详情'),
title: const Text('评论详情'),
titleSpacing: automaticallyImplyLeading ? null : 12,
automaticallyImplyLeading: automaticallyImplyLeading,
),

View File

@@ -1,8 +1,9 @@
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/http/index.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/member/view.dart' show radioWidget;
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -30,127 +31,211 @@ class AuthorPanel extends StatelessWidget {
this.onRemove,
});
Widget _buildAvatar(double size) => NetworkImgLayer(
width: size,
height: size,
type: 'avatar',
src: item.modules.moduleAuthor.face,
);
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(item.modules.moduleAuthor.mid);
return Row(
return Stack(
alignment: Alignment.center,
children: [
GestureDetector(
onTap: () {
// 番剧
if (item.modules.moduleAuthor.type == 'AUTHOR_TYPE_PGC' ||
item.modules.moduleAuthor.type == 'AUTHOR_TYPE_UGC_SEASON') {
return;
}
feedBack();
Get.toNamed(
'/member?mid=${item.modules.moduleAuthor.mid}',
arguments: {
'face': item.modules.moduleAuthor.face,
'heroTag': heroTag
},
);
},
child: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 40,
height: 40,
type: 'avatar',
src: item.modules.moduleAuthor.face,
),
),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
item.modules.moduleAuthor.name,
// semanticsLabel: "UP主${item.modules.moduleAuthor.name}",
style: TextStyle(
color: item.modules.moduleAuthor!.vip != null &&
item.modules.moduleAuthor!.vip['status'] > 0 &&
item.modules.moduleAuthor!.vip['type'] == 2
? context.vipColor
: Theme.of(context).colorScheme.onSurface,
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
),
),
],
),
DefaultTextStyle.merge(
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
Align(
alignment: Alignment.centerLeft,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
onTap: () {
// 番剧
if (item.modules.moduleAuthor.type == 'AUTHOR_TYPE_PGC' ||
item.modules.moduleAuthor.type ==
'AUTHOR_TYPE_UGC_SEASON') {
return;
}
feedBack();
Get.toNamed(
'/member?mid=${item.modules.moduleAuthor.mid}',
arguments: {
'face': item.modules.moduleAuthor.face,
},
);
},
child: (item.modules.moduleAuthor?.pendant?['image'] as String?)
?.isNotEmpty ==
true
? Padding(
padding: const EdgeInsets.all(3),
child: _buildAvatar(34),
)
: _buildAvatar(40),
),
child: Row(
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item is ItemOrigModel
? Utils.dateFormat(item.modules.moduleAuthor.pubTs)
: item.modules.moduleAuthor.pubTime),
if (item.modules.moduleAuthor.pubTime != '' &&
item.modules.moduleAuthor.pubAction != '')
const Text(' '),
Text(item.modules.moduleAuthor.pubAction),
Text(
item.modules.moduleAuthor.name,
// semanticsLabel: "UP主${item.modules.moduleAuthor.name}",
style: TextStyle(
color: item.modules.moduleAuthor!.vip != null &&
item.modules.moduleAuthor!.vip['status'] > 0 &&
item.modules.moduleAuthor!.vip['type'] == 2
? context.vipColor
: Theme.of(context).colorScheme.onSurface,
fontSize:
Theme.of(context).textTheme.titleSmall!.fontSize,
),
),
DefaultTextStyle.merge(
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
),
child: Row(
children: [
Text(item is ItemOrigModel
? Utils.dateFormat(item.modules.moduleAuthor.pubTs)
: item.modules.moduleAuthor.pubTime),
if (item.modules.moduleAuthor.pubTime != '' &&
item.modules.moduleAuthor.pubAction != '')
const Text(' '),
Text(item.modules.moduleAuthor.pubAction),
],
),
)
],
),
)
],
),
const Spacer(),
if (source != 'detail' && item.modules?.moduleTag?.text != null)
Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.all(Radius.circular(4)),
border: Border.all(
width: 1.25,
color: Theme.of(context).colorScheme.primary,
),
),
child: Text(
item.modules.moduleTag.text,
style: TextStyle(
height: 1,
fontSize: 12,
color: Theme.of(context).colorScheme.primary,
),
strutStyle: const StrutStyle(
leading: 0,
height: 1,
fontSize: 12,
),
),
),
SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: '更多',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return morePanel(context);
},
);
},
icon: const Icon(Icons.more_vert_outlined, size: 18),
],
),
),
Align(
alignment: Alignment.centerRight,
child: source != 'detail' && item.modules?.moduleTag?.text != null
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 4, vertical: 2),
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(4)),
border: Border.all(
width: 1.25,
color: Theme.of(context).colorScheme.primary,
),
),
child: Text(
item.modules.moduleTag.text,
style: TextStyle(
height: 1,
fontSize: 12,
color: Theme.of(context).colorScheme.primary,
),
strutStyle: const StrutStyle(
leading: 0,
height: 1,
fontSize: 12,
),
),
),
_moreWidget(context),
],
)
: item.modules.moduleAuthor.decorate != null
? Row(
mainAxisSize: MainAxisSize.min,
children: [
// GestureDetector(
// onTap:
// item.modules.moduleAuthor.decorate['jump_url'] != null
// ? () {
// Get.toNamed(
// '/webview',
// parameters: {
// 'url':
// '${item.modules.moduleAuthor.decorate['jump_url']}'
// },
// );
// }
// : null,
// child:
Stack(
clipBehavior: Clip.none,
alignment: Alignment.centerRight,
children: [
CachedNetworkImage(
height: 32,
imageUrl: item
.modules.moduleAuthor.decorate['card_url'],
),
if ((item.modules.moduleAuthor.decorate?['fan']
?['num_str'] as String?)
?.isNotEmpty ==
true)
Padding(
padding: const EdgeInsets.only(right: 32),
child: Text(
'${item.modules.moduleAuthor.decorate['fan']['num_str']}',
style: TextStyle(
height: 1,
fontSize: 11,
fontFamily: 'digital_id_num',
color: (item.modules.moduleAuthor
.decorate?['fan']
?['color'] as String?)
?.startsWith('#') ==
true
? Color(
int.parse(
item.modules.moduleAuthor
.decorate['fan']['color']
.replaceFirst('#', '0xFF'),
),
)
: null,
),
),
),
],
),
// ),
_moreWidget(context),
],
)
: _moreWidget(context),
)
],
);
}
Widget _moreWidget(context) => SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: '更多',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return morePanel(context);
},
);
},
icon: const Icon(Icons.more_vert_outlined, size: 18),
),
);
Widget morePanel(context) {
return Container(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
@@ -227,6 +312,25 @@ class AuthorPanel extends StatelessWidget {
},
minLeadingWidth: 0,
),
if (GStorage.isLogin)
ListTile(
title: Text(
'举报',
style: Theme.of(context).textTheme.titleSmall!.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
leading: Icon(
Icons.error_outline_outlined,
size: 19,
color: Theme.of(context).colorScheme.error,
),
onTap: () {
Get.back();
_showReportDynDialog(context);
},
minLeadingWidth: 0,
),
if (item.modules.moduleAuthor.mid ==
GStorage.userInfo.get('userInfoCache')?.mid &&
onRemove != null)
@@ -266,25 +370,6 @@ class AuthorPanel extends StatelessWidget {
.titleSmall!
.copyWith(color: Theme.of(context).colorScheme.error)),
),
if (GStorage.isLogin)
ListTile(
title: Text(
'举报',
style: Theme.of(context).textTheme.titleSmall!.copyWith(
color: Theme.of(context).colorScheme.error,
),
),
leading: Icon(
Icons.error_outline_outlined,
size: 19,
color: Theme.of(context).colorScheme.error,
),
onTap: () {
Get.back();
_showReportDynDialog(context);
},
minLeadingWidth: 0,
),
const Divider(thickness: 0.1, height: 1),
ListTile(
onTap: Get.back,
@@ -309,10 +394,7 @@ class AuthorPanel extends StatelessWidget {
context: context,
builder: (context) {
return AlertDialog(
title: Text(
'举报动态',
style: TextStyle(fontSize: 18),
),
title: const Text('举报动态'),
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
contentPadding: const EdgeInsets.only(top: 5),
actionsPadding:

View File

@@ -16,6 +16,7 @@ Widget content(context, item, source, callback) {
width: item.width,
height: item.height,
url: item.url ?? '',
liveUrl: item.liveUrl,
),
)
.toList(),

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'action_panel.dart';
import 'author_panel.dart';
@@ -79,27 +80,50 @@ class DynamicPanel extends StatelessWidget {
);
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 6),
child: AuthorPanel(
item: item,
source: source,
onRemove: onRemove,
),
),
if (item!.modules!.moduleDynamic!.desc != null ||
item!.modules!.moduleDynamic!.major != null)
content(context, item, source, callback),
forWard(item, context, source, callback),
const SizedBox(height: 2),
if (source == null) ActionPanel(item: item),
],
),
child: (item.modules.moduleAuthor?.pendant?['image'] as String?)
?.isNotEmpty ==
true
? Stack(
clipBehavior: Clip.none,
children: [
_buildContent(context, item, source, callback),
Positioned(
left: 2,
top: 2,
child: IgnorePointer(
child: CachedNetworkImage(
width: 60,
height: 60,
imageUrl: item.modules.moduleAuthor.pendant['image'],
),
),
),
],
)
: _buildContent(context, item, source, callback),
),
),
);
}
Widget _buildContent(context, item, source, callback) => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(12, 12, 12, 6),
child: AuthorPanel(
item: item,
source: source,
onRemove: onRemove,
),
),
if (item!.modules!.moduleDynamic!.desc != null ||
item!.modules!.moduleDynamic!.major != null)
content(context, item, source, callback),
forWard(item, context, source, callback),
const SizedBox(height: 2),
if (source == null) ActionPanel(item: item),
],
);
}

View File

@@ -27,6 +27,7 @@ InlineSpan picsNodes(List<OpusPicsModel> pics, callback) {
width: item.width,
height: item.height,
url: item.url ?? '',
liveUrl: item.liveUrl,
),
)
.toList(),
@@ -37,21 +38,14 @@ InlineSpan picsNodes(List<OpusPicsModel> pics, callback) {
}
Widget forWard(item, context, source, callback, {floor = 1}) {
TextStyle authorStyle =
TextStyle(color: Theme.of(context).colorScheme.primary);
List<OpusPicsModel> pics = [];
bool hasPics = item.modules.moduleDynamic.major != null &&
item.modules.moduleDynamic.major.opus != null &&
item.modules.moduleDynamic.major.opus.pics.isNotEmpty;
if (hasPics) {
pics = item.modules.moduleDynamic.major.opus.pics;
}
InlineSpan? richNodes = richNode(item, context);
switch (item.type) {
// 图文
case 'DYNAMIC_TYPE_DRAW':
bool hasPics = item.modules.moduleDynamic.major != null &&
item.modules.moduleDynamic.major.opus != null &&
item.modules.moduleDynamic.major.opus.pics.isNotEmpty;
InlineSpan? richNodes = richNode(item, context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -64,7 +58,8 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
arguments: {'face': item.modules.moduleAuthor.face}),
child: Text(
'@${item.modules.moduleAuthor.name}',
style: authorStyle,
style:
TextStyle(color: Theme.of(context).colorScheme.primary),
),
),
const SizedBox(width: 6),
@@ -93,6 +88,7 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
// ),
// ),
// ],
if (richNodes != null)
Text.rich(
richNodes,
@@ -104,11 +100,11 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
),
if (hasPics) ...[
Text.rich(
picsNodes(pics, callback),
picsNodes(item.modules.moduleDynamic.major.opus.pics, callback),
// semanticsLabel: '动态图片',
),
if (item.modules.moduleDynamic.additional != null)
const SizedBox(height: 4),
// if (item.modules.moduleDynamic.additional != null)
// const SizedBox(height: 4),
],
const SizedBox(height: 4),
],
@@ -126,7 +122,38 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
context,
item.modules.moduleDynamic.additional.type,
floor: floor,
)
),
if (item.modules.moduleDynamic.major.blocked != null) ...[
Container(
width: double.infinity,
padding: EdgeInsets.only(
left: 12, right: 12, bottom: source == 'detail' ? 8 : 0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (item.modules.moduleDynamic.major.blocked['title'] != null)
Text(
item.modules.moduleDynamic.major.blocked['title'],
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
),
),
if (item.modules.moduleDynamic.major
.blocked['hint_message'] !=
null)
Text(
item.modules.moduleDynamic.major.blocked['hint_message'],
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
),
],
],
);
// 视频
@@ -198,6 +225,7 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
case 'DYNAMIC_TYPE_UGC_SEASON':
return videoSeasonWidget(item, context, 'ugcSeason');
case 'DYNAMIC_TYPE_WORD':
InlineSpan? richNodes = richNode(item, context);
return floor == 2
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -210,7 +238,8 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
arguments: {'face': item.modules.moduleAuthor.face}),
child: Text(
'@${item.modules.moduleAuthor.name}',
style: authorStyle,
style: TextStyle(
color: Theme.of(context).colorScheme.primary),
),
),
const SizedBox(width: 6),
@@ -276,11 +305,16 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
case 'DYNAMIC_TYPE_COMMON_SQUARE':
return InkWell(
onTap: () {
Get.toNamed('/webview', parameters: {
'url': item.modules.moduleDynamic.major.common['jump_url'],
'type': 'url',
'pageTitle': item.modules.moduleDynamic.major.common['title']
});
try {
String url = item.modules.moduleDynamic.major.common['jump_url'];
if (url.contains('bangumi/play') && Utils.viewPgcFromUri(url)) {
return;
}
Get.toNamed(
'/webview',
parameters: {'url': url},
);
} catch (_) {}
},
child: Container(
width: double.infinity,

View File

@@ -2,8 +2,8 @@ import 'package:PiliPlus/common/widgets/imageview.dart';
import 'package:flutter/material.dart';
Widget picWidget(item, context, callback) {
String type = item.modules.moduleDynamic.major.type;
if (type == 'MAJOR_TYPE_OPUS') {
if (item.modules.moduleDynamic.major?.draw?.items == null ||
item.modules.moduleDynamic.major.type == 'MAJOR_TYPE_OPUS') {
/// fix 图片跟rich_node_panel重复
// pictures = item.modules.moduleDynamic.major.opus.pics;
return const SizedBox();
@@ -17,6 +17,7 @@ Widget picWidget(item, context, callback) {
width: item.width,
height: item.height,
url: item.url ?? '',
liveUrl: item.liveUrl,
),
)
.toList(),

View File

@@ -26,7 +26,7 @@ class _FavPageState extends State<FavPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('我的收藏'),
title: const Text('我的收藏'),
actions: [
IconButton(
onPressed: () {

View File

@@ -53,7 +53,8 @@ class FavItem extends StatelessWidget {
},
),
),
videoContent(context)
const SizedBox(width: 10),
videoContent(context),
],
),
);
@@ -65,43 +66,40 @@ class FavItem extends StatelessWidget {
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
favFolderItem.title ?? '',
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
favFolderItem.title ?? '',
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
if (favFolderItem.intro?.isNotEmpty == true)
Text(
favFolderItem.intro!,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
),
if (favFolderItem.intro?.isNotEmpty == true)
Text(
'${favFolderItem.mediaCount}个内容',
favFolderItem.intro!,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
const Spacer(),
Text(
Utils.isPublicText(favFolderItem.attr ?? 0),
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
Text(
'${favFolderItem.mediaCount}个内容',
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
],
),
),
const Spacer(),
Text(
Utils.isPublicText(favFolderItem.attr ?? 0),
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
);
}

View File

@@ -17,6 +17,8 @@ class FavDetailController extends MultiSelectController {
RxBool isOwner = false.obs;
RxBool titleCtr = false.obs;
dynamic mid;
@override
void onInit() {
// item = Get.arguments;
@@ -26,6 +28,8 @@ class FavDetailController extends MultiSelectController {
}
super.onInit();
mid = GStorage.userInfo.get('userInfoCache')?.mid;
queryData();
}
@@ -33,8 +37,7 @@ class FavDetailController extends MultiSelectController {
bool customHandleResponse(Success response) {
if (currentPage == 1) {
item.value = response.response.info;
isOwner.value = response.response.info.mid ==
GStorage.userInfo.get('userInfoCache')?.mid;
isOwner.value = response.response.info.mid == mid;
}
if (response.response.medias.isEmpty) {
isEnd = true;

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
@@ -60,6 +61,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
}
},
child: Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: Obx(
() => (_favDetailController.item.value.mediaCount ?? -1) > 0
? FloatingActionButton.extended(
@@ -85,7 +87,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
pinned: true,
title: _favDetailController.enableMultiSelect.value
? Text(
'已选${_favDetailController.checkedCount.value}',
'已选: ${_favDetailController.checkedCount.value}',
)
: Obx(
() => AnimatedOpacity(
@@ -110,11 +112,59 @@ class _FavDetailPageState extends State<FavDetailPage> {
actions: _favDetailController.enableMultiSelect.value
? [
TextButton(
style: TextButton.styleFrom(
visualDensity:
VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () =>
_favDetailController.handleSelect(true),
child: const Text('全选'),
),
TextButton(
style: TextButton.styleFrom(
visualDensity:
VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => Utils.onCopyOrMove(
context: context,
isCopy: true,
ctr: _favDetailController,
mediaId: _favDetailController.mediaId,
),
child: Text(
'复制',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
),
),
),
TextButton(
style: TextButton.styleFrom(
visualDensity:
VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => Utils.onCopyOrMove(
context: context,
isCopy: false,
ctr: _favDetailController,
mediaId: _favDetailController.mediaId,
),
child: Text(
'移动',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurfaceVariant,
),
),
),
TextButton(
style: TextButton.styleFrom(
visualDensity:
VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () =>
_favDetailController.onDelChecked(context),
child: Text(
@@ -159,23 +209,55 @@ class _FavDetailPageState extends State<FavDetailPage> {
},
child: Text('编辑信息'),
),
PopupMenuItem(
onTap: () {
UserHttp.cleanFav(mediaId: mediaId)
.then((data) {
if (data['status']) {
SmartDialog.showToast('清除成功');
Future.delayed(
const Duration(
milliseconds: 200), () {
_favDetailController.onReload();
});
} else {
SmartDialog.showToast(data['msg']);
}
});
},
child: Text('清除失效内容'),
),
if (!Utils.isDefault(
_favDetailController.item.value.attr ??
0))
PopupMenuItem(
onTap: () {
UserHttp.deleteFolder(
mediaIds: [mediaId]).then((data) {
if (data['status']) {
SmartDialog.showToast('删除成功');
Get.back(result: true);
} else {
SmartDialog.showToast(
data['msg']);
}
});
showConfirmDialog(
context: context,
title: '确定删除该收藏夹?',
onConfirm: () {
UserHttp.deleteFolder(
mediaIds: [mediaId])
.then((data) {
if (data['status']) {
SmartDialog.showToast('删除成功');
Get.back(result: true);
} else {
SmartDialog.showToast(
data['msg']);
}
});
},
);
},
child: Text('删除'),
child: Text(
'删除',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.error,
),
),
),
],
)

View File

@@ -137,7 +137,8 @@ class FavVideoCardH extends StatelessWidget {
},
),
),
videoContent(context)
const SizedBox(width: 10),
videoContent(context),
],
),
);
@@ -149,113 +150,106 @@ class FavVideoCardH extends StatelessWidget {
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
videoItem.title,
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
videoItem.title,
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
if (videoItem.ogv != null) ...[
Text(
videoItem.intro,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
],
const Spacer(),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
if (videoItem.ogv != null) ...[
Text(
Utils.dateFormat(videoItem.favTime),
videoItem.intro,
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(
context: context,
theme: 'gray',
view: videoItem.cntInfo['play'],
),
const SizedBox(width: 8),
statDanMu(
context: context,
theme: 'gray',
danmu: videoItem.cntInfo['danmaku'],
),
const Spacer(),
],
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
if (searchType != 1 && isOwner)
Positioned(
right: 0,
bottom: 0,
child: iconButton(
context: context,
icon: Icons.clear,
tooltip: '取消收藏',
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
bgColor: Colors.transparent,
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?.call();
Get.back();
},
child: const Text('确定取消'),
)
],
);
},
);
},
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(
context: context,
theme: 'gray',
view: videoItem.cntInfo['play'],
),
const SizedBox(width: 8),
statDanMu(
context: context,
theme: 'gray',
danmu: videoItem.cntInfo['danmaku'],
),
const Spacer(),
],
),
),
],
),
],
),
if (searchType != 1 && isOwner)
Positioned(
right: 0,
bottom: 0,
child: iconButton(
context: context,
icon: Icons.clear,
tooltip: '取消收藏',
iconColor: Theme.of(context).colorScheme.onSurfaceVariant,
bgColor: Colors.transparent,
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?.call();
Get.back();
},
child: const Text('确定取消'),
)
],
);
},
);
},
),
),
],
),
);
}

View File

@@ -36,7 +36,7 @@ class _HistoryPageState extends State<HistoryPage> {
appBar: AppBarWidget(
visible: _historyController.enableMultiSelect.value,
child1: AppBar(
title: Text('观看记录'),
title: const Text('观看记录'),
actions: [
IconButton(
tooltip: '搜索',
@@ -103,7 +103,7 @@ class _HistoryPageState extends State<HistoryPage> {
),
title: Obx(
() => Text(
'已选${_historyController.checkedCount.value}',
'已选: ${_historyController.checkedCount.value}',
),
),
actions: [

View File

@@ -291,7 +291,8 @@ class HistoryItem extends StatelessWidget {
),
],
),
videoContent(context)
const SizedBox(width: 10),
videoContent(context),
],
),
);
@@ -303,113 +304,108 @@ class HistoryItem extends StatelessWidget {
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
videoItem.title,
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
maxLines: videoItem.videos > 1 ? 1 : 2,
overflow: TextOverflow.ellipsis,
),
if (videoItem.isFullScreen != null) ...[
const SizedBox(height: 2),
Text(
videoItem.title,
videoItem.isFullScreen,
textAlign: TextAlign.start,
style: const TextStyle(
letterSpacing: 0.3,
),
maxLines: videoItem.videos > 1 ? 1 : 2,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (videoItem.isFullScreen != null) ...[
const SizedBox(height: 2),
Text(
videoItem.isFullScreen,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
const Spacer(),
if (videoItem.authorName != '')
Row(
children: [
Text(
videoItem.authorName,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
],
const Spacer(),
if (videoItem.authorName != '')
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
Utils.dateFormat(videoItem.viewAt!),
videoItem.authorName,
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline),
),
SizedBox(
width: 24,
height: 24,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '功能菜单',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
position: PopupMenuPosition.under,
// constraints: const BoxConstraints(maxHeight: 35),
onSelected: (String type) {},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
if (videoItem.history?.business != 'pgc' &&
videoItem.badge != '番剧' &&
!videoItem.tagName.contains('动画') &&
videoItem.history.business != 'live' &&
!videoItem.history.business.contains('article'))
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.history.bvid);
SmartDialog.showToast(res['msg']);
},
value: 'pause',
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
PopupMenuItem<String>(
onTap: () => ctr!.delHistory(
videoItem.kid, videoItem.history.business),
value: 'pause',
height: 35,
child: const Row(
children: [
Icon(Icons.close_outlined, size: 16),
SizedBox(width: 6),
Text('删除记录', style: TextStyle(fontSize: 13))
],
),
),
],
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline,
),
),
],
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
Utils.dateFormat(videoItem.viewAt!),
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline),
),
SizedBox(
width: 24,
height: 24,
child: PopupMenuButton<String>(
padding: EdgeInsets.zero,
tooltip: '功能菜单',
icon: Icon(
Icons.more_vert_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
position: PopupMenuPosition.under,
// constraints: const BoxConstraints(maxHeight: 35),
onSelected: (String type) {},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
if (videoItem.history?.business != 'pgc' &&
videoItem.badge != '番剧' &&
!videoItem.tagName.contains('动画') &&
videoItem.history.business != 'live' &&
!videoItem.history.business.contains('article'))
PopupMenuItem<String>(
onTap: () async {
var res = await UserHttp.toViewLater(
bvid: videoItem.history.bvid);
SmartDialog.showToast(res['msg']);
},
value: 'pause',
height: 35,
child: const Row(
children: [
Icon(Icons.watch_later_outlined, size: 16),
SizedBox(width: 6),
Text('稍后再看', style: TextStyle(fontSize: 13))
],
),
),
PopupMenuItem<String>(
onTap: () => ctr!.delHistory(
videoItem.kid, videoItem.history.business),
value: 'pause',
height: 35,
child: const Row(
children: [
Icon(Icons.close_outlined, size: 16),
SizedBox(width: 6),
Text('删除记录', style: TextStyle(fontSize: 13))
],
),
),
],
),
),
],
),
],
),
);
}

View File

@@ -55,7 +55,7 @@ class HtmlRenderController extends ReplyController {
oid: oid.value,
cursor: CursorReq(
next: cursor?.next ?? $fixnum.Int64(0),
mode: mode,
mode: mode.value,
),
banWordForReply: banWordForReply,
)

View File

@@ -145,7 +145,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text('评论详情'),
title: const Text('评论详情'),
titleSpacing: automaticallyImplyLeading ? null : 12,
automaticallyImplyLeading: automaticallyImplyLeading,
),

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
@@ -11,9 +12,12 @@ import 'package:PiliPlus/http/user.dart';
class LaterController extends MultiSelectController {
RxInt count = (-1).obs;
dynamic mid;
@override
void onInit() {
super.onInit();
mid = GStorage.userInfo.get('userInfoCache')?.mid;
queryData();
}
@@ -79,34 +83,18 @@ class LaterController extends MultiSelectController {
}
// 一键清空
Future toViewClear(BuildContext context) async {
await showDialog(
void toViewClear(BuildContext context) {
showConfirmDialog(
context: 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 {
var res = await UserHttp.toViewClear();
if (res['status']) {
loadingState.value = LoadingState.success([]);
}
Get.back();
SmartDialog.showToast(res['msg']);
},
child: const Text('确认'),
)
],
);
title: '清空确认',
content: '确定要清空你的稍后再看列表吗?',
onConfirm: () async {
var res = await UserHttp.toViewClear();
if (res['status']) {
loadingState.value = LoadingState.success([]);
}
Get.back();
SmartDialog.showToast(res['msg']);
},
);
}
@@ -185,7 +173,7 @@ class LaterController extends MultiSelectController {
'sourceType': 'watchLater',
'count': list.length,
'favTitle': '稍后再看',
'mediaId': GStorage.userInfo.get('userInfoCache')?.mid,
'mediaId': mid,
'desc': false,
},
);

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/icon_button.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/history/view.dart' show AppBarWidget;
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
@@ -33,6 +34,7 @@ class _LaterPageState extends State<LaterPage> {
}
},
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBarWidget(
visible: _laterController.enableMultiSelect.value,
child1: AppBar(
@@ -75,15 +77,55 @@ class _LaterPageState extends State<LaterPage> {
),
title: Obx(
() => Text(
'已选${_laterController.checkedCount.value}',
'已选: ${_laterController.checkedCount.value}',
),
),
actions: [
TextButton(
style: TextButton.styleFrom(
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => _laterController.handleSelect(true),
child: const Text('全选'),
),
TextButton(
style: TextButton.styleFrom(
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => Utils.onCopyOrMove(
context: context,
isCopy: true,
ctr: _laterController,
mediaId: null,
),
child: Text(
'复制',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
TextButton(
style: TextButton.styleFrom(
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => Utils.onCopyOrMove(
context: context,
isCopy: false,
ctr: _laterController,
mediaId: null,
),
child: Text(
'移动',
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
TextButton(
style: TextButton.styleFrom(
visualDensity: VisualDensity(horizontal: -2, vertical: -2),
),
onPressed: () => _laterController.onDelChecked(context),
child: Text(
'移除',

View File

@@ -313,7 +313,6 @@ class LoginPageController extends GetxController
title: const Text(
"本次登录需要验证您的手机号",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18),
),
content: Column(
mainAxisSize: MainAxisSize.min,

View File

@@ -58,7 +58,8 @@ class SeasonSeriesCard extends StatelessWidget {
},
),
),
videoContent(context)
const SizedBox(width: 10),
videoContent(context),
],
),
),
@@ -67,36 +68,33 @@ class SeasonSeriesCard extends StatelessWidget {
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item['meta']['name'],
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item['meta']['name'],
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
const Spacer(),
Text(
Utils.dateFormat(item['meta']['ptime']),
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Text(
Utils.dateFormat(item['meta']['ptime']),
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
const Spacer(),
],
),
),
const Spacer(),
],
),
);
}

View File

@@ -55,7 +55,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
_getInfo() async {
Map<String, String> data = {
'access_key': GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
'',
'appkey': Constants.appKey,
'build': '1462100',
'c_locale': 'zh_CN',
@@ -276,10 +277,7 @@ class _EditProfilePageState extends State<EditProfilePage> {
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
'修改$title',
style: TextStyle(fontSize: 18),
),
title: Text('修改$title'),
content: TextField(
controller: _textController,
minLines: type == ProfileType.uname ? 1 : 4,
@@ -332,7 +330,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
}) async {
Map<String, String> data = {
'access_key': GStorage.localCache
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
'',
'appkey': Constants.appKey,
'build': '1462100',
'c_locale': 'zh_CN',

View File

@@ -1,4 +1,7 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/models/space/card.dart' as space;
import 'package:PiliPlus/models/space/images.dart' as space;
import 'package:PiliPlus/utils/extension.dart';
@@ -76,11 +79,11 @@ class UserInfoCard extends StatelessWidget {
: images.nightImgurl?.http2https)
: images.imgUrl?.http2https;
return Hero(
tag: imgUrl ?? 'bgTag',
tag: imgUrl ?? '',
child: GestureDetector(
onTap: () {
context.imageView(
imgList: [imgUrl ?? 'bgTag'],
imgList: [SourceModel(url: imgUrl ?? '')],
);
},
child: CachedNetworkImage(
@@ -445,11 +448,11 @@ class UserInfoCard extends StatelessWidget {
);
_buildAvatar(BuildContext context) => Hero(
tag: card.face ?? 'avatarTag',
tag: card.face ?? '',
child: GestureDetector(
onTap: () {
context.imageView(
imgList: [card.face ?? 'avatarTag'],
imgList: [SourceModel(url: card.face ?? '')],
);
},
child: Container(
@@ -489,6 +492,19 @@ class UserInfoCard extends StatelessWidget {
left: 20,
child: _buildAvatar(context),
),
if (ModuleAuthorModel.showDynDecorate &&
card.pendant?.image?.isNotEmpty == true)
Positioned(
top: 82.5,
left: -7.5,
child: IgnorePointer(
child: CachedNetworkImage(
width: 140,
height: 140,
imageUrl: card.pendant!.image!,
),
),
),
if (card.officialVerify?.icon?.isNotEmpty == true ||
(card.vip?.vipStatus ?? -1) > 0)
Positioned(
@@ -572,6 +588,19 @@ class UserInfoCard extends StatelessWidget {
clipBehavior: Clip.none,
children: [
_buildAvatar(context),
if (ModuleAuthorModel.showDynDecorate &&
card.pendant?.image?.isNotEmpty == true)
Positioned(
top: -27.5,
left: -27.5,
child: IgnorePointer(
child: CachedNetworkImage(
width: 140,
height: 140,
imageUrl: card.pendant!.image!,
),
),
),
if (card.officialVerify?.icon?.isNotEmpty == true ||
(card.vip?.vipStatus ?? -1) > 0)
Positioned(

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:cached_network_image/cached_network_image.dart';
@@ -649,27 +650,3 @@ Widget _checkBoxWidget(
),
);
}
Widget radioWidget<T>({
required T value,
T? groupValue,
required ValueChanged onChanged,
required String title,
double? paddingStart,
}) {
return InkWell(
onTap: () => onChanged(value),
child: Row(
children: [
if (paddingStart != null) SizedBox(width: paddingStart),
Radio(
value: value,
groupValue: groupValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
),
);
}

View File

@@ -1,3 +1,5 @@
import 'package:PiliPlus/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart'
show SourceModel;
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -28,7 +30,10 @@ class ProfilePanel extends StatelessWidget {
GestureDetector(
onTap: () => context.imageView(
imgList: [
!loadingStatus ? memberInfo.face : ctr.face.value
SourceModel(
url:
!loadingStatus ? memberInfo.face : ctr.face.value,
)
],
),
child: NetworkImgLayer(

View File

@@ -53,7 +53,7 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Ta的投稿'),
title: const Text('Ta的投稿'),
actions: [
Obx(
() => TextButton.icon(

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