Compare commits

...

33 Commits

Author SHA1 Message Date
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
134 changed files with 2656 additions and 1636 deletions

View File

@@ -47,6 +47,14 @@
## feat
- [x] 滑动跳转预览视频缩略图
- [x] Live Photo
- [x] 复制/移动收藏夹/稍后再看视频
- [x] 超分辨率
- [x] 合并弹幕
- [x] 会员彩色弹幕
- [x] 播放全部/继续播放/倒序播放
- [x] Cookie登录
- [x] 显示视频分段信息
- [x] 调节字幕大小
- [x] 调节全屏弹幕大小
@@ -74,7 +82,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

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

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

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

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

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

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

@@ -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,205 @@ 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),
// 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,
// ),
// ),
// ),
],
),
),
Align(
alignment: Alignment.centerRight,
child: 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(
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),
@@ -309,10 +388,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),

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

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

@@ -276,10 +276,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,

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';
@@ -80,7 +83,7 @@ class UserInfoCard extends StatelessWidget {
child: GestureDetector(
onTap: () {
context.imageView(
imgList: [imgUrl ?? 'bgTag'],
imgList: [SourceModel(url: imgUrl ?? 'bgTag')],
);
},
child: CachedNetworkImage(
@@ -444,22 +447,22 @@ class UserInfoCard extends StatelessWidget {
),
);
_buildAvatar(BuildContext context) => Hero(
tag: card.face ?? 'avatarTag',
child: GestureDetector(
onTap: () {
context.imageView(
imgList: [card.face ?? 'avatarTag'],
);
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 2.5,
color: Theme.of(context).colorScheme.surface,
),
shape: BoxShape.circle,
),
_buildAvatar(BuildContext context) => Container(
decoration: BoxDecoration(
border: Border.all(
width: 2.5,
color: Theme.of(context).colorScheme.surface,
),
shape: BoxShape.circle,
),
child: Hero(
tag: card.face ?? 'avatarTag',
child: GestureDetector(
onTap: () {
context.imageView(
imgList: [SourceModel(url: card.face ?? 'avatarTag')],
);
},
child: NetworkImgLayer(
src: card.face,
type: 'avatar',
@@ -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(

View File

@@ -46,7 +46,7 @@ class _MemberDynamicsPageState extends State<MemberDynamicsPage>
super.build(context);
return widget.mid == null
? Scaffold(
appBar: AppBar(title: Text('Ta的动态')),
appBar: AppBar(title: const Text('Ta的动态')),
body: _buildBody,
)
: _buildBody;

View File

@@ -47,7 +47,7 @@ class _MemberSeasonsPageState extends State<MemberSeasonsPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Ta的专栏')),
appBar: AppBar(title: const Text('Ta的专栏')),
body: Padding(
padding: const EdgeInsets.only(
left: StyleString.safeSpace,

View File

@@ -69,7 +69,11 @@ class _SearchPanelState extends State<SearchPanel>
SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.smallCardWidth * 2,
maxCrossAxisExtent: (widget.searchType == SearchType.video ||
widget.searchType == SearchType.article
? Grid.mediumCardWidth
: Grid.smallCardWidth) *
2,
childAspectRatio: StyleString.aspectRatio *
(widget.searchType == SearchType.media_bangumi ||
widget.searchType == SearchType.media_ft

View File

@@ -153,61 +153,54 @@ Widget searchArticlePanel(context, searchPanelCtr, LoadingState loadingState) {
);
}),
),
const SizedBox(width: 10),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(
10, 2, 6, 0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
RichText(
maxLines: 2,
textScaler:
MediaQuery.textScalerOf(
context),
text: TextSpan(
children: [
for (var i in loadingState
.response[index]
.title) ...[
TextSpan(
text: i['text'],
style: TextStyle(
color: i['type'] == 'em'
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.onSurface,
),
),
]
],
),
),
const Spacer(),
Text(
Utils.dateFormat(
loadingState.response[index]
.pubTime,
formatType: 'detail'),
style: textStyle),
Row(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text.rich(
maxLines: 2,
TextSpan(
children: [
Text(
'${loadingState.response[index].view}浏览',
style: textStyle),
Text('', style: textStyle),
Text(
'${loadingState.response[index].reply}评论',
style: textStyle),
for (var i in loadingState
.response[index].title) ...[
TextSpan(
text: i['text'],
style: TextStyle(
color: i['type'] == 'em'
? Theme.of(context)
.colorScheme
.primary
: Theme.of(context)
.colorScheme
.onSurface,
),
),
]
],
),
],
),
),
const Spacer(),
Text(
Utils.dateFormat(
loadingState
.response[index].pubTime,
formatType: 'detail'),
style: textStyle),
Row(
children: [
Text(
'${loadingState.response[index].view}浏览',
style: textStyle),
Text('', style: textStyle),
Text(
'${loadingState.response[index].reply}评论',
style: textStyle),
],
),
],
),
),
],

View File

@@ -127,9 +127,8 @@ class LiveContent extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
RichText(
textScaler: MediaQuery.textScalerOf(context),
text: TextSpan(
Text.rich(
TextSpan(
children: [
for (var i in liveItem.title) ...[
TextSpan(

View File

@@ -75,12 +75,10 @@ Widget searchBangumiPanel(context, ctr, LoadingState loadingState) {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 4),
RichText(
Text.rich(
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
textScaler:
MediaQuery.textScalerOf(context),
text: TextSpan(
TextSpan(
style: TextStyle(
color: Theme.of(context)
.colorScheme

View File

@@ -95,7 +95,7 @@ class _LogsPageState extends State<LogsPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('日志'),
title: const Text('日志'),
actions: [
PopupMenuButton<String>(
onSelected: (String type) {

View File

@@ -191,7 +191,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('倍速设置'),
title: const Text('倍速设置'),
actions: [
TextButton(
onPressed: () async {

View File

@@ -23,7 +23,9 @@ class _PlaySettingState extends State<PlaySetting> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: widget.showAppBar == false ? null : AppBar(title: Text('播放器设置')),
appBar: widget.showAppBar == false
? null
: AppBar(title: const Text('播放器设置')),
body: ListView(
children: [
...playSettings.map((item) => item.widget),

View File

@@ -9,7 +9,7 @@ class PrivacySetting extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: showAppBar == false ? null : AppBar(title: Text('隐私设置')),
appBar: showAppBar == false ? null : AppBar(title: const Text('隐私设置')),
body: ListView(
children: [
...privacySettings.map((item) => item.widget),

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