Compare commits
138 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
81713a6bc4 | ||
|
|
959bcfaa30 | ||
|
|
fa465f792d | ||
|
|
74bf78b9cd | ||
|
|
8c408e59f6 | ||
|
|
25d27e42ed | ||
|
|
0f2b0cc5f2 | ||
|
|
00ea34f45d | ||
|
|
ec936c1821 | ||
|
|
2ff84857e7 | ||
|
|
84ed34f3a7 | ||
|
|
f0508e1bc2 | ||
|
|
8ea7bf36d7 | ||
|
|
8819461eed | ||
|
|
7c30668c87 | ||
|
|
a3424950ca | ||
|
|
ebc42eb05e | ||
|
|
fc6ff44471 | ||
|
|
be03377449 | ||
|
|
e52934093a | ||
|
|
ebfd98488e | ||
|
|
6a68af77dc | ||
|
|
e5c0fb7cb2 | ||
|
|
d9611cce80 | ||
|
|
4b48aba2ae | ||
|
|
47fbb6cd0e | ||
|
|
dae71d427c | ||
|
|
46bc2ceb78 | ||
|
|
6f98200179 | ||
|
|
a57b4c56a5 | ||
|
|
6c3062ba2d | ||
|
|
064c8a9dfe | ||
|
|
7dd47736fb | ||
|
|
84cc65489f | ||
|
|
2b9cb54d91 | ||
|
|
54c7fef217 | ||
|
|
ba74cb8c01 | ||
|
|
675932aa69 | ||
|
|
d996e0a7dd | ||
|
|
b6279f702a | ||
|
|
695a89b91a | ||
|
|
09753b6bbd | ||
|
|
6502b97388 | ||
|
|
95d84647b7 | ||
|
|
8f5065332e | ||
|
|
71c8cbb8da | ||
|
|
3217731486 | ||
|
|
a4e63fe0e8 | ||
|
|
cdb8f6845c | ||
|
|
0a7d286c47 | ||
|
|
e17fd0071d | ||
|
|
a9ba30b9b9 | ||
|
|
4267a3b8e0 | ||
|
|
50022ae635 | ||
|
|
0991621152 | ||
|
|
192f8924c8 | ||
|
|
51a12d7266 | ||
|
|
1417fcda6e | ||
|
|
6114e6f033 | ||
|
|
bc2dbc59ce | ||
|
|
7c5075413e | ||
|
|
52175b0b69 | ||
|
|
f0a3515279 | ||
|
|
3c2ccf7d40 | ||
|
|
abd01e1a27 | ||
|
|
0f63976a00 | ||
|
|
6817eb6e56 | ||
|
|
a951d42623 | ||
|
|
8f5c2bf3ba | ||
|
|
7744217d17 | ||
|
|
a84c153bdd | ||
|
|
31a0a90ba4 | ||
|
|
383ce777e3 | ||
|
|
e7ac88ffb1 | ||
|
|
9657c77999 | ||
|
|
afd508f28b | ||
|
|
634612c1a2 | ||
|
|
76545397d4 | ||
|
|
d2f586a7f1 | ||
|
|
7cfebcb6ed | ||
|
|
9a3766e7b7 | ||
|
|
588a06bece | ||
|
|
e45a126862 | ||
|
|
a581945c9e | ||
|
|
331fd0d619 | ||
|
|
c6e229d571 | ||
|
|
b2c3b1ff95 | ||
|
|
3fc12fcc09 | ||
|
|
e098631553 | ||
|
|
0fcd55755e | ||
|
|
65e7c0c4f4 | ||
|
|
70aecd1e38 | ||
|
|
a40c773491 | ||
|
|
b4abb58a41 | ||
|
|
e368436bc6 | ||
|
|
6c96b3a7f5 | ||
|
|
149f0c082d | ||
|
|
994199b5a2 | ||
|
|
8db3d80151 | ||
|
|
93af1e7c44 | ||
|
|
54e90bd986 | ||
|
|
ca16551917 | ||
|
|
f4977d2855 | ||
|
|
bd91fb7c6d | ||
|
|
e1805896f4 | ||
|
|
31a639400e | ||
|
|
d6b24561fa | ||
|
|
7ba9646d38 | ||
|
|
58a7cf1e75 | ||
|
|
1a327198f7 | ||
|
|
e4fe91ef92 | ||
|
|
afcf817c4f | ||
|
|
21550815db | ||
|
|
02af3a18ff | ||
|
|
a5a13b45cf | ||
|
|
0fd232ab3a | ||
|
|
8d83143ca6 | ||
|
|
74452cd622 | ||
|
|
cf2e8cec54 | ||
|
|
5231faf254 | ||
|
|
959d4de78a | ||
|
|
f5d7dc6b6a | ||
|
|
b761c35d10 | ||
|
|
7f3f7f6bdd | ||
|
|
c5877b7c5e | ||
|
|
9e4187ef17 | ||
|
|
bf7ce3e5a2 | ||
|
|
2c55314491 | ||
|
|
d28efef672 | ||
|
|
49b631d560 | ||
|
|
896510f852 | ||
|
|
1d8e469a46 | ||
|
|
caee40a5d9 | ||
|
|
7de051e6bb | ||
|
|
18cec3c752 | ||
|
|
3b46655051 | ||
|
|
f72ad572fb | ||
|
|
a57ea2adb6 |
13
README.md
@@ -47,6 +47,17 @@
|
||||
|
||||
## feat
|
||||
|
||||
- [x] 互动视频
|
||||
- [x] 发评反诈
|
||||
- [x] 高能进度条
|
||||
- [x] 滑动跳转预览视频缩略图
|
||||
- [x] Live Photo
|
||||
- [x] 复制/移动收藏夹/稍后再看视频
|
||||
- [x] 超分辨率
|
||||
- [x] 合并弹幕
|
||||
- [x] 会员彩色弹幕
|
||||
- [x] 播放全部/继续播放/倒序播放
|
||||
- [x] Cookie登录
|
||||
- [x] 显示视频分段信息
|
||||
- [x] 调节字幕大小
|
||||
- [x] 调节全屏弹幕大小
|
||||
@@ -74,7 +85,7 @@
|
||||
- [x] 转发动态
|
||||
- [x] 合集图片
|
||||
- [x] 删除/置顶私信
|
||||
- [x] 举报用户/评论/视频
|
||||
- [x] 举报用户/评论/视频/动态
|
||||
- [x] 删除/发布文本/图片动态
|
||||
- [x] 其他
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 7.0 KiB |
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FF5CB67B</color>
|
||||
</resources>
|
||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 8.8 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -2,15 +2,11 @@
|
||||
<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: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="M57.54,54L28.82,54l3.93,-19.36h24.78c5.35,0 9.68,4.33 9.68,9.68 0,5.35 -4.33,9.68 -9.68,9.68zM57.54,21.73L40.5,21.73L27.88,86.27h13.3l3.83,-19.36h12.54c12.48,0 22.59,-10.11 22.59,-22.59 0,-12.48 -10.11,-22.59 -22.59,-22.59z"
|
||||
android:strokeWidth="0.252073"
|
||||
android:fillType="evenOdd" />
|
||||
</vector>
|
||||
|
||||
@@ -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>
|
||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 914 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 3.4 KiB |
3
android/app/src/main/res/raw/keep.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:keep="@drawable/*" />
|
||||
5
android/app/src/main/res/values-night-v31/colors.xml
Normal 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>
|
||||
5
android/app/src/main/res/values-v31/colors.xml
Normal 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>
|
||||
@@ -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>
|
||||
BIN
assets/fonts/digital_id_num.ttf
Normal file
BIN
assets/images/logo/logo_2.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
assets/images/logo/logo_3.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
assets/images/paycoins/ic_panel_close.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
@@ -26,6 +26,9 @@ class Constants {
|
||||
'%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D';
|
||||
//Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
|
||||
|
||||
static const urlPattern =
|
||||
r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]';
|
||||
|
||||
// 超分辨率滤镜
|
||||
static const List<String> mpvAnime4KShaders = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
@@ -9,7 +11,9 @@ Widget articleContent({
|
||||
required BuildContext context,
|
||||
required List<ArticleContentModel> list,
|
||||
Function(List<String>, int)? callback,
|
||||
required double maxWidth,
|
||||
}) {
|
||||
debugPrint('articleContent');
|
||||
List<String>? imgList = list
|
||||
.where((item) => item.pic != null)
|
||||
.toList()
|
||||
@@ -55,30 +59,28 @@ Widget articleContent({
|
||||
),
|
||||
);
|
||||
} else if (item.pic != null) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => Hero(
|
||||
tag: item.pic!.pics!.first.url!,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (callback != null) {
|
||||
callback(
|
||||
imgList,
|
||||
imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
);
|
||||
} else {
|
||||
context.imageView(
|
||||
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
imgList: imgList,
|
||||
);
|
||||
}
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxWidth *
|
||||
item.pic!.pics!.first.height! /
|
||||
item.pic!.pics!.first.width!,
|
||||
src: item.pic!.pics!.first.url,
|
||||
),
|
||||
return Hero(
|
||||
tag: item.pic!.pics!.first.url!,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (callback != null) {
|
||||
callback(
|
||||
imgList,
|
||||
imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
);
|
||||
} else {
|
||||
context.imageView(
|
||||
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
imgList: imgList.map((url) => SourceModel(url: url)).toList(),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: maxWidth,
|
||||
height: maxWidth *
|
||||
item.pic!.pics!.first.height! /
|
||||
item.pic!.pics!.first.width!,
|
||||
src: item.pic!.pics!.first.url,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ class PBadge extends StatelessWidget {
|
||||
final double? fs;
|
||||
final String? semanticsLabel;
|
||||
final bool bold;
|
||||
final double? textScaleFactor;
|
||||
|
||||
const PBadge({
|
||||
super.key,
|
||||
@@ -26,6 +27,7 @@ class PBadge extends StatelessWidget {
|
||||
this.fs = 11,
|
||||
this.semanticsLabel,
|
||||
this.bold = true,
|
||||
this.textScaleFactor,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -71,6 +73,9 @@ class PBadge extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
text ?? "",
|
||||
textScaler: textScaleFactor != null
|
||||
? TextScaler.linear(textScaleFactor!)
|
||||
: null,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: fs ?? fontSize,
|
||||
|
||||
@@ -19,8 +19,16 @@ class CustomSliverPersistentHeaderDelegate
|
||||
//创建child子组件
|
||||
//shrinkOffset:child偏移值minExtent~maxExtent
|
||||
//overlapsContent:SliverPersistentHeader覆盖其他子组件返回true,否则返回false
|
||||
return ColoredBox(
|
||||
color: bgColor,
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: bgColor,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
32
lib/common/widgets/dialog.dart
Normal 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('确认'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -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';
|
||||
@@ -11,6 +13,7 @@ Widget htmlRender({
|
||||
required double constrainedWidth,
|
||||
Function(List<String>, int)? callback,
|
||||
}) {
|
||||
debugPrint('htmlRender');
|
||||
return SelectionArea(
|
||||
child: Html(
|
||||
data: htmlContent,
|
||||
@@ -54,7 +57,7 @@ Widget htmlRender({
|
||||
callback([imgUrl], 0);
|
||||
} else {
|
||||
context.imageView(
|
||||
imgList: [imgUrl],
|
||||
imgList: [SourceModel(url: imgUrl)],
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -275,8 +275,7 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: Utils.getSheetHeight(context),
|
||||
return ColoredBox(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -412,6 +411,7 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
),
|
||||
if (_isList)
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: TabBar(
|
||||
controller: _ctr,
|
||||
padding: const EdgeInsets.only(right: 60),
|
||||
@@ -425,15 +425,21 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
),
|
||||
Expanded(
|
||||
child: _isList
|
||||
? TabBarView(
|
||||
controller: _ctr,
|
||||
children: List.generate(
|
||||
widget.season.sections.length,
|
||||
(index) => _buildBody(
|
||||
index, widget.season.sections[index].episodes),
|
||||
? Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: TabBarView(
|
||||
controller: _ctr,
|
||||
children: List.generate(
|
||||
widget.season.sections.length,
|
||||
(index) => _buildBody(
|
||||
index, widget.season.sections[index].episodes),
|
||||
),
|
||||
),
|
||||
)
|
||||
: _buildBody(null, episodes),
|
||||
: Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: _buildBody(null, episodes),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -464,30 +470,28 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
},
|
||||
);
|
||||
|
||||
Widget _buildBody(i, episodes) => Material(
|
||||
child: ScrollablePositionedList.separated(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 80,
|
||||
),
|
||||
reverse: reverse[i ?? 0],
|
||||
itemCount: episodes.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildEpisodeListItem(
|
||||
episodes[index],
|
||||
index,
|
||||
episodes.length,
|
||||
i != null
|
||||
? i == (_index)
|
||||
? currentIndex == index
|
||||
: false
|
||||
: currentIndex == index,
|
||||
);
|
||||
},
|
||||
itemScrollController: itemScrollController[i ?? 0],
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
Widget _buildBody(i, episodes) => ScrollablePositionedList.separated(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 80,
|
||||
),
|
||||
reverse: reverse[i ?? 0],
|
||||
itemCount: episodes.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildEpisodeListItem(
|
||||
episodes[index],
|
||||
index,
|
||||
episodes.length,
|
||||
i != null
|
||||
? i == (_index)
|
||||
? currentIndex == index
|
||||
: false
|
||||
: currentIndex == index,
|
||||
);
|
||||
},
|
||||
itemScrollController: itemScrollController[i ?? 0],
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
25
lib/common/widgets/radio_widget.dart
Normal 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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ class Segment {
|
||||
|
||||
class SegmentProgressBar extends CustomPainter {
|
||||
final List<Segment> segmentColors;
|
||||
late double _defHeight;
|
||||
double? _defHeight;
|
||||
|
||||
SegmentProgressBar({
|
||||
required this.segmentColors,
|
||||
@@ -42,6 +42,18 @@ class SegmentProgressBar extends CustomPainter {
|
||||
if (segmentColors[i].title != null) {
|
||||
double fontSize = 10;
|
||||
|
||||
_defHeight ??= (TextPainter(
|
||||
text: TextSpan(
|
||||
text: segmentColors[i].title,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout())
|
||||
.height +
|
||||
2;
|
||||
|
||||
TextPainter getTextPainter() => TextPainter(
|
||||
text: TextSpan(
|
||||
text: segmentColors[i].title,
|
||||
@@ -51,14 +63,12 @@ class SegmentProgressBar extends CustomPainter {
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
strutStyle: StrutStyle(height: 1, leading: 0),
|
||||
strutStyle:
|
||||
StrutStyle(leading: 0, height: 1, fontSize: fontSize),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
|
||||
TextPainter textPainter = getTextPainter();
|
||||
if (i == 0) {
|
||||
_defHeight = textPainter.height;
|
||||
}
|
||||
|
||||
late double prevStart;
|
||||
if (i != 0) {
|
||||
@@ -75,7 +85,7 @@ class SegmentProgressBar extends CustomPainter {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(
|
||||
0,
|
||||
-_defHeight - 2,
|
||||
-_defHeight!,
|
||||
size.width,
|
||||
0,
|
||||
),
|
||||
@@ -86,9 +96,9 @@ class SegmentProgressBar extends CustomPainter {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
segmentStart,
|
||||
-_defHeight - 2,
|
||||
-_defHeight!,
|
||||
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
|
||||
size.height + _defHeight + 2,
|
||||
size.height + _defHeight!,
|
||||
),
|
||||
paint,
|
||||
);
|
||||
@@ -98,7 +108,7 @@ class SegmentProgressBar extends CustomPainter {
|
||||
: (segmentStart - prevStart - textPainter.width) / 2 +
|
||||
prevStart +
|
||||
1;
|
||||
double textY = (-_defHeight - textPainter.height) / 2 - 1;
|
||||
double textY = (-_defHeight! - textPainter.height) / 2;
|
||||
textPainter.paint(canvas, Offset(textX, textY));
|
||||
} else {
|
||||
canvas.drawRect(
|
||||
|
||||
@@ -39,7 +39,13 @@ class _SelfSizedHorizontalListState extends State<SelfSizedHorizontalList> {
|
||||
WidgetsBinding.instance.addPostFrameCallback((v) => setState(() {}));
|
||||
}
|
||||
if (widget.itemCount == 0) return const SizedBox();
|
||||
if (isInit) return Container(key: infoKey, child: widget.childBuilder(0));
|
||||
if (isInit) {
|
||||
return Container(
|
||||
key: infoKey,
|
||||
padding: widget.padding,
|
||||
child: widget.childBuilder(0),
|
||||
);
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
height: height,
|
||||
|
||||
@@ -6,13 +6,14 @@ Widget statDanMu({
|
||||
String? theme,
|
||||
dynamic danmu,
|
||||
String? size,
|
||||
Color? textColor,
|
||||
}) {
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
Color color = textColor ?? colorObject[theme]!;
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
|
||||
@@ -7,13 +7,14 @@ Widget statView({
|
||||
dynamic view,
|
||||
String? size,
|
||||
String? goto,
|
||||
Color? textColor,
|
||||
}) {
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
Color color = textColor ?? colorObject[theme]!;
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
|
||||
@@ -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),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class VideoCardHGrpc extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
PiliScheme.routePush(Uri.parse(videoItem.smallCoverV5.base.uri));
|
||||
PiliScheme.routePushFromUrl(videoItem.smallCoverV5.base.uri);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
@@ -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),
|
||||
// ],
|
||||
// ),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/view.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_progress_indicator.dart';
|
||||
import 'package:PiliPlus/models/space_archive/item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -98,12 +99,29 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
if (videoItem.history != null)
|
||||
Builder(builder: (context) {
|
||||
try {
|
||||
return Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: videoProgressIndicator(
|
||||
videoItem.history!['progress'] /
|
||||
videoItem.history!['duration'],
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
videoContent(context)
|
||||
const SizedBox(width: 10),
|
||||
videoContent(context),
|
||||
],
|
||||
);
|
||||
},
|
||||
@@ -125,66 +143,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,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
27
lib/common/widgets/video_progress_indicator.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget videoProgressIndicator(double progress) => ClipRect(
|
||||
clipper: ProgressClipper(),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10),
|
||||
),
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 10,
|
||||
value: progress,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
class ProgressClipper extends CustomClipper<Rect> {
|
||||
@override
|
||||
Rect getClip(Size size) {
|
||||
return Rect.fromLTWH(0, 6, size.width, size.height - 6);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomClipper<Rect> oldClipper) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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 当前页
|
||||
@@ -708,4 +718,8 @@ class Api {
|
||||
/// 我的关注 - 正在直播
|
||||
static const String getFollowingLive =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following';
|
||||
|
||||
static const String pgcIndexCondition = '/pgc/season/index/condition';
|
||||
|
||||
static const String pgcIndexResult = '/pgc/season/index/result';
|
||||
}
|
||||
|
||||
@@ -1,9 +1,55 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
|
||||
import '../models/bangumi/list.dart';
|
||||
import '../models/bangumi/pgc_index/condition.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class BangumiHttp {
|
||||
static Future<LoadingState> pgcIndexResult({
|
||||
required int page,
|
||||
required Map<String, dynamic> params,
|
||||
seasonType,
|
||||
type,
|
||||
indexType,
|
||||
}) async {
|
||||
dynamic res = await Request().get(
|
||||
Api.pgcIndexResult,
|
||||
queryParameters: {
|
||||
...params,
|
||||
if (seasonType != null) 'season_type': seasonType,
|
||||
if (type != null) 'type': type,
|
||||
if (indexType != null) 'index_type': indexType,
|
||||
'page': page,
|
||||
'pagesize': 21,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(res.data['data']);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> pgcIndexCondition({
|
||||
seasonType,
|
||||
type,
|
||||
indexType,
|
||||
}) async {
|
||||
dynamic res = await Request().get(
|
||||
Api.pgcIndexCondition,
|
||||
queryParameters: {
|
||||
if (seasonType != null) 'season_type': seasonType,
|
||||
if (type != null) 'type': type,
|
||||
if (indexType != null) 'index_type': indexType,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return LoadingState.success(Condition.fromJson(res.data['data']));
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState> bangumiList({
|
||||
int? page,
|
||||
int? indexType,
|
||||
|
||||
@@ -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;
|
||||
@@ -79,10 +79,14 @@ class DynamicsHttp {
|
||||
//
|
||||
static Future dynamicDetail({
|
||||
String? id,
|
||||
dynamic rid,
|
||||
dynamic type,
|
||||
}) async {
|
||||
var res = await Request().get(Api.dynamicDetail, queryParameters: {
|
||||
'timezone_offset': -480,
|
||||
'id': id,
|
||||
if (id != null) 'id': id,
|
||||
if (rid != null) 'rid': rid,
|
||||
if (type != null) 'type': type,
|
||||
'features': 'itemOpusStyle',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math' show Random;
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
@@ -14,7 +15,6 @@ import '../utils/utils.dart';
|
||||
import 'api.dart';
|
||||
import 'constants.dart';
|
||||
import 'interceptor.dart';
|
||||
import 'interceptor_anonymity.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
|
||||
|
||||
class Request {
|
||||
@@ -37,7 +37,7 @@ class Request {
|
||||
);
|
||||
cookieManager = CookieManager(cookieJar);
|
||||
dio.interceptors.add(cookieManager);
|
||||
dio.interceptors.add(AnonymityInterceptor());
|
||||
dio.interceptors.add(ApiInterceptor());
|
||||
final List<Cookie> cookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||
for (Cookie item in cookies) {
|
||||
@@ -175,15 +175,14 @@ class Request {
|
||||
);
|
||||
}
|
||||
|
||||
//添加拦截器
|
||||
dio.interceptors.add(ApiInterceptor());
|
||||
|
||||
// 日志拦截器 输出请求、响应内容
|
||||
dio.interceptors.add(LogInterceptor(
|
||||
request: false,
|
||||
requestHeader: false,
|
||||
responseHeader: false,
|
||||
));
|
||||
if (BuildConfig.isDebug) {
|
||||
dio.interceptors.add(LogInterceptor(
|
||||
request: false,
|
||||
requestHeader: false,
|
||||
responseHeader: false,
|
||||
));
|
||||
}
|
||||
|
||||
dio.transformer = BackgroundTransformer();
|
||||
dio.options.validateStatus = (int? status) {
|
||||
|
||||
@@ -1,17 +1,67 @@
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/pages/mine/controller.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
|
||||
class ApiInterceptor extends Interceptor {
|
||||
// @override
|
||||
// void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
// debugPrint("请求之前");
|
||||
// // 在请求之前添加头部或认证信息
|
||||
// options.headers['Authorization'] = 'Bearer token';
|
||||
// options.headers['Content-Type'] = 'application/json';
|
||||
// handler.next(options);
|
||||
// }
|
||||
static const List<String> anonymityList = [
|
||||
Api.videoUrl,
|
||||
Api.videoIntro,
|
||||
Api.relatedList,
|
||||
Api.replyList,
|
||||
Api.replyReplyList,
|
||||
Api.searchSuggest,
|
||||
Api.searchByType,
|
||||
Api.heartBeat,
|
||||
Api.ab2c,
|
||||
Api.bangumiInfo,
|
||||
Api.liveRoomInfo,
|
||||
Api.onlineTotal,
|
||||
Api.webDanmaku,
|
||||
Api.dynamicDetail,
|
||||
Api.aiConclusion,
|
||||
Api.getSeasonDetailApi,
|
||||
Api.liveRoomDmToken,
|
||||
Api.liveRoomDmPrefetch,
|
||||
];
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
void onRemoveCookie() {
|
||||
options.headers.remove('x-bili-mid');
|
||||
options.headers.remove('x-bili-aurora-eid');
|
||||
options.headers.remove('x-bili-aurora-zone');
|
||||
options.headers['cookie'] = '';
|
||||
options.queryParameters.remove('access_key');
|
||||
options.queryParameters.remove('csrf');
|
||||
options.queryParameters.remove('csrf_token');
|
||||
if (options.data is Map) {
|
||||
options.data.remove('access_key');
|
||||
options.data.remove('csrf');
|
||||
options.data.remove('csrf_token');
|
||||
}
|
||||
}
|
||||
|
||||
if (options.extra['clearCookie'] == true) {
|
||||
onRemoveCookie();
|
||||
} else if (MineController.anonymity.value) {
|
||||
String uri = options.uri.toString();
|
||||
for (var i in anonymityList) {
|
||||
// 如果请求的url包含无痕列表中的url,则清空cookie
|
||||
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
|
||||
int index = uri.indexOf(i);
|
||||
if (index == -1) continue;
|
||||
if (uri.lastIndexOf('/') >= index + i.length) continue;
|
||||
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
|
||||
onRemoveCookie();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
// @override
|
||||
// void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
// import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import '../pages/mine/controller.dart';
|
||||
import 'api.dart';
|
||||
|
||||
class AnonymityInterceptor extends Interceptor {
|
||||
static const List<String> anonymityList = [
|
||||
Api.videoUrl,
|
||||
Api.videoIntro,
|
||||
Api.relatedList,
|
||||
Api.replyList,
|
||||
Api.replyReplyList,
|
||||
Api.searchSuggest,
|
||||
Api.searchByType,
|
||||
Api.heartBeat,
|
||||
Api.ab2c,
|
||||
Api.bangumiInfo,
|
||||
Api.liveRoomInfo,
|
||||
Api.onlineTotal,
|
||||
Api.webDanmaku,
|
||||
Api.dynamicDetail,
|
||||
Api.aiConclusion,
|
||||
Api.getSeasonDetailApi,
|
||||
Api.liveRoomDmToken,
|
||||
Api.liveRoomDmPrefetch,
|
||||
];
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (MineController.anonymity.value) {
|
||||
String uri = options.uri.toString();
|
||||
for (var i in anonymityList) {
|
||||
// 如果请求的url包含无痕列表中的url,则清空cookie
|
||||
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
|
||||
int index = uri.indexOf(i);
|
||||
if (index == -1) continue;
|
||||
if (uri.lastIndexOf('/') >= index + i.length) continue;
|
||||
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
|
||||
options.headers[HttpHeaders.cookieHeader] = "";
|
||||
if (options.data != null && options.data.csrf != null) {
|
||||
options.data.csrf = "";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
handler.next(options);
|
||||
}
|
||||
}
|
||||
@@ -334,8 +334,13 @@ class MemberHttp {
|
||||
}
|
||||
|
||||
static Future memberCardInfo({int? mid}) async {
|
||||
var res = await Request()
|
||||
.get(Api.memberCardInfo, queryParameters: {'mid': mid, 'photo': true});
|
||||
var res = await Request().get(
|
||||
Api.memberCardInfo,
|
||||
queryParameters: {
|
||||
'mid': mid,
|
||||
'photo': false,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@@ -404,7 +409,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': '[]',
|
||||
|
||||
@@ -560,9 +560,7 @@ class MsgHttp {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': "message: ${res.data['message']},"
|
||||
" msg: ${res.data['msg']},"
|
||||
" code: ${res.data['code']}",
|
||||
'msg': res.data['message'] ?? res.data['msg'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../models/video/reply/data.dart';
|
||||
import '../models/video/reply/emote.dart';
|
||||
import 'api.dart';
|
||||
import 'constants.dart';
|
||||
import 'init.dart';
|
||||
|
||||
class ReplyHttp {
|
||||
static Options get _options => Options(extra: {'clearCookie': true});
|
||||
|
||||
static Future<LoadingState> replyList({
|
||||
required bool isLogin,
|
||||
required int oid,
|
||||
@@ -23,13 +21,9 @@ class ReplyHttp {
|
||||
int sort = 1,
|
||||
required String banWordForReply,
|
||||
}) async {
|
||||
Options? options = !isLogin
|
||||
? Options(
|
||||
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
|
||||
: null;
|
||||
var res = !isLogin
|
||||
? await Request().get(
|
||||
'${HttpString.apiBaseUrl}${Api.replyList}/main',
|
||||
'${Api.replyList}/main',
|
||||
queryParameters: {
|
||||
'oid': oid,
|
||||
'type': type,
|
||||
@@ -37,10 +31,10 @@ class ReplyHttp {
|
||||
'{"offset":"${nextOffset.replaceAll('"', '\\"')}"}',
|
||||
'mode': sort + 2, //2:按时间排序;3:按热度排序
|
||||
},
|
||||
options: options,
|
||||
options: isLogin.not ? _options : null,
|
||||
)
|
||||
: await Request().get(
|
||||
'${HttpString.apiBaseUrl}${Api.replyList}',
|
||||
Api.replyList,
|
||||
queryParameters: {
|
||||
'oid': oid,
|
||||
'type': type,
|
||||
@@ -48,7 +42,7 @@ class ReplyHttp {
|
||||
'pn': page,
|
||||
'ps': 20,
|
||||
},
|
||||
options: options,
|
||||
options: isLogin.not ? _options : null,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
ReplyData replyData = ReplyData.fromJson(res.data['data']);
|
||||
@@ -134,28 +128,25 @@ class ReplyHttp {
|
||||
}
|
||||
|
||||
static Future<LoadingState> replyReplyList({
|
||||
required bool isLogin,
|
||||
required int oid,
|
||||
required int root,
|
||||
required int pageNum,
|
||||
required int type,
|
||||
int sort = 1,
|
||||
required String banWordForReply,
|
||||
bool? isCheck,
|
||||
}) async {
|
||||
Options? options = GStorage.userInfo.get('userInfoCache') == null
|
||||
? Options(
|
||||
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
|
||||
: null;
|
||||
var res = await Request().get(
|
||||
'${HttpString.apiBaseUrl}${Api.replyReplyList}',
|
||||
Api.replyReplyList,
|
||||
queryParameters: {
|
||||
'oid': oid,
|
||||
'root': root,
|
||||
'pn': pageNum,
|
||||
'type': type,
|
||||
'sort': 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
if (isLogin) 'csrf': await Request.getCsrf(),
|
||||
},
|
||||
options: options,
|
||||
options: isLogin.not ? _options : null,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
ReplyReplyData replyData = ReplyReplyData.fromJson(res.data['data']);
|
||||
@@ -168,7 +159,11 @@ class ReplyHttp {
|
||||
}
|
||||
return LoadingState.success(replyData);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
return LoadingState.error(
|
||||
isCheck == true
|
||||
? '${res.data['code']}${res.data['message']}'
|
||||
: res.data['message'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -153,7 +174,10 @@ class UserHttp {
|
||||
var res = await Request().get(Api.seeYouLater);
|
||||
if (res.data['code'] == 0) {
|
||||
if (res.data['data']['count'] == 0) {
|
||||
return LoadingState.success([]);
|
||||
return LoadingState.success({
|
||||
'list': [],
|
||||
'count': 0,
|
||||
});
|
||||
}
|
||||
List<HotVideoItemModel> list = [];
|
||||
for (var i in res.data['data']['list']) {
|
||||
|
||||
@@ -421,14 +421,18 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 投币
|
||||
static Future coinVideo({required String bvid, required int multiply}) async {
|
||||
static Future coinVideo({
|
||||
required String bvid,
|
||||
required int multiply,
|
||||
int selectLike = 0,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.coinVideo,
|
||||
queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
// 'bvid': bvid,
|
||||
'multiply': multiply,
|
||||
'select_like': 0,
|
||||
'select_like': selectLike,
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
// 'csrf': await Request.getCsrf(),
|
||||
@@ -676,6 +680,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 +733,7 @@ class VideoHttp {
|
||||
dynamic type,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.videoInFolder,
|
||||
Api.favFolder,
|
||||
queryParameters: {
|
||||
'up_mid': mid,
|
||||
'rid': rid,
|
||||
@@ -713,6 +763,7 @@ class VideoHttp {
|
||||
int? root,
|
||||
int? parent,
|
||||
List? pictures,
|
||||
bool? syncToDynamic,
|
||||
}) async {
|
||||
if (message == '') {
|
||||
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
||||
@@ -720,10 +771,11 @@ class VideoHttp {
|
||||
Map<String, dynamic> data = {
|
||||
'type': type.index,
|
||||
'oid': oid,
|
||||
'root': root == null || root == 0 ? '' : root,
|
||||
'parent': parent == null || parent == 0 ? '' : parent,
|
||||
if (root != null && root != 0) 'root': root,
|
||||
if (parent != null && parent != 0) 'parent': parent,
|
||||
'message': message,
|
||||
if (pictures != null) 'pictures': jsonEncode(pictures),
|
||||
if (syncToDynamic == true) 'sync_to_dynamic': 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
};
|
||||
var res = await Request().post(
|
||||
@@ -929,7 +981,6 @@ class VideoHttp {
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
dynamic data = res.data['data'];
|
||||
List subtitlesJson = data['subtitle']['subtitles'];
|
||||
/*
|
||||
[
|
||||
{
|
||||
@@ -947,10 +998,11 @@ class VideoHttp {
|
||||
*/
|
||||
return {
|
||||
'status': true,
|
||||
'data': subtitlesJson,
|
||||
'data': data['subtitle']['subtitles'],
|
||||
'view_points': data['view_points'],
|
||||
// 'last_play_time': data['last_play_time'],
|
||||
'last_play_cid': data['last_play_cid'],
|
||||
'interaction': data['interaction'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/utils/cache_manage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
@@ -177,6 +178,7 @@ class MyApp extends StatelessWidget {
|
||||
// tones: FlexTones.soft(Brightness.dark),
|
||||
);
|
||||
}
|
||||
|
||||
// 图片缓存
|
||||
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
|
||||
return GetMaterialApp(
|
||||
@@ -229,7 +231,7 @@ class MyApp extends StatelessWidget {
|
||||
bool isDark = false,
|
||||
required FlexSchemeVariant variant,
|
||||
}) {
|
||||
return ThemeData(
|
||||
ThemeData themeData = ThemeData(
|
||||
colorScheme: colorScheme,
|
||||
useMaterial3: true,
|
||||
appBarTheme: AppBarTheme(
|
||||
@@ -275,7 +277,17 @@ class MyApp extends StatelessWidget {
|
||||
progressIndicatorTheme: ProgressIndicatorThemeData(
|
||||
refreshBackgroundColor: colorScheme.onSecondary,
|
||||
),
|
||||
dialogTheme: DialogTheme(
|
||||
titleTextStyle: TextStyle(
|
||||
fontSize: 18,
|
||||
color: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (isDark && GStorage.isPureBlackTheme) {
|
||||
themeData = Utils.darkenTheme(themeData);
|
||||
}
|
||||
return themeData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,9 @@ class BangumiListDataModel {
|
||||
|
||||
BangumiListDataModel.fromJson(Map<String, dynamic> json) {
|
||||
hasNext = json['has_next'];
|
||||
list = json['list'] != null
|
||||
? json['list']
|
||||
.map<BangumiListItemModel>((e) => BangumiListItemModel.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
list = (json['list'] as List?)
|
||||
?.map<BangumiListItemModel>((e) => BangumiListItemModel.fromJson(e))
|
||||
.toList();
|
||||
num = json['num'];
|
||||
size = json['size'];
|
||||
total = json['total'];
|
||||
|
||||
70
lib/models/bangumi/pgc_index/condition.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
class Condition {
|
||||
List<Filter>? filter;
|
||||
List<Order>? order;
|
||||
|
||||
Condition({
|
||||
this.filter,
|
||||
this.order,
|
||||
});
|
||||
|
||||
Condition.fromJson(Map json) {
|
||||
filter = (json['filter'] as List?)
|
||||
?.map((item) => Filter.fromJson(item))
|
||||
.toList();
|
||||
order =
|
||||
(json['order'] as List?)?.map((item) => Order.fromJson(item)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class Order {
|
||||
String? field;
|
||||
String? name;
|
||||
String? sort;
|
||||
|
||||
Order({
|
||||
this.field,
|
||||
this.name,
|
||||
this.sort,
|
||||
});
|
||||
|
||||
Order.fromJson(Map json) {
|
||||
field = json['field'];
|
||||
name = json['name'];
|
||||
sort = json['sort'];
|
||||
}
|
||||
}
|
||||
|
||||
class Filter {
|
||||
String? field;
|
||||
String? name;
|
||||
List<Values>? values;
|
||||
|
||||
Filter({
|
||||
this.field,
|
||||
this.name,
|
||||
this.values,
|
||||
});
|
||||
|
||||
Filter.fromJson(Map json) {
|
||||
field = json['field'];
|
||||
name = json['name'];
|
||||
values = (json['values'] as List?)
|
||||
?.map((item) => Values.fromJson(item))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
class Values {
|
||||
String? keyword;
|
||||
String? name;
|
||||
|
||||
Values({
|
||||
this.keyword,
|
||||
this.name,
|
||||
});
|
||||
|
||||
Values.fromJson(Map json) {
|
||||
keyword = json['keyword'];
|
||||
name = json['name'];
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
|
||||
class DynamicsDataModel {
|
||||
DynamicsDataModel({
|
||||
this.hasMore,
|
||||
@@ -12,7 +14,7 @@ class DynamicsDataModel {
|
||||
|
||||
DynamicsDataModel.fromJson(Map<String, dynamic> json) {
|
||||
hasMore = json['has_more'];
|
||||
items = json['items']
|
||||
items = (json['items'] as List?)
|
||||
?.map<DynamicItemModel>((e) => DynamicItemModel.fromJson(e))
|
||||
.toList();
|
||||
offset = json['offset'];
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
// 单个动态详情 - 动态信息
|
||||
@@ -352,7 +364,9 @@ class Good {
|
||||
Good.fromJson(Map<String, dynamic> json) {
|
||||
headIcon = json['head_icon'];
|
||||
headText = json['head_text'];
|
||||
items = json['items'].map<GoodItem>((e) => GoodItem.fromJson(e)).toList();
|
||||
items = (json['items'] as List?)
|
||||
?.map<GoodItem>((e) => GoodItem.fromJson(e))
|
||||
.toList();
|
||||
jumpUrl = json['jump_url'];
|
||||
}
|
||||
}
|
||||
@@ -397,11 +411,9 @@ class DynamicDescModel {
|
||||
String? text;
|
||||
|
||||
DynamicDescModel.fromJson(Map<String, dynamic> json) {
|
||||
richTextNodes = json['rich_text_nodes'] != null
|
||||
? json['rich_text_nodes']
|
||||
.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
richTextNodes = (json['rich_text_nodes'] as List?)
|
||||
?.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
|
||||
.toList();
|
||||
text = json['text'];
|
||||
}
|
||||
}
|
||||
@@ -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'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,12 +557,9 @@ class DynamicDrawModel {
|
||||
|
||||
DynamicDrawModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
// ignore: prefer_null_aware_operators
|
||||
items = json['items'] != null
|
||||
? json['items']
|
||||
.map<DynamicDrawItemModel>((e) => DynamicDrawItemModel.fromJson(e))
|
||||
.toList()
|
||||
: null;
|
||||
items = (json['items'] as List?)
|
||||
?.map<DynamicDrawItemModel>((e) => DynamicDrawItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,7 +577,7 @@ class DynamicOpusModel {
|
||||
String? title;
|
||||
DynamicOpusModel.fromJson(Map<String, dynamic> json) {
|
||||
jumpUrl = json['jump_url'];
|
||||
pics = json['pics']
|
||||
pics = (json['pics'] as List?)
|
||||
?.map<OpusPicsModel>((e) => OpusPicsModel.fromJson(e))
|
||||
.toList();
|
||||
summary =
|
||||
@@ -584,8 +596,8 @@ class SummaryModel {
|
||||
String? text;
|
||||
|
||||
SummaryModel.fromJson(Map<String, dynamic> json) {
|
||||
richTextNodes = json['rich_text_nodes']
|
||||
.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
|
||||
richTextNodes = (json['rich_text_nodes'] as List?)
|
||||
?.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
|
||||
.toList();
|
||||
text = json['text'];
|
||||
}
|
||||
@@ -623,11 +635,15 @@ class Emoji {
|
||||
});
|
||||
|
||||
String? iconUrl;
|
||||
String? webpUrl;
|
||||
String? gifUrl;
|
||||
double? size;
|
||||
String? text;
|
||||
int? type;
|
||||
Emoji.fromJson(Map<String, dynamic> json) {
|
||||
iconUrl = json['icon_url'];
|
||||
webpUrl = json['webp_url'];
|
||||
gifUrl = json['gif_url'];
|
||||
size = json['size'].toDouble();
|
||||
text = json['text'];
|
||||
type = json['type'];
|
||||
@@ -658,6 +674,7 @@ class OpusPicsModel {
|
||||
int? size;
|
||||
String? src;
|
||||
String? url;
|
||||
String? liveUrl;
|
||||
|
||||
OpusPicsModel.fromJson(Map<String, dynamic> json) {
|
||||
width = json['width'];
|
||||
@@ -665,6 +682,7 @@ class OpusPicsModel {
|
||||
size = json['size'] != null ? json['size'].toInt() : 0;
|
||||
src = json['src'];
|
||||
url = json['url'];
|
||||
liveUrl = json['live_url'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ class FollowUpModel {
|
||||
liveUsers = json['live_users'] != null
|
||||
? LiveUsers.fromJson(json['live_users'])
|
||||
: null;
|
||||
upList = json['up_list'] != null
|
||||
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList()
|
||||
: [];
|
||||
upList = (json['up_list'] as List?)
|
||||
?.map<UpItem>((e) => UpItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ class LiveUsers {
|
||||
LiveUsers.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
group = json['group'];
|
||||
items = json['items']
|
||||
.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
|
||||
items = (json['items'] as List?)
|
||||
?.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ class FansDataModel {
|
||||
|
||||
FansDataModel.fromJson(Map<String, dynamic> json) {
|
||||
total = json['total'];
|
||||
list = json['list']
|
||||
.map<FansItemModel>((e) => FansItemModel.fromJson(e))
|
||||
list = (json['list'] as List?)
|
||||
?.map<FansItemModel>((e) => FansItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ class FollowDataModel {
|
||||
|
||||
FollowDataModel.fromJson(Map<String, dynamic> json) {
|
||||
total = json['total'] ?? 0;
|
||||
list = json['list']
|
||||
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||
list = (json['list'] as List?)
|
||||
?.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ class LatestDataModel {
|
||||
url = json['url'];
|
||||
tagName = json['tag_name'];
|
||||
createdAt = json['created_at'];
|
||||
assets = json['assets'] != null
|
||||
? json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList()
|
||||
: [];
|
||||
assets = (json['assets'] as List?)
|
||||
?.map<AssetItem>((e) => AssetItem.fromJson(e))
|
||||
.toList();
|
||||
body = json['body'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,12 @@ class Playurl {
|
||||
|
||||
Playurl.fromJson(Map<String, dynamic> json) {
|
||||
cid = json['cid'];
|
||||
gQnDesc =
|
||||
json['g_qn_desc'].map<GQnDesc>((e) => GQnDesc.fromJson(e)).toList();
|
||||
stream = json['stream'].map<Streams>((e) => Streams.fromJson(e)).toList();
|
||||
gQnDesc = (json['g_qn_desc'] as List?)
|
||||
?.map<GQnDesc>((e) => GQnDesc.fromJson(e))
|
||||
.toList();
|
||||
stream = (json['stream'] as List?)
|
||||
?.map<Streams>((e) => Streams.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +86,9 @@ class Streams {
|
||||
|
||||
Streams.fromJson(Map<String, dynamic> json) {
|
||||
protocolName = json['protocol_name'];
|
||||
format =
|
||||
json['format'].map<FormatItem>((e) => FormatItem.fromJson(e)).toList();
|
||||
format = (json['format'] as List?)
|
||||
?.map<FormatItem>((e) => FormatItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +103,9 @@ class FormatItem {
|
||||
|
||||
FormatItem.fromJson(Map<String, dynamic> json) {
|
||||
formatName = json['format_name'];
|
||||
codec = json['codec'].map<CodecItem>((e) => CodecItem.fromJson(e)).toList();
|
||||
codec = (json['codec'] as List?)
|
||||
?.map<CodecItem>((e) => CodecItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +135,8 @@ class CodecItem {
|
||||
currentQn = json['current_qn'];
|
||||
acceptQn = json['accept_qn'];
|
||||
baseUrl = json['base_url'];
|
||||
urlInfo = json['url_info']
|
||||
.map<UrlInfoItem>((e) => UrlInfoItem.fromJson(e))
|
||||
urlInfo = (json['url_info'] as List?)
|
||||
?.map<UrlInfoItem>((e) => UrlInfoItem.fromJson(e))
|
||||
.toList();
|
||||
hdrQn = json['hdr_n'];
|
||||
dolbyType = json['dolby_type'];
|
||||
|
||||
@@ -27,8 +27,8 @@ class ArchiveListModel {
|
||||
? Map.from(json['tlist']).map((k, v) =>
|
||||
MapEntry<String, TListItemModel>(k, TListItemModel.fromJson(v)))
|
||||
: {};
|
||||
vlist = json['vlist']
|
||||
.map<VListItemModel>((e) => VListItemModel.fromJson(e))
|
||||
vlist = (json['vlist'] as List?)
|
||||
?.map<VListItemModel>((e) => VListItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ class MemberSeasonsDataModel {
|
||||
|
||||
MemberSeasonsDataModel.fromJson(Map<String, dynamic> json) {
|
||||
page = json['page'];
|
||||
seasonsList = json['seasons_list'] != null
|
||||
? json['seasons_list']
|
||||
.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
seasonsList = (json['seasons_list'] as List?)
|
||||
?.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +29,9 @@ class MemberSeasonsList {
|
||||
Map? page;
|
||||
|
||||
MemberSeasonsList.fromJson(Map<String, dynamic> json) {
|
||||
archives = json['archives'] != null
|
||||
? json['archives']
|
||||
.map<MemberArchiveItem>((e) => MemberArchiveItem.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
archives = (json['archives'] as List?)
|
||||
?.map<MemberArchiveItem>((e) => MemberArchiveItem.fromJson(e))
|
||||
.toList();
|
||||
meta = MamberMeta.fromJson(json['meta']);
|
||||
page = json['page'];
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class SessionDataModel {
|
||||
int? hasMore;
|
||||
|
||||
SessionDataModel.fromJson(Map<String, dynamic> json) {
|
||||
sessionList = json['session_list']
|
||||
sessionList = (json['session_list'] as List?)
|
||||
?.map<SessionList>((e) => SessionList.fromJson(e))
|
||||
.toList();
|
||||
hasMore = json['has_more'];
|
||||
@@ -175,8 +175,8 @@ class SessionMsgDataModel {
|
||||
List<dynamic>? eInfos;
|
||||
|
||||
SessionMsgDataModel.fromJson(Map<String, dynamic> json) {
|
||||
messages = json['messages']
|
||||
.map<MessageItem>((e) => MessageItem.fromJson(e))
|
||||
messages = (json['messages'] as List?)
|
||||
?.map<MessageItem>((e) => MessageItem.fromJson(e))
|
||||
.toList();
|
||||
hasMore = json['has_more'];
|
||||
minSeqno = json['min_seqno'];
|
||||
|
||||
@@ -12,8 +12,8 @@ class HotSearchModel {
|
||||
List<HotSearchItem>? list;
|
||||
|
||||
HotSearchModel.fromJson(Map<String, dynamic> json) {
|
||||
list = json['list']
|
||||
.map<HotSearchItem>((e) => HotSearchItem.fromJson(e))
|
||||
list = (json['list'] as List?)
|
||||
?.map<HotSearchItem>((e) => HotSearchItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ class SearchVideoModel {
|
||||
|
||||
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
||||
numResults = (json['numResults'] as num?)?.toInt();
|
||||
list = json['result']
|
||||
list = (json['result'] as List?)
|
||||
?.where((e) => e['available'] == true)
|
||||
?.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ class SearchUserModel {
|
||||
|
||||
SearchUserModel.fromJson(Map<String, dynamic> json) {
|
||||
numResults = (json['numResults'] as num?)?.toInt();
|
||||
list = json['result']
|
||||
list = (json['result'] as List?)
|
||||
?.map<SearchUserItemModel>((e) => SearchUserItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
@@ -316,12 +316,10 @@ class SearchMBangumiModel {
|
||||
|
||||
SearchMBangumiModel.fromJson(Map<String, dynamic> json) {
|
||||
numResults = (json['numResults'] as num?)?.toInt();
|
||||
list = json['result'] != null
|
||||
? json['result']
|
||||
.map<SearchMBangumiItemModel>(
|
||||
(e) => SearchMBangumiItemModel.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
list = (json['result'] as List?)
|
||||
?.map<SearchMBangumiItemModel>(
|
||||
(e) => SearchMBangumiItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,12 +418,9 @@ class SearchArticleModel {
|
||||
|
||||
SearchArticleModel.fromJson(Map<String, dynamic> json) {
|
||||
numResults = (json['numResults'] as num?)?.toInt();
|
||||
list = json['result'] != null
|
||||
? json['result']
|
||||
.map<SearchArticleItemModel>(
|
||||
(e) => SearchArticleItemModel.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
list = (json['result'] as List?)
|
||||
?.map<SearchArticleItemModel>((e) => SearchArticleItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ class SearchSuggestModel {
|
||||
String? term;
|
||||
|
||||
SearchSuggestModel.fromJson(Map<String, dynamic> json) {
|
||||
tag = json['tag']
|
||||
.map<SearchSuggestItem>(
|
||||
tag = (json['tag'] as List?)
|
||||
?.map<SearchSuggestItem>(
|
||||
(e) => SearchSuggestItem.fromJson(e, json['term']))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ class Item {
|
||||
String? publishTimeText;
|
||||
List<Badge>? badges;
|
||||
Map? season;
|
||||
Map? history;
|
||||
|
||||
Item({
|
||||
this.title,
|
||||
@@ -97,6 +98,7 @@ class Item {
|
||||
this.publishTimeText,
|
||||
this.badges,
|
||||
this.season,
|
||||
this.history,
|
||||
});
|
||||
|
||||
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
|
||||
|
||||
@@ -48,6 +48,7 @@ Item _$ItemFromJson(Map<String, dynamic> json) => Item(
|
||||
?.map((e) => Badge.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
season: json['season'],
|
||||
history: json['history'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ItemToJson(Item instance) => <String, dynamic>{
|
||||
|
||||
@@ -8,8 +8,8 @@ class BlackListDataModel {
|
||||
int? total;
|
||||
|
||||
BlackListDataModel.fromJson(Map<String, dynamic> json) {
|
||||
list = json['list']
|
||||
.map<BlackListItem>((e) => BlackListItem.fromJson(e))
|
||||
list = (json['list'] as List?)
|
||||
?.map<BlackListItem>((e) => BlackListItem.fromJson(e))
|
||||
.toList();
|
||||
total = json['total'];
|
||||
}
|
||||
|
||||
@@ -15,11 +15,9 @@ class FavDetailData {
|
||||
FavDetailData.fromJson(Map<String, dynamic> json) {
|
||||
info =
|
||||
json['info'] == null ? null : FavFolderItemData.fromJson(json['info']);
|
||||
medias = json['medias'] != null
|
||||
? json['medias']
|
||||
.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
medias = (json['medias'] as List?)
|
||||
?.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e))
|
||||
.toList();
|
||||
hasMore = json['has_more'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,10 @@ class FavFolderData {
|
||||
|
||||
FavFolderData.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
list = json['list'] != null
|
||||
? json['list']
|
||||
.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
|
||||
.toList()
|
||||
: [FavFolderItemData()];
|
||||
list = (json['list'] as List?)
|
||||
?.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
|
||||
.toList() ??
|
||||
[FavFolderItemData()];
|
||||
hasMore = json['has_more'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ class HistoryData {
|
||||
|
||||
HistoryData.fromJson(Map<String, dynamic> json) {
|
||||
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||
tab = json['tab'] != null
|
||||
? json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList()
|
||||
: [];
|
||||
list = json['list'] != null
|
||||
? json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList()
|
||||
: [];
|
||||
tab = (json['tab'] as List?)
|
||||
?.map<HisTabItem>((e) => HisTabItem.fromJson(e))
|
||||
.toList();
|
||||
list = (json['list'] as List?)
|
||||
?.map<HisListItem>((e) => HisListItem.fromJson(e))
|
||||
.toList();
|
||||
page = json['page'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ class Emote {
|
||||
|
||||
class EmoteMeta {
|
||||
int? size;
|
||||
List<String>? suggest;
|
||||
List? suggest;
|
||||
String? alias;
|
||||
String? gifUrl;
|
||||
|
||||
@@ -245,9 +245,7 @@ class EmoteMeta {
|
||||
|
||||
EmoteMeta.fromJson(Map<String, dynamic> json) {
|
||||
size = json['size'];
|
||||
suggest = json['suggest'] == null
|
||||
? null
|
||||
: List<String>.from(json['suggest'].map((x) => x));
|
||||
suggest = json['suggest'];
|
||||
alias = json['alias'];
|
||||
gifUrl = json['gif_url'];
|
||||
}
|
||||
|
||||