Merge branch 'main' into feature-appScheme
This commit is contained in:
3
.github/workflows/beta_ci.yml
vendored
3
.github/workflows/beta_ci.yml
vendored
@ -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: 下载项目依赖
|
||||
|
||||
4
.github/workflows/release_ci.yml
vendored
4
.github/workflows/release_ci.yml
vendored
@ -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: |
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 7.0 KiB After Width: | Height: | Size: 1.7 KiB |
23
change_log/1.0.24.0626.md
Normal file
23
change_log/1.0.24.0626.md
Normal file
@ -0,0 +1,23 @@
|
||||
## 1.0.24
|
||||
|
||||
### 功能
|
||||
+ 私信功能
|
||||
+ 回复我的、收到的赞查看
|
||||
+ 新的登录方式
|
||||
+ 全屏选集
|
||||
+ 一键三连
|
||||
+ 按分区搜索
|
||||
|
||||
### 优化
|
||||
+ 页面跳转动画
|
||||
+ 评论区跳转
|
||||
|
||||
### 修复
|
||||
+ 音画不同步问题
|
||||
+ 分集字幕未同步
|
||||
+ 多语言字幕
|
||||
+ 弹幕设置未生效
|
||||
+
|
||||
|
||||
|
||||
问题反馈、功能建议请查看「关于」页面。
|
||||
39
change_log/1.0.25.1010.md
Normal file
39
change_log/1.0.25.1010.md
Normal file
@ -0,0 +1,39 @@
|
||||
## 1.0.25
|
||||
|
||||
### 功能
|
||||
+ 直播弹幕
|
||||
+ 稍后再看、收藏夹播放全部
|
||||
+ 收藏夹新建、编辑
|
||||
+ 评论删除
|
||||
+ 评论保存为图片
|
||||
+ 动态页滑动切换up
|
||||
+ up投稿筛选充电视频
|
||||
+ 直播tab展示关注up
|
||||
+ up主页专栏展示
|
||||
|
||||
### 优化
|
||||
+ 视频详情页一键三连
|
||||
+ 动态页标识充电视频
|
||||
+ 播放器亮度、音量调整百分比展示
|
||||
+ 封面预览时视频标题可复制
|
||||
+ 竖屏直播布局
|
||||
+ 图片预览
|
||||
+ 专栏渲染优化
|
||||
+ 私信图片查看
|
||||
|
||||
### 修复
|
||||
+ 收藏夹点击异常
|
||||
+ 搜索up异常
|
||||
+ 系统通知已读异常
|
||||
+ [赞了我的]展示错误
|
||||
+ 部分up合集无法打开
|
||||
+ 切换合集视频投币个数未重置
|
||||
+ 搜索条件筛选面板无法滚动
|
||||
+ 部分机型导航条未沉浸
|
||||
+ 专栏图片渲染问题
|
||||
+ 专栏浏览历史记录
|
||||
+ 直播间历史记录
|
||||
|
||||
|
||||
更多更新日志可在Github上查看
|
||||
问题反馈、功能建议请查看「关于」页面。
|
||||
@ -1,5 +1,6 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
import AVFoundation
|
||||
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
@ -8,6 +9,14 @@ import Flutter
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
|
||||
// 设置音频会话类别,确保在静音模式下播放音频
|
||||
do {
|
||||
try AVAudioSession.sharedInstance().setCategory(.playback, options: [.duckOthers])
|
||||
} catch {
|
||||
print("Failed to set audio session category: \(error)")
|
||||
}
|
||||
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -66,7 +66,7 @@ class PBadge extends StatelessWidget {
|
||||
border: Border.all(color: borderColor),
|
||||
),
|
||||
child: Text(
|
||||
text!,
|
||||
text ?? '',
|
||||
style: TextStyle(fontSize: fs ?? fontSize, color: color),
|
||||
),
|
||||
);
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'network_img_layer.dart';
|
||||
import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart';
|
||||
import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart';
|
||||
import 'package:pilipala/utils/highlight.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class HtmlRender extends StatelessWidget {
|
||||
@ -22,6 +24,20 @@ class HtmlRender extends StatelessWidget {
|
||||
data: htmlContent,
|
||||
onLinkTap: (String? url, Map<String, String> buildContext, attributes) {},
|
||||
extensions: [
|
||||
TagExtension(
|
||||
tagsToExtend: <String>{'pre'},
|
||||
builder: (ExtensionContext extensionContext) {
|
||||
final Map<String, dynamic> attributes = extensionContext.attributes;
|
||||
final String lang = attributes['data-lang'] as String;
|
||||
final String code = attributes['codecontent'] as String;
|
||||
List<String> selectedLanguages = [lang.split('@').first];
|
||||
TextSpan? result = highlightExistingText(code, selectedLanguages);
|
||||
if (result == null) {
|
||||
return const Center(child: Text('代码块渲染失败'));
|
||||
}
|
||||
return SelectableText.rich(result);
|
||||
},
|
||||
),
|
||||
TagExtension(
|
||||
tagsToExtend: <String>{'img'},
|
||||
builder: (ExtensionContext extensionContext) {
|
||||
@ -44,20 +60,52 @@ class HtmlRender extends StatelessWidget {
|
||||
if (isMall) {
|
||||
return const SizedBox();
|
||||
}
|
||||
// bool inTable =
|
||||
// extensionContext.element!.previousElementSibling == null ||
|
||||
// extensionContext.element!.nextElementSibling == null;
|
||||
// imgUrl = Utils().imageUrl(imgUrl!);
|
||||
// return Image.network(
|
||||
// imgUrl,
|
||||
// width: isEmote ? 22 : null,
|
||||
// height: isEmote ? 22 : null,
|
||||
// );
|
||||
return NetworkImgLayer(
|
||||
width: isEmote ? 22 : Get.size.width - 24,
|
||||
height: isEmote ? 22 : 200,
|
||||
src: imgUrl,
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
HeroDialogRoute<void>(
|
||||
builder: (BuildContext context) =>
|
||||
InteractiveviewerGallery(
|
||||
sources: imgList ?? [imgUrl],
|
||||
initIndex: imgList?.indexOf(imgUrl) ?? 0,
|
||||
itemBuilder: (
|
||||
BuildContext context,
|
||||
int index,
|
||||
bool isFocus,
|
||||
bool enablePageView,
|
||||
) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
if (enablePageView) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
child: Center(
|
||||
child: Hero(
|
||||
tag: imgList?[index] ?? imgUrl,
|
||||
child: CachedNetworkImage(
|
||||
fadeInDuration:
|
||||
const Duration(milliseconds: 0),
|
||||
imageUrl: imgList?[index] ?? imgUrl,
|
||||
fit: BoxFit.contain,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onPageChanged: (int pageIndex) {},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: CachedNetworkImage(imageUrl: imgUrl),
|
||||
);
|
||||
// return NetworkImgLayer(
|
||||
// width: isEmote ? 22 : Get.size.width - 24,
|
||||
// height: isEmote ? 22 : 200,
|
||||
// src: imgUrl,
|
||||
// );
|
||||
} catch (err) {
|
||||
return const SizedBox();
|
||||
}
|
||||
@ -66,7 +114,7 @@ class HtmlRender extends StatelessWidget {
|
||||
],
|
||||
style: {
|
||||
'html': Style(
|
||||
fontSize: FontSize.medium,
|
||||
fontSize: FontSize.large,
|
||||
lineHeight: LineHeight.percent(140),
|
||||
),
|
||||
'body': Style(margin: Margins.zero, padding: HtmlPaddings.zero),
|
||||
@ -78,7 +126,7 @@ class HtmlRender extends StatelessWidget {
|
||||
margin: Margins.only(bottom: 10),
|
||||
),
|
||||
'span': Style(
|
||||
fontSize: FontSize.medium,
|
||||
fontSize: FontSize.large,
|
||||
height: Height(1.65),
|
||||
),
|
||||
'div': Style(height: Height.auto()),
|
||||
|
||||
@ -2,12 +2,18 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class HttpError extends StatelessWidget {
|
||||
const HttpError(
|
||||
{required this.errMsg, required this.fn, this.btnText, super.key});
|
||||
const HttpError({
|
||||
required this.errMsg,
|
||||
required this.fn,
|
||||
this.btnText,
|
||||
this.isShowBtn = true,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String? errMsg;
|
||||
final Function()? fn;
|
||||
final String? btnText;
|
||||
final bool isShowBtn;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -29,20 +35,22 @@ class HttpError extends StatelessWidget {
|
||||
style: Theme.of(context).textTheme.titleSmall,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
FilledButton.tonal(
|
||||
onPressed: () {
|
||||
fn!();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
return Theme.of(context).colorScheme.primary.withAlpha(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),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
btnText ?? '点击重试',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -2,7 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/utils/extension.dart';
|
||||
import 'package:pilipala/utils/global_data.dart';
|
||||
import 'package:pilipala/utils/global_data_cache.dart';
|
||||
import '../../utils/storage.dart';
|
||||
import '../constants.dart';
|
||||
|
||||
@ -33,7 +33,11 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final int defaultImgQuality = GlobalData().imgQuality;
|
||||
int defaultImgQuality = 10;
|
||||
try {
|
||||
defaultImgQuality = GlobalDataCache().imgQuality;
|
||||
} catch (_) {}
|
||||
|
||||
if (src == '' || src == null) {
|
||||
return placeholder(context);
|
||||
}
|
||||
|
||||
@ -1,130 +0,0 @@
|
||||
// ignore_for_file: depend_on_referenced_packages
|
||||
|
||||
import 'dart:math';
|
||||
import 'dart:ui' as ui show Image;
|
||||
|
||||
import 'package:extended_image/extended_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
|
||||
|
||||
double get maxDragOffset => 100;
|
||||
double hideHeight = maxDragOffset / 2.3;
|
||||
double refreshHeight = maxDragOffset / 1.5;
|
||||
|
||||
class PullToRefreshHeader extends StatelessWidget {
|
||||
const PullToRefreshHeader(
|
||||
this.info,
|
||||
this.lastRefreshTime, {
|
||||
this.color,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final PullToRefreshScrollNotificationInfo? info;
|
||||
final DateTime? lastRefreshTime;
|
||||
final Color? color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final PullToRefreshScrollNotificationInfo? infos = info;
|
||||
if (infos == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
String text = '';
|
||||
if (infos.mode == PullToRefreshIndicatorMode.armed) {
|
||||
text = 'Release to refresh';
|
||||
} else if (infos.mode == PullToRefreshIndicatorMode.refresh ||
|
||||
infos.mode == PullToRefreshIndicatorMode.snap) {
|
||||
text = 'Loading...';
|
||||
} else if (infos.mode == PullToRefreshIndicatorMode.done) {
|
||||
text = 'Refresh completed.';
|
||||
} else if (infos.mode == PullToRefreshIndicatorMode.drag) {
|
||||
text = 'Pull to refresh';
|
||||
} else if (infos.mode == PullToRefreshIndicatorMode.canceled) {
|
||||
text = 'Cancel refresh';
|
||||
}
|
||||
|
||||
final TextStyle ts = const TextStyle(
|
||||
color: Colors.grey,
|
||||
).copyWith(fontSize: 14);
|
||||
|
||||
final double dragOffset = info?.dragOffset ?? 0.0;
|
||||
|
||||
final DateTime time = lastRefreshTime ?? DateTime.now();
|
||||
final double top = -hideHeight + dragOffset;
|
||||
return Container(
|
||||
height: dragOffset,
|
||||
color: color ?? Colors.transparent,
|
||||
// padding: EdgeInsets.only(top: dragOffset / 3),
|
||||
// padding: EdgeInsets.only(bottom: 5.0),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Positioned(
|
||||
left: 0.0,
|
||||
right: 0.0,
|
||||
top: top,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: Container(
|
||||
alignment: Alignment.centerRight,
|
||||
margin: const EdgeInsets.only(right: 12.0),
|
||||
child: RefreshImage(top, null),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Text(text, style: ts),
|
||||
Text(
|
||||
'Last updated:${DateFormat('yyyy-MM-dd hh:mm').format(time)}',
|
||||
style: ts.copyWith(fontSize: 14),
|
||||
)
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RefreshImage extends StatelessWidget {
|
||||
const RefreshImage(this.top, Key? key) : super(key: key);
|
||||
|
||||
final double top;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const double imageSize = 30;
|
||||
return ExtendedImage.asset(
|
||||
'assets/flutterCandies_grey.png',
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
afterPaintImage: (Canvas canvas, Rect rect, ui.Image image, Paint paint) {
|
||||
final double imageHeight = image.height.toDouble();
|
||||
final double imageWidth = image.width.toDouble();
|
||||
final Size size = rect.size;
|
||||
final double y =
|
||||
(1 - min(top / (refreshHeight - hideHeight), 1)) * imageHeight;
|
||||
|
||||
canvas.drawImageRect(
|
||||
image,
|
||||
Rect.fromLTWH(0.0, y, imageWidth, imageHeight - y),
|
||||
Rect.fromLTWH(rect.left, rect.top + y / imageHeight * size.height,
|
||||
size.width, (imageHeight - y) / imageHeight * size.height),
|
||||
Paint()
|
||||
..colorFilter =
|
||||
const ColorFilter.mode(Color(0xFFea5504), BlendMode.srcIn)
|
||||
..isAntiAlias = false
|
||||
..filterQuality = FilterQuality.low,
|
||||
);
|
||||
|
||||
//canvas.restore();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -6,7 +6,7 @@ class StatDanMu extends StatelessWidget {
|
||||
final dynamic danmu;
|
||||
final String? size;
|
||||
|
||||
const StatDanMu({Key? key, this.theme, this.danmu, this.size})
|
||||
const StatDanMu({Key? key, this.theme = 'gray', this.danmu, this.size})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
@ -17,21 +17,46 @@ class StatDanMu extends StatelessWidget {
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
return StatIconText(
|
||||
icon: Icons.subtitles_outlined,
|
||||
text: Utils.numFormat(danmu!),
|
||||
color: color,
|
||||
size: size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StatIconText extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final Color color;
|
||||
final String? size;
|
||||
|
||||
const StatIconText({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
required this.text,
|
||||
required this.color,
|
||||
this.size,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.subtitles_outlined,
|
||||
icon,
|
||||
size: 14,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
Utils.numFormat(danmu!),
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: size == 'medium' ? 12 : 11,
|
||||
color: color,
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,8 +6,12 @@ class StatView extends StatelessWidget {
|
||||
final dynamic view;
|
||||
final String? size;
|
||||
|
||||
const StatView({Key? key, this.theme, this.view, this.size})
|
||||
: super(key: key);
|
||||
const StatView({
|
||||
Key? key,
|
||||
this.theme = 'gray',
|
||||
this.view,
|
||||
this.size,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -17,16 +21,41 @@ class StatView extends StatelessWidget {
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.8),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
return StatIconText(
|
||||
icon: Icons.play_circle_outlined,
|
||||
text: Utils.numFormat(view!),
|
||||
color: color,
|
||||
size: size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class StatIconText extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final String text;
|
||||
final Color color;
|
||||
final String? size;
|
||||
|
||||
const StatIconText({
|
||||
Key? key,
|
||||
required this.icon,
|
||||
required this.text,
|
||||
required this.color,
|
||||
this.size,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.play_circle_outlined,
|
||||
icon,
|
||||
size: 13,
|
||||
color: color,
|
||||
),
|
||||
const SizedBox(width: 2),
|
||||
Text(
|
||||
Utils.numFormat(view!),
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: size == 'medium' ? 12 : 11,
|
||||
color: color,
|
||||
|
||||
@ -266,17 +266,11 @@ class VideoContent extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
if (showView) ...[
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoItem.stat.view as int,
|
||||
),
|
||||
StatView(view: videoItem.stat.view as int),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
if (showDanmaku)
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: videoItem.stat.danmaku as int,
|
||||
),
|
||||
StatDanMu(danmu: videoItem.stat.danmaku as int),
|
||||
const Spacer(),
|
||||
if (source == 'normal')
|
||||
SizedBox(
|
||||
|
||||
@ -60,17 +60,13 @@ class VideoCardV extends StatelessWidget {
|
||||
// 动态
|
||||
case 'picture':
|
||||
try {
|
||||
String dynamicType = 'picture';
|
||||
String uri = videoItem.uri;
|
||||
String id = '';
|
||||
if (videoItem.uri.startsWith('bilibili://article/')) {
|
||||
// https://www.bilibili.com/read/cv27063554
|
||||
dynamicType = 'read';
|
||||
RegExp regex = RegExp(r'\d+');
|
||||
Match match = regex.firstMatch(videoItem.uri)!;
|
||||
String matchedNumber = match.group(0)!;
|
||||
videoItem.param = int.parse(matchedNumber);
|
||||
id = 'cv${videoItem.param}';
|
||||
}
|
||||
if (uri.startsWith('http')) {
|
||||
String path = Uri.parse(uri).path;
|
||||
@ -88,11 +84,10 @@ class VideoCardV extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
}
|
||||
Get.toNamed('/htmlRender', parameters: {
|
||||
'url': uri,
|
||||
Get.toNamed('/read', parameters: {
|
||||
'title': videoItem.title,
|
||||
'id': id,
|
||||
'dynamicType': dynamicType
|
||||
'id': videoItem.param,
|
||||
'articleType': 'read'
|
||||
});
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
@ -215,9 +210,8 @@ class VideoContent extends StatelessWidget {
|
||||
children: [
|
||||
if (videoItem.goto == 'bangumi')
|
||||
_buildBadge(videoItem.bangumiBadge, 'line', 9),
|
||||
if (videoItem.rcmdReason?.content != null &&
|
||||
videoItem.rcmdReason.content != '')
|
||||
_buildBadge(videoItem.rcmdReason.content, 'color'),
|
||||
if (videoItem.rcmdReason != null)
|
||||
_buildBadge(videoItem.rcmdReason, 'color'),
|
||||
if (videoItem.goto == 'picture') _buildBadge('动态', 'line', 9),
|
||||
if (videoItem.isFollowed == 1) _buildBadge('已关注', 'color'),
|
||||
Expanded(
|
||||
@ -288,9 +282,9 @@ class VideoStat extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
StatView(theme: 'gray', view: videoItem.stat.view),
|
||||
StatView(view: videoItem.stat.view),
|
||||
const SizedBox(width: 8),
|
||||
StatDanMu(theme: 'gray', danmu: videoItem.stat.danmu),
|
||||
StatDanMu(danmu: videoItem.stat.danmu),
|
||||
if (videoItem is RecVideoItemModel) ...<Widget>[
|
||||
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
|
||||
RichText(
|
||||
|
||||
@ -487,6 +487,8 @@ class Api {
|
||||
static const getSeasonDetailApi =
|
||||
'/x/polymer/web-space/seasons_archives_list';
|
||||
|
||||
static const getSeriesDetailApi = '/x/series/archives';
|
||||
|
||||
/// 获取未读动态数
|
||||
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
|
||||
|
||||
@ -548,4 +550,49 @@ class Api {
|
||||
|
||||
/// 收到的赞
|
||||
static const String messageLikeAPi = '/x/msgfeed/like';
|
||||
|
||||
/// 系统通知
|
||||
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';
|
||||
|
||||
/// 编辑收藏夹
|
||||
static const String editFavFolder = '/x/v3/fav/folder/edit';
|
||||
|
||||
/// 新建收藏夹
|
||||
static const String addFavFolder = '/x/v3/fav/folder/add';
|
||||
|
||||
/// 直播间弹幕信息
|
||||
static const String getDanmuInfo =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/getDanmuInfo';
|
||||
|
||||
/// 直播间发送弹幕
|
||||
static const String sendLiveMsg = '${HttpString.liveBaseUrl}/msg/send';
|
||||
|
||||
/// 我的关注 - 正在直播
|
||||
static const String getFollowingLive =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/following';
|
||||
|
||||
/// 稍后再看&收藏夹视频列表
|
||||
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 replyDel = '/x/v2/reply/del';
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -5,6 +5,8 @@ class HttpString {
|
||||
static const String appBaseUrl = 'https://app.bilibili.com';
|
||||
static const String liveBaseUrl = 'https://api.live.bilibili.com';
|
||||
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 List<int> validateStatusCodes = [
|
||||
302,
|
||||
304,
|
||||
|
||||
@ -67,9 +67,6 @@ class DanmakaHttp {
|
||||
var response = await Request().post(
|
||||
Api.shootDanmaku,
|
||||
data: params,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (response.statusCode != 200) {
|
||||
return {
|
||||
|
||||
@ -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(),
|
||||
@ -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,
|
||||
|
||||
67
lib/http/fav.dart
Normal file
67
lib/http/fav.dart
Normal file
@ -0,0 +1,67 @@
|
||||
import 'index.dart';
|
||||
|
||||
class FavHttp {
|
||||
/// 编辑收藏夹
|
||||
static Future editFolder({
|
||||
required String title,
|
||||
required String intro,
|
||||
required String mediaId,
|
||||
String? cover,
|
||||
int? privacy,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.editFavFolder,
|
||||
data: {
|
||||
'title': title,
|
||||
'intro': intro,
|
||||
'media_id': mediaId,
|
||||
'cover': cover ?? '',
|
||||
'privacy': privacy ?? 0,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// 新建收藏夹
|
||||
static Future addFolder({
|
||||
required String title,
|
||||
required String intro,
|
||||
String? cover,
|
||||
int? privacy,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.addFavFolder,
|
||||
data: {
|
||||
'title': title,
|
||||
'intro': intro,
|
||||
'cover': cover ?? '',
|
||||
'privacy': privacy ?? 0,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -27,11 +27,14 @@ class Request {
|
||||
late bool enableSystemProxy;
|
||||
late String systemProxyHost;
|
||||
late String systemProxyPort;
|
||||
static final RegExp spmPrefixExp = RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
|
||||
static final RegExp spmPrefixExp =
|
||||
RegExp(r'<meta name="spm_prefix" content="([^"]+?)">');
|
||||
static String? buvid;
|
||||
|
||||
/// 设置cookie
|
||||
static setCookie() async {
|
||||
Box userInfoCache = GStrorage.userInfo;
|
||||
Box setting = GStrorage.setting;
|
||||
final String cookiePath = await Utils.getCookiePath();
|
||||
final PersistCookieJar cookieJar = PersistCookieJar(
|
||||
ignoreExpires: true,
|
||||
@ -54,7 +57,11 @@ class Request {
|
||||
}
|
||||
}
|
||||
setOptionsHeaders(userInfo, userInfo != null && userInfo.mid != null);
|
||||
|
||||
String baseUrlType = 'default';
|
||||
if (setting.get(SettingBoxKey.enableGATMode, defaultValue: false)) {
|
||||
baseUrlType = 'bangumi';
|
||||
}
|
||||
setBaseUrl(type: baseUrlType);
|
||||
try {
|
||||
await buvidActivate();
|
||||
} catch (e) {
|
||||
@ -64,6 +71,7 @@ class Request {
|
||||
final String cookieString = cookie
|
||||
.map((Cookie cookie) => '${cookie.name}=${cookie.value}')
|
||||
.join('; ');
|
||||
|
||||
dio.options.headers['cookie'] = cookieString;
|
||||
}
|
||||
|
||||
@ -78,6 +86,30 @@ class Request {
|
||||
return token;
|
||||
}
|
||||
|
||||
static Future<String> getBuvid() async {
|
||||
if (buvid != null) {
|
||||
return buvid!;
|
||||
}
|
||||
|
||||
final List<Cookie> cookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||
buvid = cookies.firstWhere((cookie) => cookie.name == 'buvid3').value;
|
||||
if (buvid == null) {
|
||||
try {
|
||||
var result = await Request().get(
|
||||
"${HttpString.apiBaseUrl}/x/frontend/finger/spi",
|
||||
);
|
||||
buvid = result["data"]["b_3"].toString();
|
||||
} catch (e) {
|
||||
// 处理请求错误
|
||||
buvid = '';
|
||||
print("Error fetching buvid: $e");
|
||||
}
|
||||
}
|
||||
|
||||
return buvid!;
|
||||
}
|
||||
|
||||
static setOptionsHeaders(userInfo, bool status) {
|
||||
if (status) {
|
||||
dio.options.headers['x-bili-mid'] = userInfo.mid.toString();
|
||||
@ -95,11 +127,10 @@ class Request {
|
||||
String spmPrefix = spmPrefixExp.firstMatch(html.data)!.group(1)!;
|
||||
Random rand = Random();
|
||||
String rand_png_end = base64.encode(
|
||||
List<int>.generate(32, (_) => rand.nextInt(256)) +
|
||||
List<int>.filled(4, 0) +
|
||||
[73, 69, 78, 68] +
|
||||
List<int>.generate(4, (_) => rand.nextInt(256))
|
||||
);
|
||||
List<int>.generate(32, (_) => rand.nextInt(256)) +
|
||||
List<int>.filled(4, 0) +
|
||||
[73, 69, 78, 68] +
|
||||
List<int>.generate(4, (_) => rand.nextInt(256)));
|
||||
|
||||
String jsonData = json.encode({
|
||||
'3064': 1,
|
||||
@ -110,11 +141,9 @@ class Request {
|
||||
},
|
||||
});
|
||||
|
||||
await Request().post(
|
||||
Api.activateBuvidApi,
|
||||
data: {'payload': jsonData},
|
||||
options: Options(contentType: 'application/json')
|
||||
);
|
||||
await Request().post(Api.activateBuvidApi,
|
||||
data: {'payload': jsonData},
|
||||
options: Options(contentType: 'application/json'));
|
||||
}
|
||||
|
||||
/*
|
||||
@ -188,12 +217,13 @@ class Request {
|
||||
/*
|
||||
* get请求
|
||||
*/
|
||||
get(url, {data, options, cancelToken, extra}) async {
|
||||
get(url, {data, Options? options, cancelToken, extra}) async {
|
||||
Response response;
|
||||
final Options options = Options();
|
||||
options ??= Options(); // 如果 options 为 null,则初始化一个新的 Options 对象
|
||||
ResponseType resType = ResponseType.json;
|
||||
|
||||
if (extra != null) {
|
||||
resType = extra!['resType'] ?? ResponseType.json;
|
||||
resType = extra['resType'] ?? ResponseType.json;
|
||||
if (extra['ua'] != null) {
|
||||
options.headers = {'user-agent': headerUa(type: extra['ua'])};
|
||||
}
|
||||
@ -209,14 +239,11 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,7 +258,8 @@ class Request {
|
||||
url,
|
||||
data: data,
|
||||
queryParameters: queryParameters,
|
||||
options: options,
|
||||
options:
|
||||
options ?? Options(contentType: Headers.formUrlEncodedContentType),
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
// print('post success: ${response.data}');
|
||||
@ -294,4 +322,17 @@ class Request {
|
||||
}
|
||||
return headerUa;
|
||||
}
|
||||
|
||||
static setBaseUrl({String type = 'default'}) {
|
||||
switch (type) {
|
||||
case 'default':
|
||||
dio.options.baseUrl = HttpString.apiBaseUrl;
|
||||
break;
|
||||
case 'bangumi':
|
||||
dio.options.baseUrl = HttpString.bangumiBaseUrl;
|
||||
break;
|
||||
default:
|
||||
dio.options.baseUrl = HttpString.apiBaseUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -46,7 +46,8 @@ class ApiInterceptor extends Interceptor {
|
||||
// 处理网络请求错误
|
||||
// handler.next(err);
|
||||
String url = err.requestOptions.uri.toString();
|
||||
if (!url.contains('heartbeat')) {
|
||||
final excludedPatterns = RegExp(r'heartbeat|seg\.so|online/total');
|
||||
if (!excludedPatterns.hasMatch(url)) {
|
||||
SmartDialog.showToast(
|
||||
await dioError(err),
|
||||
displayType: SmartToastType.onlyRefresh,
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import 'package:pilipala/models/live/follow.dart';
|
||||
|
||||
import '../models/live/item.dart';
|
||||
import '../models/live/room_info.dart';
|
||||
import '../models/live/room_info_h5.dart';
|
||||
@ -65,4 +67,96 @@ class LiveHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取弹幕信息
|
||||
static Future liveDanmakuInfo({roomId}) async {
|
||||
var res = await Request().get(Api.getDanmuInfo, data: {
|
||||
'id': roomId,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 发送弹幕
|
||||
static Future sendDanmaku({roomId, msg}) async {
|
||||
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,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 我的关注 正在直播
|
||||
static Future liveFollowing({int? pn, int? ps}) async {
|
||||
var res = await Request().get(Api.getFollowingLive, data: {
|
||||
'page': pn,
|
||||
'page_size': ps,
|
||||
'platform': 'web',
|
||||
'ignoreRecord': 1,
|
||||
'hit_ab': true,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': LiveFollowingModel.fromJson(res.data['data'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 直播历史记录
|
||||
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': '',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,10 @@
|
||||
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/utils/global_data_cache.dart';
|
||||
import '../common/constants.dart';
|
||||
import '../models/dynamics/result.dart';
|
||||
import '../models/follow/result.dart';
|
||||
@ -15,14 +20,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,
|
||||
@ -95,7 +106,14 @@ class MemberHttp {
|
||||
'dm_img_str': dmImgStr.substring(0, dmImgStr.length - 2),
|
||||
'dm_cover_img_str': dmCoverImgStr.substring(0, dmCoverImgStr.length - 2),
|
||||
'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}',
|
||||
...order == 'charge'
|
||||
? {
|
||||
'order': 'pubdate',
|
||||
'special_type': 'charging',
|
||||
}
|
||||
: {}
|
||||
});
|
||||
|
||||
var res = await Request().get(
|
||||
Api.memberArchive,
|
||||
data: params,
|
||||
@ -187,13 +205,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 {
|
||||
@ -328,7 +348,9 @@ class MemberHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
||||
'data': res.data['data']['list']
|
||||
.map<MemberLikeDataModel>((e) => MemberLikeDataModel.fromJson(e))
|
||||
.toList(),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
@ -409,11 +431,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) {
|
||||
@ -510,4 +535,96 @@ class MemberHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
static Future getSeriesDetail({
|
||||
required int mid,
|
||||
required int currentMid,
|
||||
required int seriesId,
|
||||
required int pn,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.getSeriesDetailApi,
|
||||
data: {
|
||||
'mid': mid,
|
||||
'series_id': seriesId,
|
||||
'only_normal': true,
|
||||
'sort': 'desc',
|
||||
'pn': pn,
|
||||
'ps': 30,
|
||||
'current_mid': currentMid,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': MemberSeasonsDataModel.fromJson(res.data['data'])
|
||||
};
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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'<script id="__RENDER_DATA__" type="application/json">(.*?)</script>');
|
||||
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<String, dynamic> 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'] ?? '请求异常',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:pilipala/models/msg/like.dart';
|
||||
import 'package:pilipala/models/msg/reply.dart';
|
||||
import 'package:pilipala/models/msg/system.dart';
|
||||
import '../models/msg/account.dart';
|
||||
import '../models/msg/session.dart';
|
||||
import '../utils/wbi_sign.dart';
|
||||
@ -149,7 +149,7 @@ class MsgHttp {
|
||||
'msg[msg_status]': 0,
|
||||
'msg[content]': jsonEncode(content),
|
||||
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
'msg[new_face_version]': 0,
|
||||
'msg[new_face_version]': 1,
|
||||
'msg[dev_id]': getDevId(),
|
||||
'from_firework': 0,
|
||||
'build': 0,
|
||||
@ -157,9 +157,6 @@ class MsgHttp {
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
},
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
@ -287,4 +284,69 @@ class MsgHttp {
|
||||
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
static Future messageSystem() async {
|
||||
var res = await Request().get(Api.messageSystemAPi, 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<MessageSystemModel>((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 systemMarkRead(int cursor) async {
|
||||
String csrf = await Request.getCsrf();
|
||||
var res = await Request().get(Api.systemMarkRead, data: {
|
||||
'csrf': csrf,
|
||||
'cursor': cursor,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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<MessageSystemModel>((e) => MessageSystemModel.fromJson(e))
|
||||
.toList(),
|
||||
};
|
||||
} catch (err) {
|
||||
return {'status': false, 'date': [], 'msg': err.toString()};
|
||||
}
|
||||
} else {
|
||||
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
116
lib/http/read.dart
Normal file
116
lib/http/read.dart
Normal file
@ -0,0 +1,116 @@
|
||||
import 'dart:convert';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:pilipala/models/read/opus.dart';
|
||||
import 'package:pilipala/models/read/read.dart';
|
||||
import 'package:pilipala/utils/wbi_sign.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class ReadHttp {
|
||||
static List<String> extractScriptContents(String htmlContent) {
|
||||
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
||||
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
||||
List<String> scriptContents = [];
|
||||
for (Match match in matches) {
|
||||
String scriptContent = match.group(1)!;
|
||||
scriptContents.add(scriptContent);
|
||||
}
|
||||
return scriptContents;
|
||||
}
|
||||
|
||||
// 解析专栏 opus格式
|
||||
static Future parseArticleOpus({required String id}) async {
|
||||
var res = await Request().get('https://www.bilibili.com/opus/$id', extra: {
|
||||
'ua': 'pc',
|
||||
});
|
||||
String? headContent = parse(res.data).head?.outerHtml;
|
||||
var document = parse(headContent);
|
||||
var linkTags = document.getElementsByTagName('link');
|
||||
bool isCv = false;
|
||||
String cvId = '';
|
||||
for (var linkTag in linkTags) {
|
||||
var attributes = linkTag.attributes;
|
||||
if (attributes.containsKey('rel') &&
|
||||
attributes['rel'] == 'canonical' &&
|
||||
attributes.containsKey('data-vue-meta') &&
|
||||
attributes['data-vue-meta'] == 'true') {
|
||||
final String cvHref = linkTag.attributes['href']!;
|
||||
RegExp regex = RegExp(r'cv(\d+)');
|
||||
RegExpMatch? match = regex.firstMatch(cvHref);
|
||||
if (match != null) {
|
||||
cvId = match.group(1)!;
|
||||
} else {
|
||||
print('No match found.');
|
||||
}
|
||||
isCv = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
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<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
return {
|
||||
'status': true,
|
||||
'data': OpusDataModel.fromJson(jsonData),
|
||||
'isCv': isCv,
|
||||
'cvId': cvId,
|
||||
};
|
||||
}
|
||||
|
||||
// 解析专栏 cv格式
|
||||
static Future parseArticleCv({required String id}) async {
|
||||
var res = await Request().get(
|
||||
'https://www.bilibili.com/read/cv$id',
|
||||
extra: {'ua': 'pc'},
|
||||
);
|
||||
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<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
return {
|
||||
'status': true,
|
||||
'data': ReadDataModel.fromJson(jsonData),
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
static Future getViewInfo({required String id}) async {
|
||||
Map params = await WbiSign().makSign({
|
||||
'id': id,
|
||||
'mobi_app': 'pc',
|
||||
'from': 'web',
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.976,
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.getViewInfo,
|
||||
data: {
|
||||
'id': id,
|
||||
'mobi_app': 'pc',
|
||||
'from': 'web',
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.976,
|
||||
'w_rid': params['w_rid'],
|
||||
'wts': params['wts'],
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,7 +78,7 @@ class ReplyHttp {
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.likeReply,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'type': type,
|
||||
'oid': oid,
|
||||
'rpid': rpid,
|
||||
@ -115,4 +115,25 @@ 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']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +88,11 @@ class SearchHttp {
|
||||
if (tids != null && tids != -1) 'tids': tids,
|
||||
};
|
||||
var res = await Request().get(Api.searchByType, data: reqData);
|
||||
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {
|
||||
if (res.data['code'] == 0) {
|
||||
if (res.data['data']['numPages'] == 0) {
|
||||
// 我想返回数据,使得可以通过data.list 取值,结果为[]
|
||||
return {'status': true, 'data': Data()};
|
||||
}
|
||||
Object data;
|
||||
try {
|
||||
switch (searchType) {
|
||||
@ -125,9 +129,7 @@ class SearchHttp {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['data'] != null && res.data['data']['numPages'] == 0
|
||||
? '没有相关数据'
|
||||
: res.data['message'],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -141,7 +143,11 @@ class SearchHttp {
|
||||
}
|
||||
final dynamic res =
|
||||
await Request().get(Api.ab2c, data: <String, dynamic>{...data});
|
||||
return res.data['data'].first['cid'];
|
||||
if (res.data['code'] == 0) {
|
||||
return res.data['data'].first['cid'];
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<Map<String, dynamic>> bangumiInfo(
|
||||
@ -206,3 +212,9 @@ class SearchHttp {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Data {
|
||||
List<dynamic> list;
|
||||
|
||||
Data({this.list = const []});
|
||||
}
|
||||
|
||||
@ -1,4 +1,9 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.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';
|
||||
@ -57,6 +62,8 @@ class UserHttp {
|
||||
if (res.data['data'] != null) {
|
||||
data = FavFolderData.fromJson(res.data['data']);
|
||||
return {'status': true, 'data': data};
|
||||
} else {
|
||||
return {'status': false, 'msg': '收藏夹为空'};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
@ -146,7 +153,7 @@ class UserHttp {
|
||||
// 暂停switchStatus传true 否则false
|
||||
var res = await Request().post(
|
||||
Api.pauseHistory,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'switch': switchStatus,
|
||||
'jsonp': 'jsonp',
|
||||
'csrf': await Request.getCsrf(),
|
||||
@ -165,7 +172,7 @@ class UserHttp {
|
||||
static Future clearHistory() async {
|
||||
var res = await Request().post(
|
||||
Api.clearHistory,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'jsonp': 'jsonp',
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
@ -183,7 +190,7 @@ class UserHttp {
|
||||
}
|
||||
var res = await Request().post(
|
||||
Api.toViewLater,
|
||||
queryParameters: data,
|
||||
data: data,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'msg': 'yeah!稍后再看'};
|
||||
@ -202,7 +209,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!成功移除'};
|
||||
@ -234,7 +241,7 @@ class UserHttp {
|
||||
static Future toViewClear() async {
|
||||
var res = await Request().post(
|
||||
Api.toViewClear,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'jsonp': 'jsonp',
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
@ -250,7 +257,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(),
|
||||
@ -399,7 +406,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(),
|
||||
@ -416,7 +423,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(),
|
||||
@ -428,4 +435,106 @@ class UserHttp {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 稍后再看播放全部
|
||||
// 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<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
// // 输出解析后的数据
|
||||
// return {
|
||||
// 'status': true,
|
||||
// 'data': jsonData['resourceList']
|
||||
// .map((e) => MediaVideoItemModel.fromJson(e))
|
||||
// .toList()
|
||||
// };
|
||||
// }
|
||||
|
||||
static List<String> extractScriptContents(String htmlContent) {
|
||||
RegExp scriptRegExp = RegExp(r'<script>([\s\S]*?)<\/script>');
|
||||
Iterable<Match> matches = scriptRegExp.allMatches(htmlContent);
|
||||
List<String> scriptContents = [];
|
||||
for (Match match in matches) {
|
||||
String scriptContent = match.group(1)!;
|
||||
scriptContents.add(scriptContent);
|
||||
}
|
||||
return scriptContents;
|
||||
}
|
||||
|
||||
// 稍后再看列表
|
||||
static Future getMediaList({
|
||||
required int type,
|
||||
required int bizId,
|
||||
required int ps,
|
||||
int? oid,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.mediaList,
|
||||
data: {
|
||||
'mobi_app': 'web',
|
||||
'type': type,
|
||||
'biz_id': bizId,
|
||||
'oid': oid ?? '',
|
||||
'otype': 2,
|
||||
'ps': ps,
|
||||
'direction': false,
|
||||
'desc': true,
|
||||
'sort_field': 1,
|
||||
'tid': 0,
|
||||
'with_current': false,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data']['media_list'] != null
|
||||
? res.data['data']['media_list']
|
||||
.map<MediaVideoItemModel>(
|
||||
(e) => MediaVideoItemModel.fromJson(e))
|
||||
.toList()
|
||||
: []
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 解析收藏夹视频
|
||||
static Future parseFavVideo({
|
||||
required int mediaId,
|
||||
required int oid,
|
||||
required String bvid,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
'https://www.bilibili.com/list/ml$mediaId',
|
||||
data: {
|
||||
'oid': mediaId,
|
||||
'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<String, dynamic> jsonData = json.decode(jsonContent);
|
||||
return {
|
||||
'status': true,
|
||||
'data': jsonData['resourceList']
|
||||
.map<MediaVideoItemModel>((e) => MediaVideoItemModel.fromJson(e))
|
||||
.toList()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,47 +70,43 @@ class VideoHttp {
|
||||
// 添加额外的loginState变量模拟未登录状态
|
||||
static Future rcmdVideoListApp(
|
||||
{bool loginStatus = true, required int freshIdx}) async {
|
||||
try {
|
||||
var res = await Request().get(
|
||||
Api.recommendListApp,
|
||||
data: {
|
||||
'idx': freshIdx,
|
||||
'flush': '5',
|
||||
'column': '4',
|
||||
'device': 'pad',
|
||||
'device_type': 0,
|
||||
'device_name': 'vivo',
|
||||
'pull': freshIdx == 0 ? 'true' : 'false',
|
||||
'appkey': Constants.appKey,
|
||||
'access_key': loginStatus
|
||||
? (localCache.get(LocalCacheKey.accessKey,
|
||||
defaultValue: {})['value'] ??
|
||||
'')
|
||||
: ''
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemAppModel> list = [];
|
||||
List<int> blackMidsList =
|
||||
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||
for (var i in res.data['data']['items']) {
|
||||
// 屏蔽推广和拉黑用户
|
||||
if (i['card_goto'] != 'ad_av' &&
|
||||
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
|
||||
(i['args'] != null &&
|
||||
!blackMidsList.contains(i['args']['up_mid']))) {
|
||||
RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem)) {
|
||||
list.add(videoItem);
|
||||
}
|
||||
var res = await Request().get(
|
||||
Api.recommendListApp,
|
||||
data: {
|
||||
'idx': freshIdx,
|
||||
'flush': '5',
|
||||
'column': '4',
|
||||
'device': 'pad',
|
||||
'device_type': 0,
|
||||
'device_name': 'vivo',
|
||||
'pull': freshIdx == 0 ? 'true' : 'false',
|
||||
'appkey': Constants.appKey,
|
||||
'access_key': loginStatus
|
||||
? (localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'')
|
||||
: ''
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemAppModel> list = [];
|
||||
List<int> blackMidsList =
|
||||
setting.get(SettingBoxKey.blackMidsList, defaultValue: [-1]);
|
||||
for (var i in res.data['data']['items']) {
|
||||
// 屏蔽推广和拉黑用户
|
||||
if (i['card_goto'] != 'ad_av' &&
|
||||
(!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) &&
|
||||
(i['args'] != null &&
|
||||
!blackMidsList.contains(i['args']['up_mid']))) {
|
||||
RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem)) {
|
||||
list.add(videoItem);
|
||||
}
|
||||
}
|
||||
return {'status': true, 'data': list};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
} catch (err) {
|
||||
return {'status': false, 'data': [], 'msg': err.toString()};
|
||||
return {'status': true, 'data': list};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,7 +243,7 @@ class VideoHttp {
|
||||
static Future coinVideo({required String bvid, required int multiply}) async {
|
||||
var res = await Request().post(
|
||||
Api.coinVideo,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'bvid': bvid,
|
||||
'multiply': multiply,
|
||||
'select_like': 0,
|
||||
@ -275,7 +271,7 @@ class VideoHttp {
|
||||
static Future oneThree({required String bvid}) async {
|
||||
var res = await Request().post(
|
||||
Api.oneThree,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'bvid': bvid,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
@ -291,7 +287,7 @@ class VideoHttp {
|
||||
static Future likeVideo({required String bvid, required bool type}) async {
|
||||
var res = await Request().post(
|
||||
Api.likeVideo,
|
||||
queryParameters: {
|
||||
data: {
|
||||
'bvid': bvid,
|
||||
'like': type ? 1 : 2,
|
||||
'csrf': await Request.getCsrf(),
|
||||
@ -307,13 +303,16 @@ class VideoHttp {
|
||||
// (取消)收藏
|
||||
static Future favVideo(
|
||||
{required int aid, String? addIds, String? delIds}) async {
|
||||
var res = await Request().post(Api.favVideo, queryParameters: {
|
||||
'rid': aid,
|
||||
'type': 2,
|
||||
'add_media_ids': addIds ?? '',
|
||||
'del_media_ids': delIds ?? '',
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
var res = await Request().post(
|
||||
Api.favVideo,
|
||||
data: {
|
||||
'rid': aid,
|
||||
'type': 2,
|
||||
'add_media_ids': addIds ?? '',
|
||||
'del_media_ids': delIds ?? '',
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
@ -351,14 +350,17 @@ class VideoHttp {
|
||||
if (message == '') {
|
||||
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
||||
}
|
||||
var res = await Request().post(Api.replyAdd, queryParameters: {
|
||||
'type': type.index,
|
||||
'oid': oid,
|
||||
'root': root == null || root == 0 ? '' : root,
|
||||
'parent': parent == null || parent == 0 ? '' : parent,
|
||||
'message': message,
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
var res = await Request().post(
|
||||
Api.replyAdd,
|
||||
data: {
|
||||
'type': type.index,
|
||||
'oid': oid,
|
||||
'root': root == null || root == 0 ? '' : root,
|
||||
'parent': parent == null || parent == 0 ? '' : parent,
|
||||
'message': message,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
log(res.toString());
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
@ -380,12 +382,15 @@ class VideoHttp {
|
||||
// 操作用户关系
|
||||
static Future relationMod(
|
||||
{required int mid, required int act, required int reSrc}) async {
|
||||
var res = await Request().post(Api.relationMod, queryParameters: {
|
||||
'fid': mid,
|
||||
'act': act,
|
||||
're_src': reSrc,
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
var res = await Request().post(
|
||||
Api.relationMod,
|
||||
data: {
|
||||
'fid': mid,
|
||||
'act': act,
|
||||
're_src': reSrc,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
if (act == 5) {
|
||||
List<int> blackMidsList =
|
||||
@ -401,27 +406,33 @@ class VideoHttp {
|
||||
|
||||
// 视频播放进度
|
||||
static Future heartBeat({bvid, cid, progress, realtime}) async {
|
||||
await Request().post(Api.heartBeat, queryParameters: {
|
||||
// 'aid': aid,
|
||||
'bvid': bvid,
|
||||
'cid': cid,
|
||||
// 'epid': '',
|
||||
// 'sid': '',
|
||||
// 'mid': '',
|
||||
'played_time': progress,
|
||||
// 'realtime': realtime,
|
||||
// 'type': '',
|
||||
// 'sub_type': '',
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
await Request().post(
|
||||
Api.heartBeat,
|
||||
data: {
|
||||
// 'aid': aid,
|
||||
'bvid': bvid,
|
||||
'cid': cid,
|
||||
// 'epid': '',
|
||||
// 'sid': '',
|
||||
// 'mid': '',
|
||||
'played_time': progress,
|
||||
// 'realtime': realtime,
|
||||
// 'type': '',
|
||||
// 'sub_type': '',
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 添加追番
|
||||
static Future bangumiAdd({int? seasonId}) async {
|
||||
var res = await Request().post(Api.bangumiAdd, queryParameters: {
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
var res = await Request().post(
|
||||
Api.bangumiAdd,
|
||||
data: {
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'msg': res.data['result']['toast']};
|
||||
} else {
|
||||
@ -431,10 +442,13 @@ class VideoHttp {
|
||||
|
||||
// 取消追番
|
||||
static Future bangumiDel({int? seasonId}) async {
|
||||
var res = await Request().post(Api.bangumiDel, queryParameters: {
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
});
|
||||
var res = await Request().post(
|
||||
Api.bangumiDel,
|
||||
data: {
|
||||
'season_id': seasonId,
|
||||
'csrf': await Request.getCsrf(),
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'msg': res.data['result']['toast']};
|
||||
} else {
|
||||
@ -492,7 +506,7 @@ class VideoHttp {
|
||||
return {'status': false, 'data': [], 'msg': res.data['msg']};
|
||||
}
|
||||
} catch (err) {
|
||||
print(err);
|
||||
return {'status': false, 'data': [], 'msg': res.data['msg']};
|
||||
}
|
||||
}
|
||||
|
||||
@ -522,7 +536,8 @@ class VideoHttp {
|
||||
// 获取字幕内容
|
||||
static Future<Map<String, dynamic>> getSubtitleContent(url) async {
|
||||
var res = await Request().get('https:$url');
|
||||
final String content = SubTitleUtils.convertToWebVTT(res.data['body']);
|
||||
final String content =
|
||||
await SubTitleUtils.convertToWebVTT(res.data['body']);
|
||||
final List body = res.data['body'];
|
||||
return {'content': content, 'body': body};
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@ import 'package:pilipala/pages/main/view.dart';
|
||||
import 'package:pilipala/services/service_locator.dart';
|
||||
import 'package:pilipala/utils/app_scheme.dart';
|
||||
import 'package:pilipala/utils/data.dart';
|
||||
import 'package:pilipala/utils/global_data.dart';
|
||||
import 'package:pilipala/utils/global_data_cache.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:pilipala/utils/recommend_filter.dart';
|
||||
@ -33,7 +33,6 @@ void main() async {
|
||||
await SystemChrome.setPreferredOrientations(
|
||||
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
|
||||
await GStrorage.init();
|
||||
await setupServiceLocator();
|
||||
clearLogs();
|
||||
Request();
|
||||
await Request.setCookie();
|
||||
@ -61,10 +60,12 @@ void main() async {
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarDividerColor: Colors.transparent,
|
||||
statusBarColor: Colors.transparent,
|
||||
systemNavigationBarContrastEnforced: false,
|
||||
));
|
||||
}
|
||||
|
||||
PiliSchame.init();
|
||||
await GlobalDataCache().initialize();
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@ -266,10 +267,10 @@ class BuildMainApp extends StatelessWidget {
|
||||
VideoDetailPage.routeObserver,
|
||||
SearchPage.routeObserver,
|
||||
],
|
||||
onInit: () {
|
||||
onReady: () async {
|
||||
RecommendFilter();
|
||||
Data.init();
|
||||
GlobalData();
|
||||
setupServiceLocator();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,9 +4,12 @@ enum FullScreenGestureMode {
|
||||
|
||||
/// 从下滑到上
|
||||
fromBottomtoTop,
|
||||
|
||||
/// 关闭手势
|
||||
none,
|
||||
}
|
||||
|
||||
extension FullScreenGestureModeExtension on FullScreenGestureMode {
|
||||
String get values => ['fromToptoBottom', 'fromBottomtoTop'][index];
|
||||
String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏'][index];
|
||||
String get values => ['fromToptoBottom', 'fromBottomtoTop', 'none'][index];
|
||||
String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏', '关闭手势'][index];
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ extension SearchTypeExtension on SearchType {
|
||||
String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index];
|
||||
}
|
||||
|
||||
// 搜索类型为视频、专栏及相簿时
|
||||
// 搜索类型为视频时
|
||||
enum ArchiveFilterType {
|
||||
totalrank,
|
||||
click,
|
||||
@ -44,3 +44,21 @@ extension ArchiveFilterTypeExtension on ArchiveFilterType {
|
||||
String get description =>
|
||||
['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index];
|
||||
}
|
||||
|
||||
// 搜索类型为专栏时
|
||||
enum ArticleFilterType {
|
||||
// 综合排序
|
||||
totalrank,
|
||||
// 最新发布
|
||||
pubdate,
|
||||
// 最多点击
|
||||
click,
|
||||
// 最多喜欢
|
||||
attention,
|
||||
// 最多评论
|
||||
scores,
|
||||
}
|
||||
|
||||
extension ArticleFilterTypeExtension on ArticleFilterType {
|
||||
String get description => ['综合排序', '最新发布', '最多点击', '最多喜欢', '最多评论'][index];
|
||||
}
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
enum SubtitleType {
|
||||
// 中文(中国)
|
||||
zhCN,
|
||||
// 中文(自动翻译)
|
||||
aizh,
|
||||
// 英语(自动生成)
|
||||
aien,
|
||||
// 中文(简体)
|
||||
zhHans,
|
||||
// 英文(美国)
|
||||
enUS,
|
||||
}
|
||||
|
||||
extension SubtitleTypeExtension on SubtitleType {
|
||||
String get description {
|
||||
switch (this) {
|
||||
case SubtitleType.zhCN:
|
||||
return '中文(中国)';
|
||||
case SubtitleType.aizh:
|
||||
return '中文(自动翻译)';
|
||||
case SubtitleType.aien:
|
||||
return '英语(自动生成)';
|
||||
case SubtitleType.zhHans:
|
||||
return '中文(简体)';
|
||||
case SubtitleType.enUS:
|
||||
return '英文(美国)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SubtitleIdExtension on SubtitleType {
|
||||
String get id {
|
||||
switch (this) {
|
||||
case SubtitleType.zhCN:
|
||||
return 'zh-CN';
|
||||
case SubtitleType.aizh:
|
||||
return 'ai-zh';
|
||||
case SubtitleType.aien:
|
||||
return 'ai-en';
|
||||
case SubtitleType.zhHans:
|
||||
return 'zh-Hans';
|
||||
case SubtitleType.enUS:
|
||||
return 'en-US';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SubtitleCodeExtension on SubtitleType {
|
||||
int get code {
|
||||
switch (this) {
|
||||
case SubtitleType.zhCN:
|
||||
return 1;
|
||||
case SubtitleType.aizh:
|
||||
return 2;
|
||||
case SubtitleType.aien:
|
||||
return 3;
|
||||
case SubtitleType.zhHans:
|
||||
return 4;
|
||||
case SubtitleType.enUS:
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -34,7 +34,7 @@ class RecVideoItemAppModel {
|
||||
String? title;
|
||||
int? isFollowed;
|
||||
RcmdOwner? owner;
|
||||
RcmdReason? rcmdReason;
|
||||
String? rcmdReason;
|
||||
String? goto;
|
||||
int? param;
|
||||
String? uri;
|
||||
@ -62,25 +62,19 @@ class RecVideoItemAppModel {
|
||||
duration =
|
||||
json['player_args'] != null ? json['player_args']['duration'] : -1;
|
||||
//duration = json['cover_right_text'];
|
||||
title = json['title'];
|
||||
title = json['title'] ?? '获取标题失败';
|
||||
owner = RcmdOwner.fromJson(json);
|
||||
rcmdReason = json['rcmd_reason_style'] != null
|
||||
? RcmdReason.fromJson(json['rcmd_reason_style'])
|
||||
: null;
|
||||
rcmdReason = json['bottom_rcmd_reason'] ?? json['top_rcmd_reason'];
|
||||
// 由于app端api并不会直接返回与owner的关注状态
|
||||
// 所以借用推荐原因是否为“已关注”、“新关注”等判别关注状态,从而与web端接口等效
|
||||
RegExp regex = RegExp(r'已关注|新关注');
|
||||
isFollowed = rcmdReason != null &&
|
||||
rcmdReason!.content != null &&
|
||||
regex.hasMatch(rcmdReason!.content!)
|
||||
? 1
|
||||
: 0;
|
||||
isFollowed = regex.hasMatch(rcmdReason ?? '') ? 1 : 0;
|
||||
// 如果是,就无需再显示推荐原因,交由view统一处理即可
|
||||
if (isFollowed == 1) {
|
||||
rcmdReason = null;
|
||||
}
|
||||
goto = json['goto'];
|
||||
param = int.parse(json['param']);
|
||||
param = int.parse(json['param'] ?? '-1');
|
||||
uri = json['uri'];
|
||||
talkBack = json['talk_back'];
|
||||
|
||||
|
||||
126
lib/models/live/follow.dart
Normal file
126
lib/models/live/follow.dart
Normal file
@ -0,0 +1,126 @@
|
||||
class LiveFollowingModel {
|
||||
int? count;
|
||||
List<LiveFollowingItemModel>? list;
|
||||
int? liveCount;
|
||||
int? neverLivedCount;
|
||||
List? neverLivedFaces;
|
||||
int? pageSize;
|
||||
String? title;
|
||||
int? totalPage;
|
||||
|
||||
LiveFollowingModel({
|
||||
this.count,
|
||||
this.list,
|
||||
this.liveCount,
|
||||
this.neverLivedCount,
|
||||
this.neverLivedFaces,
|
||||
this.pageSize,
|
||||
this.title,
|
||||
this.totalPage,
|
||||
});
|
||||
|
||||
LiveFollowingModel.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
if (json['list'] != null) {
|
||||
list = <LiveFollowingItemModel>[];
|
||||
json['list'].forEach((v) {
|
||||
list!.add(LiveFollowingItemModel.fromJson(v));
|
||||
});
|
||||
}
|
||||
liveCount = json['live_count'];
|
||||
neverLivedCount = json['never_lived_count'];
|
||||
if (json['never_lived_faces'] != null) {
|
||||
neverLivedFaces = <dynamic>[];
|
||||
json['never_lived_faces'].forEach((v) {
|
||||
neverLivedFaces!.add(v);
|
||||
});
|
||||
}
|
||||
pageSize = json['pageSize'];
|
||||
title = json['title'];
|
||||
totalPage = json['totalPage'];
|
||||
}
|
||||
}
|
||||
|
||||
class LiveFollowingItemModel {
|
||||
int? roomId;
|
||||
int? uid;
|
||||
String? uname;
|
||||
String? title;
|
||||
String? face;
|
||||
int? liveStatus;
|
||||
int? recordNum;
|
||||
String? recentRecordId;
|
||||
int? isAttention;
|
||||
int? clipNum;
|
||||
int? fansNum;
|
||||
String? areaName;
|
||||
String? areaValue;
|
||||
String? tags;
|
||||
String? recentRecordIdV2;
|
||||
int? recordNumV2;
|
||||
int? recordLiveTime;
|
||||
String? areaNameV2;
|
||||
String? roomNews;
|
||||
String? watchIcon;
|
||||
String? textSmall;
|
||||
String? roomCover;
|
||||
String? pic;
|
||||
int? parentAreaId;
|
||||
int? areaId;
|
||||
|
||||
LiveFollowingItemModel({
|
||||
this.roomId,
|
||||
this.uid,
|
||||
this.uname,
|
||||
this.title,
|
||||
this.face,
|
||||
this.liveStatus,
|
||||
this.recordNum,
|
||||
this.recentRecordId,
|
||||
this.isAttention,
|
||||
this.clipNum,
|
||||
this.fansNum,
|
||||
this.areaName,
|
||||
this.areaValue,
|
||||
this.tags,
|
||||
this.recentRecordIdV2,
|
||||
this.recordNumV2,
|
||||
this.recordLiveTime,
|
||||
this.areaNameV2,
|
||||
this.roomNews,
|
||||
this.watchIcon,
|
||||
this.textSmall,
|
||||
this.roomCover,
|
||||
this.pic,
|
||||
this.parentAreaId,
|
||||
this.areaId,
|
||||
});
|
||||
|
||||
LiveFollowingItemModel.fromJson(Map<String, dynamic> json) {
|
||||
roomId = json['roomid'];
|
||||
uid = json['uid'];
|
||||
uname = json['uname'];
|
||||
title = json['title'];
|
||||
face = json['face'];
|
||||
liveStatus = json['live_status'];
|
||||
recordNum = json['record_num'];
|
||||
recentRecordId = json['recent_record_id'];
|
||||
isAttention = json['is_attention'];
|
||||
clipNum = json['clipnum'];
|
||||
fansNum = json['fans_num'];
|
||||
areaName = json['area_name'];
|
||||
areaValue = json['area_value'];
|
||||
tags = json['tags'];
|
||||
recentRecordIdV2 = json['recent_record_id_v2'];
|
||||
recordNumV2 = json['record_num_v2'];
|
||||
recordLiveTime = json['record_live_time'];
|
||||
areaNameV2 = json['area_name_v2'];
|
||||
roomNews = json['room_news'];
|
||||
watchIcon = json['watch_icon'];
|
||||
textSmall = json['text_small'];
|
||||
roomCover = json['room_cover'];
|
||||
pic = json['room_cover'];
|
||||
parentAreaId = json['parent_area_id'];
|
||||
areaId = json['area_id'];
|
||||
}
|
||||
}
|
||||
101
lib/models/live/message.dart
Normal file
101
lib/models/live/message.dart
Normal file
@ -0,0 +1,101 @@
|
||||
class LiveMessageModel {
|
||||
// 消息类型
|
||||
final LiveMessageType type;
|
||||
|
||||
// 用户名
|
||||
final String userName;
|
||||
|
||||
// 信息
|
||||
final String? message;
|
||||
|
||||
// 数据
|
||||
final dynamic data;
|
||||
|
||||
final String? face;
|
||||
final int? uid;
|
||||
final Map<String, dynamic>? emots;
|
||||
|
||||
// 颜色
|
||||
final LiveMessageColor color;
|
||||
|
||||
LiveMessageModel({
|
||||
required this.type,
|
||||
required this.userName,
|
||||
required this.message,
|
||||
required this.color,
|
||||
this.data,
|
||||
this.face,
|
||||
this.uid,
|
||||
this.emots,
|
||||
});
|
||||
}
|
||||
|
||||
class LiveSuperChatMessage {
|
||||
final String backgroundBottomColor;
|
||||
final String backgroundColor;
|
||||
final DateTime endTime;
|
||||
final String face;
|
||||
final String message;
|
||||
final String price;
|
||||
final DateTime startTime;
|
||||
final String userName;
|
||||
|
||||
LiveSuperChatMessage({
|
||||
required this.backgroundBottomColor,
|
||||
required this.backgroundColor,
|
||||
required this.endTime,
|
||||
required this.face,
|
||||
required this.message,
|
||||
required this.price,
|
||||
required this.startTime,
|
||||
required this.userName,
|
||||
});
|
||||
}
|
||||
|
||||
enum LiveMessageType {
|
||||
// 普通留言
|
||||
chat,
|
||||
// 醒目留言
|
||||
superChat,
|
||||
//
|
||||
online,
|
||||
// 加入
|
||||
join,
|
||||
// 关注
|
||||
follow,
|
||||
}
|
||||
|
||||
class LiveMessageColor {
|
||||
final int r, g, b;
|
||||
LiveMessageColor(this.r, this.g, this.b);
|
||||
static LiveMessageColor get white => LiveMessageColor(255, 255, 255);
|
||||
static LiveMessageColor numberToColor(int intColor) {
|
||||
var obj = intColor.toRadixString(16);
|
||||
|
||||
LiveMessageColor color = LiveMessageColor.white;
|
||||
if (obj.length == 4) {
|
||||
obj = "00$obj";
|
||||
}
|
||||
if (obj.length == 6) {
|
||||
var R = int.parse(obj.substring(0, 2), radix: 16);
|
||||
var G = int.parse(obj.substring(2, 4), radix: 16);
|
||||
var B = int.parse(obj.substring(4, 6), radix: 16);
|
||||
|
||||
color = LiveMessageColor(R, G, B);
|
||||
}
|
||||
if (obj.length == 8) {
|
||||
var R = int.parse(obj.substring(2, 4), radix: 16);
|
||||
var G = int.parse(obj.substring(4, 6), radix: 16);
|
||||
var B = int.parse(obj.substring(6, 8), radix: 16);
|
||||
//var A = int.parse(obj.substring(0, 2), radix: 16);
|
||||
color = LiveMessageColor(R, G, B);
|
||||
}
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "#${r.toRadixString(16).padLeft(2, '0')}${g.toRadixString(16).padLeft(2, '0')}${b.toRadixString(16).padLeft(2, '0')}";
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,13 @@
|
||||
class RoomInfoModel {
|
||||
RoomInfoModel({
|
||||
this.roomId,
|
||||
this.isPortrait,
|
||||
this.liveStatus,
|
||||
this.liveTime,
|
||||
this.playurlInfo,
|
||||
});
|
||||
int? roomId;
|
||||
bool? isPortrait;
|
||||
int? liveStatus;
|
||||
int? liveTime;
|
||||
PlayurlInfo? playurlInfo;
|
||||
@ -13,6 +15,7 @@ class RoomInfoModel {
|
||||
RoomInfoModel.fromJson(Map<String, dynamic> json) {
|
||||
roomId = json['room_id'];
|
||||
liveStatus = json['live_status'];
|
||||
isPortrait = json['is_portrait'];
|
||||
liveTime = json['live_time'];
|
||||
playurlInfo = PlayurlInfo.fromJson(json['playurl_info']);
|
||||
}
|
||||
|
||||
46
lib/models/member/article.dart
Normal file
46
lib/models/member/article.dart
Normal file
@ -0,0 +1,46 @@
|
||||
class MemberArticleDataModel {
|
||||
MemberArticleDataModel({
|
||||
this.hasMore,
|
||||
this.items,
|
||||
this.offset,
|
||||
this.updateNum,
|
||||
});
|
||||
|
||||
bool? hasMore;
|
||||
List<MemberArticleItemModel>? items;
|
||||
String? offset;
|
||||
int? updateNum;
|
||||
|
||||
MemberArticleDataModel.fromJson(Map<String, dynamic> json) {
|
||||
hasMore = json['has_more'];
|
||||
items = json['items']
|
||||
.map<MemberArticleItemModel>((e) => MemberArticleItemModel.fromJson(e))
|
||||
.toList();
|
||||
offset = json['offset'];
|
||||
updateNum = json['update_num'];
|
||||
}
|
||||
}
|
||||
|
||||
class MemberArticleItemModel {
|
||||
MemberArticleItemModel({
|
||||
this.content,
|
||||
this.cover,
|
||||
this.jumpUrl,
|
||||
this.opusId,
|
||||
this.stat,
|
||||
});
|
||||
|
||||
String? content;
|
||||
Map? cover;
|
||||
String? jumpUrl;
|
||||
String? opusId;
|
||||
Map? stat;
|
||||
|
||||
MemberArticleItemModel.fromJson(Map<String, dynamic> json) {
|
||||
content = json['content'];
|
||||
cover = json['cover'];
|
||||
jumpUrl = json['jump_url'];
|
||||
opusId = json['opus_id'];
|
||||
stat = json['stat'];
|
||||
}
|
||||
}
|
||||
210
lib/models/member/like.dart
Normal file
210
lib/models/member/like.dart
Normal file
@ -0,0 +1,210 @@
|
||||
class MemberLikeDataModel {
|
||||
MemberLikeDataModel({
|
||||
this.aid,
|
||||
this.videos,
|
||||
this.tid,
|
||||
this.tname,
|
||||
this.pic,
|
||||
this.title,
|
||||
this.pubdate,
|
||||
this.ctime,
|
||||
this.desc,
|
||||
this.state,
|
||||
this.duration,
|
||||
this.redirectUrl,
|
||||
this.rights,
|
||||
this.owner,
|
||||
this.stat,
|
||||
this.dimension,
|
||||
this.cover43,
|
||||
this.bvid,
|
||||
this.interVideo,
|
||||
this.resourceType,
|
||||
this.subtitle,
|
||||
this.enableVt,
|
||||
});
|
||||
|
||||
final int? aid;
|
||||
final int? videos;
|
||||
final int? tid;
|
||||
final String? tname;
|
||||
final String? pic;
|
||||
final String? title;
|
||||
final int? pubdate;
|
||||
final int? ctime;
|
||||
final String? desc;
|
||||
final int? state;
|
||||
final int? duration;
|
||||
final String? redirectUrl;
|
||||
final Rights? rights;
|
||||
final Owner? owner;
|
||||
final Stat? stat;
|
||||
final Dimension? dimension;
|
||||
final String? cover43;
|
||||
final String? bvid;
|
||||
final bool? interVideo;
|
||||
final String? resourceType;
|
||||
final String? subtitle;
|
||||
final int? enableVt;
|
||||
|
||||
factory MemberLikeDataModel.fromJson(Map<String, dynamic> json) =>
|
||||
MemberLikeDataModel(
|
||||
aid: json["aid"],
|
||||
videos: json["videos"],
|
||||
tid: json["tid"],
|
||||
tname: json["tname"],
|
||||
pic: json["pic"],
|
||||
title: json["title"],
|
||||
pubdate: json["pubdate"],
|
||||
ctime: json["ctime"],
|
||||
desc: json["desc"],
|
||||
state: json["state"],
|
||||
duration: json["duration"],
|
||||
redirectUrl: json["redirect_url"],
|
||||
rights: Rights.fromJson(json["rights"]),
|
||||
owner: Owner.fromJson(json["owner"]),
|
||||
stat: Stat.fromJson(json["stat"]),
|
||||
dimension: Dimension.fromJson(json["dimension"]),
|
||||
cover43: json["cover43"],
|
||||
bvid: json["bvid"],
|
||||
interVideo: json["inter_video"],
|
||||
resourceType: json["resource_type"],
|
||||
subtitle: json["subtitle"],
|
||||
enableVt: json["enable_vt"],
|
||||
);
|
||||
}
|
||||
|
||||
class Dimension {
|
||||
Dimension({
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.rotate,
|
||||
});
|
||||
|
||||
final int width;
|
||||
final int height;
|
||||
final int rotate;
|
||||
|
||||
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
|
||||
width: json["width"],
|
||||
height: json["height"],
|
||||
rotate: json["rotate"],
|
||||
);
|
||||
}
|
||||
|
||||
class Owner {
|
||||
Owner({
|
||||
required this.mid,
|
||||
required this.name,
|
||||
required this.face,
|
||||
});
|
||||
|
||||
final int mid;
|
||||
final String name;
|
||||
final String face;
|
||||
|
||||
factory Owner.fromJson(Map<String, dynamic> json) => Owner(
|
||||
mid: json["mid"],
|
||||
name: json["name"],
|
||||
face: json["face"],
|
||||
);
|
||||
}
|
||||
|
||||
class Rights {
|
||||
Rights({
|
||||
required this.bp,
|
||||
required this.elec,
|
||||
required this.download,
|
||||
required this.movie,
|
||||
required this.pay,
|
||||
required this.hd5,
|
||||
required this.noReprint,
|
||||
required this.autoplay,
|
||||
required this.ugcPay,
|
||||
required this.isCooperation,
|
||||
required this.ugcPayPreview,
|
||||
required this.noBackground,
|
||||
required this.arcPay,
|
||||
required this.payFreeWatch,
|
||||
});
|
||||
|
||||
final int bp;
|
||||
final int elec;
|
||||
final int download;
|
||||
final int movie;
|
||||
final int pay;
|
||||
final int hd5;
|
||||
final int noReprint;
|
||||
final int autoplay;
|
||||
final int ugcPay;
|
||||
final int isCooperation;
|
||||
final int ugcPayPreview;
|
||||
final int noBackground;
|
||||
final int arcPay;
|
||||
final int payFreeWatch;
|
||||
|
||||
factory Rights.fromJson(Map<String, dynamic> json) => Rights(
|
||||
bp: json["bp"],
|
||||
elec: json["elec"],
|
||||
download: json["download"],
|
||||
movie: json["movie"],
|
||||
pay: json["pay"],
|
||||
hd5: json["hd5"],
|
||||
noReprint: json["no_reprint"],
|
||||
autoplay: json["autoplay"],
|
||||
ugcPay: json["ugc_pay"],
|
||||
isCooperation: json["is_cooperation"],
|
||||
ugcPayPreview: json["ugc_pay_preview"],
|
||||
noBackground: json["no_background"],
|
||||
arcPay: json["arc_pay"],
|
||||
payFreeWatch: json["pay_free_watch"],
|
||||
);
|
||||
}
|
||||
|
||||
class Stat {
|
||||
Stat({
|
||||
required this.aid,
|
||||
required this.view,
|
||||
required this.danmaku,
|
||||
required this.reply,
|
||||
required this.favorite,
|
||||
required this.coin,
|
||||
required this.share,
|
||||
required this.nowRank,
|
||||
required this.hisRank,
|
||||
required this.like,
|
||||
required this.dislike,
|
||||
required this.vt,
|
||||
required this.vv,
|
||||
});
|
||||
|
||||
final int aid;
|
||||
final int view;
|
||||
final int danmaku;
|
||||
final int reply;
|
||||
final int favorite;
|
||||
final int coin;
|
||||
final int share;
|
||||
final int nowRank;
|
||||
final int hisRank;
|
||||
final int like;
|
||||
final int dislike;
|
||||
final int vt;
|
||||
final int vv;
|
||||
|
||||
factory Stat.fromJson(Map<String, dynamic> json) => Stat(
|
||||
aid: json["aid"],
|
||||
view: json["view"],
|
||||
danmaku: json["danmaku"],
|
||||
reply: json["reply"],
|
||||
favorite: json["favorite"],
|
||||
coin: json["coin"],
|
||||
share: json["share"],
|
||||
nowRank: json["now_rank"],
|
||||
hisRank: json["his_rank"],
|
||||
like: json["like"],
|
||||
dislike: json["dislike"],
|
||||
vt: json["vt"],
|
||||
vv: json["vv"],
|
||||
);
|
||||
}
|
||||
@ -2,18 +2,32 @@ class MemberSeasonsDataModel {
|
||||
MemberSeasonsDataModel({
|
||||
this.page,
|
||||
this.seasonsList,
|
||||
this.seriesList,
|
||||
});
|
||||
|
||||
Map? page;
|
||||
List<MemberSeasonsList>? seasonsList;
|
||||
List<MemberArchiveItem>? seriesList;
|
||||
|
||||
MemberSeasonsDataModel.fromJson(Map<String, dynamic> json) {
|
||||
page = json['page'];
|
||||
seasonsList = json['seasons_list'] != null
|
||||
var tempList1 = json['seasons_list'] != null
|
||||
? json['seasons_list']
|
||||
.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
var tempList2 = json['series_list'] != null
|
||||
? json['series_list']
|
||||
.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
seriesList = json['archives'] != null
|
||||
? json['archives']
|
||||
.map<MemberArchiveItem>((e) => MemberArchiveItem.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
|
||||
seasonsList = [...tempList1, ...tempList2];
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,6 +100,8 @@ class MamberMeta {
|
||||
this.ptime,
|
||||
this.seasonId,
|
||||
this.total,
|
||||
this.seriesId,
|
||||
this.category,
|
||||
});
|
||||
|
||||
String? cover;
|
||||
@ -95,6 +111,8 @@ class MamberMeta {
|
||||
int? ptime;
|
||||
int? seasonId;
|
||||
int? total;
|
||||
int? seriesId;
|
||||
int? category;
|
||||
|
||||
MamberMeta.fromJson(Map<String, dynamic> json) {
|
||||
cover = json['cover'];
|
||||
@ -104,5 +122,7 @@ class MamberMeta {
|
||||
ptime = json['ptime'];
|
||||
seasonId = json['season_id'];
|
||||
total = json['total'];
|
||||
seriesId = json['series_id'];
|
||||
category = json['category'];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,19 +1,12 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'model_owner.g.dart';
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
class Owner {
|
||||
Owner({
|
||||
this.mid,
|
||||
this.name,
|
||||
this.face,
|
||||
});
|
||||
@HiveField(0)
|
||||
|
||||
int? mid;
|
||||
@HiveField(1)
|
||||
String? name;
|
||||
@HiveField(2)
|
||||
String? face;
|
||||
|
||||
Owner.fromJson(Map<String, dynamic> json) {
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'model_owner.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class OwnerAdapter extends TypeAdapter<Owner> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
Owner read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return Owner(
|
||||
mid: fields[0] as int?,
|
||||
name: fields[1] as String?,
|
||||
face: fields[2] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, Owner obj) {
|
||||
writer
|
||||
..writeByte(3)
|
||||
..writeByte(0)
|
||||
..write(obj.mid)
|
||||
..writeByte(1)
|
||||
..write(obj.name)
|
||||
..writeByte(2)
|
||||
..write(obj.face);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is OwnerAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@ -1,9 +1,5 @@
|
||||
import './model_owner.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'model_rec_video_item.g.dart';
|
||||
|
||||
@HiveType(typeId: 0)
|
||||
class RecVideoItemModel {
|
||||
RecVideoItemModel({
|
||||
this.id,
|
||||
@ -21,32 +17,19 @@ class RecVideoItemModel {
|
||||
this.rcmdReason,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
int? id = -1;
|
||||
@HiveField(1)
|
||||
String? bvid = '';
|
||||
@HiveField(2)
|
||||
int? cid = -1;
|
||||
@HiveField(3)
|
||||
String? goto = '';
|
||||
@HiveField(4)
|
||||
String? uri = '';
|
||||
@HiveField(5)
|
||||
String? pic = '';
|
||||
@HiveField(6)
|
||||
String? title = '';
|
||||
@HiveField(7)
|
||||
int? duration = -1;
|
||||
@HiveField(8)
|
||||
int? pubdate = -1;
|
||||
@HiveField(9)
|
||||
Owner? owner;
|
||||
@HiveField(10)
|
||||
Stat? stat;
|
||||
@HiveField(11)
|
||||
int? isFollowed;
|
||||
@HiveField(12)
|
||||
RcmdReason? rcmdReason;
|
||||
String? rcmdReason;
|
||||
|
||||
RecVideoItemModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json["id"];
|
||||
@ -61,26 +44,20 @@ class RecVideoItemModel {
|
||||
owner = Owner.fromJson(json["owner"]);
|
||||
stat = Stat.fromJson(json["stat"]);
|
||||
isFollowed = json["is_followed"] ?? 0;
|
||||
rcmdReason = json["rcmd_reason"] != null
|
||||
? RcmdReason.fromJson(json["rcmd_reason"])
|
||||
: RcmdReason(content: '');
|
||||
rcmdReason = json["rcmd_reason"]?['content'];
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
class Stat {
|
||||
Stat({
|
||||
this.view,
|
||||
this.like,
|
||||
this.danmu,
|
||||
});
|
||||
@HiveField(0)
|
||||
int? view;
|
||||
@HiveField(1)
|
||||
int? like;
|
||||
@HiveField(2)
|
||||
int? danmu;
|
||||
|
||||
int? view;
|
||||
int? like;
|
||||
int? danmu;
|
||||
Stat.fromJson(Map<String, dynamic> json) {
|
||||
// 无需在model中转换以保留原始数据,在view层处理即可
|
||||
view = json["view"];
|
||||
@ -88,20 +65,3 @@ class Stat {
|
||||
danmu = json['danmaku'];
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 2)
|
||||
class RcmdReason {
|
||||
RcmdReason({
|
||||
this.reasonType,
|
||||
this.content,
|
||||
});
|
||||
@HiveField(0)
|
||||
int? reasonType;
|
||||
@HiveField(1)
|
||||
String? content = '';
|
||||
|
||||
RcmdReason.fromJson(Map<String, dynamic> json) {
|
||||
reasonType = json["reason_type"];
|
||||
content = json["content"] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,154 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'model_rec_video_item.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class RecVideoItemModelAdapter extends TypeAdapter<RecVideoItemModel> {
|
||||
@override
|
||||
final int typeId = 0;
|
||||
|
||||
@override
|
||||
RecVideoItemModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return RecVideoItemModel(
|
||||
id: fields[0] as int?,
|
||||
bvid: fields[1] as String?,
|
||||
cid: fields[2] as int?,
|
||||
goto: fields[3] as String?,
|
||||
uri: fields[4] as String?,
|
||||
pic: fields[5] as String?,
|
||||
title: fields[6] as String?,
|
||||
duration: fields[7] as int?,
|
||||
pubdate: fields[8] as int?,
|
||||
owner: fields[9] as Owner?,
|
||||
stat: fields[10] as Stat?,
|
||||
isFollowed: fields[11] as int?,
|
||||
rcmdReason: fields[12] as RcmdReason?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, RecVideoItemModel obj) {
|
||||
writer
|
||||
..writeByte(13)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
..write(obj.bvid)
|
||||
..writeByte(2)
|
||||
..write(obj.cid)
|
||||
..writeByte(3)
|
||||
..write(obj.goto)
|
||||
..writeByte(4)
|
||||
..write(obj.uri)
|
||||
..writeByte(5)
|
||||
..write(obj.pic)
|
||||
..writeByte(6)
|
||||
..write(obj.title)
|
||||
..writeByte(7)
|
||||
..write(obj.duration)
|
||||
..writeByte(8)
|
||||
..write(obj.pubdate)
|
||||
..writeByte(9)
|
||||
..write(obj.owner)
|
||||
..writeByte(10)
|
||||
..write(obj.stat)
|
||||
..writeByte(11)
|
||||
..write(obj.isFollowed)
|
||||
..writeByte(12)
|
||||
..write(obj.rcmdReason);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RecVideoItemModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class StatAdapter extends TypeAdapter<Stat> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
Stat read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return Stat(
|
||||
view: fields[0] as int?,
|
||||
like: fields[1] as int?,
|
||||
danmu: fields[2] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, Stat obj) {
|
||||
writer
|
||||
..writeByte(3)
|
||||
..writeByte(0)
|
||||
..write(obj.view)
|
||||
..writeByte(1)
|
||||
..write(obj.like)
|
||||
..writeByte(2)
|
||||
..write(obj.danmu);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is StatAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class RcmdReasonAdapter extends TypeAdapter<RcmdReason> {
|
||||
@override
|
||||
final int typeId = 2;
|
||||
|
||||
@override
|
||||
RcmdReason read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return RcmdReason(
|
||||
reasonType: fields[0] as int?,
|
||||
content: fields[1] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, RcmdReason obj) {
|
||||
writer
|
||||
..writeByte(2)
|
||||
..writeByte(0)
|
||||
..write(obj.reasonType)
|
||||
..writeByte(1)
|
||||
..write(obj.content);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is RcmdReasonAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
88
lib/models/msg/system.dart
Normal file
88
lib/models/msg/system.dart
Normal file
@ -0,0 +1,88 @@
|
||||
import 'dart:convert';
|
||||
|
||||
class MessageSystemModel {
|
||||
int? id;
|
||||
int? cursor;
|
||||
int? type;
|
||||
String? title;
|
||||
dynamic content;
|
||||
Source? source;
|
||||
String? timeAt;
|
||||
int? cardType;
|
||||
String? cardBrief;
|
||||
String? cardMsgBrief;
|
||||
String? cardCover;
|
||||
String? cardStoryTitle;
|
||||
String? cardLink;
|
||||
String? mc;
|
||||
int? isStation;
|
||||
int? isSend;
|
||||
int? notifyCursor;
|
||||
|
||||
MessageSystemModel({
|
||||
this.id,
|
||||
this.cursor,
|
||||
this.type,
|
||||
this.title,
|
||||
this.content,
|
||||
this.source,
|
||||
this.timeAt,
|
||||
this.cardType,
|
||||
this.cardBrief,
|
||||
this.cardMsgBrief,
|
||||
this.cardCover,
|
||||
this.cardStoryTitle,
|
||||
this.cardLink,
|
||||
this.mc,
|
||||
this.isStation,
|
||||
this.isSend,
|
||||
this.notifyCursor,
|
||||
});
|
||||
|
||||
factory MessageSystemModel.fromJson(Map<String, dynamic> jsons) =>
|
||||
MessageSystemModel(
|
||||
id: jsons["id"],
|
||||
cursor: jsons["cursor"],
|
||||
type: jsons["type"],
|
||||
title: jsons["title"],
|
||||
content: isValidJson(jsons["content"])
|
||||
? json.decode(jsons["content"])
|
||||
: jsons["content"],
|
||||
source: Source.fromJson(jsons["source"]),
|
||||
timeAt: jsons["time_at"],
|
||||
cardType: jsons["card_type"],
|
||||
cardBrief: jsons["card_brief"],
|
||||
cardMsgBrief: jsons["card_msg_brief"],
|
||||
cardCover: jsons["card_cover"],
|
||||
cardStoryTitle: jsons["card_story_title"],
|
||||
cardLink: jsons["card_link"],
|
||||
mc: jsons["mc"],
|
||||
isStation: jsons["is_station"],
|
||||
isSend: jsons["is_send"],
|
||||
notifyCursor: jsons["notify_cursor"],
|
||||
);
|
||||
}
|
||||
|
||||
class Source {
|
||||
String? name;
|
||||
String? logo;
|
||||
|
||||
Source({
|
||||
this.name,
|
||||
this.logo,
|
||||
});
|
||||
|
||||
factory Source.fromJson(Map<String, dynamic> json) => Source(
|
||||
name: json["name"],
|
||||
logo: json["logo"],
|
||||
);
|
||||
}
|
||||
|
||||
bool isValidJson(String str) {
|
||||
try {
|
||||
json.decode(str);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
485
lib/models/read/opus.dart
Normal file
485
lib/models/read/opus.dart
Normal file
@ -0,0 +1,485 @@
|
||||
class OpusDataModel {
|
||||
OpusDataModel({
|
||||
this.id,
|
||||
this.detail,
|
||||
this.type,
|
||||
this.theme,
|
||||
this.themeMode,
|
||||
});
|
||||
|
||||
String? id;
|
||||
OpusDetailDataModel? detail;
|
||||
int? type;
|
||||
String? theme;
|
||||
String? themeMode;
|
||||
|
||||
OpusDataModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
detail = json['detail'] != null
|
||||
? OpusDetailDataModel.fromJson(json['detail'])
|
||||
: null;
|
||||
type = json['type'];
|
||||
theme = json['theme'];
|
||||
themeMode = json['themeMode'];
|
||||
}
|
||||
}
|
||||
|
||||
class OpusDetailDataModel {
|
||||
OpusDetailDataModel({
|
||||
this.basic,
|
||||
this.idStr,
|
||||
this.modules,
|
||||
this.type,
|
||||
});
|
||||
|
||||
Basic? basic;
|
||||
String? idStr;
|
||||
List<OpusModuleDataModel>? modules;
|
||||
int? type;
|
||||
|
||||
OpusDetailDataModel.fromJson(Map<String, dynamic> json) {
|
||||
basic = json['basic'] != null ? Basic.fromJson(json['basic']) : null;
|
||||
idStr = json['id_str'];
|
||||
if (json['modules'] != null) {
|
||||
modules = <OpusModuleDataModel>[];
|
||||
json['modules'].forEach((v) {
|
||||
modules!.add(OpusModuleDataModel.fromJson(v));
|
||||
});
|
||||
}
|
||||
type = json['type'];
|
||||
}
|
||||
}
|
||||
|
||||
class Basic {
|
||||
Basic({
|
||||
this.commentIdStr,
|
||||
this.commentType,
|
||||
this.ridStr,
|
||||
this.title,
|
||||
this.uid,
|
||||
});
|
||||
|
||||
String? commentIdStr;
|
||||
int? commentType;
|
||||
String? ridStr;
|
||||
String? title;
|
||||
int? uid;
|
||||
|
||||
Basic.fromJson(Map<String, dynamic> json) {
|
||||
commentIdStr = json['comment_id_str'];
|
||||
commentType = json['comment_type'];
|
||||
ridStr = json['rid_str'];
|
||||
title = json['title'];
|
||||
uid = json['uid'];
|
||||
}
|
||||
}
|
||||
|
||||
class OpusModuleDataModel {
|
||||
OpusModuleDataModel({
|
||||
this.moduleTitle,
|
||||
this.moduleAuthor,
|
||||
this.moduleContent,
|
||||
this.moduleExtend,
|
||||
this.moduleBottom,
|
||||
this.moduleStat,
|
||||
});
|
||||
|
||||
ModuleTop? moduleTop;
|
||||
ModuleTitle? moduleTitle;
|
||||
ModuleAuthor? moduleAuthor;
|
||||
ModuleContent? moduleContent;
|
||||
ModuleExtend? moduleExtend;
|
||||
ModuleBottom? moduleBottom;
|
||||
ModuleStat? moduleStat;
|
||||
|
||||
OpusModuleDataModel.fromJson(Map<String, dynamic> json) {
|
||||
moduleTop = json['module_top'] != null
|
||||
? ModuleTop.fromJson(json['module_top'])
|
||||
: null;
|
||||
moduleTitle = json['module_title'] != null
|
||||
? ModuleTitle.fromJson(json['module_title'])
|
||||
: null;
|
||||
moduleAuthor = json['module_author'] != null
|
||||
? ModuleAuthor.fromJson(json['module_author'])
|
||||
: null;
|
||||
moduleContent = json['module_content'] != null
|
||||
? ModuleContent.fromJson(json['module_content'])
|
||||
: null;
|
||||
moduleExtend = json['module_extend'] != null
|
||||
? ModuleExtend.fromJson(json['module_extend'])
|
||||
: null;
|
||||
moduleBottom = json['module_bottom'] != null
|
||||
? ModuleBottom.fromJson(json['module_bottom'])
|
||||
: null;
|
||||
moduleStat = json['module_stat'] != null
|
||||
? ModuleStat.fromJson(json['module_stat'])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleTop {
|
||||
ModuleTop({
|
||||
this.type,
|
||||
this.video,
|
||||
});
|
||||
|
||||
int? type;
|
||||
Map? video;
|
||||
|
||||
ModuleTop.fromJson(Map<String, dynamic> json) {
|
||||
type = json['type'];
|
||||
video = json['video'];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleTitle {
|
||||
ModuleTitle({
|
||||
this.text,
|
||||
});
|
||||
|
||||
String? text;
|
||||
|
||||
ModuleTitle.fromJson(Map<String, dynamic> json) {
|
||||
text = json['text'];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleAuthor {
|
||||
ModuleAuthor({
|
||||
this.face,
|
||||
this.mid,
|
||||
this.name,
|
||||
this.pubTime,
|
||||
});
|
||||
|
||||
String? face;
|
||||
int? mid;
|
||||
String? name;
|
||||
String? pubTime;
|
||||
|
||||
ModuleAuthor.fromJson(Map<String, dynamic> json) {
|
||||
face = json['face'];
|
||||
mid = json['mid'];
|
||||
name = json['name'];
|
||||
pubTime = json['pub_time'];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleContent {
|
||||
ModuleContent({
|
||||
this.paragraphs,
|
||||
this.moduleType,
|
||||
});
|
||||
|
||||
List<ModuleParagraph>? paragraphs;
|
||||
String? moduleType;
|
||||
|
||||
ModuleContent.fromJson(Map<String, dynamic> json) {
|
||||
if (json['paragraphs'] != null) {
|
||||
paragraphs = <ModuleParagraph>[];
|
||||
json['paragraphs'].forEach((v) {
|
||||
paragraphs!.add(ModuleParagraph.fromJson(v));
|
||||
});
|
||||
}
|
||||
moduleType = json['module_type'];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleParagraph {
|
||||
ModuleParagraph({
|
||||
this.align,
|
||||
this.paraType,
|
||||
this.pic,
|
||||
this.text,
|
||||
});
|
||||
|
||||
// 0 左对齐 1 居中 2 右对齐
|
||||
int? align;
|
||||
int? paraType;
|
||||
Pics? pic;
|
||||
ModuleParagraphText? text;
|
||||
LinkCard? linkCard;
|
||||
|
||||
ModuleParagraph.fromJson(Map<String, dynamic> json) {
|
||||
align = json['align'];
|
||||
paraType = json['para_type'] == null && json['link_card'] != null
|
||||
? 3
|
||||
: json['para_type'];
|
||||
pic = json['pic'] != null ? Pics.fromJson(json['pic']) : null;
|
||||
text = json['text'] != null
|
||||
? ModuleParagraphText.fromJson(json['text'])
|
||||
: null;
|
||||
linkCard =
|
||||
json['link_card'] != null ? LinkCard.fromJson(json['link_card']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
class Pics {
|
||||
Pics({
|
||||
this.pics,
|
||||
this.style,
|
||||
});
|
||||
|
||||
List<Pic>? pics;
|
||||
int? style;
|
||||
|
||||
Pics.fromJson(Map<String, dynamic> json) {
|
||||
if (json['pics'] != null) {
|
||||
pics = <Pic>[];
|
||||
json['pics'].forEach((v) {
|
||||
pics!.add(Pic.fromJson(v));
|
||||
});
|
||||
}
|
||||
style = json['style'];
|
||||
}
|
||||
}
|
||||
|
||||
class Pic {
|
||||
Pic({
|
||||
this.height,
|
||||
this.size,
|
||||
this.url,
|
||||
this.width,
|
||||
this.aspectRatio,
|
||||
this.scale,
|
||||
});
|
||||
|
||||
int? height;
|
||||
double? size;
|
||||
String? url;
|
||||
int? width;
|
||||
double? aspectRatio;
|
||||
double? scale;
|
||||
|
||||
Pic.fromJson(Map<String, dynamic> json) {
|
||||
height = json['height'];
|
||||
size = json['size'];
|
||||
url = json['url'];
|
||||
width = json['width'];
|
||||
aspectRatio = json['width'] / json['height'];
|
||||
scale = customDivision(json['width'], 600);
|
||||
}
|
||||
}
|
||||
|
||||
class LinkCard {
|
||||
LinkCard({
|
||||
this.cover,
|
||||
this.descSecond,
|
||||
this.duration,
|
||||
this.jumpUrl,
|
||||
this.title,
|
||||
});
|
||||
|
||||
String? cover;
|
||||
String? descSecond;
|
||||
String? duration;
|
||||
String? jumpUrl;
|
||||
String? title;
|
||||
|
||||
LinkCard.fromJson(Map<String, dynamic> json) {
|
||||
cover = json['card']['cover'];
|
||||
descSecond = json['card']['desc_second'];
|
||||
duration = json['card']['duration'];
|
||||
jumpUrl = json['card']['jump_url'];
|
||||
title = json['card']['title'];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleParagraphText {
|
||||
ModuleParagraphText({
|
||||
this.nodes,
|
||||
});
|
||||
|
||||
List<ModuleParagraphTextNode>? nodes;
|
||||
|
||||
ModuleParagraphText.fromJson(Map<String, dynamic> json) {
|
||||
if (json['nodes'] != null) {
|
||||
nodes = <ModuleParagraphTextNode>[];
|
||||
json['nodes'].forEach((v) {
|
||||
nodes!.add(ModuleParagraphTextNode.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleParagraphTextNode {
|
||||
ModuleParagraphTextNode({
|
||||
this.type,
|
||||
this.nodeType,
|
||||
this.word,
|
||||
});
|
||||
|
||||
String? type;
|
||||
int? nodeType;
|
||||
ModuleParagraphTextNodeWord? word;
|
||||
|
||||
ModuleParagraphTextNode.fromJson(Map<String, dynamic> json) {
|
||||
type = json['type'];
|
||||
nodeType = json['node_type'];
|
||||
word = json['word'] != null
|
||||
? ModuleParagraphTextNodeWord.fromJson(json['word'])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleParagraphTextNodeWord {
|
||||
ModuleParagraphTextNodeWord({
|
||||
this.color,
|
||||
this.fontSize,
|
||||
this.style,
|
||||
this.words,
|
||||
});
|
||||
|
||||
String? color;
|
||||
int? fontSize;
|
||||
ModuleParagraphTextNodeWordStyle? style;
|
||||
String? words;
|
||||
|
||||
ModuleParagraphTextNodeWord.fromJson(Map<String, dynamic> json) {
|
||||
color = json['color'];
|
||||
fontSize = json['font_size'];
|
||||
style = json['style'] != null
|
||||
? ModuleParagraphTextNodeWordStyle.fromJson(json['style'])
|
||||
: null;
|
||||
words = json['words'];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleParagraphTextNodeWordStyle {
|
||||
ModuleParagraphTextNodeWordStyle({
|
||||
this.bold,
|
||||
});
|
||||
|
||||
bool? bold;
|
||||
|
||||
ModuleParagraphTextNodeWordStyle.fromJson(Map<String, dynamic> json) {
|
||||
bold = json['bold'];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleExtend {
|
||||
ModuleExtend({
|
||||
this.items,
|
||||
});
|
||||
|
||||
List<ModuleExtendItem>? items;
|
||||
|
||||
ModuleExtend.fromJson(Map<String, dynamic> json) {
|
||||
if (json['items'] != null) {
|
||||
items = <ModuleExtendItem>[];
|
||||
json['items'].forEach((v) {
|
||||
items!.add(ModuleExtendItem.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleExtendItem {
|
||||
ModuleExtendItem({
|
||||
this.bizId,
|
||||
this.bizType,
|
||||
this.icon,
|
||||
this.jumpUrl,
|
||||
this.text,
|
||||
});
|
||||
|
||||
dynamic bizId;
|
||||
int? bizType;
|
||||
dynamic icon;
|
||||
String? jumpUrl;
|
||||
String? text;
|
||||
|
||||
ModuleExtendItem.fromJson(Map<String, dynamic> json) {
|
||||
bizId = json['biz_id'];
|
||||
bizType = json['biz_type'];
|
||||
icon = json['icon'];
|
||||
jumpUrl = json['jump_url'];
|
||||
text = json['text'];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleBottom {
|
||||
ModuleBottom({
|
||||
this.shareInfo,
|
||||
});
|
||||
|
||||
ShareInfo? shareInfo;
|
||||
|
||||
ModuleBottom.fromJson(Map<String, dynamic> json) {
|
||||
shareInfo = json['share_info'] != null
|
||||
? ShareInfo.fromJson(json['share_info'])
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
class ShareInfo {
|
||||
ShareInfo({
|
||||
this.pic,
|
||||
this.summary,
|
||||
this.title,
|
||||
});
|
||||
|
||||
String? pic;
|
||||
String? summary;
|
||||
String? title;
|
||||
|
||||
ShareInfo.fromJson(Map<String, dynamic> json) {
|
||||
pic = json['pic'];
|
||||
summary = json['summary'];
|
||||
title = json['title'];
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleStat {
|
||||
ModuleStat({
|
||||
this.coin,
|
||||
this.comment,
|
||||
this.favorite,
|
||||
this.forward,
|
||||
this.like,
|
||||
});
|
||||
|
||||
StatItem? coin;
|
||||
StatItem? comment;
|
||||
StatItem? favorite;
|
||||
StatItem? forward;
|
||||
StatItem? like;
|
||||
|
||||
ModuleStat.fromJson(Map<String, dynamic> json) {
|
||||
coin = json['coin'] != null ? StatItem.fromJson(json['coin']) : null;
|
||||
comment =
|
||||
json['comment'] != null ? StatItem.fromJson(json['comment']) : null;
|
||||
favorite =
|
||||
json['favorite'] != null ? StatItem.fromJson(json['favorite']) : null;
|
||||
forward =
|
||||
json['forward'] != null ? StatItem.fromJson(json['forward']) : null;
|
||||
like = json['like'] != null ? StatItem.fromJson(json['like']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
class StatItem {
|
||||
StatItem({
|
||||
this.count,
|
||||
this.forbidden,
|
||||
this.status,
|
||||
});
|
||||
|
||||
int? count;
|
||||
bool? forbidden;
|
||||
bool? status;
|
||||
|
||||
StatItem.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
forbidden = json['forbidden'];
|
||||
status = json['status'];
|
||||
}
|
||||
}
|
||||
|
||||
double customDivision(int a, int b) {
|
||||
double result = a / b;
|
||||
if (result < 1) {
|
||||
return result;
|
||||
} else {
|
||||
return 1.0;
|
||||
}
|
||||
}
|
||||
286
lib/models/read/read.dart
Normal file
286
lib/models/read/read.dart
Normal file
@ -0,0 +1,286 @@
|
||||
import 'package:pilipala/models/member/info.dart';
|
||||
|
||||
import 'opus.dart';
|
||||
|
||||
class ReadDataModel {
|
||||
ReadDataModel({
|
||||
this.cvid,
|
||||
this.readInfo,
|
||||
this.readViewInfo,
|
||||
this.upInfo,
|
||||
this.catalogList,
|
||||
this.recommendInfoList,
|
||||
this.hiddenInteraction,
|
||||
this.isModern,
|
||||
});
|
||||
|
||||
int? cvid;
|
||||
ReadInfo? readInfo;
|
||||
Map? readViewInfo;
|
||||
Map? upInfo;
|
||||
List<dynamic>? catalogList;
|
||||
List<dynamic>? recommendInfoList;
|
||||
bool? hiddenInteraction;
|
||||
bool? isModern;
|
||||
|
||||
ReadDataModel.fromJson(Map<String, dynamic> json) {
|
||||
cvid = json['cvid'];
|
||||
readInfo =
|
||||
json['readInfo'] != null ? ReadInfo.fromJson(json['readInfo']) : null;
|
||||
readViewInfo = json['readViewInfo'];
|
||||
upInfo = json['upInfo'];
|
||||
if (json['catalogList'] != null) {
|
||||
catalogList = <dynamic>[];
|
||||
json['catalogList'].forEach((v) {
|
||||
catalogList!.add(v);
|
||||
});
|
||||
}
|
||||
if (json['recommendInfoList'] != null) {
|
||||
recommendInfoList = <dynamic>[];
|
||||
json['recommendInfoList'].forEach((v) {
|
||||
recommendInfoList!.add(v);
|
||||
});
|
||||
}
|
||||
hiddenInteraction = json['hiddenInteraction'];
|
||||
isModern = json['isModern'];
|
||||
}
|
||||
}
|
||||
|
||||
class ReadInfo {
|
||||
ReadInfo({
|
||||
this.id,
|
||||
this.category,
|
||||
this.title,
|
||||
this.summary,
|
||||
this.bannerUrl,
|
||||
this.author,
|
||||
this.publishTime,
|
||||
this.ctime,
|
||||
this.mtime,
|
||||
this.stats,
|
||||
this.attributes,
|
||||
this.words,
|
||||
this.originImageUrls,
|
||||
this.content,
|
||||
this.opus,
|
||||
this.dynIdStr,
|
||||
this.totalArtNum,
|
||||
});
|
||||
|
||||
int? id;
|
||||
Map? category;
|
||||
String? title;
|
||||
String? summary;
|
||||
String? bannerUrl;
|
||||
Author? author;
|
||||
int? publishTime;
|
||||
int? ctime;
|
||||
int? mtime;
|
||||
Map? stats;
|
||||
int? attributes;
|
||||
int? words;
|
||||
List<String>? originImageUrls;
|
||||
String? content;
|
||||
Opus? opus;
|
||||
String? dynIdStr;
|
||||
int? totalArtNum;
|
||||
|
||||
ReadInfo.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
category = json['category'];
|
||||
title = json['title'];
|
||||
summary = json['summary'];
|
||||
bannerUrl = json['banner_url'];
|
||||
author = Author.fromJson(json['author']);
|
||||
publishTime = json['publish_time'];
|
||||
ctime = json['ctime'];
|
||||
mtime = json['mtime'];
|
||||
stats = json['stats'];
|
||||
attributes = json['attributes'];
|
||||
words = json['words'];
|
||||
if (json['origin_image_urls'] != null) {
|
||||
originImageUrls = <String>[];
|
||||
json['origin_image_urls'].forEach((v) {
|
||||
originImageUrls!.add(v);
|
||||
});
|
||||
}
|
||||
content = json['content'];
|
||||
opus = json['opus'] != null ? Opus.fromJson(json['opus']) : null;
|
||||
dynIdStr = json['dyn_id_str'];
|
||||
totalArtNum = json['total_art_num'];
|
||||
}
|
||||
}
|
||||
|
||||
class Author {
|
||||
Author({
|
||||
this.mid,
|
||||
this.name,
|
||||
this.face,
|
||||
this.vip,
|
||||
this.fans,
|
||||
this.level,
|
||||
});
|
||||
|
||||
int? mid;
|
||||
String? name;
|
||||
String? face;
|
||||
Vip? vip;
|
||||
int? fans;
|
||||
int? level;
|
||||
|
||||
Author.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
name = json['name'];
|
||||
face = json['face'];
|
||||
vip = json['vip'] != null ? Vip.fromJson(json['vip']) : null;
|
||||
fans = json['fans'];
|
||||
level = json['level'];
|
||||
}
|
||||
}
|
||||
|
||||
class Opus {
|
||||
// "opus_id": 976625853207150600,
|
||||
// "opus_source": 2,
|
||||
// "title": "真的很想骂人 但又没什么好骂的",
|
||||
// "content": {
|
||||
// "paragraphs": [{
|
||||
// "para_type": 1,
|
||||
// "text": {
|
||||
// "nodes": [{
|
||||
// "node_type": 1,
|
||||
// "word": {
|
||||
// "words": "21年玩到今年4月的号没了 ow1的时候45的号 玩了三年 后面第9赛季一个英杰5的号(虽然是偷的 但我任何违规行为都没有还是给我封了) 最近玩的号叫velleity 只和队友打天梯以及训练赛 又没了 连带着我一个一把没玩过只玩过一场训练赛的小号也没了 实在是无话可说了...",
|
||||
// "font_size": 17,
|
||||
// "style": {},
|
||||
// "font_level": "regular"
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// }, {
|
||||
// "para_type": 2,
|
||||
// "pic": {
|
||||
// "pics": [{
|
||||
// "url": "https:\u002F\u002Fi0.hdslb.com\u002Fbfs\u002Fnew_dyn\u002Fba4e57459451fe74dcb70fd20bde9823316082117.jpg",
|
||||
// "width": 1600,
|
||||
// "height": 1000,
|
||||
// "size": 588.482421875
|
||||
// }],
|
||||
// "style": 1
|
||||
// }
|
||||
// }, {
|
||||
// "para_type": 1,
|
||||
// "text": {
|
||||
// "nodes": [{
|
||||
// "node_type": 1,
|
||||
// "word": {
|
||||
// "words": "\n",
|
||||
// "font_size": 17,
|
||||
// "style": {},
|
||||
// "font_level": "regular"
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// }, {
|
||||
// "para_type": 2,
|
||||
// "pic": {
|
||||
// "pics": [{
|
||||
// "url": "https:\u002F\u002Fi0.hdslb.com\u002Fbfs\u002Fnew_dyn\u002F0945be6b621091ddb8189482a87a36fb316082117.jpg",
|
||||
// "width": 1600,
|
||||
// "height": 1002,
|
||||
// "size": 665.7861328125
|
||||
// }],
|
||||
// "style": 1
|
||||
// }
|
||||
// }, {
|
||||
// "para_type": 1,
|
||||
// "text": {
|
||||
// "nodes": [{
|
||||
// "node_type": 1,
|
||||
// "word": {
|
||||
// "words": "\n",
|
||||
// "font_size": 17,
|
||||
// "style": {},
|
||||
// "font_level": "regular"
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// }, {
|
||||
// "para_type": 2,
|
||||
// "pic": {
|
||||
// "pics": [{
|
||||
// "url": "https:\u002F\u002Fi0.hdslb.com\u002Fbfs\u002Fnew_dyn\u002Ffa60649f8786578a764a1e68a2c5d23f316082117.jpg",
|
||||
// "width": 1600,
|
||||
// "height": 999,
|
||||
// "size": 332.970703125
|
||||
// }],
|
||||
// "style": 1
|
||||
// }
|
||||
// }, {
|
||||
// "para_type": 1,
|
||||
// "text": {
|
||||
// "nodes": [{
|
||||
// "node_type": 1,
|
||||
// "word": {
|
||||
// "words": "\n",
|
||||
// "font_size": 17,
|
||||
// "style": {},
|
||||
// "font_level": "regular"
|
||||
// }
|
||||
// }]
|
||||
// }
|
||||
// }]
|
||||
// },
|
||||
// "pub_info": {
|
||||
// "uid": 316082117,
|
||||
// "pub_time": 1726226826
|
||||
// },
|
||||
// "article": {
|
||||
// "category_id": 15,
|
||||
// "cover": [{
|
||||
// "url": "https:\u002F\u002Fi0.hdslb.com\u002Fbfs\u002Fnew_dyn\u002Fbanner\u002Feb10074186a62f98c18e1b5b9deb38be316082117.png",
|
||||
// "width": 1071,
|
||||
// "height": 315,
|
||||
// "size": 225.625
|
||||
// }]
|
||||
// },
|
||||
// "version": {
|
||||
// "cvid": 38660379,
|
||||
// "version_id": 101683514411343360
|
||||
// }
|
||||
Opus({
|
||||
this.opusId,
|
||||
this.opusSource,
|
||||
this.title,
|
||||
this.content,
|
||||
});
|
||||
|
||||
int? opusId;
|
||||
int? opusSource;
|
||||
String? title;
|
||||
Content? content;
|
||||
|
||||
Opus.fromJson(Map<String, dynamic> json) {
|
||||
opusId = json['opus_id'];
|
||||
opusSource = json['opus_source'];
|
||||
title = json['title'];
|
||||
content =
|
||||
json['content'] != null ? Content.fromJson(json['content']) : null;
|
||||
}
|
||||
}
|
||||
|
||||
class Content {
|
||||
Content({
|
||||
this.paragraphs,
|
||||
});
|
||||
|
||||
List<ModuleParagraph>? paragraphs;
|
||||
|
||||
Content.fromJson(Map<String, dynamic> json) {
|
||||
if (json['paragraphs'] != null) {
|
||||
paragraphs = <ModuleParagraph>[];
|
||||
json['paragraphs'].forEach((v) {
|
||||
paragraphs!.add(ModuleParagraph.fromJson(v));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,8 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'hot.g.dart';
|
||||
|
||||
@HiveType(typeId: 6)
|
||||
class HotSearchModel {
|
||||
HotSearchModel({
|
||||
this.list,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
List<HotSearchItem>? list;
|
||||
|
||||
HotSearchModel.fromJson(Map<String, dynamic> json) {
|
||||
@ -18,7 +12,6 @@ class HotSearchModel {
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 7)
|
||||
class HotSearchItem {
|
||||
HotSearchItem({
|
||||
this.keyword,
|
||||
@ -27,14 +20,10 @@ class HotSearchItem {
|
||||
this.icon,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
String? keyword;
|
||||
@HiveField(1)
|
||||
String? showName;
|
||||
// 4/5热 11话题 8普通 7直播
|
||||
@HiveField(2)
|
||||
int? wordType;
|
||||
@HiveField(3)
|
||||
String? icon;
|
||||
|
||||
HotSearchItem.fromJson(Map<String, dynamic> json) {
|
||||
|
||||
@ -1,84 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'hot.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class HotSearchModelAdapter extends TypeAdapter<HotSearchModel> {
|
||||
@override
|
||||
final int typeId = 6;
|
||||
|
||||
@override
|
||||
HotSearchModel read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return HotSearchModel(
|
||||
list: (fields[0] as List?)?.cast<HotSearchItem>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, HotSearchModel obj) {
|
||||
writer
|
||||
..writeByte(1)
|
||||
..writeByte(0)
|
||||
..write(obj.list);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is HotSearchModelAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
|
||||
class HotSearchItemAdapter extends TypeAdapter<HotSearchItem> {
|
||||
@override
|
||||
final int typeId = 7;
|
||||
|
||||
@override
|
||||
HotSearchItem read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return HotSearchItem(
|
||||
keyword: fields[0] as String?,
|
||||
showName: fields[1] as String?,
|
||||
wordType: fields[2] as int?,
|
||||
icon: fields[3] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, HotSearchItem obj) {
|
||||
writer
|
||||
..writeByte(4)
|
||||
..writeByte(0)
|
||||
..write(obj.keyword)
|
||||
..writeByte(1)
|
||||
..write(obj.showName)
|
||||
..writeByte(2)
|
||||
..write(obj.wordType)
|
||||
..writeByte(3)
|
||||
..write(obj.icon);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is HotSearchItemAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@ -5,10 +5,12 @@ class SearchVideoModel {
|
||||
SearchVideoModel({this.list});
|
||||
List<SearchVideoItemModel>? list;
|
||||
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
||||
list = json['result']
|
||||
.where((e) => e['available'] == true)
|
||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||
.toList();
|
||||
list = json['result'] == null
|
||||
? []
|
||||
: json['result']
|
||||
.where((e) => e['available'] == true)
|
||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,8 +1,3 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'stat.g.dart';
|
||||
|
||||
@HiveType(typeId: 1)
|
||||
class UserStat {
|
||||
UserStat({
|
||||
this.following,
|
||||
@ -10,11 +5,8 @@ class UserStat {
|
||||
this.dynamicCount,
|
||||
});
|
||||
|
||||
@HiveField(0)
|
||||
int? following;
|
||||
@HiveField(1)
|
||||
int? follower;
|
||||
@HiveField(2)
|
||||
int? dynamicCount;
|
||||
|
||||
UserStat.fromJson(Map<String, dynamic> json) {
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'stat.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class UserStatAdapter extends TypeAdapter<UserStat> {
|
||||
@override
|
||||
final int typeId = 1;
|
||||
|
||||
@override
|
||||
UserStat read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return UserStat(
|
||||
following: fields[0] as int?,
|
||||
follower: fields[1] as int?,
|
||||
dynamicCount: fields[2] as int?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, UserStat obj) {
|
||||
writer
|
||||
..writeByte(3)
|
||||
..writeByte(0)
|
||||
..write(obj.following)
|
||||
..writeByte(1)
|
||||
..write(obj.follower)
|
||||
..writeByte(2)
|
||||
..write(obj.dynamicCount);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is UserStatAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@ -39,11 +39,11 @@ class ModelResult {
|
||||
ModelResult.fromJson(Map<String, dynamic> json) {
|
||||
resultType = json['result_type'];
|
||||
summary = json['summary'];
|
||||
outline = json['result_type'] == 2
|
||||
? json['outline']
|
||||
outline = json['result_type'] == 0
|
||||
? <OutlineItem>[]
|
||||
: json['outline']
|
||||
.map<OutlineItem>((e) => OutlineItem.fromJson(e))
|
||||
.toList()
|
||||
: <OutlineItem>[];
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
276
lib/models/video/later.dart
Normal file
276
lib/models/video/later.dart
Normal file
@ -0,0 +1,276 @@
|
||||
class MediaVideoItemModel {
|
||||
MediaVideoItemModel({
|
||||
this.id,
|
||||
this.aid,
|
||||
this.offset,
|
||||
this.index,
|
||||
this.intro,
|
||||
this.attr,
|
||||
this.tid,
|
||||
this.copyRight,
|
||||
this.cntInfo,
|
||||
this.cover,
|
||||
this.duration,
|
||||
this.pubtime,
|
||||
this.likeState,
|
||||
this.favState,
|
||||
this.page,
|
||||
this.cid,
|
||||
this.pages,
|
||||
this.title,
|
||||
this.type,
|
||||
this.upper,
|
||||
this.link,
|
||||
this.bvid,
|
||||
this.shortLink,
|
||||
this.rights,
|
||||
this.elecInfo,
|
||||
this.coin,
|
||||
this.progressPercent,
|
||||
this.badge,
|
||||
this.forbidFav,
|
||||
this.moreType,
|
||||
this.businessOid,
|
||||
});
|
||||
|
||||
int? id;
|
||||
int? aid;
|
||||
int? offset;
|
||||
int? index;
|
||||
String? intro;
|
||||
int? attr;
|
||||
int? tid;
|
||||
int? copyRight;
|
||||
Map? cntInfo;
|
||||
String? cover;
|
||||
int? duration;
|
||||
int? pubtime;
|
||||
int? likeState;
|
||||
int? favState;
|
||||
int? page;
|
||||
int? cid;
|
||||
List<Page>? pages;
|
||||
String? title;
|
||||
int? type;
|
||||
Upper? upper;
|
||||
String? link;
|
||||
String? bvid;
|
||||
String? shortLink;
|
||||
Rights? rights;
|
||||
dynamic elecInfo;
|
||||
Coin? coin;
|
||||
double? progressPercent;
|
||||
dynamic badge;
|
||||
bool? forbidFav;
|
||||
int? moreType;
|
||||
int? businessOid;
|
||||
|
||||
factory MediaVideoItemModel.fromJson(Map<String, dynamic> json) =>
|
||||
MediaVideoItemModel(
|
||||
id: json["id"],
|
||||
aid: json["id"],
|
||||
offset: json["offset"],
|
||||
index: json["index"],
|
||||
intro: json["intro"],
|
||||
attr: json["attr"],
|
||||
tid: json["tid"],
|
||||
copyRight: json["copy_right"],
|
||||
cntInfo: json["cnt_info"],
|
||||
cover: json["cover"],
|
||||
duration: json["duration"],
|
||||
pubtime: json["pubtime"],
|
||||
likeState: json["like_state"],
|
||||
favState: json["fav_state"],
|
||||
page: json["page"],
|
||||
cid: json["pages"] == null ? -1 : json["pages"].first['id'],
|
||||
// json["pages"] 可能为null
|
||||
pages: json["pages"] == null
|
||||
? []
|
||||
: List<Page>.from(json["pages"].map((x) => Page.fromJson(x))),
|
||||
title: json["title"],
|
||||
type: json["type"],
|
||||
upper: Upper.fromJson(json["upper"]),
|
||||
link: json["link"],
|
||||
bvid: json["bv_id"],
|
||||
shortLink: json["short_link"],
|
||||
rights: Rights.fromJson(json["rights"]),
|
||||
elecInfo: json["elec_info"],
|
||||
coin: Coin.fromJson(json["coin"]),
|
||||
progressPercent: json["progress_percent"].toDouble(),
|
||||
badge: json["badge"],
|
||||
forbidFav: json["forbid_fav"],
|
||||
moreType: json["more_type"],
|
||||
businessOid: json["business_oid"],
|
||||
);
|
||||
}
|
||||
|
||||
class Coin {
|
||||
Coin({
|
||||
this.maxNum,
|
||||
this.coinNumber,
|
||||
});
|
||||
|
||||
int? maxNum;
|
||||
int? coinNumber;
|
||||
|
||||
factory Coin.fromJson(Map<String, dynamic> json) => Coin(
|
||||
maxNum: json["max_num"],
|
||||
coinNumber: json["coin_number"],
|
||||
);
|
||||
}
|
||||
|
||||
class Page {
|
||||
Page({
|
||||
this.id,
|
||||
this.title,
|
||||
this.intro,
|
||||
this.duration,
|
||||
this.link,
|
||||
this.page,
|
||||
this.metas,
|
||||
this.from,
|
||||
this.dimension,
|
||||
});
|
||||
|
||||
int? id;
|
||||
String? title;
|
||||
String? intro;
|
||||
int? duration;
|
||||
String? link;
|
||||
int? page;
|
||||
List<Meta>? metas;
|
||||
String? from;
|
||||
Dimension? dimension;
|
||||
|
||||
factory Page.fromJson(Map<String, dynamic> json) => Page(
|
||||
id: json["id"],
|
||||
title: json["title"],
|
||||
intro: json["intro"],
|
||||
duration: json["duration"],
|
||||
link: json["link"],
|
||||
page: json["page"],
|
||||
metas: List<Meta>.from(json["metas"].map((x) => Meta.fromJson(x))),
|
||||
from: json["from"],
|
||||
dimension: Dimension.fromJson(json["dimension"]),
|
||||
);
|
||||
}
|
||||
|
||||
class Dimension {
|
||||
Dimension({
|
||||
this.width,
|
||||
this.height,
|
||||
this.rotate,
|
||||
});
|
||||
|
||||
int? width;
|
||||
int? height;
|
||||
int? rotate;
|
||||
|
||||
factory Dimension.fromJson(Map<String, dynamic> json) => Dimension(
|
||||
width: json["width"],
|
||||
height: json["height"],
|
||||
rotate: json["rotate"],
|
||||
);
|
||||
}
|
||||
|
||||
class Meta {
|
||||
Meta({
|
||||
this.quality,
|
||||
this.size,
|
||||
});
|
||||
|
||||
int? quality;
|
||||
int? size;
|
||||
|
||||
factory Meta.fromJson(Map<String, dynamic> json) => Meta(
|
||||
quality: json["quality"],
|
||||
size: json["size"],
|
||||
);
|
||||
}
|
||||
|
||||
class Rights {
|
||||
Rights({
|
||||
this.bp,
|
||||
this.elec,
|
||||
this.download,
|
||||
this.movie,
|
||||
this.pay,
|
||||
this.ugcPay,
|
||||
this.hd5,
|
||||
this.noReprint,
|
||||
this.autoplay,
|
||||
this.noBackground,
|
||||
});
|
||||
|
||||
int? bp;
|
||||
int? elec;
|
||||
int? download;
|
||||
int? movie;
|
||||
int? pay;
|
||||
int? ugcPay;
|
||||
int? hd5;
|
||||
int? noReprint;
|
||||
int? autoplay;
|
||||
int? noBackground;
|
||||
|
||||
factory Rights.fromJson(Map<String, dynamic> json) => Rights(
|
||||
bp: json["bp"],
|
||||
elec: json["elec"],
|
||||
download: json["download"],
|
||||
movie: json["movie"],
|
||||
pay: json["pay"],
|
||||
ugcPay: json["ugc_pay"],
|
||||
hd5: json["hd5"],
|
||||
noReprint: json["no_reprint"],
|
||||
autoplay: json["autoplay"],
|
||||
noBackground: json["no_background"],
|
||||
);
|
||||
}
|
||||
|
||||
class Upper {
|
||||
Upper({
|
||||
this.mid,
|
||||
this.name,
|
||||
this.face,
|
||||
this.followed,
|
||||
this.fans,
|
||||
this.vipType,
|
||||
this.vipStatue,
|
||||
this.vipDueDate,
|
||||
this.vipPayType,
|
||||
this.officialRole,
|
||||
this.officialTitle,
|
||||
this.officialDesc,
|
||||
this.displayName,
|
||||
});
|
||||
|
||||
int? mid;
|
||||
String? name;
|
||||
String? face;
|
||||
int? followed;
|
||||
int? fans;
|
||||
int? vipType;
|
||||
int? vipStatue;
|
||||
int? vipDueDate;
|
||||
int? vipPayType;
|
||||
int? officialRole;
|
||||
String? officialTitle;
|
||||
String? officialDesc;
|
||||
String? displayName;
|
||||
|
||||
factory Upper.fromJson(Map<String, dynamic> json) => Upper(
|
||||
mid: json["mid"],
|
||||
name: json["name"],
|
||||
face: json["face"],
|
||||
followed: json["followed"],
|
||||
fans: json["fans"],
|
||||
vipType: json["vip_type"],
|
||||
vipStatue: json["vip_statue"],
|
||||
vipDueDate: json["vip_due_date"],
|
||||
vipPayType: json["vip_pay_type"],
|
||||
officialRole: json["official_role"],
|
||||
officialTitle: json["official_title"],
|
||||
officialDesc: json["official_desc"],
|
||||
displayName: json["display_name"],
|
||||
);
|
||||
}
|
||||
6
lib/models/video/play/ao_output.dart
Normal file
6
lib/models/video/play/ao_output.dart
Normal file
@ -0,0 +1,6 @@
|
||||
final List aoOutputList = [
|
||||
{'title': 'audiotrack,opensles', 'value': '0'},
|
||||
{'title': 'opensles,audiotrack', 'value': '1'},
|
||||
{'title': 'audiotrack', 'value': '2'},
|
||||
{'title': 'opensles', 'value': '3'},
|
||||
];
|
||||
@ -1,6 +1,3 @@
|
||||
import 'package:get/get.dart';
|
||||
import '../../common/subtitle_type.dart';
|
||||
|
||||
class SubTitlteModel {
|
||||
SubTitlteModel({
|
||||
this.aid,
|
||||
@ -78,11 +75,6 @@ class SubTitlteItemModel {
|
||||
aiType: json["ai_type"],
|
||||
aiStatus: json["ai_status"],
|
||||
title: json["lan_doc"],
|
||||
code: SubtitleType.values
|
||||
.firstWhereOrNull(
|
||||
(element) => element.id.toString() == json["lan"])
|
||||
?.index ??
|
||||
-1,
|
||||
content: '',
|
||||
body: [],
|
||||
);
|
||||
|
||||
@ -295,7 +295,7 @@ class AboutController extends GetxController {
|
||||
displayTime: const Duration(milliseconds: 500),
|
||||
).then(
|
||||
(value) => launchUrl(
|
||||
Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'),
|
||||
Uri.parse('https://www.123684.com/s/9sVqVv-DEZ0A'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
);
|
||||
@ -349,7 +349,7 @@ class AboutController extends GetxController {
|
||||
// 官网
|
||||
webSiteUrl() {
|
||||
launchUrl(
|
||||
Uri.parse('https://pilipalanet.mysxl.cn'),
|
||||
Uri.parse('https://pilipala.life'),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
}
|
||||
|
||||
@ -225,6 +225,8 @@ class BangumiIntroController extends GetxController {
|
||||
videoDetailCtr.oid.value = aid;
|
||||
videoDetailCtr.cover.value = cover;
|
||||
videoDetailCtr.queryVideoUrl();
|
||||
videoDetailCtr.getSubtitle();
|
||||
videoDetailCtr.setSubtitleContent();
|
||||
// 重新请求评论
|
||||
try {
|
||||
/// 未渲染回复组件时可能异常
|
||||
|
||||
@ -189,8 +189,8 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
width: 105,
|
||||
height: 160,
|
||||
width: 115,
|
||||
height: 115 / 0.75,
|
||||
src: widget.bangumiDetail!.cover!,
|
||||
),
|
||||
PBadge(
|
||||
@ -208,7 +208,7 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
child: InkWell(
|
||||
onTap: () => showIntroDetail(),
|
||||
child: SizedBox(
|
||||
height: 158,
|
||||
height: 115 / 0.75,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@ -255,13 +255,11 @@ class _BangumiInfoState extends State<BangumiInfo> {
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: widget.bangumiDetail!.stat!['views'],
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: widget.bangumiDetail!.stat!['danmakus'],
|
||||
size: 'medium',
|
||||
),
|
||||
|
||||
@ -60,13 +60,11 @@ class IntroDetail extends StatelessWidget {
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: bangumiDetail!.stat!['views'],
|
||||
size: 'medium',
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: bangumiDetail!.stat!['danmakus'],
|
||||
size: 'medium',
|
||||
),
|
||||
|
||||
@ -96,7 +96,8 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: 268,
|
||||
height: Get.size.width / 3 / 0.75 +
|
||||
MediaQuery.textScalerOf(context).scale(50.0),
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFutureFollow,
|
||||
builder:
|
||||
@ -117,7 +118,6 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
itemBuilder: (context, index) {
|
||||
return Container(
|
||||
width: Get.size.width / 3,
|
||||
height: 254,
|
||||
margin: EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: index ==
|
||||
@ -208,8 +208,8 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
crossAxisSpacing: StyleString.cardSpace,
|
||||
// 列数
|
||||
crossAxisCount: 3,
|
||||
mainAxisExtent: Get.size.width / 3 / 0.65 +
|
||||
MediaQuery.textScalerOf(context).scale(32.0),
|
||||
mainAxisExtent: Get.size.width / 3 / 0.75 +
|
||||
MediaQuery.textScalerOf(context).scale(42.0),
|
||||
),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
|
||||
@ -86,9 +86,11 @@ class _BangumiPanelState extends State<BangumiPanel> {
|
||||
item.aid,
|
||||
item.cover,
|
||||
);
|
||||
if (_bottomSheetController != null) {
|
||||
_bottomSheetController?.close();
|
||||
}
|
||||
try {
|
||||
if (_bottomSheetController != null) {
|
||||
_bottomSheetController?.close();
|
||||
}
|
||||
} catch (_) {}
|
||||
currentIndex.value = i;
|
||||
scrollToIndex();
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ class BangumiCardV extends StatelessWidget {
|
||||
StyleString.imgRadius,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 0.65,
|
||||
aspectRatio: 0.75,
|
||||
child: LayoutBuilder(builder: (context, boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
final double maxHeight = boxConstraints.maxHeight;
|
||||
|
||||
@ -61,7 +61,7 @@ class _BlackListPageState extends State<BlackListPage> {
|
||||
centerTitle: false,
|
||||
title: Obx(
|
||||
() => Text(
|
||||
'黑名单管理 - ${_blackListController.total.value}',
|
||||
'黑名单管理 ${_blackListController.total.value == 0 ? '' : '- ${_blackListController.total.value}'}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
@ -76,8 +76,12 @@ class _BlackListPageState extends State<BlackListPage> {
|
||||
if (data['status']) {
|
||||
List<BlackListItem> list = _blackListController.blackList;
|
||||
return Obx(
|
||||
() => list.length == 1
|
||||
? const SizedBox()
|
||||
() => list.isEmpty
|
||||
? CustomScrollView(
|
||||
slivers: [
|
||||
HttpError(errMsg: '你没有拉黑任何人哦~_~', fn: () => {})
|
||||
],
|
||||
)
|
||||
: ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: list.length,
|
||||
|
||||
@ -2,8 +2,9 @@ import 'package:pilipala/http/danmaku.dart';
|
||||
import 'package:pilipala/models/danmaku/dm.pb.dart';
|
||||
|
||||
class PlDanmakuController {
|
||||
PlDanmakuController(this.cid);
|
||||
PlDanmakuController(this.cid, this.type);
|
||||
final int cid;
|
||||
final String type;
|
||||
Map<int, List<DanmakuElem>> dmSegMap = {};
|
||||
// 已请求的段落标记
|
||||
List<bool> requestedSeg = [];
|
||||
@ -17,7 +18,11 @@ class PlDanmakuController {
|
||||
int segCount = (videoDuration / segmentLength).ceil();
|
||||
requestedSeg = List<bool>.generate(segCount, (index) => false);
|
||||
}
|
||||
queryDanmaku(calcSegment(progress));
|
||||
try {
|
||||
queryDanmaku(calcSegment(progress));
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
@ -31,16 +36,18 @@ class PlDanmakuController {
|
||||
|
||||
void queryDanmaku(int segmentIndex) async {
|
||||
assert(requestedSeg[segmentIndex] == false);
|
||||
requestedSeg[segmentIndex] = true;
|
||||
final DmSegMobileReply result = await DanmakaHttp.queryDanmaku(
|
||||
cid: cid, segmentIndex: segmentIndex + 1);
|
||||
if (result.elems.isNotEmpty) {
|
||||
for (var element in result.elems) {
|
||||
int pos = element.progress ~/ 100; //每0.1秒存储一次
|
||||
if (dmSegMap[pos] == null) {
|
||||
dmSegMap[pos] = [];
|
||||
if (requestedSeg.length > segmentIndex) {
|
||||
requestedSeg[segmentIndex] = true;
|
||||
final DmSegMobileReply result = await DanmakaHttp.queryDanmaku(
|
||||
cid: cid, segmentIndex: segmentIndex + 1);
|
||||
if (result.elems.isNotEmpty) {
|
||||
for (var element in result.elems) {
|
||||
int pos = element.progress ~/ 100; //每0.1秒存储一次
|
||||
if (dmSegMap[pos] == null) {
|
||||
dmSegMap[pos] = [];
|
||||
}
|
||||
dmSegMap[pos]!.add(element);
|
||||
}
|
||||
dmSegMap[pos]!.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,11 +12,15 @@ import 'package:pilipala/utils/storage.dart';
|
||||
class PlDanmaku extends StatefulWidget {
|
||||
final int cid;
|
||||
final PlPlayerController playerController;
|
||||
final String type;
|
||||
final Function(DanmakuController)? createdController;
|
||||
|
||||
const PlDanmaku({
|
||||
super.key,
|
||||
required this.cid,
|
||||
required this.playerController,
|
||||
this.type = 'video',
|
||||
this.createdController,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -43,9 +47,9 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
||||
super.initState();
|
||||
enableShowDanmaku =
|
||||
setting.get(SettingBoxKey.enableShowDanmaku, defaultValue: false);
|
||||
_plDanmakuController = PlDanmakuController(widget.cid);
|
||||
if (mounted) {
|
||||
playerController = widget.playerController;
|
||||
_plDanmakuController = PlDanmakuController(widget.cid, widget.type);
|
||||
playerController = widget.playerController;
|
||||
if (mounted && widget.type == 'video') {
|
||||
if (enableShowDanmaku || playerController.isOpenDanmu.value) {
|
||||
_plDanmakuController.initiate(
|
||||
playerController.duration.value.inMilliseconds,
|
||||
@ -55,13 +59,15 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
||||
..addStatusLister(playerListener)
|
||||
..addPositionListener(videoPositionListen);
|
||||
}
|
||||
playerController.isOpenDanmu.listen((p0) {
|
||||
if (p0 && !_plDanmakuController.initiated) {
|
||||
_plDanmakuController.initiate(
|
||||
playerController.duration.value.inMilliseconds,
|
||||
playerController.position.value.inMilliseconds);
|
||||
}
|
||||
});
|
||||
if (widget.type == 'video') {
|
||||
playerController.isOpenDanmu.listen((p0) {
|
||||
if (p0 && !_plDanmakuController.initiated) {
|
||||
_plDanmakuController.initiate(
|
||||
playerController.duration.value.inMilliseconds,
|
||||
playerController.position.value.inMilliseconds);
|
||||
}
|
||||
});
|
||||
}
|
||||
blockTypes = playerController.blockTypes;
|
||||
showArea = playerController.showArea;
|
||||
opacityVal = playerController.opacityVal;
|
||||
@ -128,6 +134,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
||||
child: DanmakuView(
|
||||
createdController: (DanmakuController e) async {
|
||||
playerController.danmakuController = _controller = e;
|
||||
widget.createdController?.call(e);
|
||||
},
|
||||
option: DanmakuOption(
|
||||
fontSize: 15 * fontSizeVal,
|
||||
@ -136,8 +143,7 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
||||
hideTop: blockTypes.contains(5),
|
||||
hideScroll: blockTypes.contains(2),
|
||||
hideBottom: blockTypes.contains(4),
|
||||
duration:
|
||||
danmakuDurationVal / playerController.playbackSpeed,
|
||||
duration: danmakuDurationVal / playerController.playbackSpeed,
|
||||
strokeWidth: strokeWidth,
|
||||
// initDuration /
|
||||
// (danmakuSpeedVal * widget.playerController.playbackSpeed),
|
||||
|
||||
@ -6,9 +6,7 @@ import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/dynamics.dart';
|
||||
import 'package:pilipala/http/search.dart';
|
||||
import 'package:pilipala/models/bangumi/info.dart';
|
||||
import 'package:pilipala/models/common/dynamics_type.dart';
|
||||
import 'package:pilipala/models/common/search_type.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
import 'package:pilipala/models/live/item.dart';
|
||||
@ -16,7 +14,6 @@ import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/id_utils.dart';
|
||||
import 'package:pilipala/utils/route_push.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class DynamicsController extends GetxController {
|
||||
int page = 1;
|
||||
@ -149,20 +146,26 @@ class DynamicsController extends GetxController {
|
||||
/// 专栏文章查看
|
||||
case 'DYNAMIC_TYPE_ARTICLE':
|
||||
String title = item.modules.moduleDynamic.major.opus.title;
|
||||
String url = item.modules.moduleDynamic.major.opus.jumpUrl;
|
||||
if (url.contains('opus') || url.contains('read')) {
|
||||
String jumpUrl = item.modules.moduleDynamic.major.opus.jumpUrl;
|
||||
String url =
|
||||
jumpUrl.startsWith('//') ? jumpUrl.split('//').last : jumpUrl;
|
||||
if (jumpUrl.contains('opus') || jumpUrl.contains('read')) {
|
||||
RegExp digitRegExp = RegExp(r'\d+');
|
||||
Iterable<Match> matches = digitRegExp.allMatches(url);
|
||||
Iterable<Match> matches = digitRegExp.allMatches(jumpUrl);
|
||||
String number = matches.first.group(0)!;
|
||||
if (url.contains('read')) {
|
||||
number = 'cv$number';
|
||||
if (jumpUrl.contains('read')) {
|
||||
Get.toNamed('/read', parameters: {
|
||||
'title': title,
|
||||
'id': number,
|
||||
'articleType': url.split('/')[1]
|
||||
});
|
||||
} else {
|
||||
Get.toNamed('/opus', parameters: {
|
||||
'title': title,
|
||||
'id': number,
|
||||
'articleType': 'opus'
|
||||
});
|
||||
}
|
||||
Get.toNamed('/htmlRender', parameters: {
|
||||
'url': url.startsWith('//') ? url.split('//').last : url,
|
||||
'title': title,
|
||||
'id': number,
|
||||
'dynamicType': url.split('//').last.split('/')[1]
|
||||
});
|
||||
} else {
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
@ -282,4 +285,11 @@ class DynamicsController extends GetxController {
|
||||
dynamicsList.value = <DynamicItemModel>[];
|
||||
queryFollowDynamic();
|
||||
}
|
||||
|
||||
// 点击up主
|
||||
void onTapUp(data) {
|
||||
mid.value = data.mid;
|
||||
upInfo.value = data;
|
||||
onSelectUp(data.mid);
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,8 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
late DynamicDetailController _dynamicDetailController;
|
||||
late AnimationController fabAnimationCtr;
|
||||
Future? _futureBuilderFuture;
|
||||
late StreamController<bool> titleStreamC; // appBar title
|
||||
late StreamController<bool> titleStreamC =
|
||||
StreamController<bool>.broadcast(); // appBar title
|
||||
late ScrollController scrollController;
|
||||
bool _visibleTitle = false;
|
||||
String? action;
|
||||
@ -48,7 +49,6 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
super.initState();
|
||||
// floor 1原创 2转发
|
||||
init();
|
||||
titleStreamC = StreamController<bool>();
|
||||
if (action == 'comment') {
|
||||
_visibleTitle = true;
|
||||
titleStreamC.add(true);
|
||||
@ -106,7 +106,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
}
|
||||
|
||||
// 查看二级评论
|
||||
void replyReply(replyItem) {
|
||||
void replyReply(replyItem, currentReply, loadMore) {
|
||||
int oid = replyItem.oid;
|
||||
int rpid = replyItem.rpid!;
|
||||
Get.to(
|
||||
@ -125,6 +125,7 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
source: 'dynamic',
|
||||
replyType: ReplyType.values[replyType],
|
||||
firstFloor: replyItem,
|
||||
loadMore: loadMore,
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -324,8 +325,10 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
|
||||
replyItem: replyList[index],
|
||||
showReplyRow: true,
|
||||
replyLevel: '1',
|
||||
replyReply: (replyItem) =>
|
||||
replyReply(replyItem),
|
||||
replyReply:
|
||||
(replyItem, currentReply, loadMore) =>
|
||||
replyReply(replyItem,
|
||||
currentReply, loadMore),
|
||||
replyType: ReplyType.values[replyType],
|
||||
addReply: (replyItem) {
|
||||
replyList[index]
|
||||
|
||||
46
lib/pages/dynamics/up_dynamic/controller.dart
Normal file
46
lib/pages/dynamics/up_dynamic/controller.dart
Normal file
@ -0,0 +1,46 @@
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/dynamics.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
|
||||
class UpDynamicsController extends GetxController {
|
||||
UpDynamicsController(this.upInfo);
|
||||
UpItem upInfo;
|
||||
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs;
|
||||
RxBool isLoadingDynamic = false.obs;
|
||||
String? offset = '';
|
||||
int page = 1;
|
||||
|
||||
Future queryFollowDynamic({type = 'init'}) async {
|
||||
if (type == 'init') {
|
||||
dynamicsList.clear();
|
||||
}
|
||||
// 下拉刷新数据渲染时会触发onLoad
|
||||
if (type == 'onLoad' && page == 1) {
|
||||
return;
|
||||
}
|
||||
isLoadingDynamic.value = true;
|
||||
var res = await DynamicsHttp.followDynamic(
|
||||
page: type == 'init' ? 1 : page,
|
||||
type: 'all',
|
||||
offset: offset,
|
||||
mid: upInfo.mid,
|
||||
);
|
||||
isLoadingDynamic.value = false;
|
||||
if (res['status']) {
|
||||
if (type == 'onLoad' && res['data'].items.isEmpty) {
|
||||
SmartDialog.showToast('没有更多了');
|
||||
return;
|
||||
}
|
||||
if (type == 'init') {
|
||||
dynamicsList.value = res['data'].items;
|
||||
} else {
|
||||
dynamicsList.addAll(res['data'].items);
|
||||
}
|
||||
offset = res['data'].offset;
|
||||
page++;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
4
lib/pages/dynamics/up_dynamic/index.dart
Normal file
4
lib/pages/dynamics/up_dynamic/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library up_dynamics;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
151
lib/pages/dynamics/up_dynamic/route_panel.dart
Normal file
151
lib/pages/dynamics/up_dynamic/route_panel.dart
Normal file
@ -0,0 +1,151 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import '../controller.dart';
|
||||
import 'index.dart';
|
||||
|
||||
class OverlayPanel extends StatefulWidget {
|
||||
const OverlayPanel({super.key, required this.ctr, required this.upInfo});
|
||||
|
||||
final DynamicsController ctr;
|
||||
final UpItem upInfo;
|
||||
|
||||
@override
|
||||
State<OverlayPanel> createState() => _OverlayPanelState();
|
||||
}
|
||||
|
||||
class _OverlayPanelState extends State<OverlayPanel>
|
||||
with SingleTickerProviderStateMixin {
|
||||
static const itemPadding = EdgeInsets.symmetric(horizontal: 6, vertical: 0);
|
||||
final PageController pageController = PageController();
|
||||
late double contentWidth = 50;
|
||||
late List<UpItem> upList;
|
||||
late RxInt currentMid = (-1).obs;
|
||||
TabController? _tabController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
upList = widget.ctr.upData.value.upList!
|
||||
.map<UpItem>((element) => element)
|
||||
.toList();
|
||||
upList.removeAt(0);
|
||||
_tabController = TabController(length: upList.length, vsync: this);
|
||||
|
||||
currentMid.value = widget.upInfo.mid!;
|
||||
|
||||
pageController.addListener(() {
|
||||
int index = pageController.page!.round();
|
||||
int mid = upList[index].mid!;
|
||||
if (mid != currentMid.value) {
|
||||
currentMid.value = mid;
|
||||
_tabController?.animateTo(index,
|
||||
duration: Duration.zero, curve: Curves.linear);
|
||||
onClickUp(upList[index], index, type: 'pageChange');
|
||||
}
|
||||
});
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
int index =
|
||||
upList.indexWhere((element) => element.mid == widget.upInfo.mid);
|
||||
pageController.jumpToPage(index);
|
||||
onClickUp(widget.upInfo, index);
|
||||
_tabController?.animateTo(index,
|
||||
duration: Duration.zero, curve: Curves.linear);
|
||||
onClickUp(upList[index], index, type: 'pageChange');
|
||||
});
|
||||
}
|
||||
|
||||
void onClickUp(data, i, {type = 'click'}) {
|
||||
if (type == 'click') {
|
||||
pageController.jumpToPage(i);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: Get.width,
|
||||
height: Get.height,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
margin: EdgeInsets.fromLTRB(
|
||||
0,
|
||||
MediaQuery.of(context).padding.top + 4,
|
||||
0,
|
||||
MediaQuery.of(context).padding.bottom + 4,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 50,
|
||||
child: TabBar(
|
||||
controller: _tabController,
|
||||
dividerColor: Colors.transparent,
|
||||
automaticIndicatorColorAdjustment: false,
|
||||
tabAlignment: TabAlignment.start,
|
||||
padding: const EdgeInsets.only(left: 12, right: 12),
|
||||
indicatorPadding: EdgeInsets.zero,
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
indicator: const BoxDecoration(),
|
||||
labelPadding: itemPadding,
|
||||
indicatorWeight: 1,
|
||||
isScrollable: true,
|
||||
tabs: upList.map((e) => Tab(child: upItemBuild(e))).toList(),
|
||||
onTap: (index) {
|
||||
feedBack();
|
||||
EasyThrottle.throttle(
|
||||
'follow', const Duration(milliseconds: 200), () {
|
||||
onClickUp(upList[index], index);
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: PageView.builder(
|
||||
itemCount: upList.length,
|
||||
controller: pageController,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
margin: const EdgeInsets.fromLTRB(10, 12, 10, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: UpDyanmicsPage(upInfo: upList[index], ctr: widget.ctr),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget upItemBuild(data) {
|
||||
return Obx(
|
||||
() => AnimatedOpacity(
|
||||
opacity: currentMid == data.mid ? 1 : 0.3,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeInOut,
|
||||
child: AnimatedScale(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
scale: currentMid == data.mid ? 1 : 0.9,
|
||||
child: NetworkImgLayer(
|
||||
width: contentWidth,
|
||||
height: contentWidth,
|
||||
src: data.face,
|
||||
type: 'avatar',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
178
lib/pages/dynamics/up_dynamic/view.dart
Normal file
178
lib/pages/dynamics/up_dynamic/view.dart
Normal file
@ -0,0 +1,178 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/no_data.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
import 'package:pilipala/pages/dynamics/up_dynamic/index.dart';
|
||||
|
||||
import '../index.dart';
|
||||
import '../widgets/dynamic_panel.dart';
|
||||
|
||||
class UpDyanmicsPage extends StatefulWidget {
|
||||
final UpItem upInfo;
|
||||
final DynamicsController ctr;
|
||||
|
||||
const UpDyanmicsPage({
|
||||
required this.upInfo,
|
||||
required this.ctr,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<UpDyanmicsPage> createState() => _UpDyanmicsPageState();
|
||||
}
|
||||
|
||||
class _UpDyanmicsPageState extends State<UpDyanmicsPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
late UpDynamicsController _upDynamicsController;
|
||||
final ScrollController scrollController = ScrollController();
|
||||
late Future _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_upDynamicsController = Get.put(UpDynamicsController(widget.upInfo),
|
||||
tag: widget.upInfo.mid.toString());
|
||||
_futureBuilderFuture = _upDynamicsController.queryFollowDynamic();
|
||||
|
||||
scrollController.addListener(
|
||||
() async {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
EasyThrottle.throttle(
|
||||
'queryFollowDynamic', const Duration(seconds: 1), () {
|
||||
_upDynamicsController.queryFollowDynamic(type: 'onLoad');
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return CustomScrollView(
|
||||
controller: scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
floating: true,
|
||||
delegate: _MySliverPersistentHeaderDelegate(
|
||||
child: Container(
|
||||
height: 50,
|
||||
padding: const EdgeInsets.fromLTRB(20, 4, 4, 4),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
width: 0.1,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
widget.upInfo.uname!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
icon: const Icon(Icons.close),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data == null) {
|
||||
return const SliverToBoxAdapter(child: SizedBox());
|
||||
}
|
||||
Map? data = snapshot.data;
|
||||
if (data != null && data['status']) {
|
||||
List<DynamicItemModel> list =
|
||||
_upDynamicsController.dynamicsList;
|
||||
return Obx(
|
||||
() {
|
||||
if (list.isEmpty) {
|
||||
if (_upDynamicsController.isLoadingDynamic.value) {
|
||||
return skeleton();
|
||||
} else {
|
||||
return const NoData();
|
||||
}
|
||||
} else {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return DynamicPanel(item: list[index]);
|
||||
},
|
||||
childCount: list.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data?['msg'] ?? '请求异常',
|
||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||
fn: () {},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return skeleton();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget skeleton() {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const DynamicCardSkeleton();
|
||||
}, childCount: 5),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MySliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
|
||||
_MySliverPersistentHeaderDelegate({required this.child});
|
||||
final double _minExtent = 50;
|
||||
final double _maxExtent = 50;
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(
|
||||
BuildContext context, double shrinkOffset, bool overlapsContent) {
|
||||
return child;
|
||||
}
|
||||
|
||||
@override
|
||||
double get maxExtent => _maxExtent;
|
||||
|
||||
@override
|
||||
double get minExtent => _minExtent;
|
||||
|
||||
@override
|
||||
bool shouldRebuild(covariant _MySliverPersistentHeaderDelegate oldDelegate) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@ import 'package:pilipala/common/skeleton/dynamic_card.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/common/widgets/no_data.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/plugin/pl_popup/index.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/main_stream.dart';
|
||||
import 'package:pilipala/utils/route_push.dart';
|
||||
@ -17,6 +18,7 @@ import 'package:pilipala/utils/storage.dart';
|
||||
import '../mine/controller.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/dynamic_panel.dart';
|
||||
import 'up_dynamic/route_panel.dart';
|
||||
import 'widgets/up_panel.dart';
|
||||
|
||||
class DynamicsPage extends StatefulWidget {
|
||||
@ -80,7 +82,6 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
titleSpacing: 0,
|
||||
title: SizedBox(
|
||||
height: 34,
|
||||
child: Stack(
|
||||
@ -202,7 +203,21 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
}
|
||||
Map data = snapshot.data;
|
||||
if (data['status']) {
|
||||
return Obx(() => UpPanel(_dynamicsController.upData.value));
|
||||
return Obx(
|
||||
() => UpPanel(
|
||||
upData: _dynamicsController.upData.value,
|
||||
onClickUpCb: (data) {
|
||||
// _dynamicsController.onTapUp(data);
|
||||
Navigator.push(
|
||||
context,
|
||||
PlPopupRoute(
|
||||
child: OverlayPanel(
|
||||
ctr: _dynamicsController, upInfo: data),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SliverToBoxAdapter(
|
||||
child: SizedBox(height: 80),
|
||||
|
||||
@ -4,8 +4,7 @@ import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/pages/preview/index.dart';
|
||||
|
||||
import 'package:pilipala/plugin/pl_gallery/index.dart';
|
||||
import 'rich_node_panel.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
@ -59,17 +58,15 @@ class _ContentState extends State<Content> {
|
||||
(pictureItem.height != null && pictureItem.width != null
|
||||
? pictureItem.height! / pictureItem.width!
|
||||
: 1);
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
useSafeArea: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImagePreview(initialPage: 0, imgList: picList);
|
||||
},
|
||||
);
|
||||
return Hero(
|
||||
tag: pictureItem.url!,
|
||||
placeholderBuilder:
|
||||
(BuildContext context, Size heroSize, Widget child) {
|
||||
return child;
|
||||
},
|
||||
child: Container(
|
||||
child: GestureDetector(
|
||||
onTap: () => onPreviewImg(picList, 1, context),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
constraints: BoxConstraints(maxHeight: maxHeight),
|
||||
width: box.maxWidth / 2,
|
||||
@ -91,7 +88,9 @@ class _ContentState extends State<Content> {
|
||||
)
|
||||
: const SizedBox(),
|
||||
],
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -102,26 +101,23 @@ class _ContentState extends State<Content> {
|
||||
List<Widget> list = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
picList.add(pics[i].url!);
|
||||
}
|
||||
for (var i = 0; i < len; i++) {
|
||||
list.add(
|
||||
LayoutBuilder(
|
||||
builder: (context, BoxConstraints box) {
|
||||
double maxWidth = box.maxWidth.truncateToDouble();
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
useSafeArea: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImagePreview(initialPage: i, imgList: picList);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
src: pics[i].url,
|
||||
width: maxWidth,
|
||||
height: maxWidth,
|
||||
origAspectRatio:
|
||||
pics[i].width!.toInt() / pics[i].height!.toInt(),
|
||||
return Hero(
|
||||
tag: picList[i],
|
||||
child: GestureDetector(
|
||||
onTap: () => onPreviewImg(picList, i, context),
|
||||
child: NetworkImgLayer(
|
||||
src: pics[i].url,
|
||||
width: maxWidth,
|
||||
height: maxWidth,
|
||||
origAspectRatio:
|
||||
pics[i].width!.toInt() / pics[i].height!.toInt(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
@ -163,6 +159,18 @@ class _ContentState extends State<Content> {
|
||||
);
|
||||
}
|
||||
|
||||
void onPreviewImg(picList, initIndex, context) {
|
||||
Navigator.of(context).push(
|
||||
HeroDialogRoute<void>(
|
||||
builder: (BuildContext context) => InteractiveviewerGallery(
|
||||
sources: picList,
|
||||
initIndex: initIndex,
|
||||
onPageChanged: (int pageIndex) {},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
TextStyle authorStyle =
|
||||
|
||||
@ -3,7 +3,19 @@ import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/badge.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/pages/preview/index.dart';
|
||||
import 'package:pilipala/plugin/pl_gallery/index.dart';
|
||||
|
||||
void onPreviewImg(currentUrl, picList, initIndex, context) {
|
||||
Navigator.of(context).push(
|
||||
HeroDialogRoute<void>(
|
||||
builder: (BuildContext context) => InteractiveviewerGallery(
|
||||
sources: picList,
|
||||
initIndex: initIndex,
|
||||
onPageChanged: (int pageIndex) {},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget picWidget(item, context) {
|
||||
String type = item.modules.moduleDynamic.major.type;
|
||||
@ -21,25 +33,25 @@ Widget picWidget(item, context) {
|
||||
List<Widget> list = [];
|
||||
for (var i = 0; i < len; i++) {
|
||||
picList.add(pictures[i].src ?? pictures[i].url);
|
||||
}
|
||||
for (var i = 0; i < len; i++) {
|
||||
list.add(
|
||||
LayoutBuilder(
|
||||
builder: (context, BoxConstraints box) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
useSafeArea: false,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return ImagePreview(initialPage: i, imgList: picList);
|
||||
},
|
||||
);
|
||||
return Hero(
|
||||
tag: picList[i],
|
||||
placeholderBuilder:
|
||||
(BuildContext context, Size heroSize, Widget child) {
|
||||
return child;
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
src: pictures[i].src ?? pictures[i].url,
|
||||
width: box.maxWidth,
|
||||
height: box.maxWidth,
|
||||
child: GestureDetector(
|
||||
onTap: () => onPreviewImg(picList[i], picList, i, context),
|
||||
child: NetworkImgLayer(
|
||||
src: pictures[i].src ?? pictures[i].url,
|
||||
width: box.maxWidth,
|
||||
height: box.maxWidth,
|
||||
),
|
||||
),
|
||||
// ),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@ -4,13 +4,18 @@ import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/models/dynamics/up.dart';
|
||||
import 'package:pilipala/models/live/item.dart';
|
||||
import 'package:pilipala/pages/dynamics/controller.dart';
|
||||
import 'package:pilipala/utils/feed_back.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class UpPanel extends StatefulWidget {
|
||||
final FollowUpModel upData;
|
||||
const UpPanel(this.upData, {Key? key}) : super(key: key);
|
||||
final Function? onClickUpCb;
|
||||
|
||||
const UpPanel({
|
||||
super.key,
|
||||
required this.upData,
|
||||
this.onClickUpCb,
|
||||
});
|
||||
|
||||
@override
|
||||
State<UpPanel> createState() => _UpPanelState();
|
||||
@ -33,27 +38,25 @@ class _UpPanelState extends State<UpPanel> {
|
||||
|
||||
void onClickUp(data, i) {
|
||||
currentMid = data.mid;
|
||||
Get.find<DynamicsController>().mid.value = data.mid;
|
||||
Get.find<DynamicsController>().upInfo.value = data;
|
||||
Get.find<DynamicsController>().onSelectUp(data.mid);
|
||||
int liveLen = liveList.length;
|
||||
int upLen = upList.length;
|
||||
double itemWidth = contentWidth + itemPadding.horizontal;
|
||||
double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
double moveDistance = 0.0;
|
||||
if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
|
||||
} else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
|
||||
moveDistance = (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
|
||||
} else {
|
||||
moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
|
||||
}
|
||||
data.hasUpdate = false;
|
||||
scrollController.animateTo(
|
||||
moveDistance,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.linear,
|
||||
);
|
||||
setState(() {});
|
||||
widget.onClickUpCb?.call(data);
|
||||
// int liveLen = liveList.length;
|
||||
// int upLen = upList.length;
|
||||
// double itemWidth = contentWidth + itemPadding.horizontal;
|
||||
// double screenWidth = MediaQuery.sizeOf(context).width;
|
||||
// double moveDistance = 0.0;
|
||||
// if (itemWidth * (upList.length + liveList.length) <= screenWidth) {
|
||||
// } else if ((upLen - i - 0.5) * itemWidth > screenWidth / 2) {
|
||||
// moveDistance = (i + liveLen + 0.5) * itemWidth + 46 - screenWidth / 2;
|
||||
// } else {
|
||||
// moveDistance = (upLen + liveLen) * itemWidth + 46 - screenWidth;
|
||||
// }
|
||||
// data.hasUpdate = false;
|
||||
// scrollController.animateTo(
|
||||
// moveDistance,
|
||||
// duration: const Duration(milliseconds: 200),
|
||||
// curve: Curves.linear,
|
||||
// );
|
||||
// setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -86,7 +86,7 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) {
|
||||
height: width / StyleString.aspectRatio,
|
||||
src: content.cover,
|
||||
),
|
||||
if (content.badge != null && type == 'pgc')
|
||||
if (content.badge != null && content.badge['text'] != null)
|
||||
PBadge(
|
||||
text: content.badge['text'],
|
||||
top: 8.0,
|
||||
|
||||
@ -49,7 +49,6 @@ class FansController extends GetxController {
|
||||
} else if (type == 'onLoad') {
|
||||
fansList.addAll(res['data'].list);
|
||||
}
|
||||
print(total);
|
||||
if ((pn == 1 && total < ps) || res['data'].list.isEmpty) {
|
||||
loadingText.value = '没有更多了';
|
||||
}
|
||||
|
||||
@ -103,9 +103,17 @@ class _FansPageState extends State<FansPage> {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => _fansController.queryFans('init'),
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () {
|
||||
_futureBuilderFuture =
|
||||
_fansController.queryFans('init');
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -16,10 +16,16 @@ class FavController extends GetxController {
|
||||
int currentPage = 1;
|
||||
int pageSize = 60;
|
||||
RxBool hasMore = true.obs;
|
||||
late int mid;
|
||||
late int ownerMid;
|
||||
RxBool isOwner = false.obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
mid = int.parse(Get.parameters['mid'] ?? '-1');
|
||||
userInfo = userInfoCache.get('userInfoCache');
|
||||
ownerMid = userInfo != null ? userInfo!.mid! : -1;
|
||||
isOwner.value = mid == -1 || mid == ownerMid;
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
@ -33,7 +39,7 @@ class FavController extends GetxController {
|
||||
var res = await UserHttp.userfavFolder(
|
||||
pn: currentPage,
|
||||
ps: pageSize,
|
||||
mid: userInfo!.mid!,
|
||||
mid: isOwner.value ? ownerMid : mid,
|
||||
);
|
||||
if (res['status']) {
|
||||
if (type == 'init') {
|
||||
|
||||
@ -42,67 +42,105 @@ class _FavPageState extends State<FavPage> {
|
||||
appBar: AppBar(
|
||||
centerTitle: false,
|
||||
titleSpacing: 0,
|
||||
title: Text(
|
||||
'我的收藏',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
title: Obx(() => Text(
|
||||
'${_favController.isOwner.value ? '我' : 'Ta'}的收藏',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
)),
|
||||
actions: [
|
||||
Obx(() => !_favController.isOwner.value
|
||||
? IconButton(
|
||||
onPressed: () =>
|
||||
Get.toNamed('/subscription?mid=${_favController.mid}'),
|
||||
icon: const Icon(Icons.subscriptions_outlined, size: 21),
|
||||
tooltip: 'Ta的订阅',
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
|
||||
// 新建收藏夹
|
||||
Obx(() => _favController.isOwner.value
|
||||
? IconButton(
|
||||
onPressed: () async {
|
||||
await Get.toNamed('/favEdit');
|
||||
_favController.hasMore.value = true;
|
||||
_favController.currentPage = 1;
|
||||
setState(() {
|
||||
_futureBuilderFuture = _favController.queryFavFolder();
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
tooltip: '新建收藏夹',
|
||||
)
|
||||
: const SizedBox.shrink()),
|
||||
IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/favSearch?searchType=1&mediaId=${_favController.favFolderData.value.list!.first.id}'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const SizedBox(width: 14),
|
||||
],
|
||||
),
|
||||
body: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map? data = snapshot.data;
|
||||
if (data != null && data['status']) {
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: _favController.favFolderList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return FavItem(
|
||||
favFolderItem: _favController.favFolderList[index]);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data?['msg'] ?? '请求异常',
|
||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||
fn: () {
|
||||
if (data?['code'] == -101) {
|
||||
RoutePush.loginRedirectPush();
|
||||
} else {
|
||||
setState(() {
|
||||
_futureBuilderFuture =
|
||||
_favController.queryFavFolder();
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
},
|
||||
itemCount: 10,
|
||||
);
|
||||
}
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
_favController.hasMore.value = true;
|
||||
_favController.currentPage = 1;
|
||||
setState(() {
|
||||
_futureBuilderFuture = _favController.queryFavFolder(type: 'init');
|
||||
});
|
||||
},
|
||||
child: _buildBody(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map? data = snapshot.data;
|
||||
if (data != null && data['status']) {
|
||||
return Obx(
|
||||
() => ListView.builder(
|
||||
controller: scrollController,
|
||||
itemCount: _favController.favFolderList.length,
|
||||
itemBuilder: (context, index) {
|
||||
return FavItem(
|
||||
favFolderItem: _favController.favFolderList[index],
|
||||
isOwner: _favController.isOwner.value,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return CustomScrollView(
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
HttpError(
|
||||
errMsg: data?['msg'] ?? '请求异常',
|
||||
btnText: data?['code'] == -101 ? '去登录' : null,
|
||||
fn: () {
|
||||
if (data?['code'] == -101) {
|
||||
RoutePush.loginRedirectPush();
|
||||
} else {
|
||||
setState(() {
|
||||
_futureBuilderFuture = _favController.queryFavFolder();
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
},
|
||||
itemCount: 10,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,7 +7,9 @@ import 'package:pilipala/utils/utils.dart';
|
||||
class FavItem extends StatelessWidget {
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
final favFolderItem;
|
||||
const FavItem({super.key, required this.favFolderItem});
|
||||
final bool isOwner;
|
||||
const FavItem(
|
||||
{super.key, required this.favFolderItem, required this.isOwner});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -20,6 +22,7 @@ class FavItem extends StatelessWidget {
|
||||
parameters: {
|
||||
'heroTag': heroTag,
|
||||
'mediaId': favFolderItem.id.toString(),
|
||||
'isOwner': isOwner ? '1' : '0',
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -71,7 +74,7 @@ class VideoContent extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
||||
padding: const EdgeInsets.fromLTRB(10, 2, 6, 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -91,6 +94,15 @@ class VideoContent extends StatelessWidget {
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
[23, 1].contains(favFolderItem.attr) ? '私密' : '公开',
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@ -6,26 +6,30 @@ import 'package:pilipala/http/video.dart';
|
||||
import 'package:pilipala/models/user/fav_detail.dart';
|
||||
import 'package:pilipala/models/user/fav_folder.dart';
|
||||
import 'package:pilipala/pages/fav/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class FavDetailController extends GetxController {
|
||||
FavFolderItemData? item;
|
||||
Rx<FavDetailData> favDetailData = FavDetailData().obs;
|
||||
RxString title = ''.obs;
|
||||
|
||||
int? mediaId;
|
||||
late String heroTag;
|
||||
int currentPage = 1;
|
||||
bool isLoadingMore = false;
|
||||
RxMap favInfo = {}.obs;
|
||||
RxList favList = [].obs;
|
||||
RxList<FavDetailItemData> favList = <FavDetailItemData>[].obs;
|
||||
RxString loadingText = '加载中...'.obs;
|
||||
RxInt mediaCount = 0.obs;
|
||||
late String isOwner;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
item = Get.arguments;
|
||||
title.value = item!.title!;
|
||||
if (Get.parameters.keys.isNotEmpty) {
|
||||
mediaId = int.parse(Get.parameters['mediaId']!);
|
||||
heroTag = Get.parameters['heroTag']!;
|
||||
isOwner = Get.parameters['isOwner']!;
|
||||
}
|
||||
super.onInit();
|
||||
}
|
||||
@ -113,4 +117,37 @@ class FavDetailController extends GetxController {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onEditFavFolder() async {
|
||||
var res = await Get.toNamed(
|
||||
'/favEdit',
|
||||
arguments: {
|
||||
'mediaId': mediaId.toString(),
|
||||
'title': item!.title,
|
||||
'intro': item!.intro,
|
||||
'cover': item!.cover,
|
||||
'privacy': [23, 1].contains(item!.attr) ? 1 : 0,
|
||||
},
|
||||
);
|
||||
title.value = res['title'];
|
||||
print(title);
|
||||
}
|
||||
|
||||
Future toViewPlayAll() async {
|
||||
final FavDetailItemData firstItem = favList.first;
|
||||
final String heroTag = Utils.makeHeroTag(firstItem.bvid);
|
||||
Get.toNamed(
|
||||
'/video?bvid=${firstItem.bvid}&cid=${firstItem.cid}',
|
||||
arguments: {
|
||||
'videoItem': firstItem,
|
||||
'heroTag': heroTag,
|
||||
'sourceType': 'fav',
|
||||
'mediaId': favInfo['id'],
|
||||
'oid': firstItem.id,
|
||||
'favTitle': favInfo['title'],
|
||||
'favInfo': favInfo,
|
||||
'count': favInfo['media_count'],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,7 +22,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
late final ScrollController _controller = ScrollController();
|
||||
final FavDetailController _favDetailController =
|
||||
Get.put(FavDetailController());
|
||||
late StreamController<bool> titleStreamC; // a
|
||||
late StreamController<bool> titleStreamC =
|
||||
StreamController<bool>.broadcast(); // a
|
||||
Future? _futureBuilderFuture;
|
||||
late String mediaId;
|
||||
|
||||
@ -31,7 +32,6 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
super.initState();
|
||||
mediaId = Get.parameters['mediaId']!;
|
||||
_futureBuilderFuture = _favDetailController.queryUserFavFolderDetail();
|
||||
titleStreamC = StreamController<bool>();
|
||||
_controller.addListener(
|
||||
() {
|
||||
if (_controller.offset > 160) {
|
||||
@ -80,9 +80,11 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_favDetailController.item!.title!,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
Obx(
|
||||
() => Text(
|
||||
_favDetailController.title.value,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'共${_favDetailController.mediaCount}条视频',
|
||||
@ -106,6 +108,11 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
position: PopupMenuPosition.under,
|
||||
onSelected: (String type) {},
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
PopupMenuItem<String>(
|
||||
onTap: () => _favDetailController.onEditFavFolder(),
|
||||
value: 'edit',
|
||||
child: const Text('编辑收藏夹'),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
onTap: () => _favDetailController.onDelFavFolder(),
|
||||
value: 'pause',
|
||||
@ -151,14 +158,16 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
_favDetailController.item!.title!,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.fontSize,
|
||||
fontWeight: FontWeight.bold),
|
||||
Obx(
|
||||
() => Text(
|
||||
_favDetailController.title.value,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.fontSize,
|
||||
fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
@ -212,6 +221,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
SliverChildBuilderDelegate((context, index) {
|
||||
return FavVideoCardH(
|
||||
videoItem: favList[index],
|
||||
isOwner: _favDetailController.isOwner,
|
||||
callFn: () => _favDetailController
|
||||
.onCancelFav(favList[index].id),
|
||||
);
|
||||
@ -254,6 +264,15 @@ class _FavDetailPageState extends State<FavDetailPage> {
|
||||
)
|
||||
],
|
||||
),
|
||||
floatingActionButton: Obx(
|
||||
() => _favDetailController.mediaCount > 0
|
||||
? FloatingActionButton.extended(
|
||||
onPressed: _favDetailController.toViewPlayAll,
|
||||
label: const Text('播放全部'),
|
||||
icon: const Icon(Icons.playlist_play),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,12 +18,14 @@ class FavVideoCardH extends StatelessWidget {
|
||||
final dynamic videoItem;
|
||||
final Function? callFn;
|
||||
final int? searchType;
|
||||
final String isOwner;
|
||||
|
||||
const FavVideoCardH({
|
||||
Key? key,
|
||||
required this.videoItem,
|
||||
this.callFn,
|
||||
this.searchType,
|
||||
required this.isOwner,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -123,6 +125,7 @@ class FavVideoCardH extends StatelessWidget {
|
||||
videoItem: videoItem,
|
||||
callFn: callFn,
|
||||
searchType: searchType,
|
||||
isOwner: isOwner,
|
||||
)
|
||||
],
|
||||
),
|
||||
@ -140,11 +143,13 @@ class VideoContent extends StatelessWidget {
|
||||
final dynamic videoItem;
|
||||
final Function? callFn;
|
||||
final int? searchType;
|
||||
final String isOwner;
|
||||
const VideoContent({
|
||||
super.key,
|
||||
required this.videoItem,
|
||||
this.callFn,
|
||||
this.searchType,
|
||||
required this.isOwner,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -198,20 +203,16 @@ class VideoContent extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(top: 2),
|
||||
child: Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoItem.cntInfo['play'],
|
||||
),
|
||||
StatView(view: videoItem.cntInfo['play']),
|
||||
const SizedBox(width: 8),
|
||||
StatDanMu(
|
||||
theme: 'gray', danmu: videoItem.cntInfo['danmaku']),
|
||||
StatDanMu(danmu: videoItem.cntInfo['danmaku']),
|
||||
const Spacer(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
searchType != 1
|
||||
searchType != 1 && isOwner == '1'
|
||||
? Positioned(
|
||||
right: 0,
|
||||
bottom: -4,
|
||||
|
||||
77
lib/pages/fav_edit/controller.dart
Normal file
77
lib/pages/fav_edit/controller.dart
Normal file
@ -0,0 +1,77 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/fav.dart';
|
||||
|
||||
class FavEditController extends GetxController {
|
||||
final GlobalKey formKey = GlobalKey<FormState>();
|
||||
final TextEditingController titleController = TextEditingController();
|
||||
final TextEditingController contentController = TextEditingController();
|
||||
|
||||
final FocusNode titleTextFieldNode = FocusNode();
|
||||
final FocusNode contentTextFieldNode = FocusNode();
|
||||
|
||||
// 默认新建
|
||||
RxString type = 'add'.obs;
|
||||
|
||||
String? mediaId;
|
||||
String cover = ''; // 封面
|
||||
String title = ''; // 名称
|
||||
String intro = ''; // 简介
|
||||
RxInt privacy = 0.obs; // 是否公开 0公开 1私密
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
var args = Get.arguments;
|
||||
if (args != null) {
|
||||
type.value = 'edit';
|
||||
mediaId = args['mediaId'];
|
||||
title = args['title'];
|
||||
intro = args['intro'];
|
||||
cover = args['cover'];
|
||||
privacy.value = args['privacy'];
|
||||
titleController.text = title;
|
||||
contentController.text = intro;
|
||||
}
|
||||
}
|
||||
|
||||
void onSubmit() async {
|
||||
// 表单验证
|
||||
if ((formKey.currentState as FormState).validate()) {
|
||||
if (type.value == 'edit') {
|
||||
await editFolder();
|
||||
} else {
|
||||
await addFolder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> editFolder() async {
|
||||
var res = await FavHttp.editFolder(
|
||||
title: title,
|
||||
intro: intro,
|
||||
mediaId: mediaId!,
|
||||
cover: cover,
|
||||
);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('编辑成功');
|
||||
Get.back(result: {'title': title});
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addFolder() async {
|
||||
var res = await FavHttp.addFolder(
|
||||
title: title,
|
||||
intro: intro,
|
||||
);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('新建成功');
|
||||
Get.back();
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
library preview;
|
||||
library fav_edit;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
111
lib/pages/fav_edit/view.dart
Normal file
111
lib/pages/fav_edit/view.dart
Normal file
@ -0,0 +1,111 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
class FavEditPage extends StatefulWidget {
|
||||
const FavEditPage({super.key});
|
||||
|
||||
@override
|
||||
State<FavEditPage> createState() => _FavEditPageState();
|
||||
}
|
||||
|
||||
class _FavEditPageState extends State<FavEditPage> {
|
||||
final FavEditController _favEditController = Get.put(FavEditController());
|
||||
String title = '';
|
||||
String content = '';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
title: Obx(
|
||||
() => _favEditController.type.value == 'add'
|
||||
? Text(
|
||||
'新建收藏夹',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
)
|
||||
: Text(
|
||||
'编辑收藏夹',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
Obx(
|
||||
() => _favEditController.privacy.value == 0
|
||||
? IconButton(
|
||||
onPressed: () {
|
||||
_favEditController.privacy.value = 1;
|
||||
},
|
||||
icon: const Icon(Icons.lock_open_outlined))
|
||||
: IconButton(
|
||||
onPressed: () {
|
||||
_favEditController.privacy.value = 0;
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.lock_outlined,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
)),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: _favEditController.onSubmit, child: const Text('保存')),
|
||||
const SizedBox(width: 14),
|
||||
],
|
||||
),
|
||||
body: Form(
|
||||
key: _favEditController.formKey, //设置globalKey,用于后面获取FormState
|
||||
autovalidateMode: AutovalidateMode.disabled,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.fromLTRB(14, 10, 14, 5),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.2),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
controller: _favEditController.titleController,
|
||||
focusNode: _favEditController.titleTextFieldNode,
|
||||
decoration: const InputDecoration(
|
||||
hintText: "收藏夹名称",
|
||||
enabledBorder: InputBorder.none,
|
||||
focusedBorder: InputBorder.none,
|
||||
),
|
||||
// 校验标题
|
||||
validator: (v) {
|
||||
return v!.trim().isNotEmpty ? null : "请输入收藏夹名称";
|
||||
},
|
||||
onChanged: (val) {
|
||||
_favEditController.title = val;
|
||||
},
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 14, vertical: 5),
|
||||
child: TextFormField(
|
||||
controller: _favEditController.contentController,
|
||||
minLines: 1,
|
||||
maxLines: 5,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '输入收藏夹简介', border: InputBorder.none),
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
onChanged: (val) {
|
||||
_favEditController.intro = val;
|
||||
},
|
||||
)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -100,6 +100,7 @@ class _FavSearchPageState extends State<FavSearchPage> {
|
||||
return FavVideoCardH(
|
||||
videoItem: _favSearchCtr.favList[index],
|
||||
searchType: searchType,
|
||||
isOwner: '0',
|
||||
callFn: () => searchType != 1
|
||||
? _favSearchCtr
|
||||
.onCancelFav(_favSearchCtr.favList[index].id!)
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
@ -47,9 +48,24 @@ class FollowItem extends StatelessWidget {
|
||||
height: 34,
|
||||
child: TextButton(
|
||||
onPressed: () async {
|
||||
await Get.bottomSheet(
|
||||
GroupPanel(mid: item.mid!),
|
||||
isScrollControlled: true,
|
||||
await showFlexibleBottomSheet(
|
||||
bottomSheetBorderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
minHeight: 1,
|
||||
initHeight: 1,
|
||||
maxHeight: 1,
|
||||
context: Get.context!,
|
||||
builder: (BuildContext context,
|
||||
ScrollController scrollController, double offset) {
|
||||
return GroupPanel(
|
||||
mid: item.mid!,
|
||||
scrollController: scrollController,
|
||||
);
|
||||
},
|
||||
anchors: [1],
|
||||
isSafeArea: true,
|
||||
);
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
|
||||
@ -48,7 +48,7 @@ class FollowSearchController extends GetxController {
|
||||
return {'status': true, 'data': <FollowItemModel>[].obs};
|
||||
}
|
||||
if (type == 'init') {
|
||||
ps = 1;
|
||||
pn = 1;
|
||||
}
|
||||
var res = await MemberHttp.getfollowSearch(
|
||||
mid: mid,
|
||||
|
||||
@ -43,14 +43,17 @@ class HistoryItem extends StatelessWidget {
|
||||
}
|
||||
if (videoItem.history.business.contains('article')) {
|
||||
int cid = videoItem.history.cid ??
|
||||
// videoItem.history.oid ??
|
||||
videoItem.history.oid ??
|
||||
await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
if (cid == -1) {
|
||||
return SmartDialog.showToast('无法获取文章内容');
|
||||
}
|
||||
Get.toNamed(
|
||||
'/webview',
|
||||
'/read',
|
||||
parameters: {
|
||||
'url': 'https://www.bilibili.com/read/cv$cid',
|
||||
'type': 'note',
|
||||
'pageTitle': videoItem.title
|
||||
'title': videoItem.title,
|
||||
'id': cid.toString(),
|
||||
'articleType': 'read',
|
||||
},
|
||||
);
|
||||
} else if (videoItem.history.business == 'live') {
|
||||
|
||||
@ -8,7 +8,7 @@ import 'package:pilipala/utils/storage.dart';
|
||||
import '../../http/index.dart';
|
||||
|
||||
class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
bool flag = false;
|
||||
bool flag = true;
|
||||
late RxList tabs = [].obs;
|
||||
RxInt initialIndex = 1.obs;
|
||||
late TabController tabController;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -46,104 +47,73 @@ class _HomePageState extends State<HomePage>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
Brightness currentBrightness = MediaQuery.of(context).platformBrightness;
|
||||
// 设置状态栏图标的亮度
|
||||
if (_homeController.enableGradientBg) {
|
||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: currentBrightness == Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
));
|
||||
}
|
||||
return Scaffold(
|
||||
extendBody: true,
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: _homeController.enableGradientBg
|
||||
? null
|
||||
: AppBar(toolbarHeight: 0, elevation: 0),
|
||||
body: Stack(
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
toolbarHeight: 0,
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
systemOverlayStyle: Platform.isAndroid
|
||||
? SystemUiOverlayStyle(
|
||||
statusBarIconBrightness:
|
||||
Theme.of(context).brightness == Brightness.dark
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
)
|
||||
: Theme.of(context).brightness == Brightness.dark
|
||||
? SystemUiOverlayStyle.light
|
||||
: SystemUiOverlayStyle.dark,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
// gradient background
|
||||
if (_homeController.enableGradientBg) ...[
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Opacity(
|
||||
opacity: 0.6,
|
||||
child: Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.9),
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.primary
|
||||
.withOpacity(0.5),
|
||||
Theme.of(context).colorScheme.surface
|
||||
],
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
stops: const [0, 0.0034, 0.34]),
|
||||
CustomAppBar(
|
||||
stream: _homeController.hideSearchBar
|
||||
? stream
|
||||
: StreamController<bool>.broadcast().stream,
|
||||
ctr: _homeController,
|
||||
callback: showUserBottomSheet,
|
||||
),
|
||||
if (_homeController.tabs.length > 1) ...[
|
||||
if (_homeController.enableGradientBg) ...[
|
||||
const CustomTabs(),
|
||||
] else ...[
|
||||
Container(
|
||||
width: double.infinity,
|
||||
height: 42,
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: TabBar(
|
||||
controller: _homeController.tabController,
|
||||
tabs: [
|
||||
for (var i in _homeController.tabs) Tab(text: i['label'])
|
||||
],
|
||||
isScrollable: true,
|
||||
dividerColor: Colors.transparent,
|
||||
enableFeedback: true,
|
||||
splashBorderRadius: BorderRadius.circular(10),
|
||||
tabAlignment: TabAlignment.center,
|
||||
onTap: (value) {
|
||||
feedBack();
|
||||
if (_homeController.initialIndex.value == value) {
|
||||
_homeController.tabsCtrList[value]().animateToTop();
|
||||
}
|
||||
_homeController.initialIndex.value = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Column(
|
||||
children: [
|
||||
CustomAppBar(
|
||||
stream: _homeController.hideSearchBar
|
||||
? stream
|
||||
: StreamController<bool>.broadcast().stream,
|
||||
ctr: _homeController,
|
||||
callback: showUserBottomSheet,
|
||||
),
|
||||
if (_homeController.tabs.length > 1) ...[
|
||||
if (_homeController.enableGradientBg) ...[
|
||||
const CustomTabs(),
|
||||
] else ...[
|
||||
const SizedBox(height: 4),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 42,
|
||||
child: Align(
|
||||
alignment: Alignment.center,
|
||||
child: TabBar(
|
||||
controller: _homeController.tabController,
|
||||
tabs: [
|
||||
for (var i in _homeController.tabs)
|
||||
Tab(text: i['label'])
|
||||
],
|
||||
isScrollable: true,
|
||||
dividerColor: Colors.transparent,
|
||||
enableFeedback: true,
|
||||
splashBorderRadius: BorderRadius.circular(10),
|
||||
tabAlignment: TabAlignment.center,
|
||||
onTap: (value) {
|
||||
feedBack();
|
||||
if (_homeController.initialIndex.value == value) {
|
||||
_homeController.tabsCtrList[value]().animateToTop();
|
||||
}
|
||||
_homeController.initialIndex.value = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
] else ...[
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _homeController.tabController,
|
||||
children: _homeController.tabsPageList,
|
||||
),
|
||||
),
|
||||
],
|
||||
] else ...[
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _homeController.tabController,
|
||||
children: _homeController.tabsPageList,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -280,7 +250,10 @@ class DefaultUser extends StatelessWidget {
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
backgroundColor: MaterialStateProperty.resolveWith((states) {
|
||||
return Theme.of(context).colorScheme.onInverseSurface;
|
||||
return Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer
|
||||
.withOpacity(0.05);
|
||||
}),
|
||||
),
|
||||
onPressed: () => callback?.call(),
|
||||
@ -317,7 +290,7 @@ class _CustomTabsState extends State<CustomTabs> {
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 44,
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
margin: const EdgeInsets.only(top: 8),
|
||||
child: Obx(
|
||||
() => ListView.separated(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 14.0),
|
||||
@ -357,25 +330,29 @@ class CustomChip extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final ColorScheme colorTheme = Theme.of(context).colorScheme;
|
||||
final Color secondaryContainer = colorTheme.secondaryContainer;
|
||||
final Color onPrimary = colorTheme.onPrimary;
|
||||
final Color primary = colorTheme.primary;
|
||||
final TextStyle chipTextStyle = selected
|
||||
? const TextStyle(fontWeight: FontWeight.bold, fontSize: 13)
|
||||
: const TextStyle(fontSize: 13);
|
||||
final ColorScheme colorScheme = Theme.of(context).colorScheme;
|
||||
? TextStyle(fontSize: 13, color: onPrimary)
|
||||
: TextStyle(fontSize: 13, color: colorTheme.onSecondaryContainer);
|
||||
const VisualDensity visualDensity =
|
||||
VisualDensity(horizontal: -4.0, vertical: -2.0);
|
||||
return InputChip(
|
||||
side: BorderSide(
|
||||
color: selected
|
||||
? colorScheme.onSecondaryContainer.withOpacity(0.2)
|
||||
: Colors.transparent,
|
||||
),
|
||||
side: BorderSide.none,
|
||||
backgroundColor: secondaryContainer,
|
||||
selectedColor: secondaryContainer,
|
||||
color: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) => secondaryContainer.withAlpha(200)),
|
||||
padding: const EdgeInsets.fromLTRB(7, 1, 7, 1),
|
||||
color: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.contains(MaterialState.selected) ||
|
||||
states.contains(MaterialState.hovered)) {
|
||||
return primary;
|
||||
}
|
||||
return colorTheme.secondaryContainer;
|
||||
}),
|
||||
padding: const EdgeInsets.fromLTRB(6, 1, 6, 1),
|
||||
label: Text(label, style: chipTextStyle),
|
||||
onPressed: () => onTap(),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
selected: selected,
|
||||
showCheckmark: false,
|
||||
visualDensity: visualDensity,
|
||||
|
||||
@ -6,6 +6,7 @@ import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/models/model_hot_video_item.dart';
|
||||
import 'package:pilipala/models/user/info.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class LaterController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
@ -48,7 +49,7 @@ class LaterController extends GetxController {
|
||||
aid != null ? '即将移除该视频,确定是否移除' : '即将删除所有已观看视频,此操作不可恢复。确定是否删除?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: SmartDialog.dismiss,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
@ -87,7 +88,7 @@ class LaterController extends GetxController {
|
||||
content: const Text('确定要清空你的稍后再看列表吗?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => SmartDialog.dismiss(),
|
||||
onPressed: SmartDialog.dismiss,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||
@ -109,4 +110,19 @@ class LaterController extends GetxController {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 稍后再看播放全部
|
||||
Future toViewPlayAll() async {
|
||||
final HotVideoItemModel firstItem = laterList.first;
|
||||
final String heroTag = Utils.makeHeroTag(firstItem.bvid);
|
||||
Get.toNamed(
|
||||
'/video?bvid=${firstItem.bvid}&cid=${firstItem.cid}',
|
||||
arguments: {
|
||||
'videoItem': firstItem,
|
||||
'heroTag': heroTag,
|
||||
'sourceType': 'watchLater',
|
||||
'count': laterList.length,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,6 +128,15 @@ class _LaterPageState extends State<LaterPage> {
|
||||
)
|
||||
],
|
||||
),
|
||||
floatingActionButton: Obx(
|
||||
() => _laterController.laterList.isNotEmpty
|
||||
? FloatingActionButton.extended(
|
||||
onPressed: _laterController.toViewPlayAll,
|
||||
label: const Text('播放全部'),
|
||||
icon: const Icon(Icons.playlist_play),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:pilipala/http/live.dart';
|
||||
import 'package:pilipala/models/live/follow.dart';
|
||||
import 'package:pilipala/models/live/item.dart';
|
||||
import 'package:pilipala/utils/storage.dart';
|
||||
|
||||
@ -11,6 +12,8 @@ class LiveController extends GetxController {
|
||||
int _currentPage = 1;
|
||||
RxInt crossAxisCount = 2.obs;
|
||||
RxList<LiveItemModel> liveList = <LiveItemModel>[].obs;
|
||||
RxList<LiveFollowingItemModel> liveFollowingList =
|
||||
<LiveFollowingItemModel>[].obs;
|
||||
bool flag = false;
|
||||
OverlayEntry? popupDialog;
|
||||
Box setting = GStrorage.setting;
|
||||
@ -44,6 +47,7 @@ class LiveController extends GetxController {
|
||||
// 下拉刷新
|
||||
Future onRefresh() async {
|
||||
queryLiveList('init');
|
||||
fetchLiveFollowing();
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
@ -61,4 +65,17 @@ class LiveController extends GetxController {
|
||||
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
Future fetchLiveFollowing() async {
|
||||
var res = await LiveHttp.liveFollowing(pn: 1, ps: 20);
|
||||
if (res['status']) {
|
||||
liveFollowingList.value =
|
||||
(res['data'].list as List<LiveFollowingItemModel>)
|
||||
.where((LiveFollowingItemModel item) =>
|
||||
item.liveStatus == 1 && item.recordLiveTime == 0) // 根据条件过滤
|
||||
.toList();
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user