merge main
23
.github/ISSUE_TEMPLATE/bug-反馈.md
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: Bug 反馈
|
||||||
|
about: 描述你所遇到的bug
|
||||||
|
title: ''
|
||||||
|
labels: 问题反馈
|
||||||
|
assignees: guozhigq
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 问题描述
|
||||||
|
请提供一个清晰而简明的问题描述。
|
||||||
|
|
||||||
|
### 复现步骤
|
||||||
|
请提供复现该问题所需的具体步骤。
|
||||||
|
|
||||||
|
### 预期行为
|
||||||
|
请描述你期望的正确行为或结果。
|
||||||
|
|
||||||
|
### 系统信息
|
||||||
|
请提供关于您的环境的详细信息,包括操作系统、浏览器版本等。
|
||||||
|
|
||||||
|
### 相关截图或日志
|
||||||
|
如果有的话,请提供相关的截图、错误日志或其他有助于解决问题的信息。
|
||||||
20
.github/ISSUE_TEMPLATE/功能请求.md
vendored
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: 功能请求
|
||||||
|
about: 对于功能的一些建议
|
||||||
|
title: ''
|
||||||
|
labels: 功能
|
||||||
|
assignees: guozhigq
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 功能描述
|
||||||
|
请提供对所请求功能的清晰描述。
|
||||||
|
|
||||||
|
### 目标
|
||||||
|
请描述你希望通过这个功能实现的目标。
|
||||||
|
|
||||||
|
### 解决方案
|
||||||
|
如果你有任何关于如何实现这个功能的想法或建议,请在这里提供。
|
||||||
|
|
||||||
|
### 其他
|
||||||
|
请提供已实现该功能或类似功能的应用
|
||||||
84
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
name: build_apk
|
||||||
|
|
||||||
|
# action事件触发
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
# push tag时触发
|
||||||
|
tags:
|
||||||
|
- 'v*.*.*'
|
||||||
|
|
||||||
|
# 可以有多个jobs
|
||||||
|
jobs:
|
||||||
|
build_apk:
|
||||||
|
# 运行环境 ubuntu-latest window-latest mac-latest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# 每个jobs中可以有多个steps
|
||||||
|
steps:
|
||||||
|
- name: 代码迁出
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: 构建Java环境
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: "zulu"
|
||||||
|
java-version: "17"
|
||||||
|
token: ${{secrets.GIT_TOKEN}}
|
||||||
|
|
||||||
|
- name: 检查缓存
|
||||||
|
uses: actions/cache@v2
|
||||||
|
id: cache-flutter
|
||||||
|
with:
|
||||||
|
path: /root/flutter-sdk # Flutter SDK 的路径
|
||||||
|
key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
|
||||||
|
|
||||||
|
- name: 安装Flutter
|
||||||
|
if: steps.cache-flutter.outputs.cache-hit != 'true'
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
flutter-version: 3.10.6
|
||||||
|
channel: any
|
||||||
|
|
||||||
|
- name: 下载项目依赖
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: 解码生成 jks
|
||||||
|
run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks
|
||||||
|
env:
|
||||||
|
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
|
||||||
|
|
||||||
|
- name: flutter build apk
|
||||||
|
# 对应 android/app/build.gradle signingConfigs中的配置项
|
||||||
|
run: flutter build apk --release --split-per-abi
|
||||||
|
env:
|
||||||
|
KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
|
||||||
|
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
|
||||||
|
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
|
||||||
|
|
||||||
|
- name: 获取版本号
|
||||||
|
id: version
|
||||||
|
run: echo "version=${GITHUB_REF#refs/tags/v}" >>$GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# - name: 获取当前日期
|
||||||
|
# id: date
|
||||||
|
# run: echo "date=$(date +'%m%d')" >>$GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: 重命名应用 Pili-arm64-v8a-*.*.*.0101.apk
|
||||||
|
run: |
|
||||||
|
# DATE=${{ steps.date.outputs.date }}
|
||||||
|
for file in build/app/outputs/flutter-apk/app-*-release.apk; do
|
||||||
|
if [[ $file =~ app-(.*)-release.apk ]]; then
|
||||||
|
new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}-${{ steps.version.outputs.version }}.apk"
|
||||||
|
mv "$file" "$new_file_name"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: 构建和发布release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
# release title
|
||||||
|
name: v${{ steps.version.outputs.version }}
|
||||||
|
artifacts: "build/app/outputs/flutter-apk/Pili-*.apk"
|
||||||
|
bodyFile: "change_log/${{steps.version.outputs.version}}.md"
|
||||||
|
token: ${{ secrets.GIT_TOKEN }}
|
||||||
|
allowUpdates: true
|
||||||
132
README.md
@ -1,16 +1,128 @@
|
|||||||
# pilipala
|
<div align="center">
|
||||||
|
<img width="200" height="200" src="https://github.com/guozhigq/pilipala/blob/main/assets/images/logo/logo_android.png">
|
||||||
|
</div>
|
||||||
|
|
||||||
A new Flutter project.
|
|
||||||
|
|
||||||
## Getting Started
|
<div align="center">
|
||||||
|
<h1>PiliPala</h1>
|
||||||
|
<p>使用Flutter开发的BiliBili第三方客户端</p>
|
||||||
|
<br/>
|
||||||
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/510shots_so.png" width="32%" alt="home" />
|
||||||
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/174shots_so.png" width="32%" alt="home" />
|
||||||
|
<img src="https://github.com/guozhigq/pilipala/blob/main/assets/sreenshot/850shots_so.png" width="32%" alt="home" />
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
This project is a starting point for a Flutter application.
|
### 开发环境
|
||||||
|
Xcode 13.4 不支持**auto_orientation**,请注释相关代码
|
||||||
|
|
||||||
A few resources to get you started if this is your first Flutter project:
|
```bash
|
||||||
|
[✓] Flutter (Channel stable, 3.10.6, on macOS 12.1 21C52 darwin-arm64, locale
|
||||||
|
zh-Hans-CN)
|
||||||
|
[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.2)
|
||||||
|
[✓] Xcode - develop for iOS and macOS (Xcode 13.4)
|
||||||
|
[✓] Chrome - develop for the web
|
||||||
|
[✓] Android Studio (version 2022.2)
|
||||||
|
[✓] VS Code (version 1.77.3)
|
||||||
|
|
||||||
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
```
|
||||||
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
|
|
||||||
|
|
||||||
For help getting started with Flutter development, view the
|
<br/>
|
||||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
|
||||||
samples, guidance on mobile development, and a full API reference.
|
### 功能
|
||||||
|
|
||||||
|
目前着重移动端(Android、iOS),暂时没有适配桌面端、Pad端、手表端等
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
现有功能及[开发计划](https://github.com/users/guozhigq/projects/5)
|
||||||
|
|
||||||
|
|
||||||
|
- [x] 推荐视频列表(app端)
|
||||||
|
- [x] 最热视频列表
|
||||||
|
- [x] 热门直播
|
||||||
|
- [x] 番剧列表
|
||||||
|
- [x] 屏蔽黑名单内用户视频
|
||||||
|
|
||||||
|
- [x] 用户相关
|
||||||
|
- [x] 粉丝、关注用户、拉黑用户查看
|
||||||
|
- [x] 用户主页查看
|
||||||
|
- [x] 关注/取关用户
|
||||||
|
- [ ] 离线缓存
|
||||||
|
- [x] 稍后再看
|
||||||
|
- [x] 观看记录
|
||||||
|
- [x] 我的收藏
|
||||||
|
|
||||||
|
- [x] 动态相关
|
||||||
|
- [x] 全部、投稿、番剧分类查看
|
||||||
|
- [x] 动态评论查看
|
||||||
|
- [x] 动态评论回复功能
|
||||||
|
|
||||||
|
- [x] 视频播放相关
|
||||||
|
- [x] 双击快进/快退
|
||||||
|
- [x] 双击播放/暂停
|
||||||
|
- [x] 垂直方向调节亮度/音量
|
||||||
|
- [x] 垂直方向上滑全屏、下滑退出全屏
|
||||||
|
- [x] 水平方向手势快进/快退
|
||||||
|
- [x] 全屏方向设置
|
||||||
|
- [x] 倍速选择/长按2倍速
|
||||||
|
- [x] 硬件加速(视机型而定)
|
||||||
|
- [x] 画质选择(高清画质未解锁)
|
||||||
|
- [x] 音质选择(视视频而定)
|
||||||
|
- [x] 解码格式选择(视视频而定)
|
||||||
|
- [ ] 弹幕
|
||||||
|
- [ ] 字幕
|
||||||
|
- [x] 记忆播放
|
||||||
|
|
||||||
|
- [x] 搜索相关
|
||||||
|
- [x] 热搜
|
||||||
|
- [x] 搜索历史
|
||||||
|
- [x] 默认搜索词
|
||||||
|
- [x] 投稿、番剧、直播间、用户搜索
|
||||||
|
|
||||||
|
- [x] 视频详情页相关
|
||||||
|
- [x] 视频选集(分p)切换
|
||||||
|
- [x] 点赞、投币、收藏/取消收藏
|
||||||
|
- [x] 相关视频查看
|
||||||
|
- [x] 评论用户身份标识
|
||||||
|
- [x] 评论(排序)查看、二楼评论查看
|
||||||
|
- [x] 主楼、二楼评论回复功能
|
||||||
|
- [x] 评论点赞
|
||||||
|
- [x] 评论笔记图片查看、保存
|
||||||
|
|
||||||
|
- [x] 设置相关
|
||||||
|
- [x] 画质、音质、解码方式预设
|
||||||
|
- [x] 图片质量设定
|
||||||
|
- [x] 主题模式:亮色/暗色/跟随系统
|
||||||
|
- [x] 震动反馈(可选)
|
||||||
|
- [ ] 等等
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
### 下载
|
||||||
|
|
||||||
|
可以通过右侧release进行下载或拉取代码到本地进行编译
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
### 声明
|
||||||
|
|
||||||
|
此项目(PiliPala)是个人为了兴趣而开发, 仅用于学习和测试。
|
||||||
|
所用API皆从官方网站收集, 不提供任何破解内容。
|
||||||
|
|
||||||
|
感谢使用
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
### 致谢
|
||||||
|
|
||||||
|
- [bilibili-API-collect](https://github.com/SocialSisterYi/bilibili-API-collect)
|
||||||
|
- [flutter_meedu_videoplayer](https://github.com/zezo357/flutter_meedu_videoplayer)
|
||||||
|
- [media-kit](https://github.com/media-kit/media-kit)
|
||||||
|
- [dio](https://pub.dev/packages/dio)
|
||||||
|
- 等等
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
|||||||
@ -25,6 +25,17 @@ apply plugin: 'com.android.application'
|
|||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||||
|
def keystoreProperties = new Properties()
|
||||||
|
if (keystorePropertiesFile.exists()) {
|
||||||
|
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
|
||||||
|
}
|
||||||
|
|
||||||
|
def _storeFile = file(System.getenv("KEYSTORE") ?: keystoreProperties["storeFile"] ?: "vvex.jks")
|
||||||
|
def _storePassword = System.getenv("KEYSTORE_PASSWORD") ?: keystoreProperties["storePassword"]
|
||||||
|
def _keyAlias = System.getenv("KEY_ALIAS") ?: keystoreProperties["keyAlias"]
|
||||||
|
def _keyPassword = System.getenv("KEY_PASSWORD") ?: keystoreProperties["keyPassword"]
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion flutter.compileSdkVersion
|
compileSdkVersion flutter.compileSdkVersion
|
||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
@ -54,11 +65,24 @@ android {
|
|||||||
minSdkVersion 19
|
minSdkVersion 19
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signingConfigs {
|
||||||
|
// 添加签名配置
|
||||||
|
release {
|
||||||
|
// 配置密钥库文件的位置、别名、密码等信息
|
||||||
|
storeFile _storeFile
|
||||||
|
storePassword _storePassword
|
||||||
|
keyAlias _keyAlias
|
||||||
|
keyPassword _keyPassword
|
||||||
|
v1SigningEnabled true
|
||||||
|
v2SigningEnabled true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
// TODO: Add your own signing config for the release build.
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
signingConfig signingConfigs.debug
|
signingConfig signingConfigs.release
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
<application
|
<application
|
||||||
android:label="pilipala"
|
android:label="PiliPala"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:enableOnBackInvokedCallback="true">
|
android:enableOnBackInvokedCallback="true">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 650 B |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 485 B |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 805 B |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 1.4 KiB |
@ -2,5 +2,4 @@
|
|||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 398 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 367 B |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 447 B |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 542 B |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 708 B |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 28 KiB |
BIN
assets/images/logo/logo_android_2.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 25 KiB |
BIN
assets/sreenshot/174shots_so.png
Normal file
|
After Width: | Height: | Size: 526 KiB |
BIN
assets/sreenshot/510shots_so.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
assets/sreenshot/850shots_so.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
assets/sreenshot/bangumi.png
Normal file
|
After Width: | Height: | Size: 659 KiB |
BIN
assets/sreenshot/bangumi_detail.png
Normal file
|
After Width: | Height: | Size: 300 KiB |
BIN
assets/sreenshot/dynamic.png
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
assets/sreenshot/home.png
Normal file
|
After Width: | Height: | Size: 504 KiB |
BIN
assets/sreenshot/media.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
assets/sreenshot/member.png
Normal file
|
After Width: | Height: | Size: 407 KiB |
BIN
assets/sreenshot/search.png
Normal file
|
After Width: | Height: | Size: 522 KiB |
BIN
assets/sreenshot/set.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
11
change_log/1.0.0.0817.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
### 初始版本
|
||||||
|
+ 直播、推荐、动态功能
|
||||||
|
+ 投稿、番剧播放功能
|
||||||
|
+ 播放器手势支持
|
||||||
|
+ 画质、音质、解码格式支持
|
||||||
|
+ 点赞、投币、收藏功能
|
||||||
|
+ 关注/取关、用户主页功能
|
||||||
|
+ 评论功能
|
||||||
|
+ 历史记录、稍后再看功能
|
||||||
7
change_log/1.0.1.0817.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
### 修复
|
||||||
|
+ 升级播放器依赖
|
||||||
|
+ android平台 AV1格式视频支持
|
||||||
|
+ 视频全屏功能
|
||||||
|
|
||||||
@ -5,6 +5,8 @@ PODS:
|
|||||||
- device_info_plus (0.0.1):
|
- device_info_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Flutter (1.0.0)
|
- Flutter (1.0.0)
|
||||||
|
- flutter_volume_controller (0.0.1):
|
||||||
|
- Flutter
|
||||||
- FMDB (2.7.5):
|
- FMDB (2.7.5):
|
||||||
- FMDB/standard (= 2.7.5)
|
- FMDB/standard (= 2.7.5)
|
||||||
- FMDB/standard (2.7.5)
|
- FMDB/standard (2.7.5)
|
||||||
@ -31,6 +33,8 @@ PODS:
|
|||||||
- sqflite (0.0.3):
|
- sqflite (0.0.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FMDB (>= 2.7.5)
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
- volume_controller (0.0.1):
|
- volume_controller (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
@ -44,6 +48,7 @@ DEPENDENCIES:
|
|||||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
|
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
|
||||||
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
- image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`)
|
||||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
@ -54,6 +59,7 @@ DEPENDENCIES:
|
|||||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
- webview_cookie_manager (from `.symlinks/plugins/webview_cookie_manager/ios`)
|
- webview_cookie_manager (from `.symlinks/plugins/webview_cookie_manager/ios`)
|
||||||
@ -71,6 +77,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||||
Flutter:
|
Flutter:
|
||||||
:path: Flutter
|
:path: Flutter
|
||||||
|
flutter_volume_controller:
|
||||||
|
:path: ".symlinks/plugins/flutter_volume_controller/ios"
|
||||||
image_gallery_saver:
|
image_gallery_saver:
|
||||||
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
:path: ".symlinks/plugins/image_gallery_saver/ios"
|
||||||
media_kit_libs_ios_video:
|
media_kit_libs_ios_video:
|
||||||
@ -91,6 +99,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
volume_controller:
|
volume_controller:
|
||||||
:path: ".symlinks/plugins/volume_controller/ios"
|
:path: ".symlinks/plugins/volume_controller/ios"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
@ -104,6 +114,7 @@ SPEC CHECKSUMS:
|
|||||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||||
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea
|
||||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
|
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
image_gallery_saver: cb43cc43141711190510e92c460eb1655cd343cb
|
||||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
@ -116,6 +127,7 @@ SPEC CHECKSUMS:
|
|||||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||||
|
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||||
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
webview_cookie_manager: eaf920722b493bd0f7611b5484771ca53fed03f7
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
|
Before Width: | Height: | Size: 309 B After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 443 B After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 858 B After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1020 B After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 5.0 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 5.3 KiB |
@ -5,7 +5,7 @@
|
|||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Pilipala</string>
|
<string>PiliPala</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
|
|||||||
@ -7,3 +7,10 @@ class StyleString {
|
|||||||
static const Radius imgRadius = Radius.circular(10);
|
static const Radius imgRadius = Radius.circular(10);
|
||||||
static const double aspectRatio = 16 / 10;
|
static const double aspectRatio = 16 / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Constants {
|
||||||
|
static const String appKey = '27eb53fc9058f8c3';
|
||||||
|
static const String thirdSign = '04224646d1fea004e79606d3b038c84a';
|
||||||
|
static const String thirdApi =
|
||||||
|
'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png';
|
||||||
|
}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ class VideoCardHSkeleton extends StatelessWidget {
|
|||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, boxConstraints) {
|
builder: (context, boxConstraints) {
|
||||||
double width =
|
double width =
|
||||||
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
|
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: width / StyleString.aspectRatio,
|
height: width / StyleString.aspectRatio,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class AnimatedDialog extends StatefulWidget {
|
class AnimatedDialog extends StatefulWidget {
|
||||||
const AnimatedDialog({Key? key, required this.child}) : super(key: key);
|
const AnimatedDialog({Key? key, required this.child, this.closeFn})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
final Function? closeFn;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => AnimatedDialogState();
|
State<StatefulWidget> createState() => AnimatedDialogState();
|
||||||
@ -39,12 +41,16 @@ class AnimatedDialogState extends State<AnimatedDialog>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
color: Colors.black.withOpacity(opacityAnimation!.value),
|
color: Colors.black.withOpacity(opacityAnimation!.value),
|
||||||
child: Center(
|
child: InkWell(
|
||||||
child: FadeTransition(
|
splashColor: Colors.transparent,
|
||||||
opacity: scaleAnimation!,
|
onTap: () => widget.closeFn!(),
|
||||||
child: ScaleTransition(
|
child: Center(
|
||||||
scale: scaleAnimation!,
|
child: FadeTransition(
|
||||||
child: widget.child,
|
opacity: scaleAnimation!,
|
||||||
|
child: ScaleTransition(
|
||||||
|
scale: scaleAnimation!,
|
||||||
|
child: widget.child,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class AppBarAni extends StatelessWidget implements PreferredSizeWidget {
|
|
||||||
const AppBarAni({
|
|
||||||
required this.child,
|
|
||||||
required this.controller,
|
|
||||||
required this.visible,
|
|
||||||
this.position,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
final PreferredSizeWidget child;
|
|
||||||
final AnimationController controller;
|
|
||||||
final bool visible;
|
|
||||||
final String? position;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Size get preferredSize => child.preferredSize;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
visible ? controller.reverse() : controller.forward();
|
|
||||||
return SlideTransition(
|
|
||||||
position: Tween<Offset>(
|
|
||||||
begin: Offset.zero,
|
|
||||||
end: Offset(0, position! == 'top' ? -1 : 1),
|
|
||||||
).animate(CurvedAnimation(
|
|
||||||
parent: controller,
|
|
||||||
curve: Curves.easeInOut,
|
|
||||||
)),
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
gradient: position! == 'top'
|
|
||||||
? const LinearGradient(
|
|
||||||
begin: Alignment.bottomCenter,
|
|
||||||
end: Alignment.topCenter,
|
|
||||||
colors: <Color>[
|
|
||||||
Colors.transparent,
|
|
||||||
Colors.black45,
|
|
||||||
],
|
|
||||||
tileMode: TileMode.clamp,
|
|
||||||
)
|
|
||||||
: const LinearGradient(
|
|
||||||
begin: Alignment.topCenter,
|
|
||||||
end: Alignment.bottomCenter,
|
|
||||||
colors: <Color>[
|
|
||||||
Colors.transparent,
|
|
||||||
Colors.black45,
|
|
||||||
],
|
|
||||||
tileMode: TileMode.mirror,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,33 +1,120 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
Widget pBadge(
|
// Widget pBadge(
|
||||||
text,
|
// text,
|
||||||
context,
|
// context,
|
||||||
double? top,
|
// double? top,
|
||||||
double? right,
|
// double? right,
|
||||||
double? bottom,
|
// double? bottom,
|
||||||
double? left, {
|
// double? left, {
|
||||||
type = 'primary',
|
// type = 'primary',
|
||||||
}) {
|
// }) {
|
||||||
Color bgColor = Theme.of(context).colorScheme.primary;
|
// Color bgColor = Theme.of(context).colorScheme.primary;
|
||||||
Color color = Theme.of(context).colorScheme.onPrimary;
|
// Color color = Theme.of(context).colorScheme.onPrimary;
|
||||||
if (type == 'gray') {
|
// if (type == 'gray') {
|
||||||
bgColor = Colors.black54.withOpacity(0.4);
|
// bgColor = Colors.black54.withOpacity(0.4);
|
||||||
color = Colors.white;
|
// color = Colors.white;
|
||||||
}
|
// }
|
||||||
return Positioned(
|
// return Positioned(
|
||||||
top: top,
|
// top: top,
|
||||||
left: left,
|
// left: left,
|
||||||
right: right,
|
// right: right,
|
||||||
bottom: bottom,
|
// bottom: bottom,
|
||||||
child: Container(
|
// child: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 6),
|
// padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 6),
|
||||||
decoration:
|
// decoration:
|
||||||
BoxDecoration(borderRadius: BorderRadius.circular(4), color: bgColor),
|
// BoxDecoration(borderRadius: BorderRadius.circular(4), color: bgColor),
|
||||||
child: Text(
|
// child: Text(
|
||||||
text,
|
// text,
|
||||||
style: TextStyle(fontSize: 11, color: color),
|
// style: TextStyle(fontSize: 11, color: color),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
class PBadge extends StatelessWidget {
|
||||||
|
final String? text;
|
||||||
|
final double? top;
|
||||||
|
final double? right;
|
||||||
|
final double? bottom;
|
||||||
|
final double? left;
|
||||||
|
final String? type;
|
||||||
|
final String? size;
|
||||||
|
final String? stack;
|
||||||
|
final double? fs;
|
||||||
|
|
||||||
|
const PBadge({
|
||||||
|
super.key,
|
||||||
|
this.text,
|
||||||
|
this.top,
|
||||||
|
this.right,
|
||||||
|
this.bottom,
|
||||||
|
this.left,
|
||||||
|
this.type = 'primary',
|
||||||
|
this.size = 'medium',
|
||||||
|
this.stack = 'position',
|
||||||
|
this.fs = 11,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
ColorScheme t = Theme.of(context).colorScheme;
|
||||||
|
// 背景色
|
||||||
|
Color bgColor = t.primary;
|
||||||
|
// 前景色
|
||||||
|
Color color = t.onPrimary;
|
||||||
|
// 边框色
|
||||||
|
Color borderColor = Colors.transparent;
|
||||||
|
if (type == 'gray') {
|
||||||
|
bgColor = Colors.black54.withOpacity(0.4);
|
||||||
|
color = Colors.white;
|
||||||
|
}
|
||||||
|
if (type == 'color') {
|
||||||
|
bgColor = t.primaryContainer.withOpacity(0.6);
|
||||||
|
color = t.primary;
|
||||||
|
}
|
||||||
|
if (type == 'line') {
|
||||||
|
bgColor = Colors.transparent;
|
||||||
|
color = t.primary;
|
||||||
|
borderColor = t.primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
EdgeInsets paddingStyle =
|
||||||
|
const EdgeInsets.symmetric(vertical: 1, horizontal: 6);
|
||||||
|
double fontSize = 11;
|
||||||
|
BorderRadius br = BorderRadius.circular(4);
|
||||||
|
|
||||||
|
if (size == 'small') {
|
||||||
|
paddingStyle = const EdgeInsets.symmetric(vertical: 0, horizontal: 3);
|
||||||
|
fontSize = 11;
|
||||||
|
br = BorderRadius.circular(3);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget content = Container(
|
||||||
|
padding: paddingStyle,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: br,
|
||||||
|
color: bgColor,
|
||||||
|
border: Border.all(color: borderColor),
|
||||||
),
|
),
|
||||||
),
|
child: Text(
|
||||||
);
|
text!,
|
||||||
|
style: TextStyle(fontSize: fs ?? fontSize, color: color),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (stack == 'position') {
|
||||||
|
return Positioned(
|
||||||
|
top: top,
|
||||||
|
left: left,
|
||||||
|
right: right,
|
||||||
|
bottom: bottom,
|
||||||
|
child: content,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 5),
|
||||||
|
child: content,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/pages/rcmd/controller.dart';
|
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
class LiveCard extends StatelessWidget {
|
class LiveCard extends StatelessWidget {
|
||||||
@ -95,7 +93,7 @@ class LiveContent extends StatelessWidget {
|
|||||||
liveItem.title,
|
liveItem.title,
|
||||||
textAlign: TextAlign.start,
|
textAlign: TextAlign.start,
|
||||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||||
maxLines: Get.find<RcmdController>().crossAxisCount,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
|
||||||
class NetworkImgLayer extends StatelessWidget {
|
class NetworkImgLayer extends StatelessWidget {
|
||||||
final String? src;
|
final String? src;
|
||||||
@ -24,12 +28,14 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
this.fadeOutDuration,
|
this.fadeOutDuration,
|
||||||
this.fadeInDuration,
|
this.fadeInDuration,
|
||||||
// 图片质量 默认1%
|
// 图片质量 默认1%
|
||||||
this.quality = 1,
|
this.quality,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
double pr = MediaQuery.of(context).devicePixelRatio;
|
double pr = MediaQuery.of(context).devicePixelRatio;
|
||||||
|
int picQuality = setting.get(SettingBoxKey.defaultPicQa, defaultValue: 10);
|
||||||
|
|
||||||
// double pr = 2;
|
// double pr = 2;
|
||||||
return src != ''
|
return src != ''
|
||||||
? ClipRRect(
|
? ClipRRect(
|
||||||
@ -41,7 +47,7 @@ class NetworkImgLayer extends StatelessWidget {
|
|||||||
: StyleString.imgRadius.x),
|
: StyleString.imgRadius.x),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
imageUrl:
|
imageUrl:
|
||||||
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality}q.webp',
|
'${src!.startsWith('//') ? 'https:${src!}' : src!}@${quality ?? picQuality}q.webp',
|
||||||
width: width ?? double.infinity,
|
width: width ?? double.infinity,
|
||||||
height: height ?? double.infinity,
|
height: height ?? double.infinity,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
|
|||||||
@ -1,42 +1,82 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/utils/download.dart';
|
||||||
|
|
||||||
class OverlayPop extends StatelessWidget {
|
class OverlayPop extends StatelessWidget {
|
||||||
final dynamic videoItem;
|
final dynamic videoItem;
|
||||||
const OverlayPop({super.key, this.videoItem});
|
final Function? closeFn;
|
||||||
|
const OverlayPop({super.key, this.videoItem, this.closeFn});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
double imgWidth = MediaQuery.of(context).size.width - 8 * 2;
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.background,
|
color: Theme.of(context).colorScheme.background,
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
borderRadius: BorderRadius.circular(10.0),
|
||||||
),
|
),
|
||||||
child: ClipRRect(
|
child: Column(
|
||||||
borderRadius: BorderRadius.circular(6.0),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Column(
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Stack(
|
||||||
children: [
|
children: [
|
||||||
NetworkImgLayer(
|
NetworkImgLayer(
|
||||||
width: (MediaQuery.of(context).size.width - 16),
|
width: imgWidth,
|
||||||
height: (MediaQuery.of(context).size.width - 16) /
|
height: imgWidth / StyleString.aspectRatio,
|
||||||
StyleString.aspectRatio,
|
src: videoItem.pic!,
|
||||||
src: videoItem.pic!,
|
quality: 100,
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(12, 15, 10, 15),
|
|
||||||
child: Text(
|
|
||||||
videoItem.title!,
|
|
||||||
// maxLines: 1,
|
|
||||||
// overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
],
|
right: 8,
|
||||||
),
|
top: 8,
|
||||||
|
child: Container(
|
||||||
|
width: 30,
|
||||||
|
height: 30,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
borderRadius:
|
||||||
|
const BorderRadius.all(Radius.circular(20))),
|
||||||
|
child: IconButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
|
),
|
||||||
|
onPressed: () => closeFn!(),
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 18,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 10, 8, 10),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
videoItem.title!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4),
|
||||||
|
IconButton(
|
||||||
|
tooltip: '保存封面图',
|
||||||
|
onPressed: () async {
|
||||||
|
await DownloadUtils.downloadImg(
|
||||||
|
videoItem.pic ?? videoItem.cover);
|
||||||
|
// closeFn!();
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.download, size: 20),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class UpTag extends StatelessWidget {
|
|
||||||
const UpTag({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Container(
|
|
||||||
width: 14,
|
|
||||||
height: 10,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(2),
|
|
||||||
border: Border.all(color: Theme.of(context).colorScheme.outline)),
|
|
||||||
margin: const EdgeInsets.only(right: 4),
|
|
||||||
child: Center(
|
|
||||||
child: Text(
|
|
||||||
'UP',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 6, color: Theme.of(context).colorScheme.outline),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -16,12 +16,14 @@ class VideoCardH extends StatelessWidget {
|
|||||||
final videoItem;
|
final videoItem;
|
||||||
final Function()? longPress;
|
final Function()? longPress;
|
||||||
final Function()? longPressEnd;
|
final Function()? longPressEnd;
|
||||||
|
final String source;
|
||||||
|
|
||||||
const VideoCardH({
|
const VideoCardH({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.videoItem,
|
required this.videoItem,
|
||||||
this.longPress,
|
this.longPress,
|
||||||
this.longPressEnd,
|
this.longPressEnd,
|
||||||
|
this.source = 'normal',
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -35,11 +37,11 @@ class VideoCardH extends StatelessWidget {
|
|||||||
longPress!();
|
longPress!();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPressEnd: (details) {
|
// onLongPressEnd: (details) {
|
||||||
if (longPressEnd != null) {
|
// if (longPressEnd != null) {
|
||||||
longPressEnd!();
|
// longPressEnd!();
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
try {
|
try {
|
||||||
@ -57,8 +59,9 @@ class VideoCardH extends StatelessWidget {
|
|||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, boxConstraints) {
|
builder: (context, boxConstraints) {
|
||||||
double width =
|
double width =
|
||||||
(boxConstraints.maxWidth - StyleString.cardSpace * 9) / 2;
|
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||||
return SizedBox(
|
return Container(
|
||||||
|
constraints: const BoxConstraints(minHeight: 88),
|
||||||
height: width / StyleString.aspectRatio,
|
height: width / StyleString.aspectRatio,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
@ -80,19 +83,24 @@ class VideoCardH extends StatelessWidget {
|
|||||||
height: maxHeight,
|
height: maxHeight,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pBadge(Utils.timeFormat(videoItem.duration!),
|
PBadge(
|
||||||
context, null, 6.0, 6.0, null,
|
text: Utils.timeFormat(videoItem.duration!),
|
||||||
type: 'gray'),
|
top: null,
|
||||||
if (videoItem.rcmdReason != null &&
|
right: 6.0,
|
||||||
videoItem.rcmdReason.content != '')
|
bottom: 6.0,
|
||||||
pBadge(videoItem.rcmdReason.content, context,
|
left: null,
|
||||||
6.0, 6.0, null, null),
|
type: 'gray',
|
||||||
|
),
|
||||||
|
// if (videoItem.rcmdReason != null &&
|
||||||
|
// videoItem.rcmdReason.content != '')
|
||||||
|
// pBadge(videoItem.rcmdReason.content, context,
|
||||||
|
// 6.0, 6.0, null, null),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VideoContent(videoItem: videoItem)
|
VideoContent(videoItem: videoItem, source: source)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -107,7 +115,9 @@ class VideoCardH extends StatelessWidget {
|
|||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
// ignore: prefer_typing_uninitialized_variables
|
// ignore: prefer_typing_uninitialized_variables
|
||||||
final videoItem;
|
final videoItem;
|
||||||
const VideoContent({super.key, required this.videoItem});
|
final String source;
|
||||||
|
const VideoContent(
|
||||||
|
{super.key, required this.videoItem, this.source = 'normal'});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -124,7 +134,6 @@ class VideoContent extends StatelessWidget {
|
|||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
letterSpacing: 0.3,
|
|
||||||
),
|
),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
@ -198,26 +207,62 @@ class VideoContent extends StatelessWidget {
|
|||||||
// color: Theme.of(context).colorScheme.outline),
|
// color: Theme.of(context).colorScheme.outline),
|
||||||
// )
|
// )
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
SizedBox(
|
// SizedBox(
|
||||||
width: 20,
|
// width: 20,
|
||||||
height: 20,
|
// height: 20,
|
||||||
child: IconButton(
|
// child: IconButton(
|
||||||
tooltip: '稍后再看',
|
// tooltip: '稍后再看',
|
||||||
style: ButtonStyle(
|
// style: ButtonStyle(
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
// padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||||
),
|
// ),
|
||||||
onPressed: () async {
|
// onPressed: () async {
|
||||||
var res =
|
// var res =
|
||||||
await UserHttp.toViewLater(bvid: videoItem.bvid);
|
// await UserHttp.toViewLater(bvid: videoItem.bvid);
|
||||||
SmartDialog.showToast(res['msg']);
|
// SmartDialog.showToast(res['msg']);
|
||||||
},
|
// },
|
||||||
icon: Icon(
|
// icon: Icon(
|
||||||
Icons.more_vert_outlined,
|
// Icons.more_vert_outlined,
|
||||||
color: Theme.of(context).colorScheme.outline,
|
// color: Theme.of(context).colorScheme.outline,
|
||||||
size: 14,
|
// size: 14,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
if (source == 'normal')
|
||||||
|
SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: PopupMenuButton<String>(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
tooltip: '稍后再看',
|
||||||
|
icon: Icon(
|
||||||
|
Icons.more_vert_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
|
position: PopupMenuPosition.under,
|
||||||
|
// constraints: const BoxConstraints(maxHeight: 35),
|
||||||
|
onSelected: (String type) {},
|
||||||
|
itemBuilder: (BuildContext context) =>
|
||||||
|
<PopupMenuEntry<String>>[
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
onTap: () async {
|
||||||
|
var res = await UserHttp.toViewLater(
|
||||||
|
bvid: videoItem.bvid);
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
},
|
||||||
|
value: 'pause',
|
||||||
|
height: 35,
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.watch_later_outlined, size: 16),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
Text('稍后再看', style: TextStyle(fontSize: 13))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@ -2,18 +2,19 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:pilipala/common/constants.dart';
|
import 'package:pilipala/common/constants.dart';
|
||||||
|
import 'package:pilipala/common/widgets/badge.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
import 'package:pilipala/common/widgets/stat/danmu.dart';
|
||||||
import 'package:pilipala/common/widgets/stat/view.dart';
|
import 'package:pilipala/common/widgets/stat/view.dart';
|
||||||
|
import 'package:pilipala/http/search.dart';
|
||||||
import 'package:pilipala/http/user.dart';
|
import 'package:pilipala/http/user.dart';
|
||||||
import 'package:pilipala/pages/rcmd/index.dart';
|
import 'package:pilipala/models/common/search_type.dart';
|
||||||
import 'package:pilipala/utils/id_utils.dart';
|
import 'package:pilipala/utils/id_utils.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
|
||||||
// 视频卡片 - 垂直布局
|
// 视频卡片 - 垂直布局
|
||||||
class VideoCardV extends StatelessWidget {
|
class VideoCardV extends StatelessWidget {
|
||||||
// ignore: prefer_typing_uninitialized_variables
|
final dynamic videoItem;
|
||||||
final videoItem;
|
|
||||||
final Function()? longPress;
|
final Function()? longPress;
|
||||||
final Function()? longPressEnd;
|
final Function()? longPressEnd;
|
||||||
|
|
||||||
@ -24,6 +25,54 @@ class VideoCardV extends StatelessWidget {
|
|||||||
this.longPressEnd,
|
this.longPressEnd,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
void onPushDetail(heroTag) async {
|
||||||
|
String goto = videoItem.goto;
|
||||||
|
switch (goto) {
|
||||||
|
case 'bangumi':
|
||||||
|
if (videoItem.bangumiBadge == '电影') {
|
||||||
|
SmartDialog.showToast('暂不支持电影观看');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int epId = videoItem.param;
|
||||||
|
SmartDialog.showLoading(msg: '资源获取中');
|
||||||
|
var result = await SearchHttp.bangumiInfo(seasonId: null, epId: epId);
|
||||||
|
if (result['status']) {
|
||||||
|
var bangumiDetail = result['data'];
|
||||||
|
int cid = bangumiDetail.episodes!.first.cid;
|
||||||
|
String bvid = IdUtils.av2bv(bangumiDetail.episodes!.first.aid);
|
||||||
|
SmartDialog.dismiss().then(
|
||||||
|
(value) => Get.toNamed(
|
||||||
|
'/video?bvid=$bvid&cid=$cid&epId=$epId',
|
||||||
|
arguments: {
|
||||||
|
'pic': videoItem.pic,
|
||||||
|
'heroTag': heroTag,
|
||||||
|
'videoType': SearchType.media_bangumi,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'av':
|
||||||
|
String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid);
|
||||||
|
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.cid}', arguments: {
|
||||||
|
// 'videoItem': videoItem,
|
||||||
|
'pic': videoItem.pic,
|
||||||
|
'heroTag': heroTag,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SmartDialog.showToast(videoItem.goto);
|
||||||
|
Get.toNamed(
|
||||||
|
'/webview',
|
||||||
|
parameters: {
|
||||||
|
'url': videoItem.uri,
|
||||||
|
'type': 'url',
|
||||||
|
'pageTitle': videoItem.title,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
String heroTag = Utils.makeHeroTag(videoItem.id);
|
String heroTag = Utils.makeHeroTag(videoItem.id);
|
||||||
@ -40,61 +89,29 @@ class VideoCardV extends StatelessWidget {
|
|||||||
longPress!();
|
longPress!();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onLongPressEnd: (details) {
|
// onLongPressEnd: (details) {
|
||||||
if (longPressEnd != null) {
|
// if (longPressEnd != null) {
|
||||||
longPressEnd!();
|
// longPressEnd!();
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () async {
|
onTap: () async => onPushDetail(heroTag),
|
||||||
String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid);
|
|
||||||
Get.toNamed('/video?bvid=$bvid&cid=${videoItem.cid}',
|
|
||||||
arguments: {'videoItem': videoItem, 'heroTag': heroTag});
|
|
||||||
},
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
AspectRatio(
|
||||||
borderRadius: const BorderRadius.only(
|
aspectRatio: StyleString.aspectRatio,
|
||||||
topLeft: StyleString.imgRadius,
|
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||||
topRight: StyleString.imgRadius,
|
double maxWidth = boxConstraints.maxWidth;
|
||||||
bottomLeft: StyleString.imgRadius,
|
double maxHeight = boxConstraints.maxHeight;
|
||||||
bottomRight: StyleString.imgRadius,
|
return Hero(
|
||||||
),
|
tag: heroTag,
|
||||||
child: AspectRatio(
|
child: NetworkImgLayer(
|
||||||
aspectRatio: StyleString.aspectRatio,
|
src: videoItem.pic,
|
||||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
width: maxWidth,
|
||||||
double maxWidth = boxConstraints.maxWidth;
|
height: maxHeight,
|
||||||
double maxHeight = boxConstraints.maxHeight;
|
),
|
||||||
return Stack(
|
);
|
||||||
children: [
|
}),
|
||||||
Hero(
|
|
||||||
tag: heroTag,
|
|
||||||
child: NetworkImgLayer(
|
|
||||||
src: videoItem.pic,
|
|
||||||
width: maxWidth,
|
|
||||||
height: maxHeight,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
// if (videoItem.stat.view is int &&
|
|
||||||
// videoItem.stat.danmaku is int)
|
|
||||||
// Positioned(
|
|
||||||
// left: 0,
|
|
||||||
// right: 0,
|
|
||||||
// bottom: 0,
|
|
||||||
// child: AnimatedOpacity(
|
|
||||||
// opacity: 1,
|
|
||||||
// duration: const Duration(milliseconds: 200),
|
|
||||||
// child: VideoStat(
|
|
||||||
// view: videoItem.stat.view,
|
|
||||||
// danmaku: videoItem.stat.danmaku,
|
|
||||||
// duration: videoItem.duration,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
VideoContent(videoItem: videoItem)
|
VideoContent(videoItem: videoItem)
|
||||||
],
|
],
|
||||||
@ -106,113 +123,151 @@ class VideoCardV extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class VideoContent extends StatelessWidget {
|
class VideoContent extends StatelessWidget {
|
||||||
// ignore: prefer_typing_uninitialized_variables
|
final dynamic videoItem;
|
||||||
final videoItem;
|
|
||||||
const VideoContent({Key? key, required this.videoItem}) : super(key: key);
|
const VideoContent({Key? key, required this.videoItem}) : super(key: key);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Expanded(
|
return Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
// 多列
|
padding: const EdgeInsets.fromLTRB(4, 8, 0, 3),
|
||||||
padding: const EdgeInsets.fromLTRB(4, 5, 0, 3),
|
|
||||||
// 单列
|
|
||||||
// padding: const EdgeInsets.fromLTRB(14, 10, 4, 8),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
videoItem.title,
|
videoItem.title,
|
||||||
textAlign: TextAlign.start,
|
style: const TextStyle(fontSize: 13),
|
||||||
style: const TextStyle(
|
maxLines: 2,
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
letterSpacing: 0.3,
|
|
||||||
),
|
|
||||||
maxLines: Get.find<RcmdController>().crossAxisCount,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
|
if (videoItem.goto == 'bangumi') ...[
|
||||||
|
PBadge(
|
||||||
|
text: videoItem.bangumiBadge,
|
||||||
|
stack: 'normal',
|
||||||
|
size: 'small',
|
||||||
|
type: 'line',
|
||||||
|
fs: 9,
|
||||||
|
)
|
||||||
|
],
|
||||||
if (videoItem.rcmdReason != null &&
|
if (videoItem.rcmdReason != null &&
|
||||||
videoItem.rcmdReason.content != '' ||
|
videoItem.rcmdReason.content != '') ...[
|
||||||
videoItem.isFollowed == 1) ...[
|
PBadge(
|
||||||
Container(
|
text: videoItem.rcmdReason.content,
|
||||||
padding: const EdgeInsets.fromLTRB(3, 0, 3, 0),
|
stack: 'normal',
|
||||||
decoration: BoxDecoration(
|
size: 'small',
|
||||||
color: Theme.of(context)
|
type: 'color',
|
||||||
.colorScheme
|
)
|
||||||
.primaryContainer
|
],
|
||||||
.withOpacity(0.6),
|
if (videoItem.goto == 'picture') ...[
|
||||||
borderRadius: BorderRadius.circular(3)),
|
const PBadge(
|
||||||
child: Center(
|
text: '动态',
|
||||||
child: Text(
|
stack: 'normal',
|
||||||
videoItem.rcmdReason != null &&
|
size: 'small',
|
||||||
videoItem.rcmdReason.content != ''
|
type: 'line',
|
||||||
? videoItem.rcmdReason.content
|
fs: 9,
|
||||||
: '已关注',
|
)
|
||||||
style: TextStyle(
|
|
||||||
fontSize: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.labelSmall!
|
|
||||||
.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
const SizedBox(width: 4)
|
|
||||||
],
|
],
|
||||||
Expanded(
|
Expanded(
|
||||||
child: LayoutBuilder(builder:
|
child: Text(
|
||||||
(BuildContext context, BoxConstraints constraints) {
|
videoItem.owner.name,
|
||||||
return SizedBox(
|
maxLines: 1,
|
||||||
width: constraints.maxWidth,
|
style: TextStyle(
|
||||||
child: Text(
|
fontSize:
|
||||||
videoItem.owner.name,
|
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||||
maxLines: 1,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize:
|
|
||||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 20,
|
|
||||||
height: 20,
|
|
||||||
child: IconButton(
|
|
||||||
tooltip: '稍后再看',
|
|
||||||
style: ButtonStyle(
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
),
|
|
||||||
onPressed: () async {
|
|
||||||
var res =
|
|
||||||
await UserHttp.toViewLater(bvid: videoItem.bvid);
|
|
||||||
SmartDialog.showToast(res['msg']);
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Icons.more_vert_outlined,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
color: Theme.of(context).colorScheme.outline,
|
||||||
size: 14,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (videoItem.goto == 'av')
|
||||||
|
SizedBox(
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
child: PopupMenuButton<String>(
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
tooltip: '稍后再看',
|
||||||
|
icon: Icon(
|
||||||
|
Icons.more_vert_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
size: 14,
|
||||||
|
),
|
||||||
|
position: PopupMenuPosition.under,
|
||||||
|
// constraints: const BoxConstraints(maxHeight: 35),
|
||||||
|
onSelected: (String type) {},
|
||||||
|
itemBuilder: (BuildContext context) =>
|
||||||
|
<PopupMenuEntry<String>>[
|
||||||
|
PopupMenuItem<String>(
|
||||||
|
onTap: () async {
|
||||||
|
int aid = videoItem.param;
|
||||||
|
var res = await UserHttp.toViewLater(
|
||||||
|
bvid: IdUtils.av2bv(aid));
|
||||||
|
SmartDialog.showToast(res['msg']);
|
||||||
|
},
|
||||||
|
value: 'pause',
|
||||||
|
height: 35,
|
||||||
|
child: const Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.watch_later_outlined, size: 16),
|
||||||
|
SizedBox(width: 6),
|
||||||
|
Text('稍后再看', style: TextStyle(fontSize: 13))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
// Row(
|
// Row(
|
||||||
// children: [
|
// children: [
|
||||||
|
// const SizedBox(width: 1),
|
||||||
// StatView(
|
// StatView(
|
||||||
// theme: 'black',
|
// theme: 'gray',
|
||||||
// view: videoItem.stat.view,
|
// view: videoItem.stat.view,
|
||||||
// ),
|
// ),
|
||||||
// const SizedBox(width: 6),
|
// const SizedBox(width: 10),
|
||||||
// StatDanMu(
|
// StatDanMu(
|
||||||
// theme: 'black',
|
// theme: 'gray',
|
||||||
// danmu: videoItem.stat.danmaku,
|
// danmu: videoItem.stat.danmaku,
|
||||||
// ),
|
// ),
|
||||||
|
// const Spacer(),
|
||||||
|
// SizedBox(
|
||||||
|
// width: 24,
|
||||||
|
// height: 24,
|
||||||
|
// child: PopupMenuButton<String>(
|
||||||
|
// padding: EdgeInsets.zero,
|
||||||
|
// tooltip: '稍后再看',
|
||||||
|
// icon: Icon(
|
||||||
|
// Icons.more_vert_outlined,
|
||||||
|
// color: Theme.of(context).colorScheme.outline,
|
||||||
|
// size: 14,
|
||||||
|
// ),
|
||||||
|
// position: PopupMenuPosition.under,
|
||||||
|
// // constraints: const BoxConstraints(maxHeight: 35),
|
||||||
|
// onSelected: (String type) {},
|
||||||
|
// itemBuilder: (BuildContext context) =>
|
||||||
|
// <PopupMenuEntry<String>>[
|
||||||
|
// PopupMenuItem<String>(
|
||||||
|
// onTap: () async {
|
||||||
|
// var res =
|
||||||
|
// await UserHttp.toViewLater(bvid: videoItem.bvid);
|
||||||
|
// SmartDialog.showToast(res['msg']);
|
||||||
|
// },
|
||||||
|
// value: 'pause',
|
||||||
|
// height: 35,
|
||||||
|
// child: const Row(
|
||||||
|
// children: [
|
||||||
|
// Icon(Icons.watch_later_outlined, size: 16),
|
||||||
|
// SizedBox(width: 6),
|
||||||
|
// Text('稍后再看', style: TextStyle(fontSize: 13))
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
// ],
|
// ],
|
||||||
// ),
|
// ),
|
||||||
],
|
],
|
||||||
@ -237,7 +292,7 @@ class VideoStat extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Container(
|
||||||
height: 45,
|
height: 48,
|
||||||
padding: const EdgeInsets.only(top: 22, left: 6, right: 6),
|
padding: const EdgeInsets.only(top: 22, left: 6, right: 6),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
|
|||||||
@ -248,9 +248,44 @@ class Api {
|
|||||||
// 移除已观看
|
// 移除已观看
|
||||||
static const String toViewDel = '/x/v2/history/toview/del';
|
static const String toViewDel = '/x/v2/history/toview/del';
|
||||||
|
|
||||||
|
// 清空稍后再看
|
||||||
|
static const String toViewClear = '/x/v2/history/toview/clear';
|
||||||
|
|
||||||
// 追番
|
// 追番
|
||||||
static const String bangumiAdd = '/pgc/web/follow/add';
|
static const String bangumiAdd = '/pgc/web/follow/add';
|
||||||
|
|
||||||
// 取消追番
|
// 取消追番
|
||||||
static const String bangumiDel = '/pgc/web/follow/del';
|
static const String bangumiDel = '/pgc/web/follow/del';
|
||||||
|
|
||||||
|
// 番剧列表
|
||||||
|
// https://api.bilibili.com/pgc/season/index/result?
|
||||||
|
// st=1&
|
||||||
|
// order=3
|
||||||
|
// season_version=-1 全部-1 正片1 电影2 其他3
|
||||||
|
// spoken_language_type=-1 全部-1 原生1 中文配音2
|
||||||
|
// area=-1&
|
||||||
|
// is_finish=-1&
|
||||||
|
// copyright=-1&
|
||||||
|
// season_status=-1&
|
||||||
|
// season_month=-1&
|
||||||
|
// year=-1&
|
||||||
|
// style_id=-1&
|
||||||
|
// sort=0&
|
||||||
|
// page=1&
|
||||||
|
// season_type=1&
|
||||||
|
// pagesize=20&
|
||||||
|
// type=1
|
||||||
|
static const String bangumiList =
|
||||||
|
'/pgc/season/index/result?st=1&order=3&season_version=-1&spoken_language_type=-1&area=-1&is_finish=-1©right=-1&season_status=-1&season_month=-1&year=-1&style_id=-1&sort=0&season_type=1&pagesize=20&type=1';
|
||||||
|
|
||||||
|
// 我的订阅
|
||||||
|
static const String bangumiFollow =
|
||||||
|
'/x/space/bangumi/follow/list?type=1&follow_status=0&pn=1&ps=15&ts=1691544359969';
|
||||||
|
|
||||||
|
// 黑名单
|
||||||
|
static const String blackLst = '/x/relation/blacks';
|
||||||
|
|
||||||
|
// github 获取最新版
|
||||||
|
static const String latestApp =
|
||||||
|
'https://api.github.com/repos/guozhigq/pilipala/releases/latest';
|
||||||
}
|
}
|
||||||
|
|||||||
36
lib/http/bangumi.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:pilipala/http/index.dart';
|
||||||
|
import 'package:pilipala/models/bangumi/list.dart';
|
||||||
|
|
||||||
|
class BangumiHttp {
|
||||||
|
static Future bangumiList({int? page}) async {
|
||||||
|
var res = await Request().get(Api.bangumiList, data: {'page': page});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': BangumiListDataModel.fromJson(res.data['data'])
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future bangumiFollow({int? mid}) async {
|
||||||
|
var res = await Request().get(Api.bangumiFollow, data: {'vmid': mid});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': BangumiListDataModel.fromJson(res.data['data'])
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
lib/http/black.dart
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import 'package:pilipala/http/index.dart';
|
||||||
|
import 'package:pilipala/models/user/black.dart';
|
||||||
|
|
||||||
|
class BlackHttp {
|
||||||
|
static Future blackList({required int pn, int? ps}) async {
|
||||||
|
var res = await Request().get(Api.blackLst, data: {
|
||||||
|
'pn': pn,
|
||||||
|
'ps': ps ?? 50,
|
||||||
|
're_version': 0,
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
});
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': BlackListDataModel.fromJson(res.data['data'])
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
'status': false,
|
||||||
|
'data': [],
|
||||||
|
'msg': res.data['message'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,20 +15,12 @@ import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
|||||||
class Request {
|
class Request {
|
||||||
static final Request _instance = Request._internal();
|
static final Request _instance = Request._internal();
|
||||||
static late CookieManager cookieManager;
|
static late CookieManager cookieManager;
|
||||||
|
static late final Dio dio;
|
||||||
factory Request() => _instance;
|
factory Request() => _instance;
|
||||||
|
|
||||||
static Dio dio = Dio()
|
|
||||||
..httpClientAdapter = Http2Adapter(
|
|
||||||
ConnectionManager(
|
|
||||||
idleTimeout: const Duration(milliseconds: 10000),
|
|
||||||
// Ignore bad certificate
|
|
||||||
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
/// 设置cookie
|
/// 设置cookie
|
||||||
static setCookie() async {
|
static setCookie() async {
|
||||||
|
Box user = GStrorage.user;
|
||||||
var cookiePath = await Utils.getCookiePath();
|
var cookiePath = await Utils.getCookiePath();
|
||||||
var cookieJar = PersistCookieJar(
|
var cookieJar = PersistCookieJar(
|
||||||
ignoreExpires: true,
|
ignoreExpires: true,
|
||||||
@ -38,8 +30,18 @@ class Request {
|
|||||||
dio.interceptors.add(cookieManager);
|
dio.interceptors.add(cookieManager);
|
||||||
var cookie = await cookieManager.cookieJar
|
var cookie = await cookieManager.cookieJar
|
||||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||||
var cookie2 = await cookieManager.cookieJar
|
if (user.get(UserBoxKey.userMid) != null) {
|
||||||
.loadForRequest(Uri.parse(HttpString.tUrl));
|
var cookie2 = await cookieManager.cookieJar
|
||||||
|
.loadForRequest(Uri.parse(HttpString.tUrl));
|
||||||
|
if (cookie2.isEmpty) {
|
||||||
|
try {
|
||||||
|
await Request().get(HttpString.tUrl);
|
||||||
|
} catch (e) {
|
||||||
|
log("setCookie, ${e.toString()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (cookie.isEmpty) {
|
if (cookie.isEmpty) {
|
||||||
try {
|
try {
|
||||||
await Request().get(HttpString.baseUrl);
|
await Request().get(HttpString.baseUrl);
|
||||||
@ -47,23 +49,9 @@ class Request {
|
|||||||
log("setCookie, ${e.toString()}");
|
log("setCookie, ${e.toString()}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cookie2.isEmpty) {
|
var cookieString =
|
||||||
try {
|
cookie.map((cookie) => '${cookie.name}=${cookie.value}').join('; ');
|
||||||
await Request().get(HttpString.tUrl);
|
dio.options.headers['cookie'] = cookieString;
|
||||||
} catch (e) {
|
|
||||||
log("setCookie, ${e.toString()}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 移除cookie
|
|
||||||
static removeCookie() async {
|
|
||||||
await cookieManager.cookieJar
|
|
||||||
.saveFromResponse(Uri.parse(HttpString.baseUrl), []);
|
|
||||||
await cookieManager.cookieJar
|
|
||||||
.saveFromResponse(Uri.parse(HttpString.baseApiUrl), []);
|
|
||||||
cookieManager.cookieJar.deleteAll();
|
|
||||||
dio.interceptors.add(cookieManager);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从cookie中获取 csrf token
|
// 从cookie中获取 csrf token
|
||||||
@ -95,28 +83,38 @@ class Request {
|
|||||||
//Http请求头.
|
//Http请求头.
|
||||||
headers: {
|
headers: {
|
||||||
// 'cookie': '',
|
// 'cookie': '',
|
||||||
"env": 'prod',
|
|
||||||
"app-key": 'android',
|
|
||||||
"x-bili-aurora-eid": 'UlMFQVcABlAH',
|
|
||||||
"x-bili-aurora-zone": 'sh001',
|
|
||||||
'referer': 'https://www.bilibili.com/',
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Box user = GStrorage.user;
|
Box user = GStrorage.user;
|
||||||
if (user.get(UserBoxKey.userMid) != null) {
|
if (user.get(UserBoxKey.userMid) != null) {
|
||||||
options.headers['x-bili-mid'] = user.get(UserBoxKey.userMid).toString();
|
options.headers['x-bili-mid'] = user.get(UserBoxKey.userMid).toString();
|
||||||
|
options.headers['env'] = 'prod';
|
||||||
|
options.headers['app-key'] = 'android64';
|
||||||
|
options.headers['x-bili-aurora-eid'] = 'UlMFQVcABlAH';
|
||||||
|
options.headers['x-bili-aurora-zone'] = 'sh001';
|
||||||
|
options.headers['referer'] = 'https://www.bilibili.com/';
|
||||||
}
|
}
|
||||||
dio.options = options;
|
|
||||||
|
dio = Dio(options)
|
||||||
|
..httpClientAdapter = Http2Adapter(
|
||||||
|
ConnectionManager(
|
||||||
|
idleTimeout: const Duration(milliseconds: 10000),
|
||||||
|
// Ignore bad certificate
|
||||||
|
onClientCreate: (_, config) => config.onBadCertificate = (_) => true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
//添加拦截器
|
//添加拦截器
|
||||||
dio.interceptors
|
dio.interceptors.add(ApiInterceptor());
|
||||||
..add(ApiInterceptor())
|
|
||||||
// 日志拦截器 输出请求、响应内容
|
// 日志拦截器 输出请求、响应内容
|
||||||
..add(LogInterceptor(
|
dio.interceptors.add(LogInterceptor(
|
||||||
request: false,
|
request: false,
|
||||||
requestHeader: false,
|
requestHeader: false,
|
||||||
responseHeader: false,
|
responseHeader: false,
|
||||||
));
|
));
|
||||||
|
|
||||||
dio.transformer = BackgroundTransformer();
|
dio.transformer = BackgroundTransformer();
|
||||||
dio.options.validateStatus = (status) {
|
dio.options.validateStatus = (status) {
|
||||||
return status! >= 200 && status < 300 || status == 304 || status == 302;
|
return status! >= 200 && status < 300 || status == 304 || status == 302;
|
||||||
@ -161,7 +159,7 @@ class Request {
|
|||||||
* post请求
|
* post请求
|
||||||
*/
|
*/
|
||||||
post(url, {data, queryParameters, options, cancelToken, extra}) async {
|
post(url, {data, queryParameters, options, cancelToken, extra}) async {
|
||||||
print('post-data: $data');
|
// print('post-data: $data');
|
||||||
Response response;
|
Response response;
|
||||||
try {
|
try {
|
||||||
response = await dio.post(
|
response = await dio.post(
|
||||||
@ -171,7 +169,7 @@ class Request {
|
|||||||
options: options,
|
options: options,
|
||||||
cancelToken: cancelToken,
|
cancelToken: cancelToken,
|
||||||
);
|
);
|
||||||
print('post success: ${response.data}');
|
// print('post success: ${response.data}');
|
||||||
return response;
|
return response;
|
||||||
} on DioException catch (e) {
|
} on DioException catch (e) {
|
||||||
print('post error: $e');
|
print('post error: $e');
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
|
// ignore_for_file: avoid_print
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
// import 'package:get/get.dart' hide Response;
|
// import 'package:get/get.dart' hide Response;
|
||||||
|
|
||||||
class ApiInterceptor extends Interceptor {
|
class ApiInterceptor extends Interceptor {
|
||||||
@ -13,8 +17,26 @@ class ApiInterceptor extends Interceptor {
|
|||||||
handler.next(options);
|
handler.next(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box user = GStrorage.user;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||||
|
try {
|
||||||
|
if (response.statusCode == 302) {
|
||||||
|
List<String> locations = response.headers['location']!;
|
||||||
|
if (locations.isNotEmpty) {
|
||||||
|
if (locations.first.startsWith('https://www.mcbbs.net')) {
|
||||||
|
final uri = Uri.parse(locations.first);
|
||||||
|
final accessKey = uri.queryParameters['access_key'];
|
||||||
|
final mid = uri.queryParameters['mid'];
|
||||||
|
user.put(UserBoxKey.accessKey, {'mid': mid, 'value': accessKey});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
print('ApiInterceptor: $err');
|
||||||
|
}
|
||||||
|
|
||||||
handler.next(response);
|
handler.next(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/http/api.dart';
|
import 'package:pilipala/http/api.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
import 'package:pilipala/models/model_hot_video_item.dart';
|
import 'package:pilipala/models/model_hot_video_item.dart';
|
||||||
@ -84,6 +85,12 @@ class UserHttp {
|
|||||||
static Future<dynamic> seeYouLater() async {
|
static Future<dynamic> seeYouLater() async {
|
||||||
var res = await Request().get(Api.seeYouLater);
|
var res = await Request().get(Api.seeYouLater);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
|
if (res.data['data']['count'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': {'list': [], 'count': 0}
|
||||||
|
};
|
||||||
|
}
|
||||||
List<HotVideoItemModel> list = [];
|
List<HotVideoItemModel> list = [];
|
||||||
for (var i in res.data['data']['list']) {
|
for (var i in res.data['data']['list']) {
|
||||||
list.add(HotVideoItemModel.fromJson(i));
|
list.add(HotVideoItemModel.fromJson(i));
|
||||||
@ -179,4 +186,35 @@ class UserHttp {
|
|||||||
return {'status': false, 'msg': res.data['message']};
|
return {'status': false, 'msg': res.data['message']};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取用户凭证
|
||||||
|
static Future thirdLogin() async {
|
||||||
|
var res = await Request().get(
|
||||||
|
'https://passport.bilibili.com/login/app/third',
|
||||||
|
data: {
|
||||||
|
'appkey': Constants.appKey,
|
||||||
|
'api': Constants.thirdApi,
|
||||||
|
'sign': Constants.thirdSign,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0 && res.data['data']['has_login'] == 1) {
|
||||||
|
Request().get(res.data['data']['confirm_uri']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空稍后再看
|
||||||
|
static Future toViewClear() async {
|
||||||
|
var res = await Request().post(
|
||||||
|
Api.toViewClear,
|
||||||
|
queryParameters: {
|
||||||
|
'jsonp': 'jsonp',
|
||||||
|
'csrf': await Request.getCsrf(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {'status': true, 'msg': '操作完成'};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/http/api.dart';
|
import 'package:pilipala/http/api.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
import 'package:pilipala/models/common/reply_type.dart';
|
import 'package:pilipala/models/common/reply_type.dart';
|
||||||
@ -9,12 +11,16 @@ import 'package:pilipala/models/model_rec_video_item.dart';
|
|||||||
import 'package:pilipala/models/user/fav_folder.dart';
|
import 'package:pilipala/models/user/fav_folder.dart';
|
||||||
import 'package:pilipala/models/video/play/url.dart';
|
import 'package:pilipala/models/video/play/url.dart';
|
||||||
import 'package:pilipala/models/video_detail_res.dart';
|
import 'package:pilipala/models/video_detail_res.dart';
|
||||||
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
/// res.data['code'] == 0 请求正常返回结果
|
/// res.data['code'] == 0 请求正常返回结果
|
||||||
/// res.data['data'] 为结果
|
/// res.data['data'] 为结果
|
||||||
/// 返回{'status': bool, 'data': List}
|
/// 返回{'status': bool, 'data': List}
|
||||||
/// view层根据 status 判断渲染逻辑
|
/// view层根据 status 判断渲染逻辑
|
||||||
class VideoHttp {
|
class VideoHttp {
|
||||||
|
static Box user = GStrorage.user;
|
||||||
|
static Box setting = GStrorage.setting;
|
||||||
|
|
||||||
// 首页推荐视频
|
// 首页推荐视频
|
||||||
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
static Future rcmdVideoList({required int ps, required int freshIdx}) async {
|
||||||
try {
|
try {
|
||||||
@ -42,8 +48,7 @@ class VideoHttp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future rcmdVideoListApp(
|
static Future rcmdVideoListApp({int? ps, required int freshIdx}) async {
|
||||||
{required int ps, required int freshIdx}) async {
|
|
||||||
try {
|
try {
|
||||||
var res = await Request().get(
|
var res = await Request().get(
|
||||||
Api.recommendListApp,
|
Api.recommendListApp,
|
||||||
@ -55,12 +60,22 @@ class VideoHttp {
|
|||||||
'device_type': 0,
|
'device_type': 0,
|
||||||
'device_name': 'vivo',
|
'device_name': 'vivo',
|
||||||
'pull': freshIdx == 0 ? 'true' : 'false',
|
'pull': freshIdx == 0 ? 'true' : 'false',
|
||||||
|
'appkey': Constants.appKey,
|
||||||
|
'access_key':
|
||||||
|
user.get(UserBoxKey.accessKey, defaultValue: {})['value'] ?? ''
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
List<RecVideoItemAppModel> list = [];
|
List<RecVideoItemAppModel> list = [];
|
||||||
|
List<int> blackMidsList =
|
||||||
|
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
for (var i in res.data['data']['items']) {
|
for (var i in res.data['data']['items']) {
|
||||||
list.add(RecVideoItemAppModel.fromJson(i));
|
// 屏蔽推广和拉黑用户
|
||||||
|
if (i['card_goto'] != 'ad_av' &&
|
||||||
|
(i['args'] != null &&
|
||||||
|
!blackMidsList.contains(i['args']['up_mid']))) {
|
||||||
|
list.add(RecVideoItemAppModel.fromJson(i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {'status': true, 'data': list};
|
return {'status': true, 'data': list};
|
||||||
} else {
|
} else {
|
||||||
@ -80,8 +95,12 @@ class VideoHttp {
|
|||||||
);
|
);
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
List<HotVideoItemModel> list = [];
|
List<HotVideoItemModel> list = [];
|
||||||
|
List<int> blackMidsList =
|
||||||
|
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||||
for (var i in res.data['data']['list']) {
|
for (var i in res.data['data']['list']) {
|
||||||
list.add(HotVideoItemModel.fromJson(i));
|
if (!blackMidsList.contains(i['owner']['mid'])) {
|
||||||
|
list.add(HotVideoItemModel.fromJson(i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {'status': true, 'data': list};
|
return {'status': true, 'data': list};
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -4,8 +4,10 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/custom_toast.dart';
|
import 'package:pilipala/common/widgets/custom_toast.dart';
|
||||||
import 'package:pilipala/http/init.dart';
|
import 'package:pilipala/http/init.dart';
|
||||||
|
import 'package:pilipala/models/common/theme_type.dart';
|
||||||
import 'package:pilipala/pages/search/index.dart';
|
import 'package:pilipala/pages/search/index.dart';
|
||||||
import 'package:pilipala/pages/video/detail/index.dart';
|
import 'package:pilipala/pages/video/detail/index.dart';
|
||||||
import 'package:pilipala/router/app_pages.dart';
|
import 'package:pilipala/router/app_pages.dart';
|
||||||
@ -33,19 +35,38 @@ class MyApp extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
Color brandColor = const Color.fromARGB(255, 92, 182, 123);
|
||||||
|
Box setting = GStrorage.setting;
|
||||||
|
ThemeType currentThemeValue = ThemeType.values[setting
|
||||||
|
.get(SettingBoxKey.themeMode, defaultValue: ThemeType.system.code)];
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
builder: ((lightDynamic, darkDynamic) {
|
builder: ((ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
|
||||||
|
ColorScheme? lightColorScheme;
|
||||||
|
ColorScheme? darkColorScheme;
|
||||||
|
if (lightDynamic != null && darkDynamic != null) {
|
||||||
|
// dynamic取色成功
|
||||||
|
lightColorScheme = lightDynamic.harmonized();
|
||||||
|
darkColorScheme = darkDynamic.harmonized();
|
||||||
|
} else {
|
||||||
|
// dynamic取色失败,采用品牌色
|
||||||
|
lightColorScheme = ColorScheme.fromSeed(
|
||||||
|
seedColor: brandColor,
|
||||||
|
brightness: Brightness.light,
|
||||||
|
);
|
||||||
|
darkColorScheme = ColorScheme.fromSeed(
|
||||||
|
seedColor: brandColor,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
);
|
||||||
|
}
|
||||||
// 图片缓存
|
// 图片缓存
|
||||||
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
|
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
|
||||||
return GetMaterialApp(
|
return GetMaterialApp(
|
||||||
title: 'PiLiPaLa',
|
title: 'PiLiPaLa',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
fontFamily: 'HarmonyOS',
|
// fontFamily: 'HarmonyOS',
|
||||||
colorScheme: lightDynamic ??
|
colorScheme: currentThemeValue == ThemeType.dark
|
||||||
ColorScheme.fromSeed(
|
? darkColorScheme
|
||||||
seedColor: Colors.green,
|
: lightColorScheme,
|
||||||
brightness: Brightness.light,
|
|
||||||
),
|
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
pageTransitionsTheme: const PageTransitionsTheme(
|
pageTransitionsTheme: const PageTransitionsTheme(
|
||||||
builders: <TargetPlatform, PageTransitionsBuilder>{
|
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||||
@ -56,12 +77,10 @@ class MyApp extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
darkTheme: ThemeData(
|
darkTheme: ThemeData(
|
||||||
fontFamily: 'HarmonyOS',
|
// fontFamily: 'HarmonyOS',
|
||||||
colorScheme: darkDynamic ??
|
colorScheme: currentThemeValue == ThemeType.light
|
||||||
ColorScheme.fromSeed(
|
? lightColorScheme
|
||||||
seedColor: Colors.green,
|
: darkColorScheme,
|
||||||
brightness: Brightness.dark,
|
|
||||||
),
|
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
|
|||||||
90
lib/models/bangumi/list.dart
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
class BangumiListDataModel {
|
||||||
|
BangumiListDataModel({
|
||||||
|
this.hasNext,
|
||||||
|
this.list,
|
||||||
|
this.num,
|
||||||
|
this.size,
|
||||||
|
this.total,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? hasNext;
|
||||||
|
List? list;
|
||||||
|
int? num;
|
||||||
|
int? size;
|
||||||
|
int? total;
|
||||||
|
|
||||||
|
BangumiListDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
hasNext = json['has_next'];
|
||||||
|
list = json['list'] != null
|
||||||
|
? json['list']
|
||||||
|
.map<BangumiListItemModel>((e) => BangumiListItemModel.fromJson(e))
|
||||||
|
.toList()
|
||||||
|
: [];
|
||||||
|
num = json['num'];
|
||||||
|
size = json['size'];
|
||||||
|
total = json['total'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class BangumiListItemModel {
|
||||||
|
BangumiListItemModel({
|
||||||
|
this.badge,
|
||||||
|
this.badgeType,
|
||||||
|
this.cover,
|
||||||
|
// this.firstEp,
|
||||||
|
this.indexShow,
|
||||||
|
this.isFinish,
|
||||||
|
this.link,
|
||||||
|
this.mediaId,
|
||||||
|
this.order,
|
||||||
|
this.orderType,
|
||||||
|
this.score,
|
||||||
|
this.seasonId,
|
||||||
|
this.seaconStatus,
|
||||||
|
this.seasonType,
|
||||||
|
this.subTitle,
|
||||||
|
this.title,
|
||||||
|
this.titleIcon,
|
||||||
|
this.progress,
|
||||||
|
});
|
||||||
|
|
||||||
|
String? badge;
|
||||||
|
int? badgeType;
|
||||||
|
String? cover;
|
||||||
|
String? indexShow;
|
||||||
|
int? isFinish;
|
||||||
|
String? link;
|
||||||
|
int? mediaId;
|
||||||
|
String? order;
|
||||||
|
String? orderType;
|
||||||
|
String? score;
|
||||||
|
int? seasonId;
|
||||||
|
int? seaconStatus;
|
||||||
|
int? seasonType;
|
||||||
|
String? subTitle;
|
||||||
|
String? title;
|
||||||
|
String? titleIcon;
|
||||||
|
|
||||||
|
String? progress;
|
||||||
|
|
||||||
|
BangumiListItemModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
badge = json['badge'] == '' ? null : json['badge'];
|
||||||
|
badgeType = json['badge_type'];
|
||||||
|
cover = json['cover'];
|
||||||
|
indexShow = json['index_show'];
|
||||||
|
isFinish = json['is_finish'];
|
||||||
|
link = json['link'];
|
||||||
|
mediaId = json['media_id'];
|
||||||
|
order = json['order'];
|
||||||
|
orderType = json['order_type'];
|
||||||
|
score = json['score'];
|
||||||
|
seasonId = json['season_id'];
|
||||||
|
seaconStatus = json['seacon_status'];
|
||||||
|
seasonType = json['season_type'];
|
||||||
|
subTitle = json['sub_title'];
|
||||||
|
title = json['title'];
|
||||||
|
titleIcon = json['title_icon'];
|
||||||
|
|
||||||
|
progress = json['progress'];
|
||||||
|
}
|
||||||
|
}
|
||||||
55
lib/models/common/tab_type.dart
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/pages/bangumi/index.dart';
|
||||||
|
import 'package:pilipala/pages/hot/index.dart';
|
||||||
|
import 'package:pilipala/pages/live/index.dart';
|
||||||
|
import 'package:pilipala/pages/rcmd/index.dart';
|
||||||
|
|
||||||
|
enum TabType { live, rcmd, hot, bangumi }
|
||||||
|
|
||||||
|
extension TabTypeDesc on TabType {
|
||||||
|
String get description => ['直播', '推荐', '热门', '番剧'][index];
|
||||||
|
}
|
||||||
|
|
||||||
|
List tabsConfig = [
|
||||||
|
{
|
||||||
|
'icon': const Icon(
|
||||||
|
Icons.live_tv_outlined,
|
||||||
|
size: 15,
|
||||||
|
),
|
||||||
|
'label': '直播',
|
||||||
|
'type': TabType.live,
|
||||||
|
'ctr': Get.find<LiveController>,
|
||||||
|
'page': const LivePage(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(
|
||||||
|
Icons.thumb_up_off_alt_outlined,
|
||||||
|
size: 15,
|
||||||
|
),
|
||||||
|
'label': '推荐',
|
||||||
|
'type': TabType.rcmd,
|
||||||
|
'ctr': Get.find<RcmdController>,
|
||||||
|
'page': const RcmdPage(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(
|
||||||
|
Icons.whatshot_outlined,
|
||||||
|
size: 15,
|
||||||
|
),
|
||||||
|
'label': '热门',
|
||||||
|
'type': TabType.hot,
|
||||||
|
'ctr': Get.find<HotController>,
|
||||||
|
'page': const HotPage(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': const Icon(
|
||||||
|
Icons.play_circle_outlined,
|
||||||
|
size: 15,
|
||||||
|
),
|
||||||
|
'label': '番剧',
|
||||||
|
'type': TabType.bangumi,
|
||||||
|
'ctr': Get.find<BangumiController>,
|
||||||
|
'page': const BangumiPage(),
|
||||||
|
},
|
||||||
|
];
|
||||||