diff --git a/.github/workflows/beta_ci.yml b/.github/workflows/beta_ci.yml
new file mode 100644
index 00000000..e839aca1
--- /dev/null
+++ b/.github/workflows/beta_ci.yml
@@ -0,0 +1,208 @@
+name: Pilipala Beta
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - "main"
+ paths-ignore:
+ - "**.md"
+ - "**.txt"
+ - ".github/**"
+ - ".idea/**"
+ - "!.github/workflows/**"
+
+jobs:
+ update_version:
+ name: Read and update version
+ runs-on: ubuntu-latest
+
+ outputs:
+ # 定义输出变量 version,以便在其他job中引用
+ new_version: ${{ steps.version.outputs.new_version }}
+ last_commit: ${{ steps.get-last-commit.outputs.last_commit }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.ref_name }}
+ fetch-depth: 0
+
+ - name: 获取first parent commit次数
+ id: get-first-parent-commit-count
+ run: |
+ version=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
+ recent_release_tag=$(git tag -l | grep $version | egrep -v "[-|+]" || true)
+ if [[ "x$recent_release_tag" == "x" ]]; then
+ echo "当前版本tag不存在,请手动生成tag."
+ exit 1
+ fi
+ git log --oneline --first-parent $recent_release_tag..HEAD
+ first_parent_commit_count=$(git rev-list --first-parent --count $recent_release_tag..HEAD)
+ echo "count=$first_parent_commit_count" >> $GITHUB_OUTPUT
+
+ - name: 获取最后一次提交
+ id: get-last-commit
+ run: |
+ last_commit=$(git log -1 --pretty="%h %s" --first-parent)
+ echo "last_commit=$last_commit" >> $GITHUB_OUTPUT
+
+ - name: 更新版本号
+ id: version
+ run: |
+ # 读取版本号
+ VERSION=$(yq e .version pubspec.yaml | cut -d "+" -f 1)
+
+ # 获取GitHub Actions的run_number
+ #RUN_NUMBER=${{ github.run_number }}
+
+ # 构建新版本号
+ NEW_VERSION=$VERSION-beta.${{ steps.get-first-parent-commit-count.outputs.count }}
+
+ # 输出新版本号
+ echo "New version: $NEW_VERSION"
+
+ # 设置新版本号为输出变量
+ echo "new_version=$NEW_VERSION" >>$GITHUB_OUTPUT
+
+ android:
+ name: Build CI (Android)
+ needs: update_version
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.ref_name }}
+
+ - name: 构建Java环境
+ uses: actions/setup-java@v3
+ with:
+ distribution: "zulu"
+ java-version: "17"
+ token: ${{secrets.GIT_TOKEN}}
+
+ - name: 检查缓存
+ uses: actions/cache@v2
+ id: cache-flutter
+ with:
+ path: /root/flutter-sdk
+ key: ${{ runner.os }}-flutter-${{ hashFiles('**/pubspec.lock') }}
+
+ - name: 安装Flutter
+ if: steps.cache-flutter.outputs.cache-hit != 'true'
+ uses: subosito/flutter-action@v2
+ with:
+ flutter-version: 3.16.5
+ channel: any
+
+ - name: 下载项目依赖
+ run: flutter pub get
+
+ - name: 解码生成 jks
+ run: echo $KEYSTORE_BASE64 | base64 -di > android/app/vvex.jks
+ env:
+ KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
+
+ - name: 更新版本号
+ id: version
+ run: |
+ # 更新pubspec.yaml文件中的版本号
+ sed -i "s/version: .*+/version: ${{ needs.update_version.outputs.new_version }}+/g" pubspec.yaml
+
+ - name: flutter build apk
+ run: flutter build apk --release --split-per-abi
+ env:
+ KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
+ KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
+ KEY_PASSWORD: ${{ secrets.KEY_PASSWORD}}
+
+ - name: 重命名应用
+ run: |
+ for file in build/app/outputs/flutter-apk/app-*.apk; do
+ if [[ $file =~ app-(.?*)release.apk ]]; then
+ new_file_name="build/app/outputs/flutter-apk/Pili-${BASH_REMATCH[1]}v${{ needs.update_version.outputs.new_version }}.apk"
+ mv "$file" "$new_file_name"
+ fi
+ done
+
+ - name: 上传
+ uses: actions/upload-artifact@v3
+ with:
+ name: Pilipala-Beta
+ path: |
+ build/app/outputs/flutter-apk/Pili-*.apk
+
+ iOS:
+ name: Build CI (iOS)
+ needs: update_version
+ runs-on: macos-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+ with:
+ ref: ${{ github.ref_name }}
+
+ - name: 安装Flutter
+ if: steps.cache-flutter.outputs.cache-hit != 'true'
+ uses: subosito/flutter-action@v2.10.0
+ with:
+ cache: true
+ flutter-version: 3.16.5
+
+ - name: 更新版本号
+ id: version
+ run: |
+ # 更新pubspec.yaml文件中的版本号
+ sed -i "" "s/version: .*+/version: ${{ needs.update_version.outputs.new_version }}+/g" pubspec.yaml
+
+ - name: flutter build ipa
+ run: |
+ flutter build ios --release --no-codesign
+ ln -sf ./build/ios/iphoneos Payload
+ zip -r9 app.ipa Payload/runner.app
+
+ - name: 重命名应用
+ run: |
+ DATE=${{ steps.date.outputs.date }}
+ for file in app.ipa; do
+ new_file_name="build/Pili-v${{ needs.update_version.outputs.new_version }}.ipa"
+ mv "$file" "$new_file_name"
+ done
+
+ - name: 上传
+ uses: actions/upload-artifact@v3
+ with:
+ if-no-files-found: error
+ name: Pilipala-Beta
+ path: |
+ build/Pili-*.ipa
+
+ upload:
+ runs-on: ubuntu-latest
+
+ needs:
+ - update_version
+ - android
+ - iOS
+ steps:
+ - uses: actions/download-artifact@v3
+ with:
+ name: Pilipala-Beta
+ path: ./Pilipala-Beta
+
+ - name: 发送到Telegram频道
+ uses: xireiki/channel-post@v1.0.7
+ with:
+ bot_token: ${{ secrets.BOT_TOKEN }}
+ chat_id: ${{ secrets.CHAT_ID }}
+ large_file: true
+ api_id: ${{ secrets.TELEGRAM_API_ID }}
+ api_hash: ${{ secrets.TELEGRAM_API_HASH }}
+ method: sendFile
+ path: Pilipala-Beta/*
+ parse_mode: Markdown
+ context: "*Beta版本: v${{ needs.update_version.outputs.new_version }}*\n更新内容: [${{ needs.update_version.outputs.last_commit }}](${{ github.event.head_commit.url }})"
diff --git a/.github/workflows/main.yml b/.github/workflows/release_ci.yml
similarity index 100%
rename from .github/workflows/main.yml
rename to .github/workflows/release_ci.yml
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index d82845fe..c52d8447 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -223,6 +223,10 @@
android:pathPattern="/mobile/video/.*" />
+
+
diff --git a/assets/images/video/danmu_close.svg b/assets/images/video/danmu_close.svg
new file mode 100644
index 00000000..9f48027b
--- /dev/null
+++ b/assets/images/video/danmu_close.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/images/video/danmu_open.svg b/assets/images/video/danmu_open.svg
new file mode 100644
index 00000000..24e8d7a9
--- /dev/null
+++ b/assets/images/video/danmu_open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/change_log/1.0.20.0303.md b/change_log/1.0.20.0303.md
new file mode 100644
index 00000000..1d8c4e00
--- /dev/null
+++ b/change_log/1.0.20.0303.md
@@ -0,0 +1,31 @@
+## 1.0.20
+
+
+### 功能
++ 评论区增加表情
++ 首页渐变背景开关
++ 媒体库显示「我的订阅」
++ 评论区链接解析
++ 默认启动页设置
+
+### 修复
++ 评论区内容重复
++ pip相关问题
++ 播放多p视频评论不刷新
++ 视频评论翻页重复
+
+### 优化
++ url scheme优化
++ 图片预览放大
++ 图片加载速度
++ 视频评论区复制
++ 全屏显示视频标题
++ 网络异常处理
+
+
+
+
+
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/change_log/1.0.21.0306.md b/change_log/1.0.21.0306.md
new file mode 100644
index 00000000..3a582dbb
--- /dev/null
+++ b/change_log/1.0.21.0306.md
@@ -0,0 +1,9 @@
+## 1.0.21
+
+### 修复
++ 推荐视频全屏问题
++ 番剧全屏播放时灰屏问题
++ 评论回调导致页面卡死问题
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart
index c78643db..99059a9e 100644
--- a/lib/common/widgets/video_card_h.dart
+++ b/lib/common/widgets/video_card_h.dart
@@ -38,6 +38,10 @@ class VideoCardH extends StatelessWidget {
Widget build(BuildContext context) {
final int aid = videoItem.aid;
final String bvid = videoItem.bvid;
+ String type = 'video';
+ try {
+ type = videoItem.type;
+ } catch (_) {}
final String heroTag = Utils.makeHeroTag(aid);
return GestureDetector(
onLongPress: () {
@@ -53,6 +57,10 @@ class VideoCardH extends StatelessWidget {
child: InkWell(
onTap: () async {
try {
+ if (type == 'ketang') {
+ SmartDialog.showToast('课堂视频暂不支持播放');
+ return;
+ }
final int cid =
videoItem.cid ?? await SearchHttp.ab2c(aid: aid, bvid: bvid);
Get.toNamed('/video?bvid=$bvid&cid=$cid',
@@ -95,12 +103,20 @@ class VideoCardH extends StatelessWidget {
height: maxHeight,
),
),
- PBadge(
- text: Utils.timeFormat(videoItem.duration!),
- right: 6.0,
- bottom: 6.0,
- type: 'gray',
- ),
+ if (videoItem.duration != 0)
+ PBadge(
+ text: Utils.timeFormat(videoItem.duration!),
+ right: 6.0,
+ bottom: 6.0,
+ type: 'gray',
+ ),
+ if (type != 'video')
+ PBadge(
+ text: type,
+ left: 6.0,
+ bottom: 6.0,
+ type: 'primary',
+ ),
// if (videoItem.rcmdReason != null &&
// videoItem.rcmdReason.content != '')
// pBadge(videoItem.rcmdReason.content, context,
diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart
index 43dd05ca..0d96f7b7 100644
--- a/lib/common/widgets/video_card_v.dart
+++ b/lib/common/widgets/video_card_v.dart
@@ -231,6 +231,7 @@ class VideoContent extends StatelessWidget {
const SizedBox(height: 2),
VideoStat(
videoItem: videoItem,
+ crossAxisCount: crossAxisCount,
),
],
if (crossAxisCount == 1) const SizedBox(height: 4),
@@ -294,6 +295,7 @@ class VideoContent extends StatelessWidget {
),
VideoStat(
videoItem: videoItem,
+ crossAxisCount: crossAxisCount,
),
const Spacer(),
],
@@ -317,10 +319,12 @@ class VideoContent extends StatelessWidget {
class VideoStat extends StatelessWidget {
final dynamic videoItem;
+ final int crossAxisCount;
const VideoStat({
Key? key,
required this.videoItem,
+ required this.crossAxisCount,
}) : super(key: key);
@override
@@ -337,7 +341,7 @@ class VideoStat extends StatelessWidget {
danmu: videoItem.stat.danmu,
),
if (videoItem is RecVideoItemModel) ...[
- const Spacer(),
+ crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
RichText(
maxLines: 1,
text: TextSpan(
diff --git a/lib/http/api.dart b/lib/http/api.dart
index 3a23ef80..445f6102 100644
--- a/lib/http/api.dart
+++ b/lib/http/api.dart
@@ -484,6 +484,9 @@ class Api {
/// 激活buvid3
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
+ /// 获取字幕配置
+ static const getSubtitleConfig = '/x/player/v2';
+
/// 我的订阅
static const userSubFolder = '/x/v3/fav/folder/collected/list';
@@ -499,4 +502,8 @@ class Api {
/// 发送私信
static const String sendMsg = '${HttpString.tUrl}/web_im/v1/web_im/send_msg';
+
+ /// 排行榜
+ static const String getRankApi = "/x/web-interface/ranking/v2";
+
}
diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart
index 362ff17a..a5359283 100644
--- a/lib/http/interceptor.dart
+++ b/lib/http/interceptor.dart
@@ -45,10 +45,13 @@ class ApiInterceptor extends Interceptor {
void onError(DioException err, ErrorInterceptorHandler handler) async {
// 处理网络请求错误
// handler.next(err);
- SmartDialog.showToast(
- await dioError(err),
- displayType: SmartToastType.onlyRefresh,
- );
+ String url = err.requestOptions.uri.toString();
+ if (!url.contains('heartBeat')) {
+ SmartDialog.showToast(
+ await dioError(err),
+ displayType: SmartToastType.onlyRefresh,
+ );
+ }
super.onError(err, handler);
}
@@ -75,23 +78,24 @@ class ApiInterceptor extends Interceptor {
}
static Future checkConnect() async {
- final ConnectivityResult connectivityResult =
+ final List connectivityResult =
await Connectivity().checkConnectivity();
- switch (connectivityResult) {
- case ConnectivityResult.mobile:
- return '正在使用移动流量';
- case ConnectivityResult.wifi:
- return '正在使用wifi';
- case ConnectivityResult.ethernet:
- return '正在使用局域网';
- case ConnectivityResult.vpn:
- return '正在使用代理网络';
- case ConnectivityResult.other:
- return '正在使用其他网络';
- case ConnectivityResult.none:
- return '未连接到任何网络';
- default:
- return '';
+ if (connectivityResult.contains(ConnectivityResult.mobile)) {
+ return '正在使用移动流量';
+ } else if (connectivityResult.contains(ConnectivityResult.wifi)) {
+ return '正在使用wifi';
+ } else if (connectivityResult.contains(ConnectivityResult.ethernet)) {
+ return '正在使用局域网';
+ } else if (connectivityResult.contains(ConnectivityResult.vpn)) {
+ return '正在使用代理网络';
+ } else if (connectivityResult.contains(ConnectivityResult.bluetooth)) {
+ return '正在使用蓝牙网络';
+ } else if (connectivityResult.contains(ConnectivityResult.other)) {
+ return '正在使用其他网络';
+ } else if (connectivityResult.contains(ConnectivityResult.none)) {
+ return '未连接到任何网络';
+ } else {
+ return '';
}
}
}
diff --git a/lib/http/msg.dart b/lib/http/msg.dart
index 4ba2f818..d1d31958 100644
--- a/lib/http/msg.dart
+++ b/lib/http/msg.dart
@@ -31,14 +31,14 @@ class MsgHttp {
} catch (err) {
return {
'status': false,
- 'date': [],
+ 'data': [],
'msg': err.toString(),
};
}
} else {
return {
'status': false,
- 'date': [],
+ 'data': [],
'msg': res.data['message'],
};
}
diff --git a/lib/http/video.dart b/lib/http/video.dart
index 30df62c3..d43656b2 100644
--- a/lib/http/video.dart
+++ b/lib/http/video.dart
@@ -8,9 +8,11 @@ import '../models/model_rec_video_item.dart';
import '../models/user/fav_folder.dart';
import '../models/video/ai.dart';
import '../models/video/play/url.dart';
+import '../models/video/subTitile/result.dart';
import '../models/video_detail_res.dart';
import '../utils/recommend_filter.dart';
import '../utils/storage.dart';
+import '../utils/subtitle.dart';
import '../utils/wbi_sign.dart';
import 'api.dart';
import 'init.dart';
@@ -475,4 +477,54 @@ class VideoHttp {
return {'status': false, 'data': []};
}
}
+
+ static Future getSubtitle({int? cid, String? bvid}) async {
+ var res = await Request().get(Api.getSubtitleConfig, data: {
+ 'cid': cid,
+ 'bvid': bvid,
+ });
+ try {
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': SubTitlteModel.fromJson(res.data['data']),
+ };
+ } else {
+ return {'status': false, 'data': [], 'msg': res.data['msg']};
+ }
+ } catch (err) {
+ print(err);
+ }
+ }
+
+ // 视频排行
+ static Future getRankVideoList(int rid) async {
+ try {
+ var rankApi = "${Api.getRankApi}?rid=$rid&type=all";
+ var res = await Request().get(rankApi);
+ if (res.data['code'] == 0) {
+ List list = [];
+ List blackMidsList =
+ setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
+ for (var i in res.data['data']['list']) {
+ if (!blackMidsList.contains(i['owner']['mid'])) {
+ list.add(HotVideoItemModel.fromJson(i));
+ }
+ }
+ return {'status': true, 'data': list};
+ } else {
+ return {'status': false, 'data': [], 'msg': res.data['message']};
+ }
+ } catch (err) {
+ return {'status': false, 'data': [], 'msg': err};
+ }
+ }
+
+ // 获取字幕内容
+ static Future