diff --git a/.github/workflows/beta_ci.yml b/.github/workflows/beta_ci.yml
index 14b51780..9c40de6b 100644
--- a/.github/workflows/beta_ci.yml
+++ b/.github/workflows/beta_ci.yml
@@ -12,7 +12,6 @@ on:
- ".idea/**"
- "!.github/workflows/**"
-
jobs:
update_version:
name: Read and update version
@@ -96,7 +95,7 @@ jobs:
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2
with:
- flutter-version: 3.16.5
+ flutter-version: 3.19.6
channel: any
- name: 下载项目依赖
diff --git a/.github/workflows/release_ci.yml b/.github/workflows/release_ci.yml
index 78230645..f7c06d29 100644
--- a/.github/workflows/release_ci.yml
+++ b/.github/workflows/release_ci.yml
@@ -36,7 +36,7 @@ jobs:
if: steps.cache-flutter.outputs.cache-hit != 'true'
uses: subosito/flutter-action@v2
with:
- flutter-version: 3.16.5
+ flutter-version: 3.19.6
channel: any
- name: 下载项目依赖
@@ -98,7 +98,7 @@ jobs:
uses: subosito/flutter-action@v2.10.0
with:
cache: true
- flutter-version: 3.16.5
+ flutter-version: 3.19.6
- name: flutter build ipa
run: |
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index f119eb1e..46b34c20 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -12,7 +12,6 @@
-
@@ -20,7 +19,6 @@
"android.support.customtabs.action.CustomTabsService" />
-
@@ -34,7 +32,6 @@
-
+
+
+
+
+
+
+
+
+
@@ -132,102 +141,55 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
\ No newline at end of file
diff --git a/assets/images/pay/alipay.jpg b/assets/images/pay/alipay.jpg
new file mode 100644
index 00000000..1c1fc4c6
Binary files /dev/null and b/assets/images/pay/alipay.jpg differ
diff --git a/assets/images/pay/wechat.png b/assets/images/pay/wechat.png
new file mode 100644
index 00000000..3aa3a6a2
Binary files /dev/null and b/assets/images/pay/wechat.png differ
diff --git a/assets/images/video/dlna.png b/assets/images/video/dlna.png
new file mode 100755
index 00000000..a5d65872
Binary files /dev/null and b/assets/images/video/dlna.png differ
diff --git a/assets/images/video/fullscreen.png b/assets/images/video/fullscreen.png
new file mode 100755
index 00000000..449f1425
Binary files /dev/null and b/assets/images/video/fullscreen.png differ
diff --git a/assets/images/video/fullscreen_exit.png b/assets/images/video/fullscreen_exit.png
new file mode 100755
index 00000000..9881ed1d
Binary files /dev/null and b/assets/images/video/fullscreen_exit.png differ
diff --git a/assets/images/video/pip.png b/assets/images/video/pip.png
new file mode 100755
index 00000000..1b742125
Binary files /dev/null and b/assets/images/video/pip.png differ
diff --git a/change_log/1.0.25.1010.md b/change_log/1.0.25.1010.md
new file mode 100644
index 00000000..951efcb1
--- /dev/null
+++ b/change_log/1.0.25.1010.md
@@ -0,0 +1,39 @@
+## 1.0.25
+
+### 功能
++ 直播弹幕
++ 稍后再看、收藏夹播放全部
++ 收藏夹新建、编辑
++ 评论删除
++ 评论保存为图片
++ 动态页滑动切换up
++ up投稿筛选充电视频
++ 直播tab展示关注up
++ up主页专栏展示
+
+### 优化
++ 视频详情页一键三连
++ 动态页标识充电视频
++ 播放器亮度、音量调整百分比展示
++ 封面预览时视频标题可复制
++ 竖屏直播布局
++ 图片预览
++ 专栏渲染优化
++ 私信图片查看
+
+### 修复
++ 收藏夹点击异常
++ 搜索up异常
++ 系统通知已读异常
++ [赞了我的]展示错误
++ 部分up合集无法打开
++ 切换合集视频投币个数未重置
++ 搜索条件筛选面板无法滚动
++ 部分机型导航条未沉浸
++ 专栏图片渲染问题
++ 专栏浏览历史记录
++ 直播间历史记录
+
+
+更多更新日志可在Github上查看
+问题反馈、功能建议请查看「关于」页面。
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index a400600f..27baf9e5 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1,5 +1,5 @@
PODS:
- - appscheme (1.0.4):
+ - app_links (0.0.2):
- Flutter
- audio_service (0.0.1):
- Flutter
@@ -27,6 +27,8 @@ PODS:
- Flutter
- GT3Captcha-iOS
- GT3Captcha-iOS (0.15.8.3)
+ - image_picker_ios (0.0.1):
+ - Flutter
- media_kit_libs_ios_video (1.0.4):
- Flutter
- media_kit_native_event_loop (1.0.0):
@@ -66,7 +68,7 @@ PODS:
- Flutter
DEPENDENCIES:
- - appscheme (from `.symlinks/plugins/appscheme/ios`)
+ - app_links (from `.symlinks/plugins/app_links/ios`)
- audio_service (from `.symlinks/plugins/audio_service/ios`)
- audio_session (from `.symlinks/plugins/audio_session/ios`)
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
@@ -77,6 +79,7 @@ DEPENDENCIES:
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
+ - image_picker_ios (from `.symlinks/plugins/image_picker_ios/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_video (from `.symlinks/plugins/media_kit_video/ios`)
@@ -102,8 +105,8 @@ SPEC REPOS:
- Toast
EXTERNAL SOURCES:
- appscheme:
- :path: ".symlinks/plugins/appscheme/ios"
+ app_links:
+ :path: ".symlinks/plugins/app_links/ios"
audio_service:
:path: ".symlinks/plugins/audio_service/ios"
audio_session:
@@ -124,6 +127,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluttertoast/ios"
gt3_flutter_plugin:
:path: ".symlinks/plugins/gt3_flutter_plugin/ios"
+ image_picker_ios:
+ :path: ".symlinks/plugins/image_picker_ios/ios"
media_kit_libs_ios_video:
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
media_kit_native_event_loop:
@@ -160,7 +165,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS:
- appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
+ app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
audio_service: f509d65da41b9521a61f1c404dd58651f265a567
audio_session: 4f3e461722055d21515cf3261b64c973c062f345
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
@@ -173,6 +178,7 @@ SPEC CHECKSUMS:
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
+ image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 1e7d9fed..24dceb17 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -65,44 +65,29 @@
CFBundleURLName
-
+ bilibili
CFBundleURLSchemes
http
https
-
- CFBundleURLTypes
-
-
- CFBundleURLName
-
- CFBundleURLSchemes
-
- m.bilibili.com
- bilibili.com
- www.bilibili.com
- bangumi.bilibili.com
- bilibili.cn
- www.bilibili.cn
- bangumi.bilibili.cn
- bilibili.tv
- www.bilibili.tv
- bangumi.bilibili.tv
- miniapp.bilibili.com
- live.bilibili.com
-
-
-
-
-
-
-
- CFBundleURLName
- bilibili
- CFBundleURLSchemes
-
bilibili
+ m.bilibili.com
+ bilibili.com
+ www.bilibili.com
+ bangumi.bilibili.com
+ bilibili.cn
+ www.bilibili.cn
+ bangumi.bilibili.cn
+ bilibili.tv
+ www.bilibili.tv
+ bangumi.bilibili.tv
+ miniapp.bilibili.com
+ live.bilibili.com
+ pili
+ pilipala
+ FlutterDeepLinkingEnabled
+
UIBackgroundModes
diff --git a/lib/common/constants.dart b/lib/common/constants.dart
index cac13688..0607206c 100644
--- a/lib/common/constants.dart
+++ b/lib/common/constants.dart
@@ -15,6 +15,4 @@ class Constants {
// 59b43e04ad6965f34319062b478f83dd TV端
static const String appSec = '59b43e04ad6965f34319062b478f83dd';
static const String thirdSign = '04224646d1fea004e79606d3b038c84a';
- static const String thirdApi =
- 'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png';
}
diff --git a/lib/common/pages_bottom_sheet.dart b/lib/common/pages_bottom_sheet.dart
index 49e9b4d8..49300949 100644
--- a/lib/common/pages_bottom_sheet.dart
+++ b/lib/common/pages_bottom_sheet.dart
@@ -1,35 +1,466 @@
import 'package:flutter/material.dart';
+import 'package:flutter/widgets.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:get/get.dart';
+import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
-import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
+import 'package:pilipala/http/video.dart';
+import 'package:pilipala/models/video_detail_res.dart';
+import 'package:pilipala/pages/video/detail/index.dart';
+import 'package:pilipala/utils/utils.dart';
+import 'package:scrollview_observer/scrollview_observer.dart';
import '../models/common/video_episode_type.dart';
+import 'widgets/badge.dart';
+import 'widgets/stat/danmu.dart';
+import 'widgets/stat/view.dart';
class EpisodeBottomSheet {
final List episodes;
final int currentCid;
final dynamic dataType;
- final BuildContext context;
final Function changeFucCall;
final int? cid;
final double? sheetHeight;
bool isFullScreen = false;
+ final UgcSeason? ugcSeason;
+ final int? currentEpisodeIndex;
+ final int? currentIndex;
EpisodeBottomSheet({
required this.episodes,
required this.currentCid,
required this.dataType,
- required this.context,
required this.changeFucCall,
this.cid,
this.sheetHeight,
this.isFullScreen = false,
+ this.ugcSeason,
+ this.currentEpisodeIndex,
+ this.currentIndex,
});
- Widget buildEpisodeListItem(
- dynamic episode,
- int index,
- bool isCurrentIndex,
- ) {
+ Widget buildShowContent() {
+ return PagesBottomSheet(
+ episodes: episodes,
+ currentCid: currentCid,
+ dataType: dataType,
+ changeFucCall: changeFucCall,
+ cid: cid,
+ sheetHeight: sheetHeight,
+ isFullScreen: isFullScreen,
+ ugcSeason: ugcSeason,
+ currentEpisodeIndex: currentEpisodeIndex,
+ currentIndex: currentIndex,
+ );
+ }
+
+ PersistentBottomSheetController show(BuildContext context) {
+ final PersistentBottomSheetController btmSheetCtr = showBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return buildShowContent();
+ },
+ );
+ return btmSheetCtr;
+ }
+}
+
+class PagesBottomSheet extends StatefulWidget {
+ const PagesBottomSheet({
+ super.key,
+ required this.episodes,
+ required this.currentCid,
+ required this.dataType,
+ required this.changeFucCall,
+ this.cid,
+ this.sheetHeight,
+ this.isFullScreen = false,
+ this.ugcSeason,
+ this.currentEpisodeIndex,
+ this.currentIndex,
+ });
+
+ final List episodes;
+ final int currentCid;
+ final dynamic dataType;
+ final Function changeFucCall;
+ final int? cid;
+ final double? sheetHeight;
+ final bool isFullScreen;
+ final UgcSeason? ugcSeason;
+ final int? currentEpisodeIndex;
+ final int? currentIndex;
+
+ @override
+ State createState() => _PagesBottomSheetState();
+}
+
+class _PagesBottomSheetState extends State
+ with TickerProviderStateMixin {
+ final ScrollController _listScrollController = ScrollController();
+ late ListObserverController _listObserverController;
+ late GridObserverController _gridObserverController;
+ final ScrollController _gridScrollController = ScrollController();
+ late int currentIndex;
+ TabController? tabController;
+ List? _listObserverControllerList;
+ List? _listScrollControllerList;
+ final String heroTag = Get.arguments['heroTag'];
+ VideoDetailController? _videoDetailController;
+ RxInt isSubscribe = (-1).obs;
+ bool isVisible = false;
+
+ @override
+ void initState() {
+ super.initState();
+ currentIndex = widget.currentIndex ??
+ widget.episodes.indexWhere((dynamic e) => e.cid == widget.currentCid);
+ _scrollToInit();
+ _scrollPositionInit();
+ if (widget.dataType == VideoEpidoesType.videoEpisode) {
+ _videoDetailController = Get.find(tag: heroTag);
+ _getSubscribeStatus();
+ }
+ }
+
+ String prefix() {
+ switch (widget.dataType) {
+ case VideoEpidoesType.videoEpisode:
+ return '选集';
+ case VideoEpidoesType.videoPart:
+ return '分集';
+ case VideoEpidoesType.bangumiEpisode:
+ return '选集';
+ }
+ return '选集';
+ }
+
+ // 滚动器初始化
+ void _scrollToInit() {
+ /// 单个
+ _listObserverController =
+ ListObserverController(controller: _listScrollController);
+
+ if (widget.dataType == VideoEpidoesType.videoEpisode &&
+ widget.ugcSeason?.sections != null &&
+ widget.ugcSeason!.sections!.length > 1) {
+ tabController = TabController(
+ length: widget.ugcSeason!.sections!.length,
+ vsync: this,
+ initialIndex: widget.currentEpisodeIndex ?? 0,
+ );
+
+ /// 多tab
+ _listScrollControllerList = List.generate(
+ widget.ugcSeason!.sections!.length,
+ (index) {
+ return ScrollController();
+ },
+ );
+ _listObserverControllerList = List.generate(
+ widget.ugcSeason!.sections!.length,
+ (index) {
+ return ListObserverController(
+ controller: _listScrollControllerList![index],
+ );
+ },
+ );
+ } else {
+ _gridObserverController =
+ GridObserverController(controller: _gridScrollController);
+ }
+ }
+
+ // 滚动器位置初始化
+ void _scrollPositionInit() {
+ if (widget.dataType == VideoEpidoesType.videoEpisode) {
+ // 单个 多tab
+ if (widget.ugcSeason?.sections != null) {
+ if (widget.ugcSeason!.sections!.length == 1) {
+ _listObserverController.initialIndexModel =
+ ObserverIndexPositionModel(
+ index: currentIndex,
+ isFixedHeight: true,
+ );
+ } else {
+ _listObserverControllerList![widget.currentEpisodeIndex!]
+ .initialIndexModel = ObserverIndexPositionModel(
+ index: currentIndex,
+ isFixedHeight: true,
+ );
+ }
+ }
+ } else {
+ _gridObserverController.initialIndexModel = ObserverIndexPositionModel(
+ index: currentIndex,
+ isFixedHeight: false,
+ );
+ }
+ }
+
+ // 获取订阅状态
+ void _getSubscribeStatus() async {
+ var res =
+ await VideoHttp.getSubscribeStatus(bvid: _videoDetailController!.bvid);
+ if (res['status']) {
+ isSubscribe.value = res['data']['season_fav'] ? 1 : 0;
+ }
+ }
+
+ // 更改订阅状态
+ void _changeSubscribeStatus() async {
+ if (isSubscribe.value == -1) {
+ return;
+ }
+ dynamic result = await VideoHttp.seasonFav(
+ isFav: isSubscribe.value == 1,
+ seasonId: widget.ugcSeason!.id,
+ );
+ if (result['status']) {
+ SmartDialog.showToast(isSubscribe.value == 1 ? '取消订阅成功' : '订阅成功');
+ isSubscribe.value = isSubscribe.value == 1 ? 0 : 1;
+ } else {
+ SmartDialog.showToast(result['msg']);
+ }
+ }
+
+ // 更改展开状态
+ void _changeVisible() {
+ setState(() {
+ isVisible = !isVisible;
+ });
+ }
+
+ @override
+ void dispose() {
+ try {
+ _listObserverController.controller?.dispose();
+ _gridObserverController.controller?.dispose();
+ _listScrollController.dispose();
+ _gridScrollController.dispose();
+ for (var element in _listObserverControllerList!) {
+ element.controller?.dispose();
+ }
+ for (var element in _listScrollControllerList!) {
+ element.dispose();
+ }
+ } catch (_) {}
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return StatefulBuilder(
+ builder: (BuildContext context, StateSetter setState) {
+ return SizedBox(
+ height: widget.sheetHeight,
+ child: Column(
+ children: [
+ TitleBar(
+ title: '${prefix()}(${widget.episodes.length})',
+ isFullScreen: widget.isFullScreen,
+ ),
+ if (widget.ugcSeason != null) ...[
+ UgcSeasonBuild(
+ ugcSeason: widget.ugcSeason!,
+ isSubscribe: isSubscribe,
+ isVisible: isVisible,
+ changeFucCall: _changeSubscribeStatus,
+ changeVisible: _changeVisible,
+ ),
+ ],
+ Expanded(
+ child: Material(
+ child: widget.dataType == VideoEpidoesType.videoEpisode
+ ? (widget.ugcSeason!.sections!.length == 1
+ ? ListViewObserver(
+ controller: _listObserverController,
+ child: ListView.builder(
+ controller: _listScrollController,
+ itemCount: widget.episodes.length + 1,
+ itemBuilder: (BuildContext context, int index) {
+ bool isLastItem =
+ index == widget.episodes.length;
+ bool isCurrentIndex = currentIndex == index;
+ return isLastItem
+ ? SizedBox(
+ height: MediaQuery.of(context)
+ .padding
+ .bottom +
+ 20,
+ )
+ : EpisodeListItem(
+ episode: widget.episodes[index],
+ index: index,
+ isCurrentIndex: isCurrentIndex,
+ dataType: widget.dataType,
+ changeFucCall: widget.changeFucCall,
+ isFullScreen: widget.isFullScreen,
+ );
+ },
+ ),
+ )
+ : buildTabBar())
+ : Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12.0), // 设置左右间距为12
+ child: GridViewObserver(
+ controller: _gridObserverController,
+ child: GridView.count(
+ controller: _gridScrollController,
+ crossAxisCount: 2,
+ crossAxisSpacing: StyleString.safeSpace,
+ childAspectRatio: 2.6,
+ children: List.generate(
+ widget.episodes.length,
+ (index) {
+ bool isCurrentIndex = currentIndex == index;
+ return EpisodeGridItem(
+ episode: widget.episodes[index],
+ index: index,
+ isCurrentIndex: isCurrentIndex,
+ dataType: widget.dataType,
+ changeFucCall: widget.changeFucCall,
+ isFullScreen: widget.isFullScreen,
+ );
+ },
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ });
+ }
+
+ Widget buildTabBar() {
+ return Column(
+ children: [
+ // 背景色
+ Container(
+ color: Theme.of(context).colorScheme.surface,
+ child: TabBar(
+ controller: tabController,
+ isScrollable: true,
+ indicatorSize: TabBarIndicatorSize.label,
+ tabAlignment: TabAlignment.start,
+ splashBorderRadius: BorderRadius.circular(4),
+ tabs: [
+ ...widget.ugcSeason!.sections!.map((SectionItem section) {
+ return Tab(
+ text: section.title,
+ );
+ }).toList()
+ ],
+ ),
+ ),
+ Expanded(
+ child: TabBarView(
+ controller: tabController,
+ children: [
+ ...widget.ugcSeason!.sections!.map((SectionItem section) {
+ final int fIndex = widget.ugcSeason!.sections!.indexOf(section);
+ return ListViewObserver(
+ controller: _listObserverControllerList![fIndex],
+ child: ListView.builder(
+ controller: _listScrollControllerList![fIndex],
+ itemCount: section.episodes!.length + 1,
+ itemBuilder: (BuildContext context, int index) {
+ final bool isLastItem = index == section.episodes!.length;
+ return isLastItem
+ ? SizedBox(
+ height:
+ MediaQuery.of(context).padding.bottom + 20,
+ )
+ : EpisodeListItem(
+ episode: section.episodes![index], // 调整索引
+ index: index, // 调整索引
+ isCurrentIndex: widget.currentCid ==
+ section.episodes![index].cid,
+ dataType: widget.dataType,
+ changeFucCall: widget.changeFucCall,
+ isFullScreen: widget.isFullScreen,
+ );
+ },
+ ),
+ );
+ }).toList()
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+class TitleBar extends StatelessWidget {
+ final String title;
+ final bool isFullScreen;
+
+ const TitleBar({
+ Key? key,
+ required this.title,
+ required this.isFullScreen,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return AppBar(
+ toolbarHeight: 45,
+ automaticallyImplyLeading: false,
+ centerTitle: false,
+ elevation: 1,
+ scrolledUnderElevation: 1,
+ title: Padding(
+ padding: const EdgeInsets.only(left: 12),
+ child: Text(
+ title,
+ style: Theme.of(context).textTheme.titleMedium,
+ ),
+ ),
+ actions: !isFullScreen
+ ? [
+ SizedBox(
+ width: 35,
+ height: 35,
+ child: IconButton(
+ icon: const Icon(Icons.close, size: 20),
+ style: ButtonStyle(
+ padding: MaterialStateProperty.all(EdgeInsets.zero),
+ ),
+ onPressed: () => Navigator.pop(context),
+ ),
+ ),
+ const SizedBox(width: 8),
+ ]
+ : null,
+ );
+ }
+}
+
+class EpisodeListItem extends StatelessWidget {
+ final dynamic episode;
+ final int index;
+ final bool isCurrentIndex;
+ final dynamic dataType;
+ final Function changeFucCall;
+ final bool isFullScreen;
+
+ const EpisodeListItem({
+ Key? key,
+ required this.episode,
+ required this.index,
+ required this.isCurrentIndex,
+ required this.dataType,
+ required this.changeFucCall,
+ required this.isFullScreen,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
Color primary = Theme.of(context).colorScheme.primary;
Color onSurface = Theme.of(context).colorScheme.onSurface;
@@ -45,128 +476,365 @@ class EpisodeBottomSheet {
title = '第${episode.title}话 ${episode.longTitle!}';
break;
}
+
return isFullScreen || episode?.cover == null || episode?.cover == ''
- ? ListTile(
- onTap: () {
- SmartDialog.showToast('切换至「$title」');
- changeFucCall.call(episode, index);
- },
- dense: false,
- leading: isCurrentIndex
- ? Image.asset(
- 'assets/images/live.gif',
- color: primary,
- height: 12,
+ ? _buildListTile(context, title, primary, onSurface)
+ : _buildInkWell(context, title, primary, onSurface);
+ }
+
+ Widget _buildListTile(
+ BuildContext context, String title, Color primary, Color onSurface) {
+ return ListTile(
+ onTap: () {
+ if (isCurrentIndex) {
+ return;
+ }
+ SmartDialog.showToast('切换至「$title」');
+ changeFucCall.call(episode, index);
+ },
+ dense: false,
+ leading: isCurrentIndex
+ ? Image.asset(
+ 'assets/images/live.png',
+ color: primary,
+ height: 12,
+ )
+ : null,
+ title: Text(
+ title,
+ style: TextStyle(
+ fontSize: 14,
+ color: isCurrentIndex ? primary : onSurface,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildInkWell(
+ BuildContext context, String title, Color primary, Color onSurface) {
+ return InkWell(
+ onTap: () {
+ if (isCurrentIndex) {
+ return;
+ }
+ SmartDialog.showToast('切换至「$title」');
+ changeFucCall.call(episode, index);
+ },
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(
+ StyleString.safeSpace, 6, StyleString.safeSpace, 6),
+ child: LayoutBuilder(
+ builder: (BuildContext context, BoxConstraints boxConstraints) {
+ const double width = 160;
+ return Container(
+ constraints: const BoxConstraints(minHeight: 88),
+ height: width / StyleString.aspectRatio,
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ AspectRatio(
+ aspectRatio: StyleString.aspectRatio,
+ child: LayoutBuilder(
+ builder: (BuildContext context,
+ BoxConstraints boxConstraints) {
+ final double maxWidth = boxConstraints.maxWidth;
+ final double maxHeight = boxConstraints.maxHeight;
+ return Stack(
+ children: [
+ NetworkImgLayer(
+ src: episode?.cover ?? '',
+ width: maxWidth,
+ height: maxHeight,
+ ),
+ if (episode.duration != 0)
+ PBadge(
+ text: Utils.timeFormat(episode.duration!),
+ right: 6.0,
+ bottom: 6.0,
+ type: 'gray',
+ ),
+ ],
+ );
+ },
+ ),
+ ),
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ episode.title as String,
+ textAlign: TextAlign.start,
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ fontWeight: FontWeight.w500,
+ color: isCurrentIndex ? primary : onSurface,
+ ),
+ ),
+ const Spacer(),
+ if (dataType != VideoEpidoesType.videoPart) ...[
+ if (episode?.pubdate != null ||
+ episode.pubTime != null)
+ Text(
+ Utils.dateFormat(
+ episode?.pubdate ?? episode.pubTime),
+ style: TextStyle(
+ fontSize: 11,
+ color:
+ Theme.of(context).colorScheme.outline),
+ ),
+ const SizedBox(height: 2),
+ if (episode.stat != null)
+ Row(
+ children: [
+ StatView(view: episode.stat.view),
+ const SizedBox(width: 8),
+ StatDanMu(danmu: episode.stat.danmaku),
+ const Spacer(),
+ ],
+ ),
+ const SizedBox(height: 4),
+ ]
+ ],
+ ),
+ ),
)
- : null,
- title: Text(title,
- style: TextStyle(
- fontSize: 14,
- color: isCurrentIndex ? primary : onSurface,
- )))
- : InkWell(
+ ],
+ ),
+ );
+ },
+ ),
+ ),
+ );
+ }
+}
+
+class EpisodeGridItem extends StatelessWidget {
+ final dynamic episode;
+ final int index;
+ final bool isCurrentIndex;
+ final dynamic dataType;
+ final Function changeFucCall;
+ final bool isFullScreen;
+
+ const EpisodeGridItem({
+ Key? key,
+ required this.episode,
+ required this.index,
+ required this.isCurrentIndex,
+ required this.dataType,
+ required this.changeFucCall,
+ required this.isFullScreen,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ ColorScheme colorScheme = Theme.of(context).colorScheme;
+ TextStyle textStyle = TextStyle(
+ color: isCurrentIndex ? colorScheme.primary : colorScheme.onSurface,
+ fontSize: 14,
+ );
+ return Stack(
+ children: [
+ Container(
+ width: double.infinity,
+ margin: const EdgeInsets.only(top: StyleString.safeSpace),
+ clipBehavior: Clip.antiAlias,
+ decoration: BoxDecoration(
+ color: isCurrentIndex
+ ? colorScheme.primaryContainer.withOpacity(0.6)
+ : colorScheme.onInverseSurface.withOpacity(0.6),
+ borderRadius: BorderRadius.circular(8),
+ border: Border.all(
+ color: isCurrentIndex
+ ? colorScheme.primary.withOpacity(0.8)
+ : Colors.transparent,
+ width: 1,
+ ),
+ ),
+ child: InkWell(
+ borderRadius: BorderRadius.circular(8),
onTap: () {
- SmartDialog.showToast('切换至「$title」');
+ if (isCurrentIndex) {
+ return;
+ }
+ SmartDialog.showToast('切换至「${episode.title}」');
changeFucCall.call(episode, index);
},
child: Padding(
- padding:
- const EdgeInsets.only(left: 14, right: 14, top: 8, bottom: 8),
- child: Row(
+ padding: const EdgeInsets.symmetric(horizontal: 12.0),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.max,
children: [
- NetworkImgLayer(
- width: 130, height: 75, src: episode?.cover ?? ''),
- const SizedBox(width: 10),
- Expanded(
- child: Text(
- title,
- maxLines: 2,
- style: TextStyle(
- fontSize: 14,
- color: isCurrentIndex ? primary : onSurface,
- ),
- ),
+ Text(
+ dataType == VideoEpidoesType.bangumiEpisode
+ ? '第${index + 1}话'
+ : '第${index + 1}p',
+ style: textStyle),
+ const SizedBox(height: 1),
+ Text(
+ episode.title,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: textStyle,
),
],
),
),
- );
- }
-
- Widget buildTitle() {
- return AppBar(
- toolbarHeight: 45,
- automaticallyImplyLeading: false,
- centerTitle: false,
- title: Text(
- '合集(${episodes.length})',
- style: Theme.of(context).textTheme.titleMedium,
- ),
- actions: !isFullScreen
- ? [
- IconButton(
- icon: const Icon(Icons.close, size: 20),
- onPressed: () => Navigator.pop(context),
- ),
- const SizedBox(width: 14),
- ]
- : null,
+ ),
+ ),
+ if (dataType == VideoEpidoesType.bangumiEpisode &&
+ episode.badge != '' &&
+ episode.badge != null)
+ Positioned(
+ right: 8,
+ top: 18,
+ child: Text(
+ episode.badge,
+ style: const TextStyle(fontSize: 11, color: Color(0xFFFF6699)),
+ ),
+ )
+ ],
);
}
+}
- Widget buildShowContent(BuildContext context) {
- final ItemScrollController itemScrollController = ItemScrollController();
- int currentIndex = episodes.indexWhere((dynamic e) => e.cid == currentCid);
- return StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- WidgetsBinding.instance.addPostFrameCallback((_) {
- itemScrollController.jumpTo(index: currentIndex);
- });
- return Container(
- height: sheetHeight,
- color: Theme.of(context).colorScheme.surface,
- child: Column(
- children: [
- buildTitle(),
- Expanded(
- child: Material(
- child: PageStorage(
- bucket: PageStorageBucket(),
- child: ScrollablePositionedList.builder(
- itemScrollController: itemScrollController,
- itemCount: episodes.length + 1,
- itemBuilder: (BuildContext context, int index) {
- bool isLastItem = index == episodes.length;
- bool isCurrentIndex = currentIndex == index;
- return isLastItem
- ? SizedBox(
- height:
- MediaQuery.of(context).padding.bottom + 20,
- )
- : buildEpisodeListItem(
- episodes[index],
- index,
- isCurrentIndex,
- );
- },
+class UgcSeasonBuild extends StatelessWidget {
+ final UgcSeason ugcSeason;
+ final RxInt isSubscribe;
+ final bool isVisible;
+ final Function changeFucCall;
+ final Function changeVisible;
+
+ const UgcSeasonBuild({
+ Key? key,
+ required this.ugcSeason,
+ required this.isSubscribe,
+ required this.isVisible,
+ required this.changeFucCall,
+ required this.changeVisible,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final ThemeData theme = Theme.of(context);
+ final Color outline = theme.colorScheme.outline;
+ final Color surface = theme.colorScheme.surface;
+ final Color primary = theme.colorScheme.primary;
+ final Color onPrimary = theme.colorScheme.onPrimary;
+ final Color onInverseSurface = theme.colorScheme.onInverseSurface;
+ final TextStyle titleMedium = theme.textTheme.titleMedium!;
+ final TextStyle labelMedium = theme.textTheme.labelMedium!;
+ final Color dividerColor = theme.dividerColor.withOpacity(0.1);
+
+ return isVisible
+ ? Container(
+ padding: const EdgeInsets.fromLTRB(12, 0, 12, 0),
+ color: surface,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Divider(height: 1, thickness: 1, color: dividerColor),
+ const SizedBox(height: 10),
+ Row(
+ children: [
+ Expanded(
+ child: Text(
+ '合集:${ugcSeason.title}',
+ style: titleMedium,
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ const SizedBox(width: 10),
+ Obx(
+ () => isSubscribe.value == -1
+ ? const SizedBox(height: 32)
+ : SizedBox(
+ height: 32,
+ child: FilledButton.tonal(
+ onPressed: () => changeFucCall.call(),
+ style: TextButton.styleFrom(
+ padding:
+ const EdgeInsets.only(left: 8, right: 8),
+ foregroundColor: isSubscribe.value == 1
+ ? outline
+ : onPrimary,
+ backgroundColor: isSubscribe.value == 1
+ ? onInverseSurface
+ : primary,
+ ),
+ child:
+ Text(isSubscribe.value == 1 ? '已订阅' : '订阅'),
+ ),
+ ),
+ ),
+ ],
+ ),
+ if (ugcSeason.intro != null && ugcSeason.intro != '') ...[
+ const SizedBox(height: 4),
+ Text(
+ ugcSeason.intro!,
+ style: TextStyle(color: outline, fontSize: 12),
),
+ ],
+ const SizedBox(height: 4),
+ Text.rich(
+ TextSpan(
+ style: TextStyle(
+ fontSize: labelMedium.fontSize, color: outline),
+ children: [
+ TextSpan(
+ text: '${Utils.numFormat(ugcSeason.stat!.view)}播放'),
+ const TextSpan(text: ' · '),
+ TextSpan(
+ text:
+ '${Utils.numFormat(ugcSeason.stat!.danmaku)}弹幕'),
+ ],
+ ),
+ ),
+ const SizedBox(height: 14),
+ Align(
+ alignment: Alignment.center,
+ child: Material(
+ color: surface,
+ child: InkWell(
+ onTap: () => changeVisible.call(),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(
+ vertical: 10, horizontal: 0),
+ child: Text(
+ '收起简介',
+ style: TextStyle(color: primary, fontSize: 12),
+ ),
+ ),
+ ),
+ ),
+ ),
+ Divider(height: 1, thickness: 1, color: dividerColor),
+ ],
+ ),
+ )
+ : Align(
+ alignment: Alignment.center,
+ child: InkWell(
+ onTap: () => changeVisible.call(),
+ child: Padding(
+ padding:
+ const EdgeInsets.symmetric(vertical: 10, horizontal: 0),
+ child: Text(
+ '展开简介',
+ style: TextStyle(color: primary, fontSize: 12),
),
),
),
- ],
- ),
- );
- });
- }
-
- /// The [BuildContext] of the widget that calls the bottom sheet.
- PersistentBottomSheetController show(BuildContext context) {
- final PersistentBottomSheetController btmSheetCtr = showBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return buildShowContent(context);
- },
- );
- return btmSheetCtr;
+ );
}
}
diff --git a/lib/common/skeleton/media_bangumi.dart b/lib/common/skeleton/media_bangumi.dart
index cf589254..98282cf0 100644
--- a/lib/common/skeleton/media_bangumi.dart
+++ b/lib/common/skeleton/media_bangumi.dart
@@ -3,14 +3,9 @@ import 'package:pilipala/common/constants.dart';
import 'skeleton.dart';
-class MediaBangumiSkeleton extends StatefulWidget {
+class MediaBangumiSkeleton extends StatelessWidget {
const MediaBangumiSkeleton({super.key});
- @override
- State createState() => _MediaBangumiSkeletonState();
-}
-
-class _MediaBangumiSkeletonState extends State {
@override
Widget build(BuildContext context) {
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
@@ -35,25 +30,25 @@ class _MediaBangumiSkeletonState extends State {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
- color: Theme.of(context).colorScheme.onInverseSurface,
+ color: bgColor,
width: 200,
height: 20,
margin: const EdgeInsets.only(bottom: 15),
),
Container(
- color: Theme.of(context).colorScheme.onInverseSurface,
+ color: bgColor,
width: 150,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
- color: Theme.of(context).colorScheme.onInverseSurface,
+ color: bgColor,
width: 150,
height: 13,
margin: const EdgeInsets.only(bottom: 5),
),
Container(
- color: Theme.of(context).colorScheme.onInverseSurface,
+ color: bgColor,
width: 150,
height: 13,
),
@@ -64,7 +59,7 @@ class _MediaBangumiSkeletonState extends State {
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(20)),
- color: Theme.of(context).colorScheme.onInverseSurface,
+ color: bgColor,
),
),
],
diff --git a/lib/common/skeleton/user_list.dart b/lib/common/skeleton/user_list.dart
new file mode 100644
index 00000000..cd9d4eb3
--- /dev/null
+++ b/lib/common/skeleton/user_list.dart
@@ -0,0 +1,42 @@
+import 'package:flutter/material.dart';
+import '../constants.dart';
+
+class UserListSkeleton extends StatelessWidget {
+ const UserListSkeleton({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
+ return Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: StyleString.safeSpace, vertical: 7),
+ child: Row(
+ children: [
+ ClipOval(
+ child: Container(width: 42, height: 42, color: bgColor),
+ ),
+ const SizedBox(width: 10),
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ children: [
+ Container(color: bgColor, width: 60, height: 13),
+ const SizedBox(width: 10),
+ Container(color: bgColor, width: 40, height: 13),
+ ],
+ ),
+ const SizedBox(height: 6),
+ Container(
+ color: bgColor,
+ width: 100,
+ height: 13,
+ ),
+ ],
+ ),
+ ),
+ ],
+ ));
+ }
+}
diff --git a/lib/common/skeleton/video_intro.dart b/lib/common/skeleton/video_intro.dart
new file mode 100644
index 00000000..b7a5ec74
--- /dev/null
+++ b/lib/common/skeleton/video_intro.dart
@@ -0,0 +1,126 @@
+import 'package:flutter/material.dart';
+import '../constants.dart';
+import 'skeleton.dart';
+
+class VideoIntroSkeleton extends StatelessWidget {
+ const VideoIntroSkeleton({Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
+ return Skeleton(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ const SizedBox(height: 18),
+ Container(
+ width: double.infinity,
+ height: 20,
+ margin: const EdgeInsets.only(bottom: 6),
+ color: bgColor,
+ ),
+ Container(
+ width: 220,
+ height: 20,
+ margin: const EdgeInsets.only(bottom: 12),
+ color: bgColor,
+ ),
+ Row(
+ children: [
+ Container(
+ width: 45,
+ height: 14,
+ color: bgColor,
+ ),
+ const SizedBox(width: 8),
+ Container(
+ width: 45,
+ height: 14,
+ color: bgColor,
+ ),
+ const SizedBox(width: 8),
+ Container(
+ width: 45,
+ height: 14,
+ color: bgColor,
+ ),
+ const Spacer(),
+ Container(
+ width: 35,
+ height: 14,
+ color: bgColor,
+ ),
+ const SizedBox(width: 4),
+ ],
+ ),
+ const SizedBox(height: 30),
+ LayoutBuilder(builder: (context, constraints) {
+ // 并列5个正方形
+ double width = (constraints.maxWidth - 30) / 5;
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceAround,
+ children: List.generate(5, (index) {
+ return Container(
+ width: width - 24,
+ height: width - 24,
+ decoration: BoxDecoration(
+ color: bgColor,
+ borderRadius: BorderRadius.circular(16),
+ ),
+ );
+ }),
+ );
+ }),
+ const SizedBox(height: 20),
+ Container(
+ width: double.infinity,
+ height: 30,
+ margin: const EdgeInsets.symmetric(horizontal: 6),
+ decoration: BoxDecoration(
+ color: bgColor,
+ borderRadius: BorderRadius.circular(8),
+ ),
+ ),
+ const SizedBox(height: 20),
+ Row(
+ children: [
+ ClipOval(
+ child: Container(
+ width: 44,
+ height: 44,
+ color: bgColor,
+ ),
+ ),
+ const SizedBox(width: 12),
+ Container(
+ width: 50,
+ height: 14,
+ color: bgColor,
+ ),
+ const SizedBox(width: 8),
+ Container(
+ width: 35,
+ height: 14,
+ color: bgColor,
+ ),
+ const Spacer(),
+ Container(
+ width: 55,
+ height: 30,
+ decoration: BoxDecoration(
+ color: bgColor,
+ borderRadius: BorderRadius.circular(16),
+ ),
+ ),
+ const SizedBox(width: 2)
+ ],
+ ),
+ const SizedBox(height: 10),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/common/widgets/custom_toast.dart b/lib/common/widgets/custom_toast.dart
index f732fd85..93695e0d 100644
--- a/lib/common/widgets/custom_toast.dart
+++ b/lib/common/widgets/custom_toast.dart
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/utils/storage.dart';
-Box setting = GStrorage.setting;
+Box setting = GStorage.setting;
class CustomToast extends StatelessWidget {
const CustomToast({super.key, required this.msg});
diff --git a/lib/common/widgets/drag_handle.dart b/lib/common/widgets/drag_handle.dart
new file mode 100644
index 00000000..1143a732
--- /dev/null
+++ b/lib/common/widgets/drag_handle.dart
@@ -0,0 +1,25 @@
+import 'package:flutter/material.dart';
+
+class DragHandle extends StatelessWidget {
+ const DragHandle({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return InkWell(
+ onTap: Navigator.of(context).pop,
+ child: SizedBox(
+ height: 36,
+ child: Center(
+ child: Container(
+ width: 32,
+ height: 4,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.outline,
+ borderRadius: BorderRadius.circular(4),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/common/widgets/http_error.dart b/lib/common/widgets/http_error.dart
index 0381319e..51396c0b 100644
--- a/lib/common/widgets/http_error.dart
+++ b/lib/common/widgets/http_error.dart
@@ -4,9 +4,10 @@ import 'package:flutter_svg/flutter_svg.dart';
class HttpError extends StatelessWidget {
const HttpError({
required this.errMsg,
- required this.fn,
+ this.fn,
this.btnText,
this.isShowBtn = true,
+ this.isInSliver = true,
super.key,
});
@@ -14,46 +15,41 @@ class HttpError extends StatelessWidget {
final Function()? fn;
final String? btnText;
final bool isShowBtn;
+ final bool isInSliver;
@override
Widget build(BuildContext context) {
- return SliverToBoxAdapter(
- child: SizedBox(
- height: 400,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- SvgPicture.asset(
- "assets/images/error.svg",
- height: 200,
- ),
- const SizedBox(height: 30),
- Text(
- errMsg ?? '请求异常',
- textAlign: TextAlign.center,
- style: Theme.of(context).textTheme.titleSmall,
- ),
- const SizedBox(height: 20),
- if (isShowBtn)
- FilledButton.tonal(
- onPressed: () {
- fn!();
- },
- style: ButtonStyle(
- backgroundColor: MaterialStateProperty.resolveWith((states) {
- return Theme.of(context).colorScheme.primary.withAlpha(20);
- }),
- ),
- child: Text(
- btnText ?? '点击重试',
- style:
- TextStyle(color: Theme.of(context).colorScheme.primary),
- ),
+ Color primary = Theme.of(context).colorScheme.primary;
+ final errorContent = SizedBox(
+ height: 400,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ SvgPicture.asset("assets/images/error.svg", height: 200),
+ const SizedBox(height: 30),
+ Text(
+ errMsg ?? '请求异常',
+ textAlign: TextAlign.center,
+ style: Theme.of(context).textTheme.titleSmall,
+ ),
+ const SizedBox(height: 20),
+ if (isShowBtn)
+ FilledButton.tonal(
+ onPressed: () => fn?.call(),
+ style: ButtonStyle(
+ backgroundColor: MaterialStateProperty.resolveWith((states) {
+ return primary.withAlpha(20);
+ }),
),
- ],
- ),
+ child: Text(btnText ?? '点击重试', style: TextStyle(color: primary)),
+ ),
+ ],
),
);
+ if (isInSliver) {
+ return SliverToBoxAdapter(child: errorContent);
+ } else {
+ return Align(alignment: Alignment.topCenter, child: errorContent);
+ }
}
}
diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart
index 0b715a89..b7b5de7e 100644
--- a/lib/common/widgets/network_img_layer.dart
+++ b/lib/common/widgets/network_img_layer.dart
@@ -6,7 +6,7 @@ import 'package:pilipala/utils/global_data_cache.dart';
import '../../utils/storage.dart';
import '../constants.dart';
-Box setting = GStrorage.setting;
+Box setting = GStorage.setting;
class NetworkImgLayer extends StatelessWidget {
const NetworkImgLayer({
@@ -20,6 +20,7 @@ class NetworkImgLayer extends StatelessWidget {
// 图片质量 默认1%
this.quality,
this.origAspectRatio,
+ this.radius,
});
final String? src;
@@ -30,10 +31,26 @@ class NetworkImgLayer extends StatelessWidget {
final Duration? fadeInDuration;
final int? quality;
final double? origAspectRatio;
+ final double? radius;
+
+ BorderRadius getBorderRadius(String? type, double? radius) {
+ return BorderRadius.circular(
+ radius ??
+ (type == 'avatar'
+ ? 50
+ : type == 'emote'
+ ? 0
+ : StyleString.imgRadius.x),
+ );
+ }
@override
Widget build(BuildContext context) {
- final int defaultImgQuality = GlobalDataCache().imgQuality;
+ int defaultImgQuality = 10;
+ try {
+ defaultImgQuality = GlobalDataCache.imgQuality;
+ } catch (_) {}
+
if (src == '' || src == null) {
return placeholder(context);
}
@@ -68,13 +85,7 @@ class NetworkImgLayer extends StatelessWidget {
return src != '' && src != null
? ClipRRect(
clipBehavior: Clip.antiAlias,
- borderRadius: BorderRadius.circular(
- type == 'avatar'
- ? 50
- : type == 'emote'
- ? 0
- : StyleString.imgRadius.x,
- ),
+ borderRadius: getBorderRadius(type, radius),
child: CachedNetworkImage(
imageUrl: imageUrl,
width: width,
@@ -103,11 +114,7 @@ class NetworkImgLayer extends StatelessWidget {
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
- borderRadius: BorderRadius.circular(type == 'avatar'
- ? 50
- : type == 'emote'
- ? 0
- : StyleString.imgRadius.x),
+ borderRadius: getBorderRadius(type, radius),
),
child: type == 'bg'
? const SizedBox()
diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart
index 78c4ba87..b662a3b0 100644
--- a/lib/common/widgets/video_card_h.dart
+++ b/lib/common/widgets/video_card_h.dart
@@ -12,6 +12,7 @@ import '../../http/video.dart';
import '../../utils/utils.dart';
import '../constants.dart';
import 'badge.dart';
+import 'drag_handle.dart';
import 'network_img_layer.dart';
import 'stat/danmu.dart';
import 'stat/view.dart';
@@ -373,27 +374,12 @@ class MorePanel extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Container(
+ return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- InkWell(
- onTap: () => Get.back(),
- child: Container(
- height: 35,
- padding: const EdgeInsets.only(bottom: 2),
- child: Center(
- child: Container(
- width: 32,
- height: 3,
- decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.outline,
- borderRadius: const BorderRadius.all(Radius.circular(3))),
- ),
- ),
- ),
- ),
+ const DragHandle(),
ListTile(
onTap: () async => await menuActionHandler('block'),
minLeadingWidth: 0,
diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart
index 378c9f75..72cfb998 100644
--- a/lib/common/widgets/video_card_v.dart
+++ b/lib/common/widgets/video_card_v.dart
@@ -5,6 +5,7 @@ import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/image_save.dart';
import 'package:pilipala/utils/route_push.dart';
import '../../models/model_rec_video_item.dart';
+import 'drag_handle.dart';
import 'stat/danmu.dart';
import 'stat/view.dart';
import '../../http/dynamics.dart';
@@ -282,9 +283,10 @@ class VideoStat extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: [
- StatView(view: videoItem.stat.view),
+ if (videoItem.stat.view != null) StatView(view: videoItem.stat.view),
const SizedBox(width: 8),
- StatDanMu(danmu: videoItem.stat.danmu),
+ if (videoItem.stat.danmu != null)
+ StatDanMu(danmu: videoItem.stat.danmu),
if (videoItem is RecVideoItemModel) ...[
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
RichText(
@@ -367,27 +369,12 @@ class MorePanel extends StatelessWidget {
@override
Widget build(BuildContext context) {
- return Container(
+ return Padding(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- InkWell(
- onTap: () => Get.back(),
- child: Container(
- height: 35,
- padding: const EdgeInsets.only(bottom: 2),
- child: Center(
- child: Container(
- width: 32,
- height: 3,
- decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.outline,
- borderRadius: const BorderRadius.all(Radius.circular(3))),
- ),
- ),
- ),
- ),
+ const DragHandle(),
ListTile(
onTap: () async => await menuActionHandler('block'),
minLeadingWidth: 0,
diff --git a/lib/http/api.dart b/lib/http/api.dart
index ff49b314..379540a5 100644
--- a/lib/http/api.dart
+++ b/lib/http/api.dart
@@ -104,7 +104,7 @@ class Api {
// 评论列表
// https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11
- static const String replyList = '/x/v2/reply';
+ static const String replyList = '/x/v2/reply/main';
// 楼中楼
static const String replyReplyList = '/x/v2/reply/reply';
@@ -175,7 +175,7 @@ class Api {
static const String delHistory = '/x/v2/history/delete';
// 搜索历史记录
- static const String searchHistory = '/x/web-goblin/history/search';
+ static const String searchHistory = '/x/web-interface/history/search';
// 热搜
static const String hotSearchList =
@@ -301,10 +301,6 @@ class Api {
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';
@@ -499,7 +495,7 @@ class Api {
static const activateBuvidApi = '/x/internal/gaia-gateway/ExClimbWuzhi';
/// 获取字幕配置
- static const getSubtitleConfig = '/x/player/v2';
+ static const getSubtitleConfig = '/x/player/wbi/v2';
/// 我的订阅
static const userSubFolder = '/x/v3/fav/folder/collected/list';
@@ -555,6 +551,10 @@ class Api {
static const String messageSystemAPi =
'${HttpString.messageBaseUrl}/x/sys-msg/query_unified_notify';
+ /// 系统通知 个人
+ static const String userMessageSystemAPi =
+ '${HttpString.messageBaseUrl}/x/sys-msg/query_user_notify';
+
/// 系统通知标记已读
static const String systemMarkRead =
'${HttpString.messageBaseUrl}/x/sys-msg/update_cursor';
@@ -579,6 +579,51 @@ class Api {
/// 稍后再看&收藏夹视频列表
static const String mediaList = '/x/v2/medialist/resource/list';
+ /// 用户专栏
+ static const String opusList = '/x/polymer/web-dynamic/v1/opus/feed/space';
+
///
static const String getViewInfo = '/x/article/viewinfo';
+
+ /// 直播间记录
+ static const String liveRoomEntry =
+ '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction';
+
+ /// 用户信息
+ static const String accountInfo = '/x/member/web/account';
+
+ /// 更新用户信息
+ static const String updateAccountInfo = '/x/member/web/update';
+
+ /// 删除评论
+ static const String replyDel = '/x/v2/reply/del';
+
+ /// 图片上传
+ static const String uploadImage = '/x/dynamic/feed/draw/upload_bfs';
+
+ /// 更新追番状态
+ static const String updateBangumiStatus = '/pgc/web/follow/status/update';
+
+ /// 番剧点赞投币收藏状态
+ static const String bangumiActionStatus = '/pgc/season/episode/community';
+
+ /// @我的
+ static const String messageAtAPi = '/x/msgfeed/at?';
+
+ /// 订阅
+ static const String confirmSub = '/x/v3/fav/season/fav';
+
+ /// 订阅状态
+ static const String videoRelation = '/x/web-interface/archive/relation';
+
+ /// 获取空降区间
+ static const String getSkipSegments =
+ '${HttpString.sponsorBlockBaseUrl}/api/skipSegments';
+
+ /// 视频标签
+ static const String videoTag = '/x/tag/archive/tags';
+
+ /// 修复标题和海报
+ // /api/view?id=${aid} /all/video/av${aid} /video/av${aid}/
+ static const String fixTitleAndPic = '${HttpString.biliplusBaseUrl}/api/view';
}
diff --git a/lib/http/bangumi.dart b/lib/http/bangumi.dart
index 91508682..d0c052d6 100644
--- a/lib/http/bangumi.dart
+++ b/lib/http/bangumi.dart
@@ -1,5 +1,8 @@
+import 'dart:convert';
import '../models/bangumi/list.dart';
import 'index.dart';
+import 'package:html/parser.dart' as html_parser;
+import 'package:html/dom.dart' as html_dom;
class BangumiHttp {
static Future bangumiList({int? page}) async {
@@ -18,8 +21,19 @@ class BangumiHttp {
}
}
- static Future bangumiFollow({int? mid}) async {
- var res = await Request().get(Api.bangumiFollow, data: {'vmid': mid});
+ static Future getRecentBangumi({
+ int? mid,
+ int type = 1,
+ int pn = 1,
+ int ps = 20,
+ }) async {
+ var res = await Request().get(Api.getRecentBangumiApi, data: {
+ 'vmid': mid,
+ 'type': type,
+ 'follow_status': 0,
+ 'pn': pn,
+ 'ps': ps,
+ });
if (res.data['code'] == 0) {
return {
'status': true,
@@ -33,4 +47,62 @@ class BangumiHttp {
};
}
}
+
+ // 获取追番状态
+ static Future bangumiStatus({required int seasonId}) async {
+ var res = await Request()
+ .get('https://www.bilibili.com/bangumi/play/ss$seasonId');
+ html_dom.Document document = html_parser.parse(res.data);
+ // 查找 id 为 __NEXT_DATA__ 的 script 元素
+ html_dom.Element? scriptElement =
+ document.querySelector('script#\\__NEXT_DATA__');
+ if (scriptElement != null) {
+ // 提取 script 元素的内容
+ String scriptContent = scriptElement.text;
+ final dynamic scriptContentJson = jsonDecode(scriptContent);
+ Map followState = scriptContentJson['props']['pageProps']['followState'];
+ return {
+ 'status': true,
+ 'data': {
+ 'isFollowed': followState['isFollowed'],
+ 'followStatus': followState['followStatus']
+ }
+ };
+ } else {
+ print('Script element with id "__NEXT_DATA__" not found.');
+ }
+ }
+
+ // 更新追番状态
+ static Future updateBangumiStatus({
+ required int seasonId,
+ required int status,
+ }) async {
+ var res = await Request().post(Api.updateBangumiStatus, data: {
+ 'season_id': seasonId,
+ 'status': status,
+ });
+ if (res.data['code'] == 0) {
+ return {'status': true, 'data': res.data['data']};
+ } else {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': res.data['message'],
+ };
+ }
+ }
+
+ // 获取番剧点赞投币收藏状态
+ static Future bangumiActionStatus({required int epId}) async {
+ var res = await Request().get(
+ Api.bangumiActionStatus,
+ data: {'ep_id': epId},
+ );
+ if (res.data['code'] == 0) {
+ return {'status': true, 'data': res.data['data']};
+ } else {
+ return {'status': false, 'data': [], 'msg': res.data['message']};
+ }
+ }
}
diff --git a/lib/http/black.dart b/lib/http/black.dart
index 0c6a63ab..67356a92 100644
--- a/lib/http/black.dart
+++ b/lib/http/black.dart
@@ -28,7 +28,7 @@ class BlackHttp {
static Future removeBlack({required int fid}) async {
var res = await Request().post(
Api.removeBlack,
- queryParameters: {
+ data: {
'act': 6,
'csrf': await Request.getCsrf(),
'fid': fid,
diff --git a/lib/http/common.dart b/lib/http/common.dart
index d711a7e7..8dd1cc24 100644
--- a/lib/http/common.dart
+++ b/lib/http/common.dart
@@ -1,6 +1,15 @@
+import 'package:pilipala/models/common/invalid_video.dart';
+import 'dart:convert';
+import 'dart:math';
+import 'package:dio/dio.dart';
+import 'package:flutter/material.dart';
+import 'package:pilipala/models/sponsor_block/segment.dart';
+
import 'index.dart';
class CommonHttp {
+ static final RegExp spmPrefixExp =
+ RegExp(r'');
static Future unReadDynamic() async {
var res = await Request().get(Api.getUnreadDynamic,
data: {'alltype_offset': 0, 'video_offset': '', 'article_offset': 0});
@@ -14,4 +23,95 @@ class CommonHttp {
};
}
}
+
+ static Future querySkipSegments({required String bvid}) async {
+ var res = await Request().getWithoutCookie(Api.getSkipSegments, data: {
+ 'videoID': bvid,
+ });
+ if (res.data is List && res.data.isNotEmpty) {
+ try {
+ return {
+ 'status': true,
+ 'data': res.data
+ .map((e) => SegmentDataModel.fromJson(e))
+ .toList(),
+ };
+ } catch (err) {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': 'sponsorBlock数据解析失败: $err',
+ };
+ }
+ } else {
+ return {
+ 'status': false,
+ 'data': [],
+ };
+ }
+ }
+
+ static Future fixVideoPicAndTitle({required int aid}) async {
+ var res = await Request().getWithoutCookie(Api.fixTitleAndPic, data: {
+ 'id': aid,
+ });
+ if (res != null) {
+ if (res.data['code'] == -404) {
+ return {
+ 'status': false,
+ 'data': null,
+ 'msg': '没有相关信息',
+ };
+ } else {
+ return {
+ 'status': true,
+ 'data': InvalidVideoModel.fromJson(res.data),
+ };
+ }
+ } else {
+ return {
+ 'status': false,
+ 'data': null,
+ 'msg': '没有相关信息',
+ };
+ }
+ }
+
+ static Future buvidActivate() async {
+ try {
+ // 获取 HTML 数据
+ var html = await Request().get(Api.dynamicSpmPrefix);
+
+ // 提取 spmPrefix
+ String spmPrefix = spmPrefixExp.firstMatch(html.data)?.group(1) ?? '';
+
+ // 生成随机 PNG 结束部分
+ Random rand = Random();
+ String randPngEnd = base64.encode(
+ List.generate(32, (_) => rand.nextInt(256))
+ ..addAll(List.filled(4, 0))
+ ..addAll([73, 69, 78, 68])
+ ..addAll(List.generate(4, (_) => rand.nextInt(256))),
+ );
+
+ // 构建 JSON 数据
+ String jsonData = json.encode({
+ '3064': 1,
+ '39c8': '$spmPrefix.fp.risk',
+ '3c43': {
+ 'adca': 'Linux',
+ 'bfe9': randPngEnd.substring(randPngEnd.length - 50),
+ },
+ });
+
+ // 发送 POST 请求
+ await Request().post(
+ Api.activateBuvidApi,
+ data: {'payload': jsonData},
+ options: Options(contentType: 'application/json'),
+ );
+ } catch (err) {
+ debugPrint('buvidActivate error: $err');
+ }
+ }
}
diff --git a/lib/http/constants.dart b/lib/http/constants.dart
index b734c279..e7e031bb 100644
--- a/lib/http/constants.dart
+++ b/lib/http/constants.dart
@@ -7,6 +7,9 @@ class HttpString {
static const String passBaseUrl = 'https://passport.bilibili.com';
static const String messageBaseUrl = 'https://message.bilibili.com';
static const String bangumiBaseUrl = 'https://bili.meark.me';
+ static const String sponsorBlockBaseUrl = 'https://www.bsbsb.top';
+ static const String biliplusBaseUrl = 'https://www.biliplus.com';
+
static const List validateStatusCodes = [
302,
304,
diff --git a/lib/http/danmaku.dart b/lib/http/danmaku.dart
index 0b108755..7b4283ae 100644
--- a/lib/http/danmaku.dart
+++ b/lib/http/danmaku.dart
@@ -17,7 +17,9 @@ class DanmakaHttp {
var response = await Request().get(
Api.webDanmaku,
data: params,
- extra: {'resType': ResponseType.bytes},
+ options: Options(
+ responseType: ResponseType.bytes,
+ ),
);
return DmSegMobileReply.fromBuffer(response.data);
}
@@ -67,9 +69,6 @@ class DanmakaHttp {
var response = await Request().post(
Api.shootDanmaku,
data: params,
- options: Options(
- contentType: Headers.formUrlEncodedContentType,
- ),
);
if (response.statusCode != 200) {
return {
diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart
index 69619361..53ba6fc1 100644
--- a/lib/http/dynamics.dart
+++ b/lib/http/dynamics.dart
@@ -1,4 +1,5 @@
import 'dart:math';
+import 'package:dio/dio.dart';
import '../models/dynamics/result.dart';
import '../models/dynamics/up.dart';
import 'index.dart';
@@ -69,7 +70,7 @@ class DynamicsHttp {
}) async {
var res = await Request().post(
Api.likeDynamic,
- queryParameters: {
+ data: {
'dynamic_id': dynamicId,
'up': up,
'csrf': await Request.getCsrf(),
@@ -91,7 +92,7 @@ class DynamicsHttp {
//
static Future dynamicDetail({
- String? id,
+ required String id,
}) async {
var res = await Request().get(Api.dynamicDetail, data: {
'timezone_offset': -480,
@@ -175,27 +176,32 @@ class DynamicsHttp {
'revs_id': {'dyn_type': 8, 'rid': oid}
};
}
- var res = await Request().post(Api.dynamicCreate, queryParameters: {
- 'platform': 'web',
- 'csrf': await Request.getCsrf(),
- 'x-bili-device-req-json': {'platform': 'web', 'device': 'pc'},
- 'x-bili-web-req-json': {'spm_id': '333.999'},
- }, data: {
- 'dyn_req': {
- 'content': {
- 'contents': [
- {'raw_text': rawText ?? '', 'type': 1, 'biz_id': ''}
- ]
- },
- 'scene': scene,
- 'attach_card': null,
- 'upload_id': uploadId,
- 'meta': {
- 'app_meta': {'from': 'create.dynamic.web', 'mobi_app': 'web'}
- }
+ var res = await Request().post(
+ Api.dynamicCreate,
+ queryParameters: {
+ 'platform': 'web',
+ 'csrf': await Request.getCsrf(),
+ 'x-bili-device-req-json': {'platform': 'web', 'device': 'pc'},
+ 'x-bili-web-req-json': {'spm_id': '333.999'},
},
- 'web_repost_src': webRepostSrc
- });
+ data: {
+ 'dyn_req': {
+ 'content': {
+ 'contents': [
+ {'raw_text': rawText ?? '', 'type': 1, 'biz_id': ''}
+ ]
+ },
+ 'scene': scene,
+ 'attach_card': null,
+ 'upload_id': uploadId,
+ 'meta': {
+ 'app_meta': {'from': 'create.dynamic.web', 'mobi_app': 'web'}
+ }
+ },
+ 'web_repost_src': webRepostSrc
+ },
+ options: Options(contentType: 'application/json'),
+ );
if (res.data['code'] == 0) {
return {
'status': true,
diff --git a/lib/http/fav.dart b/lib/http/fav.dart
index 6f49d68a..69577e7e 100644
--- a/lib/http/fav.dart
+++ b/lib/http/fav.dart
@@ -11,7 +11,7 @@ class FavHttp {
}) async {
var res = await Request().post(
Api.editFavFolder,
- queryParameters: {
+ data: {
'title': title,
'intro': intro,
'media_id': mediaId,
@@ -43,7 +43,7 @@ class FavHttp {
}) async {
var res = await Request().post(
Api.addFavFolder,
- queryParameters: {
+ data: {
'title': title,
'intro': intro,
'cover': cover ?? '',
diff --git a/lib/http/html.dart b/lib/http/html.dart
index 100887e5..87adacb9 100644
--- a/lib/http/html.dart
+++ b/lib/http/html.dart
@@ -21,7 +21,6 @@ class HtmlHttp {
}
try {
Document rootTree = parse(response.data);
- // log(response.data.body.toString());
Element body = rootTree.body!;
Element appDom = body.querySelector('#app')!;
Element authorHeader = appDom.querySelector('.fixed-author-header')!;
@@ -52,7 +51,6 @@ class HtmlHttp {
.className
.split(' ')[1]
.split('-')[2];
- // List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img');
return {
'status': true,
'avatar': avatar,
@@ -76,20 +74,10 @@ class HtmlHttp {
Element body = rootTree.body!;
Element appDom = body.querySelector('#app')!;
Element authorHeader = appDom.querySelector('.up-left')!;
- // 头像
- // String avatar =
- // authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!;
- // print(avatar);
- // avatar = 'https:${avatar.split('@')[0]}';
String uname = authorHeader.querySelector('.up-name')!.text.trim();
// 动态详情
Element opusDetail = appDom.querySelector('.article-content')!;
// 发布时间
- // String updateTime =
- // opusDetail.querySelector('.opus-module-author__pub__text')!.text;
- // print(updateTime);
-
- //
String opusContent =
opusDetail.querySelector('#read-article-holder')!.innerHtml;
RegExp digitRegExp = RegExp(r'\d+');
diff --git a/lib/http/init.dart b/lib/http/init.dart
index 6a90a87d..9c0f368c 100644
--- a/lib/http/init.dart
+++ b/lib/http/init.dart
@@ -1,19 +1,16 @@
// ignore_for_file: avoid_print
import 'dart:async';
-import 'dart:convert';
import 'dart:developer';
import 'dart:io';
-import 'dart:math' show Random;
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
-// import 'package:dio_http2_adapter/dio_http2_adapter.dart';
import 'package:hive/hive.dart';
+import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/utils/id_utils.dart';
import '../utils/storage.dart';
import '../utils/utils.dart';
-import 'api.dart';
import 'constants.dart';
import 'interceptor.dart';
@@ -22,19 +19,17 @@ class Request {
static late CookieManager cookieManager;
static late final Dio dio;
factory Request() => _instance;
- Box setting = GStrorage.setting;
- static Box localCache = GStrorage.localCache;
+ Box setting = GStorage.setting;
+ static Box localCache = GStorage.localCache;
late bool enableSystemProxy;
late String systemProxyHost;
late String systemProxyPort;
- static final RegExp spmPrefixExp =
- RegExp(r'');
static String? buvid;
/// 设置cookie
static setCookie() async {
- Box userInfoCache = GStrorage.userInfo;
- Box setting = GStrorage.setting;
+ Box userInfoCache = GStorage.userInfo;
+ Box setting = GStorage.setting;
final String cookiePath = await Utils.getCookiePath();
final PersistCookieJar cookieJar = PersistCookieJar(
ignoreExpires: true,
@@ -44,7 +39,7 @@ class Request {
dio.interceptors.add(cookieManager);
final List cookie = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.baseUrl));
- final userInfo = userInfoCache.get('userInfoCache');
+ final UserInfoData? userInfo = userInfoCache.get('userInfoCache');
if (userInfo != null && userInfo.mid != null) {
final List cookie2 = await cookieManager.cookieJar
.loadForRequest(Uri.parse(HttpString.tUrl));
@@ -62,11 +57,6 @@ class Request {
baseUrlType = 'bangumi';
}
setBaseUrl(type: baseUrlType);
- try {
- await buvidActivate();
- } catch (e) {
- log("setCookie, ${e.toString()}");
- }
final String cookieString = cookie
.map((Cookie cookie) => '${cookie.name}=${cookie.value}')
@@ -78,7 +68,7 @@ class Request {
// 从cookie中获取 csrf token
static Future getCsrf() async {
List cookies = await cookieManager.cookieJar
- .loadForRequest(Uri.parse(HttpString.apiBaseUrl));
+ .loadForRequest(Uri.parse(HttpString.baseUrl));
String token = '';
if (cookies.where((e) => e.name == 'bili_jct').isNotEmpty) {
token = cookies.firstWhere((e) => e.name == 'bili_jct').value;
@@ -92,9 +82,12 @@ class Request {
}
final List cookies = await cookieManager.cookieJar
- .loadForRequest(Uri.parse(HttpString.baseUrl));
- buvid = cookies.firstWhere((cookie) => cookie.name == 'buvid3').value;
- if (buvid == null) {
+ .loadForRequest(Uri.parse(HttpString.apiBaseUrl));
+ buvid = cookies
+ .firstWhere((cookie) => cookie.name == 'buvid3',
+ orElse: () => Cookie('buvid3', ''))
+ .value;
+ if (buvid == null || buvid!.isEmpty) {
try {
var result = await Request().get(
"${HttpString.apiBaseUrl}/x/frontend/finger/spi",
@@ -122,30 +115,6 @@ class Request {
dio.options.headers['referer'] = 'https://www.bilibili.com/';
}
- static Future buvidActivate() async {
- var html = await Request().get(Api.dynamicSpmPrefix);
- String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
- Random rand = Random();
- String rand_png_end = base64.encode(
- List.generate(32, (_) => rand.nextInt(256)) +
- List.filled(4, 0) +
- [73, 69, 78, 68] +
- List.generate(4, (_) => rand.nextInt(256)));
-
- String jsonData = json.encode({
- '3064': 1,
- '39c8': '${spmPrefix}.fp.risk',
- '3c43': {
- 'adca': 'Linux',
- 'bfe9': rand_png_end.substring(rand_png_end.length - 50),
- },
- });
-
- await Request().post(Api.activateBuvidApi,
- data: {'payload': jsonData},
- options: Options(contentType: 'application/json'));
- }
-
/*
* config it and create
*/
@@ -171,15 +140,6 @@ class Request {
dio = Dio(options);
- /// fix 第三方登录 302重定向 跟iOS代理问题冲突
- // ..httpClientAdapter = Http2Adapter(
- // ConnectionManager(
- // idleTimeout: const Duration(milliseconds: 10000),
- // onClientCreate: (_, ClientSetting config) =>
- // config.onBadCertificate = (_) => true,
- // ),
- // );
-
/// 设置代理
if (enableSystemProxy) {
dio.httpClientAdapter = IOHttpClientAdapter(
@@ -217,18 +177,15 @@ class Request {
/*
* get请求
*/
- get(url, {data, options, cancelToken, extra}) async {
+ get(url, {data, Options? options, cancelToken, extra}) async {
Response response;
- final Options options = Options();
- ResponseType resType = ResponseType.json;
if (extra != null) {
- resType = extra!['resType'] ?? ResponseType.json;
if (extra['ua'] != null) {
- options.headers = {'user-agent': headerUa(type: extra['ua'])};
+ options ??= Options();
+ options.headers ??= {};
+ options.headers?['user-agent'] = headerUa(type: extra['ua']);
}
}
- options.responseType = resType;
-
try {
response = await dio.get(
url,
@@ -238,32 +195,44 @@ class Request {
);
return response;
} on DioException catch (e) {
- Response errResponse = Response(
- data: {
- 'message': await ApiInterceptor.dioError(e)
- }, // 将自定义 Map 数据赋值给 Response 的 data 属性
+ return Response(
+ data: {'message': await ApiInterceptor.dioError(e)},
statusCode: 200,
requestOptions: RequestOptions(),
);
- return errResponse;
}
}
+ /*
+ * get请求
+ */
+ getWithoutCookie(url, {data}) {
+ return get(
+ url,
+ data: data,
+ options: Options(
+ headers: {
+ 'cookie': 'buvid3= ; b_nut= ; sid= ',
+ 'user-agent': headerUa(type: 'pc'),
+ },
+ ),
+ );
+ }
+
/*
* post请求
*/
post(url, {data, queryParameters, options, cancelToken, extra}) async {
- // print('post-data: $data');
Response response;
try {
response = await dio.post(
url,
data: data,
queryParameters: queryParameters,
- options: options,
+ options:
+ options ?? Options(contentType: Headers.formUrlEncodedContentType),
cancelToken: cancelToken,
);
- // print('post success: ${response.data}');
return response;
} on DioException catch (e) {
Response errResponse = Response(
@@ -319,7 +288,7 @@ class Request {
}
} else {
headerUa =
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15';
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36';
}
return headerUa;
}
diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart
index 259a3bf9..b33d18df 100644
--- a/lib/http/interceptor.dart
+++ b/lib/http/interceptor.dart
@@ -3,8 +3,7 @@
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
-import 'package:hive/hive.dart';
-import '../utils/storage.dart';
+import 'package:pilipala/utils/login.dart';
class ApiInterceptor extends Interceptor {
@override
@@ -19,20 +18,9 @@ class ApiInterceptor extends Interceptor {
@override
void onResponse(Response response, ResponseInterceptorHandler handler) {
try {
- if (response.statusCode == 302) {
- final List locations = response.headers['location']!;
- if (locations.isNotEmpty) {
- if (locations.first.startsWith('https://www.mcbbs.net')) {
- final Uri uri = Uri.parse(locations.first);
- final String? accessKey = uri.queryParameters['access_key'];
- final String? mid = uri.queryParameters['mid'];
- try {
- Box localCache = GStrorage.localCache;
- localCache.put(LocalCacheKey.accessKey,
- {'mid': mid, 'value': accessKey});
- } catch (_) {}
- }
- }
+ // 在响应之后处理数据
+ if (response.data is Map && response.data['code'] == -101) {
+ LoginUtils.loginOut();
}
} catch (err) {
print('ApiInterceptor: $err');
diff --git a/lib/http/live.dart b/lib/http/live.dart
index f5fd2a43..259f86fc 100644
--- a/lib/http/live.dart
+++ b/lib/http/live.dart
@@ -89,23 +89,26 @@ class LiveHttp {
// 发送弹幕
static Future sendDanmaku({roomId, msg}) async {
- var res = await Request().post(Api.sendLiveMsg, queryParameters: {
- 'bubble': 0,
- 'msg': msg,
- 'color': 16777215, // 颜色
- 'mode': 1, // 模式
- 'room_type': 0,
- 'jumpfrom': 71001, // 直播间来源
- 'reply_mid': 0,
- 'reply_attr': 0,
- 'replay_dmid': '',
- 'statistics': {"appId": 100, "platform": 5},
- 'fontsize': 25, // 字体大小
- 'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000, // 时间戳
- 'roomid': roomId,
- 'csrf': await Request.getCsrf(),
- 'csrf_token': await Request.getCsrf(),
- });
+ var res = await Request().post(
+ Api.sendLiveMsg,
+ data: {
+ 'bubble': 0,
+ 'msg': msg,
+ 'color': 16777215, // 颜色
+ 'mode': 1, // 模式
+ 'room_type': 0,
+ 'jumpfrom': 71001, // 直播间来源
+ 'reply_mid': 0,
+ 'reply_attr': 0,
+ 'replay_dmid': '',
+ 'statistics': {"appId": 100, "platform": 5},
+ 'fontsize': 25, // 字体大小
+ 'rnd': DateTime.now().millisecondsSinceEpoch ~/ 1000, // 时间戳
+ 'roomid': roomId,
+ 'csrf': await Request.getCsrf(),
+ 'csrf_token': await Request.getCsrf(),
+ },
+ );
if (res.data['code'] == 0) {
return {
'status': true,
@@ -142,4 +145,18 @@ class LiveHttp {
};
}
}
+
+ // 直播历史记录
+ static Future liveRoomEntry({required int roomId}) async {
+ await Request().post(
+ Api.liveRoomEntry,
+ data: {
+ 'room_id': roomId,
+ 'platform': 'pc',
+ 'csrf_token': await Request.getCsrf(),
+ 'csrf': await Request.getCsrf(),
+ 'visit_id': '',
+ },
+ );
+ }
}
diff --git a/lib/http/login.dart b/lib/http/login.dart
index 2437b72a..80f58803 100644
--- a/lib/http/login.dart
+++ b/lib/http/login.dart
@@ -71,9 +71,6 @@ class LoginHttp {
var res = await Request().post(
Api.webSmsCode,
data: formData,
- options: Options(
- contentType: Headers.formUrlEncodedContentType,
- ),
);
if (res.data['code'] == 0) {
return {
@@ -106,9 +103,6 @@ class LoginHttp {
var res = await Request().post(
Api.webSmsLogin,
data: formData,
- options: Options(
- contentType: Headers.formUrlEncodedContentType,
- ),
);
if (res.data['code'] == 0) {
return {
@@ -155,9 +149,6 @@ class LoginHttp {
var res = await Request().post(
Api.appSmsCode,
data: data,
- options: Options(
- contentType: Headers.formUrlEncodedContentType,
- ),
);
print(res);
}
@@ -208,9 +199,6 @@ class LoginHttp {
var res = await Request().post(
Api.loginInByPwdApi,
data: data,
- options: Options(
- contentType: Headers.formUrlEncodedContentType,
- ),
);
print(res);
}
@@ -239,17 +227,27 @@ class LoginHttp {
var res = await Request().post(
Api.loginInByWebPwd,
data: formData,
- options: Options(
- contentType: Headers.formUrlEncodedContentType,
- ),
);
if (res.data['code'] == 0) {
- return {
- 'status': true,
- 'data': res.data['data'],
- };
+ if (res.data['data']['status'] == 0) {
+ return {
+ 'status': true,
+ 'data': res.data['data'],
+ };
+ } else {
+ return {
+ 'status': false,
+ 'code': 1,
+ 'data': res.data['data'],
+ 'msg': res.data['data']['message'],
+ };
+ }
} else {
- return {'status': false, 'data': [], 'msg': res.data['message']};
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': res.data['message'],
+ };
}
}
diff --git a/lib/http/member.dart b/lib/http/member.dart
index e87aa42e..107a9379 100644
--- a/lib/http/member.dart
+++ b/lib/http/member.dart
@@ -1,6 +1,11 @@
+import 'dart:convert';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
+import 'package:html/parser.dart';
+import 'package:pilipala/models/member/article.dart';
import 'package:pilipala/models/member/like.dart';
+import 'package:pilipala/models/user/info.dart';
+import 'package:pilipala/utils/global_data_cache.dart';
import '../common/constants.dart';
import '../models/dynamics/result.dart';
import '../models/follow/result.dart';
@@ -16,14 +21,20 @@ import 'index.dart';
class MemberHttp {
static Future memberInfo({
- int? mid,
+ required int mid,
String token = '',
}) async {
+ String? wWebid;
+ if ((await getWWebid(mid: mid))['status']) {
+ wWebid = GlobalDataCache.wWebid;
+ }
+
Map params = await WbiSign().makSign({
'mid': mid,
'token': token,
'platform': 'web',
'web_location': 1550101,
+ ...wWebid != null ? {'w_webid': wWebid} : {},
});
var res = await Request().get(
Api.memberInfo,
@@ -195,13 +206,15 @@ class MemberHttp {
// 设置分组
static Future addUsers(int? fids, String? tagids) async {
- var res = await Request().post(Api.addUsers, queryParameters: {
- 'fids': fids,
- 'tagids': tagids ?? '0',
- 'csrf': await Request.getCsrf(),
- }, data: {
- 'cross_domain': true
- });
+ var res = await Request().post(
+ Api.addUsers,
+ data: {
+ 'fids': fids,
+ 'tagids': tagids ?? '0',
+ 'csrf': await Request.getCsrf(),
+ },
+ queryParameters: {'cross_domain': true},
+ );
if (res.data['code'] == 0) {
return {'status': true, 'data': [], 'msg': '操作成功'};
} else {
@@ -419,11 +432,14 @@ class MemberHttp {
static Future cookieToKey() async {
var authCodeRes = await getTVCode();
if (authCodeRes['status']) {
- var res = await Request().post(Api.cookieToKey, queryParameters: {
- 'auth_code': authCodeRes['data'],
- 'build': 708200,
- 'csrf': await Request.getCsrf(),
- });
+ var res = await Request().post(
+ Api.cookieToKey,
+ data: {
+ 'auth_code': authCodeRes['data'],
+ 'build': 708200,
+ 'csrf': await Request.getCsrf(),
+ },
+ );
await Future.delayed(const Duration(milliseconds: 300));
await qrcodePoll(authCodeRes['data']);
if (res.data['code'] == 0) {
@@ -455,11 +471,11 @@ class MemberHttp {
SmartDialog.dismiss();
if (res.data['code'] == 0) {
String accessKey = res.data['data']['access_token'];
- Box localCache = GStrorage.localCache;
- Box userInfoCache = GStrorage.userInfo;
- var userInfo = userInfoCache.get('userInfoCache');
+ Box localCache = GStorage.localCache;
+ Box userInfoCache = GStorage.userInfo;
+ final UserInfoData? userInfo = userInfoCache.get('userInfoCache');
localCache.put(
- LocalCacheKey.accessKey, {'mid': userInfo.mid, 'value': accessKey});
+ LocalCacheKey.accessKey, {'mid': userInfo!.mid, 'value': accessKey});
return {'status': true, 'data': [], 'msg': '操作成功'};
} else {
return {
@@ -556,4 +572,60 @@ class MemberHttp {
};
}
}
+
+ static Future getWWebid({required int mid}) async {
+ String? wWebid = GlobalDataCache.wWebid;
+ if (wWebid != null) {
+ return {'status': true, 'data': wWebid};
+ }
+ var res = await Request().get('https://space.bilibili.com/$mid/article');
+ String? headContent = parse(res.data).head?.outerHtml;
+ final regex = RegExp(
+ r'');
+ if (headContent != null) {
+ final match = regex.firstMatch(headContent);
+ if (match != null && match.groupCount >= 1) {
+ final content = match.group(1);
+ String decodedString = Uri.decodeComponent(content!);
+ Map map = jsonDecode(decodedString);
+ GlobalDataCache.wWebid = map['access_id'];
+ return {'status': true, 'data': map['access_id']};
+ } else {
+ return {'status': false, 'data': '请检查登录状态'};
+ }
+ }
+ return {'status': false, 'data': '请检查登录状态'};
+ }
+
+ // 获取用户专栏
+ static Future getMemberArticle({
+ required int mid,
+ required int pn,
+ String? offset,
+ }) async {
+ String? wWebid;
+ if ((await getWWebid(mid: mid))['status']) {
+ wWebid = GlobalDataCache.wWebid;
+ }
+ Map params = await WbiSign().makSign({
+ 'host_mid': mid,
+ 'page': pn,
+ 'offset': offset,
+ 'web_location': 333.999,
+ ...wWebid != null ? {'w_webid': wWebid} : {},
+ });
+ var res = await Request().get(Api.opusList, data: params);
+ if (res.data['code'] == 0) {
+ return {
+ 'status': true,
+ 'data': MemberArticleDataModel.fromJson(res.data['data'])
+ };
+ } else {
+ return {
+ 'status': false,
+ 'data': [],
+ 'msg': res.data['message'] ?? '请求异常',
+ };
+ }
+ }
}
diff --git a/lib/http/msg.dart b/lib/http/msg.dart
index 2de9cd49..65156e03 100644
--- a/lib/http/msg.dart
+++ b/lib/http/msg.dart
@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:math';
-import 'package:dio/dio.dart';
+import 'package:flutter/material.dart';
+import 'package:pilipala/models/msg/at.dart';
import 'package:pilipala/models/msg/like.dart';
import 'package:pilipala/models/msg/reply.dart';
import 'package:pilipala/models/msg/system.dart';
@@ -64,7 +65,7 @@ class MsgHttp {
.toList(),
};
} catch (err) {
- print('err🔟: $err');
+ debugPrint('err: $err');
}
} else {
return {
@@ -158,9 +159,6 @@ class MsgHttp {
'csrf_token': csrf,
'csrf': csrf,
},
- options: Options(
- contentType: Headers.formUrlEncodedContentType,
- ),
);
if (res.data['code'] == 0) {
return {
@@ -282,10 +280,10 @@ class MsgHttp {
'data': MessageLikeModel.fromJson(res.data['data']),
};
} catch (err) {
- return {'status': false, 'date': [], 'msg': err.toString()};
+ return {'status': false, 'data': [], 'msg': err.toString()};
}
} else {
- return {'status': false, 'date': [], 'msg': res.data['message']};
+ return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
@@ -330,4 +328,47 @@ class MsgHttp {
};
}
}
+
+ static Future messageSystemAccount() async {
+ var res = await Request().get(Api.userMessageSystemAPi, data: {
+ 'csrf': await Request.getCsrf(),
+ 'page_size': 20,
+ 'build': 0,
+ 'mobi_app': 'web',
+ });
+ if (res.data['code'] == 0) {
+ try {
+ return {
+ 'status': true,
+ 'data': res.data['data']['system_notify_list']
+ .map((e) => MessageSystemModel.fromJson(e))
+ .toList(),
+ };
+ } catch (err) {
+ return {'status': false, 'date': [], 'msg': err.toString()};
+ }
+ } else {
+ return {'status': false, 'date': [], 'msg': res.data['message']};
+ }
+ }
+
+ // @我的
+ static Future messageAt() async {
+ var res = await Request().get(Api.messageAtAPi, data: {
+ 'build': 0,
+ 'mobi_app': 'web',
+ });
+ if (res.data['code'] == 0) {
+ try {
+ return {
+ 'status': true,
+ 'data': MessageAtModel.fromJson(res.data['data']),
+ };
+ } catch (err) {
+ return {'status': false, 'data': [], 'msg': err.toString()};
+ }
+ } else {
+ return {'status': false, 'data': [], 'msg': res.data['message']};
+ }
+ }
}
diff --git a/lib/http/read.dart b/lib/http/read.dart
index 68e72e59..f2542936 100644
--- a/lib/http/read.dart
+++ b/lib/http/read.dart
@@ -1,4 +1,5 @@
import 'dart:convert';
+import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:pilipala/models/read/opus.dart';
import 'package:pilipala/models/read/read.dart';
@@ -65,6 +66,11 @@ class ReadHttp {
var res = await Request().get(
'https://www.bilibili.com/read/cv$id',
extra: {'ua': 'pc'},
+ options: Options(
+ headers: {
+ 'cookie': 'opus-goback=1',
+ },
+ ),
);
String scriptContent =
extractScriptContents(parse(res.data).body!.outerHtml)[0];
diff --git a/lib/http/reply.dart b/lib/http/reply.dart
index 880f9072..846ef45b 100644
--- a/lib/http/reply.dart
+++ b/lib/http/reply.dart
@@ -1,3 +1,8 @@
+import 'dart:convert';
+
+import 'dart:io';
+import 'package:dio/dio.dart';
+import 'package:image_picker/image_picker.dart';
import '../models/video/reply/data.dart';
import '../models/video/reply/emote.dart';
import 'api.dart';
@@ -6,17 +11,16 @@ import 'init.dart';
class ReplyHttp {
static Future replyList({
required int oid,
- required int pageNum,
+ required String nextOffset,
required int type,
int? ps,
int sort = 1,
}) async {
var res = await Request().get(Api.replyList, data: {
'oid': oid,
- 'pn': pageNum,
'type': type,
- 'sort': sort,
- 'ps': ps ?? 20
+ 'pagination_str': jsonEncode({'offset': nextOffset}),
+ 'mode': sort + 2,
});
if (res.data['code'] == 0) {
return {
@@ -52,19 +56,13 @@ class ReplyHttp {
if (res.data['code'] == 0) {
return {
'status': true,
- 'data': ReplyData.fromJson(res.data['data']),
+ 'data': ReplyReplyData.fromJson(res.data['data']),
};
} else {
- Map errMap = {
- -400: '请求错误',
- -404: '无此项',
- 12002: '评论区已关闭',
- 12009: '评论主体的type不合法',
- };
return {
'status': false,
'date': [],
- 'msg': errMap[res.data['code']] ?? '请求异常',
+ 'msg': res.data['message'],
};
}
}
@@ -78,7 +76,7 @@ class ReplyHttp {
}) async {
var res = await Request().post(
Api.likeReply,
- queryParameters: {
+ data: {
'type': type,
'oid': oid,
'rpid': rpid,
@@ -115,4 +113,65 @@ class ReplyHttp {
};
}
}
+
+ static Future replyDel({
+ required int type, //replyType
+ required int oid,
+ required int rpid,
+ }) async {
+ var res = await Request().post(
+ Api.replyDel,
+ queryParameters: {
+ 'type': type, //type.index
+ 'oid': oid,
+ 'rpid': rpid,
+ 'csrf': await Request.getCsrf(),
+ },
+ );
+ if (res.data['code'] == 0) {
+ return {'status': true, 'msg': '删除成功'};
+ } else {
+ return {'status': false, 'msg': res.data['message']};
+ }
+ }
+
+ // 图片上传
+ static Future uploadImage(
+ {required XFile xFile, String type = 'new_dyn'}) async {
+ var formData = FormData.fromMap({
+ 'file_up': await xFileToMultipartFile(xFile),
+ 'biz': type,
+ 'csrf': await Request.getCsrf(),
+ 'category': 'daily',
+ });
+ var res = await Request().post(
+ Api.uploadImage,
+ data: formData,
+ );
+ if (res.data['code'] == 0) {
+ var data = res.data['data'];
+ data['img_src'] = data['image_url'];
+ data['img_width'] = data['image_width'];
+ data['img_height'] = data['image_height'];
+ data.remove('image_url');
+ data.remove('image_width');
+ data.remove('image_height');
+ return {
+ 'status': true,
+ 'data': data,
+ };
+ } else {
+ return {
+ 'status': false,
+ 'date': [],
+ 'msg': res.data['message'],
+ };
+ }
+ }
+
+ static Future xFileToMultipartFile(XFile xFile) async {
+ var file = File(xFile.path);
+ var bytes = await file.readAsBytes();
+ return MultipartFile.fromBytes(bytes, filename: xFile.name);
+ }
}
diff --git a/lib/http/search.dart b/lib/http/search.dart
index 00e51497..a61ff406 100644
--- a/lib/http/search.dart
+++ b/lib/http/search.dart
@@ -11,7 +11,7 @@ import '../utils/storage.dart';
import 'index.dart';
class SearchHttp {
- static Box setting = GStrorage.setting;
+ static Box setting = GStorage.setting;
static Future hotSearchList() async {
var res = await Request().get(Api.hotSearchList);
if (res.data is String) {
diff --git a/lib/http/user.dart b/lib/http/user.dart
index f4535905..99888aea 100644
--- a/lib/http/user.dart
+++ b/lib/http/user.dart
@@ -1,10 +1,8 @@
import 'dart:convert';
-import 'dart:developer';
-import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
+import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:pilipala/models/video/later.dart';
-import '../common/constants.dart';
import '../models/model_hot_video_item.dart';
import '../models/user/fav_detail.dart';
import '../models/user/fav_folder.dart';
@@ -153,7 +151,7 @@ class UserHttp {
// 暂停switchStatus传true 否则false
var res = await Request().post(
Api.pauseHistory,
- queryParameters: {
+ data: {
'switch': switchStatus,
'jsonp': 'jsonp',
'csrf': await Request.getCsrf(),
@@ -172,7 +170,7 @@ class UserHttp {
static Future clearHistory() async {
var res = await Request().post(
Api.clearHistory,
- queryParameters: {
+ data: {
'jsonp': 'jsonp',
'csrf': await Request.getCsrf(),
},
@@ -190,7 +188,7 @@ class UserHttp {
}
var res = await Request().post(
Api.toViewLater,
- queryParameters: data,
+ data: data,
);
if (res.data['code'] == 0) {
return {'status': true, 'msg': 'yeah!稍后再看'};
@@ -209,7 +207,7 @@ class UserHttp {
params[aid != null ? 'aid' : 'viewed'] = aid ?? true;
var res = await Request().post(
Api.toViewDel,
- queryParameters: params,
+ data: params,
);
if (res.data['code'] == 0) {
return {'status': true, 'msg': 'yeah!成功移除'};
@@ -218,30 +216,11 @@ class UserHttp {
}
}
- // 获取用户凭证 失效
- 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,
- },
- );
- try {
- if (res.data['code'] == 0 && res.data['data']['has_login'] == 1) {
- Request().get(res.data['data']['confirm_uri']);
- }
- } catch (err) {
- SmartDialog.showNotify(msg: '获取用户凭证: $err', notifyType: NotifyType.error);
- }
- }
-
// 清空稍后再看
static Future toViewClear() async {
var res = await Request().post(
Api.toViewClear,
- queryParameters: {
+ data: {
'jsonp': 'jsonp',
'csrf': await Request.getCsrf(),
},
@@ -257,7 +236,7 @@ class UserHttp {
static Future delHistory(kid) async {
var res = await Request().post(
Api.delHistory,
- queryParameters: {
+ data: {
'kid': kid,
'jsonp': 'jsonp',
'csrf': await Request.getCsrf(),
@@ -283,30 +262,6 @@ class UserHttp {
return {'status': false, 'msg': res.data['message']};
}
}
- // // 相互关系查询
- // static Future relationSearch(int mid) async {
- // Map params = await WbiSign().makSign({
- // 'mid': mid,
- // 'token': '',
- // 'platform': 'web',
- // 'web_location': 1550101,
- // });
- // var res = await Request().get(
- // Api.relationSearch,
- // data: {
- // 'mid': mid,
- // 'w_rid': params['w_rid'],
- // 'wts': params['wts'],
- // },
- // );
- // if (res.data['code'] == 0) {
- // // relation 主动状态
- // // 被动状态
- // return {'status': true, 'data': res.data['data']};
- // } else {
- // return {'status': false, 'msg': res.data['message']};
- // }
- // }
// 搜索历史记录
static Future searchHistory(
@@ -406,7 +361,7 @@ class UserHttp {
static Future cancelSub({required int seasonId}) async {
var res = await Request().post(
Api.cancelSub,
- queryParameters: {
+ data: {
'platform': 'web',
'season_id': seasonId,
'csrf': await Request.getCsrf(),
@@ -423,7 +378,7 @@ class UserHttp {
static Future delFavFolder({required int mediaIds}) async {
var res = await Request().post(
Api.delFavFolder,
- queryParameters: {
+ data: {
'media_ids': mediaIds,
'platform': 'web',
'csrf': await Request.getCsrf(),
@@ -436,31 +391,6 @@ class UserHttp {
}
}
- // 稍后再看播放全部
- // static Future toViewPlayAll({required int oid, required String bvid}) async {
- // var res = await Request().get(
- // Api.watchLaterHtml,
- // data: {
- // 'oid': oid,
- // 'bvid': bvid,
- // },
- // );
- // String scriptContent =
- // extractScriptContents(parse(res.data).body!.outerHtml)[0];
- // int startIndex = scriptContent.indexOf('{');
- // int endIndex = scriptContent.lastIndexOf('};');
- // String jsonContent = scriptContent.substring(startIndex, endIndex + 1);
- // // 解析JSON字符串为Map
- // Map jsonData = json.decode(jsonContent);
- // // 输出解析后的数据
- // return {
- // 'status': true,
- // 'data': jsonData['resourceList']
- // .map((e) => MediaVideoItemModel.fromJson(e))
- // .toList()
- // };
- // }
-
static List extractScriptContents(String htmlContent) {
RegExp scriptRegExp = RegExp(r'