mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-10 13:04:50 +08:00
Compare commits
98 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c30668c87 | ||
|
|
a3424950ca | ||
|
|
ebc42eb05e | ||
|
|
fc6ff44471 | ||
|
|
be03377449 | ||
|
|
e52934093a | ||
|
|
ebfd98488e | ||
|
|
6a68af77dc | ||
|
|
e5c0fb7cb2 | ||
|
|
d9611cce80 | ||
|
|
4b48aba2ae | ||
|
|
47fbb6cd0e | ||
|
|
dae71d427c | ||
|
|
46bc2ceb78 | ||
|
|
6f98200179 | ||
|
|
a57b4c56a5 | ||
|
|
6c3062ba2d | ||
|
|
064c8a9dfe | ||
|
|
7dd47736fb | ||
|
|
84cc65489f | ||
|
|
2b9cb54d91 | ||
|
|
54c7fef217 | ||
|
|
ba74cb8c01 | ||
|
|
675932aa69 | ||
|
|
d996e0a7dd | ||
|
|
b6279f702a | ||
|
|
695a89b91a | ||
|
|
09753b6bbd | ||
|
|
6502b97388 | ||
|
|
95d84647b7 | ||
|
|
8f5065332e | ||
|
|
71c8cbb8da | ||
|
|
3217731486 | ||
|
|
a4e63fe0e8 | ||
|
|
cdb8f6845c | ||
|
|
0a7d286c47 | ||
|
|
e17fd0071d | ||
|
|
a9ba30b9b9 | ||
|
|
4267a3b8e0 | ||
|
|
50022ae635 | ||
|
|
0991621152 | ||
|
|
192f8924c8 | ||
|
|
51a12d7266 | ||
|
|
1417fcda6e | ||
|
|
6114e6f033 | ||
|
|
bc2dbc59ce | ||
|
|
7c5075413e | ||
|
|
52175b0b69 | ||
|
|
f0a3515279 | ||
|
|
3c2ccf7d40 | ||
|
|
abd01e1a27 | ||
|
|
0f63976a00 | ||
|
|
6817eb6e56 | ||
|
|
a951d42623 | ||
|
|
8f5c2bf3ba | ||
|
|
7744217d17 | ||
|
|
a84c153bdd | ||
|
|
31a0a90ba4 | ||
|
|
383ce777e3 | ||
|
|
e7ac88ffb1 | ||
|
|
9657c77999 | ||
|
|
afd508f28b | ||
|
|
634612c1a2 | ||
|
|
76545397d4 | ||
|
|
d2f586a7f1 | ||
|
|
7cfebcb6ed | ||
|
|
9a3766e7b7 | ||
|
|
588a06bece | ||
|
|
e45a126862 | ||
|
|
a581945c9e | ||
|
|
331fd0d619 | ||
|
|
c6e229d571 | ||
|
|
b2c3b1ff95 | ||
|
|
3fc12fcc09 | ||
|
|
e098631553 | ||
|
|
0fcd55755e | ||
|
|
65e7c0c4f4 | ||
|
|
70aecd1e38 | ||
|
|
a40c773491 | ||
|
|
b4abb58a41 | ||
|
|
e368436bc6 | ||
|
|
6c96b3a7f5 | ||
|
|
149f0c082d | ||
|
|
994199b5a2 | ||
|
|
8db3d80151 | ||
|
|
93af1e7c44 | ||
|
|
54e90bd986 | ||
|
|
ca16551917 | ||
|
|
f4977d2855 | ||
|
|
bd91fb7c6d | ||
|
|
e1805896f4 | ||
|
|
31a639400e | ||
|
|
d6b24561fa | ||
|
|
7ba9646d38 | ||
|
|
58a7cf1e75 | ||
|
|
1a327198f7 | ||
|
|
e4fe91ef92 | ||
|
|
afcf817c4f |
13
README.md
13
README.md
@@ -47,6 +47,17 @@
|
||||
|
||||
## feat
|
||||
|
||||
- [x] 互动视频
|
||||
- [x] 发评反诈
|
||||
- [x] 高能进度条
|
||||
- [x] 滑动跳转预览视频缩略图
|
||||
- [x] Live Photo
|
||||
- [x] 复制/移动收藏夹/稍后再看视频
|
||||
- [x] 超分辨率
|
||||
- [x] 合并弹幕
|
||||
- [x] 会员彩色弹幕
|
||||
- [x] 播放全部/继续播放/倒序播放
|
||||
- [x] Cookie登录
|
||||
- [x] 显示视频分段信息
|
||||
- [x] 调节字幕大小
|
||||
- [x] 调节全屏弹幕大小
|
||||
@@ -74,7 +85,7 @@
|
||||
- [x] 转发动态
|
||||
- [x] 合集图片
|
||||
- [x] 删除/置顶私信
|
||||
- [x] 举报用户/评论/视频
|
||||
- [x] 举报用户/评论/视频/动态
|
||||
- [x] 删除/发布文本/图片动态
|
||||
- [x] 其他
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="108.0"
|
||||
android:viewportHeight="108.0">
|
||||
<path
|
||||
android:fillColor="#FF5CB67B"
|
||||
android:pathData="M56,54L39.78,54l2.22,-10.94h14c3.02,0 5.47,2.45 5.47,5.47 0,3.02 -2.45,5.47 -5.47,5.47zM56,35.77h-9.62l-7.13,36.45h7.51L48.92,61.29h7.08c7.05,0 12.76,-5.71 12.76,-12.76 0,-7.05 -5.71,-12.76 -12.76,-12.76z"
|
||||
android:pathData="M57.54,54L28.82,54l3.93,-19.36h24.78c5.35,0 9.68,4.33 9.68,9.68 0,5.35 -4.33,9.68 -9.68,9.68zM57.54,21.73L40.5,21.73L27.88,86.27h13.3l3.83,-19.36h12.54c12.48,0 22.59,-10.11 22.59,-22.59 0,-12.48 -10.11,-22.59 -22.59,-22.59z"
|
||||
android:strokeWidth="0.252073"
|
||||
android:fillType="evenOdd" />
|
||||
</vector>
|
||||
|
||||
3
android/app/src/main/res/raw/keep.xml
Normal file
3
android/app/src/main/res/raw/keep.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:keep="@drawable/*" />
|
||||
BIN
assets/images/paycoins/ic_panel_close.png
Normal file
BIN
assets/images/paycoins/ic_panel_close.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -26,6 +26,9 @@ class Constants {
|
||||
'%7B%22appId%22%3A5%2C%22platform%22%3A3%2C%22version%22%3A%221.46.2%22%2C%22abtest%22%3A%22%22%7D';
|
||||
//Uri.encodeComponent('{"appId": 5,"platform": 3,"version": "1.46.2","abtest": ""}');
|
||||
|
||||
static const urlPattern =
|
||||
r'https?://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]';
|
||||
|
||||
// 超分辨率滤镜
|
||||
static const List<String> mpvAnime4KShaders = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
|
||||
@@ -11,7 +11,9 @@ Widget articleContent({
|
||||
required BuildContext context,
|
||||
required List<ArticleContentModel> list,
|
||||
Function(List<String>, int)? callback,
|
||||
required double maxWidth,
|
||||
}) {
|
||||
debugPrint('articleContent');
|
||||
List<String>? imgList = list
|
||||
.where((item) => item.pic != null)
|
||||
.toList()
|
||||
@@ -57,31 +59,28 @@ Widget articleContent({
|
||||
),
|
||||
);
|
||||
} else if (item.pic != null) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) => Hero(
|
||||
tag: item.pic!.pics!.first.url!,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (callback != null) {
|
||||
callback(
|
||||
imgList,
|
||||
imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
);
|
||||
} else {
|
||||
context.imageView(
|
||||
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
imgList:
|
||||
imgList.map((url) => SourceModel(url: url)).toList(),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: constraints.maxWidth,
|
||||
height: constraints.maxWidth *
|
||||
item.pic!.pics!.first.height! /
|
||||
item.pic!.pics!.first.width!,
|
||||
src: item.pic!.pics!.first.url,
|
||||
),
|
||||
return Hero(
|
||||
tag: item.pic!.pics!.first.url!,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (callback != null) {
|
||||
callback(
|
||||
imgList,
|
||||
imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
);
|
||||
} else {
|
||||
context.imageView(
|
||||
initialPage: imgList.indexOf(item.pic!.pics!.first.url!),
|
||||
imgList: imgList.map((url) => SourceModel(url: url)).toList(),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: NetworkImgLayer(
|
||||
width: maxWidth,
|
||||
height: maxWidth *
|
||||
item.pic!.pics!.first.height! /
|
||||
item.pic!.pics!.first.width!,
|
||||
src: item.pic!.pics!.first.url,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ class PBadge extends StatelessWidget {
|
||||
final double? fs;
|
||||
final String? semanticsLabel;
|
||||
final bool bold;
|
||||
final double? textScaleFactor;
|
||||
|
||||
const PBadge({
|
||||
super.key,
|
||||
@@ -26,6 +27,7 @@ class PBadge extends StatelessWidget {
|
||||
this.fs = 11,
|
||||
this.semanticsLabel,
|
||||
this.bold = true,
|
||||
this.textScaleFactor,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -71,6 +73,9 @@ class PBadge extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
text ?? "",
|
||||
textScaler: textScaleFactor != null
|
||||
? TextScaler.linear(textScaleFactor!)
|
||||
: null,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: fs ?? fontSize,
|
||||
|
||||
@@ -19,8 +19,16 @@ class CustomSliverPersistentHeaderDelegate
|
||||
//创建child子组件
|
||||
//shrinkOffset:child偏移值minExtent~maxExtent
|
||||
//overlapsContent:SliverPersistentHeader覆盖其他子组件返回true,否则返回false
|
||||
return ColoredBox(
|
||||
color: bgColor,
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: bgColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: bgColor,
|
||||
offset: const Offset(0, -2),
|
||||
),
|
||||
],
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ Widget htmlRender({
|
||||
required double constrainedWidth,
|
||||
Function(List<String>, int)? callback,
|
||||
}) {
|
||||
debugPrint('htmlRender');
|
||||
return SelectionArea(
|
||||
child: Html(
|
||||
data: htmlContent,
|
||||
|
||||
@@ -254,7 +254,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
|
||||
}
|
||||
|
||||
String _getActualUrl(int index) => _thumbList[index] && _quality != 100
|
||||
? '${widget.sources[index]}@${_quality}q.webp'.http2https
|
||||
? '${widget.sources[index].url}@${_quality}q.webp'.http2https
|
||||
: widget.sources[index].url.http2https;
|
||||
|
||||
void onClose() {
|
||||
|
||||
@@ -275,144 +275,145 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: Utils.getSheetHeight(context),
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: widget.showTitle != false ? 14 : 6),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.showTitle != false)
|
||||
Text(
|
||||
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
StreamBuilder(
|
||||
stream: _favStream?.stream,
|
||||
builder: (context, snapshot) => snapshot.hasData
|
||||
? mediumButton(
|
||||
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
|
||||
icon: _seasonFav == 1
|
||||
? Icons.notifications_off_outlined
|
||||
: Icons.notifications_active_outlined,
|
||||
onPressed: () async {
|
||||
dynamic result = await VideoHttp.seasonFav(
|
||||
isFav: _seasonFav == 1,
|
||||
seasonId: widget.season.id,
|
||||
);
|
||||
if (result['status']) {
|
||||
SmartDialog.showToast(
|
||||
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
|
||||
_seasonFav = _seasonFav == 1 ? 0 : 1;
|
||||
_favStream?.add(_seasonFav);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至顶部',
|
||||
icon: Icons.vertical_align_top,
|
||||
onPressed: () {
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? 0
|
||||
: _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至底部',
|
||||
icon: Icons.vertical_align_bottom,
|
||||
onPressed: () {
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至当前',
|
||||
icon: Icons.my_location,
|
||||
onPressed: () async {
|
||||
if (_ctr != null && _ctr?.index != (_index)) {
|
||||
_ctr?.animateTo(_index);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
if (widget.isSupportReverse == true)
|
||||
if (!_isList)
|
||||
_reverseButton
|
||||
else
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.data == _index
|
||||
? _reverseButton
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
child: SizedBox(
|
||||
height: Utils.getSheetHeight(context),
|
||||
child: Column(
|
||||
children: [
|
||||
Container(
|
||||
height: 45,
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: widget.showTitle != false ? 14 : 6),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.showTitle != false)
|
||||
Text(
|
||||
'合集(${_isList ? widget.season.epCount : episodes?.length ?? ''})',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
const Spacer(),
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) => mediumButton(
|
||||
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
|
||||
icon: !reverse[snapshot.data]
|
||||
? MdiIcons.sortNumericAscending
|
||||
: MdiIcons.sortNumericDescending,
|
||||
StreamBuilder(
|
||||
stream: _favStream?.stream,
|
||||
builder: (context, snapshot) => snapshot.hasData
|
||||
? mediumButton(
|
||||
tooltip: _seasonFav == 1 ? '取消订阅' : '订阅',
|
||||
icon: _seasonFav == 1
|
||||
? Icons.notifications_off_outlined
|
||||
: Icons.notifications_active_outlined,
|
||||
onPressed: () async {
|
||||
dynamic result = await VideoHttp.seasonFav(
|
||||
isFav: _seasonFav == 1,
|
||||
seasonId: widget.season.id,
|
||||
);
|
||||
if (result['status']) {
|
||||
SmartDialog.showToast(
|
||||
'${_seasonFav == 1 ? '取消' : ''}订阅成功');
|
||||
_seasonFav = _seasonFav == 1 ? 0 : 1;
|
||||
_favStream?.add(_seasonFav);
|
||||
} else {
|
||||
SmartDialog.showToast(result['msg']);
|
||||
}
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
mediumButton(
|
||||
tooltip: '跳至顶部',
|
||||
icon: Icons.vertical_align_top,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
reverse[_ctr?.index ?? 0] = !reverse[_ctr?.index ?? 0];
|
||||
});
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? 0
|
||||
: _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.onClose != null)
|
||||
mediumButton(
|
||||
tooltip: '关闭',
|
||||
icon: Icons.close,
|
||||
onPressed: widget.onClose,
|
||||
tooltip: '跳至底部',
|
||||
icon: Icons.vertical_align_bottom,
|
||||
onPressed: () {
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: !reverse[_ctr?.index ?? 0]
|
||||
? _isList
|
||||
? widget.season.sections[_ctr?.index].episodes
|
||||
.length -
|
||||
1
|
||||
: episodes.length - 1
|
||||
: 0,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
],
|
||||
mediumButton(
|
||||
tooltip: '跳至当前',
|
||||
icon: Icons.my_location,
|
||||
onPressed: () async {
|
||||
if (_ctr != null && _ctr?.index != (_index)) {
|
||||
_ctr?.animateTo(_index);
|
||||
await Future.delayed(const Duration(milliseconds: 225));
|
||||
}
|
||||
try {
|
||||
itemScrollController[_ctr?.index ?? 0].scrollTo(
|
||||
index: currentIndex,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
} catch (_) {}
|
||||
},
|
||||
),
|
||||
if (widget.isSupportReverse == true)
|
||||
if (!_isList)
|
||||
_reverseButton
|
||||
else
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.data == _index
|
||||
? _reverseButton
|
||||
: const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
StreamBuilder(
|
||||
stream: _indexStream?.stream,
|
||||
initialData: _index,
|
||||
builder: (context, snapshot) => mediumButton(
|
||||
tooltip: reverse[snapshot.data] ? '顺序' : '倒序',
|
||||
icon: !reverse[snapshot.data]
|
||||
? MdiIcons.sortNumericAscending
|
||||
: MdiIcons.sortNumericDescending,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
reverse[_ctr?.index ?? 0] =
|
||||
!reverse[_ctr?.index ?? 0];
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.onClose != null)
|
||||
mediumButton(
|
||||
tooltip: '关闭',
|
||||
icon: Icons.close,
|
||||
onPressed: widget.onClose,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
if (_isList)
|
||||
Material(
|
||||
child: TabBar(
|
||||
Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
if (_isList)
|
||||
TabBar(
|
||||
controller: _ctr,
|
||||
padding: const EdgeInsets.only(right: 60),
|
||||
isScrollable: true,
|
||||
@@ -422,20 +423,20 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
dividerHeight: 1,
|
||||
dividerColor: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
Expanded(
|
||||
child: _isList
|
||||
? TabBarView(
|
||||
controller: _ctr,
|
||||
children: List.generate(
|
||||
widget.season.sections.length,
|
||||
(index) => _buildBody(
|
||||
index, widget.season.sections[index].episodes),
|
||||
),
|
||||
)
|
||||
: _buildBody(null, episodes),
|
||||
),
|
||||
Expanded(
|
||||
child: _isList
|
||||
? TabBarView(
|
||||
controller: _ctr,
|
||||
children: List.generate(
|
||||
widget.season.sections.length,
|
||||
(index) => _buildBody(
|
||||
index, widget.season.sections[index].episodes),
|
||||
),
|
||||
)
|
||||
: _buildBody(null, episodes),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -464,30 +465,28 @@ class _ListSheetContentState extends State<ListSheetContent>
|
||||
},
|
||||
);
|
||||
|
||||
Widget _buildBody(i, episodes) => Material(
|
||||
child: ScrollablePositionedList.separated(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 80,
|
||||
),
|
||||
reverse: reverse[i ?? 0],
|
||||
itemCount: episodes.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildEpisodeListItem(
|
||||
episodes[index],
|
||||
index,
|
||||
episodes.length,
|
||||
i != null
|
||||
? i == (_index)
|
||||
? currentIndex == index
|
||||
: false
|
||||
: currentIndex == index,
|
||||
);
|
||||
},
|
||||
itemScrollController: itemScrollController[i ?? 0],
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
Widget _buildBody(i, episodes) => ScrollablePositionedList.separated(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 80,
|
||||
),
|
||||
reverse: reverse[i ?? 0],
|
||||
itemCount: episodes.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return buildEpisodeListItem(
|
||||
episodes[index],
|
||||
index,
|
||||
episodes.length,
|
||||
i != null
|
||||
? i == (_index)
|
||||
? currentIndex == index
|
||||
: false
|
||||
: currentIndex == index,
|
||||
);
|
||||
},
|
||||
itemScrollController: itemScrollController[i ?? 0],
|
||||
separatorBuilder: (context, index) => Divider(
|
||||
height: 1,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.1),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class Segment {
|
||||
|
||||
class SegmentProgressBar extends CustomPainter {
|
||||
final List<Segment> segmentColors;
|
||||
late double _defHeight;
|
||||
double? _defHeight;
|
||||
|
||||
SegmentProgressBar({
|
||||
required this.segmentColors,
|
||||
@@ -42,6 +42,18 @@ class SegmentProgressBar extends CustomPainter {
|
||||
if (segmentColors[i].title != null) {
|
||||
double fontSize = 10;
|
||||
|
||||
_defHeight ??= (TextPainter(
|
||||
text: TextSpan(
|
||||
text: segmentColors[i].title,
|
||||
style: TextStyle(
|
||||
fontSize: fontSize,
|
||||
),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout())
|
||||
.height +
|
||||
2;
|
||||
|
||||
TextPainter getTextPainter() => TextPainter(
|
||||
text: TextSpan(
|
||||
text: segmentColors[i].title,
|
||||
@@ -51,14 +63,12 @@ class SegmentProgressBar extends CustomPainter {
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
strutStyle: StrutStyle(height: 1, leading: 0),
|
||||
strutStyle:
|
||||
StrutStyle(leading: 0, height: 1, fontSize: fontSize),
|
||||
textDirection: TextDirection.ltr,
|
||||
)..layout();
|
||||
|
||||
TextPainter textPainter = getTextPainter();
|
||||
if (i == 0) {
|
||||
_defHeight = textPainter.height;
|
||||
}
|
||||
|
||||
late double prevStart;
|
||||
if (i != 0) {
|
||||
@@ -75,7 +85,7 @@ class SegmentProgressBar extends CustomPainter {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTRB(
|
||||
0,
|
||||
-_defHeight - 2,
|
||||
-_defHeight!,
|
||||
size.width,
|
||||
0,
|
||||
),
|
||||
@@ -86,9 +96,9 @@ class SegmentProgressBar extends CustomPainter {
|
||||
canvas.drawRect(
|
||||
Rect.fromLTWH(
|
||||
segmentStart,
|
||||
-_defHeight - 2,
|
||||
-_defHeight!,
|
||||
segmentEnd == segmentStart ? 2 : segmentEnd - segmentStart,
|
||||
size.height + _defHeight + 2,
|
||||
size.height + _defHeight!,
|
||||
),
|
||||
paint,
|
||||
);
|
||||
@@ -98,7 +108,7 @@ class SegmentProgressBar extends CustomPainter {
|
||||
: (segmentStart - prevStart - textPainter.width) / 2 +
|
||||
prevStart +
|
||||
1;
|
||||
double textY = (-_defHeight - textPainter.height) / 2 - 1;
|
||||
double textY = (-_defHeight! - textPainter.height) / 2;
|
||||
textPainter.paint(canvas, Offset(textX, textY));
|
||||
} else {
|
||||
canvas.drawRect(
|
||||
|
||||
@@ -6,13 +6,14 @@ Widget statDanMu({
|
||||
String? theme,
|
||||
dynamic danmu,
|
||||
String? size,
|
||||
Color? textColor,
|
||||
}) {
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
Color color = textColor ?? colorObject[theme]!;
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
|
||||
@@ -7,13 +7,14 @@ Widget statView({
|
||||
dynamic view,
|
||||
String? size,
|
||||
String? goto,
|
||||
Color? textColor,
|
||||
}) {
|
||||
Map<String, Color> colorObject = {
|
||||
'white': Colors.white,
|
||||
'gray': Theme.of(context).colorScheme.outline.withOpacity(0.8),
|
||||
'black': Theme.of(context).colorScheme.onSurface.withOpacity(0.7),
|
||||
};
|
||||
Color color = colorObject[theme]!;
|
||||
Color color = textColor ?? colorObject[theme]!;
|
||||
return Row(
|
||||
children: [
|
||||
Icon(
|
||||
|
||||
@@ -58,7 +58,7 @@ class VideoCardHGrpc extends StatelessWidget {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
PiliScheme.routePush(Uri.parse(videoItem.smallCoverV5.base.uri));
|
||||
PiliScheme.routePushFromUrl(videoItem.smallCoverV5.base.uri);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/danmu.dart';
|
||||
import 'package:PiliPlus/common/widgets/stat/view.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_popup_menu.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_progress_indicator.dart';
|
||||
import 'package:PiliPlus/models/space_archive/item.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -98,6 +99,22 @@ class VideoCardHMemberVideo extends StatelessWidget {
|
||||
bottom: 6.0,
|
||||
type: 'gray',
|
||||
),
|
||||
if (videoItem.history != null)
|
||||
Builder(builder: (context) {
|
||||
try {
|
||||
return Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: videoProgressIndicator(
|
||||
videoItem.history!['progress'] /
|
||||
videoItem.history!['duration'],
|
||||
),
|
||||
);
|
||||
} catch (_) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
27
lib/common/widgets/video_progress_indicator.dart
Normal file
27
lib/common/widgets/video_progress_indicator.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget videoProgressIndicator(double progress) => ClipRect(
|
||||
clipper: ProgressClipper(),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(10),
|
||||
bottomRight: Radius.circular(10),
|
||||
),
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 10,
|
||||
value: progress,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
class ProgressClipper extends CustomClipper<Rect> {
|
||||
@override
|
||||
Rect getClip(Size size) {
|
||||
return Rect.fromLTWH(0, 6, size.width, size.height - 6);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomClipper<Rect> oldClipper) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -79,10 +79,14 @@ class DynamicsHttp {
|
||||
//
|
||||
static Future dynamicDetail({
|
||||
String? id,
|
||||
dynamic rid,
|
||||
dynamic type,
|
||||
}) async {
|
||||
var res = await Request().get(Api.dynamicDetail, queryParameters: {
|
||||
'timezone_offset': -480,
|
||||
'id': id,
|
||||
if (id != null) 'id': id,
|
||||
if (rid != null) 'rid': rid,
|
||||
if (type != null) 'type': type,
|
||||
'features': 'itemOpusStyle',
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:math' show Random;
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
@@ -14,7 +15,6 @@ import '../utils/utils.dart';
|
||||
import 'api.dart';
|
||||
import 'constants.dart';
|
||||
import 'interceptor.dart';
|
||||
import 'interceptor_anonymity.dart';
|
||||
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as web;
|
||||
|
||||
class Request {
|
||||
@@ -37,7 +37,7 @@ class Request {
|
||||
);
|
||||
cookieManager = CookieManager(cookieJar);
|
||||
dio.interceptors.add(cookieManager);
|
||||
dio.interceptors.add(AnonymityInterceptor());
|
||||
dio.interceptors.add(ApiInterceptor());
|
||||
final List<Cookie> cookies = await cookieManager.cookieJar
|
||||
.loadForRequest(Uri.parse(HttpString.baseUrl));
|
||||
for (Cookie item in cookies) {
|
||||
@@ -175,15 +175,14 @@ class Request {
|
||||
);
|
||||
}
|
||||
|
||||
//添加拦截器
|
||||
dio.interceptors.add(ApiInterceptor());
|
||||
|
||||
// 日志拦截器 输出请求、响应内容
|
||||
dio.interceptors.add(LogInterceptor(
|
||||
request: false,
|
||||
requestHeader: false,
|
||||
responseHeader: false,
|
||||
));
|
||||
if (BuildConfig.isDebug) {
|
||||
dio.interceptors.add(LogInterceptor(
|
||||
request: false,
|
||||
requestHeader: false,
|
||||
responseHeader: false,
|
||||
));
|
||||
}
|
||||
|
||||
dio.transformer = BackgroundTransformer();
|
||||
dio.options.validateStatus = (int? status) {
|
||||
|
||||
@@ -1,17 +1,67 @@
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/pages/mine/controller.dart';
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
|
||||
class ApiInterceptor extends Interceptor {
|
||||
// @override
|
||||
// void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
// debugPrint("请求之前");
|
||||
// // 在请求之前添加头部或认证信息
|
||||
// options.headers['Authorization'] = 'Bearer token';
|
||||
// options.headers['Content-Type'] = 'application/json';
|
||||
// handler.next(options);
|
||||
// }
|
||||
static const List<String> anonymityList = [
|
||||
Api.videoUrl,
|
||||
Api.videoIntro,
|
||||
Api.relatedList,
|
||||
Api.replyList,
|
||||
Api.replyReplyList,
|
||||
Api.searchSuggest,
|
||||
Api.searchByType,
|
||||
Api.heartBeat,
|
||||
Api.ab2c,
|
||||
Api.bangumiInfo,
|
||||
Api.liveRoomInfo,
|
||||
Api.onlineTotal,
|
||||
Api.webDanmaku,
|
||||
Api.dynamicDetail,
|
||||
Api.aiConclusion,
|
||||
Api.getSeasonDetailApi,
|
||||
Api.liveRoomDmToken,
|
||||
Api.liveRoomDmPrefetch,
|
||||
];
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
void onRemoveCookie() {
|
||||
options.headers.remove('x-bili-mid');
|
||||
options.headers.remove('x-bili-aurora-eid');
|
||||
options.headers.remove('x-bili-aurora-zone');
|
||||
options.headers['cookie'] = '';
|
||||
options.queryParameters.remove('access_key');
|
||||
options.queryParameters.remove('csrf');
|
||||
options.queryParameters.remove('csrf_token');
|
||||
if (options.data is Map) {
|
||||
options.data.remove('access_key');
|
||||
options.data.remove('csrf');
|
||||
options.data.remove('csrf_token');
|
||||
}
|
||||
}
|
||||
|
||||
if (options.extra['clearCookie'] == true) {
|
||||
onRemoveCookie();
|
||||
} else if (MineController.anonymity.value) {
|
||||
String uri = options.uri.toString();
|
||||
for (var i in anonymityList) {
|
||||
// 如果请求的url包含无痕列表中的url,则清空cookie
|
||||
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
|
||||
int index = uri.indexOf(i);
|
||||
if (index == -1) continue;
|
||||
if (uri.lastIndexOf('/') >= index + i.length) continue;
|
||||
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
|
||||
onRemoveCookie();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handler.next(options);
|
||||
}
|
||||
|
||||
// @override
|
||||
// void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
// ignore_for_file: avoid_print
|
||||
|
||||
import 'dart:io';
|
||||
import 'package:dio/dio.dart';
|
||||
// import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import '../pages/mine/controller.dart';
|
||||
import 'api.dart';
|
||||
|
||||
class AnonymityInterceptor extends Interceptor {
|
||||
static const List<String> anonymityList = [
|
||||
Api.videoUrl,
|
||||
Api.videoIntro,
|
||||
Api.relatedList,
|
||||
Api.replyList,
|
||||
Api.replyReplyList,
|
||||
Api.searchSuggest,
|
||||
Api.searchByType,
|
||||
Api.heartBeat,
|
||||
Api.ab2c,
|
||||
Api.bangumiInfo,
|
||||
Api.liveRoomInfo,
|
||||
Api.onlineTotal,
|
||||
Api.webDanmaku,
|
||||
Api.dynamicDetail,
|
||||
Api.aiConclusion,
|
||||
Api.getSeasonDetailApi,
|
||||
Api.liveRoomDmToken,
|
||||
Api.liveRoomDmPrefetch,
|
||||
];
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
if (MineController.anonymity.value) {
|
||||
String uri = options.uri.toString();
|
||||
for (var i in anonymityList) {
|
||||
// 如果请求的url包含无痕列表中的url,则清空cookie
|
||||
// 但需要保证匹配到url的后半部分不再出现/符号,否则会误伤
|
||||
int index = uri.indexOf(i);
|
||||
if (index == -1) continue;
|
||||
if (uri.lastIndexOf('/') >= index + i.length) continue;
|
||||
//SmartDialog.showToast('触发无痕模式\n\n$i\n\n${options.uri}');
|
||||
options.headers[HttpHeaders.cookieHeader] = "";
|
||||
if (options.data != null && options.data.csrf != null) {
|
||||
options.data.csrf = "";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
handler.next(options);
|
||||
}
|
||||
}
|
||||
@@ -334,8 +334,13 @@ class MemberHttp {
|
||||
}
|
||||
|
||||
static Future memberCardInfo({int? mid}) async {
|
||||
var res = await Request()
|
||||
.get(Api.memberCardInfo, queryParameters: {'mid': mid, 'photo': true});
|
||||
var res = await Request().get(
|
||||
Api.memberCardInfo,
|
||||
queryParameters: {
|
||||
'mid': mid,
|
||||
'photo': false,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': res.data['data']};
|
||||
} else {
|
||||
|
||||
@@ -560,9 +560,7 @@ class MsgHttp {
|
||||
return {
|
||||
'status': false,
|
||||
'date': [],
|
||||
'msg': "message: ${res.data['message']},"
|
||||
" msg: ${res.data['msg']},"
|
||||
" code: ${res.data['code']}",
|
||||
'msg': res.data['message'] ?? res.data['msg'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_repo.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../models/video/reply/data.dart';
|
||||
import '../models/video/reply/emote.dart';
|
||||
import 'api.dart';
|
||||
import 'constants.dart';
|
||||
import 'init.dart';
|
||||
|
||||
class ReplyHttp {
|
||||
static Options get _options => Options(extra: {'clearCookie': true});
|
||||
|
||||
static Future<LoadingState> replyList({
|
||||
required bool isLogin,
|
||||
required int oid,
|
||||
@@ -23,13 +21,9 @@ class ReplyHttp {
|
||||
int sort = 1,
|
||||
required String banWordForReply,
|
||||
}) async {
|
||||
Options? options = !isLogin
|
||||
? Options(
|
||||
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
|
||||
: null;
|
||||
var res = !isLogin
|
||||
? await Request().get(
|
||||
'${HttpString.apiBaseUrl}${Api.replyList}/main',
|
||||
'${Api.replyList}/main',
|
||||
queryParameters: {
|
||||
'oid': oid,
|
||||
'type': type,
|
||||
@@ -37,10 +31,10 @@ class ReplyHttp {
|
||||
'{"offset":"${nextOffset.replaceAll('"', '\\"')}"}',
|
||||
'mode': sort + 2, //2:按时间排序;3:按热度排序
|
||||
},
|
||||
options: options,
|
||||
options: isLogin.not ? _options : null,
|
||||
)
|
||||
: await Request().get(
|
||||
'${HttpString.apiBaseUrl}${Api.replyList}',
|
||||
Api.replyList,
|
||||
queryParameters: {
|
||||
'oid': oid,
|
||||
'type': type,
|
||||
@@ -48,7 +42,7 @@ class ReplyHttp {
|
||||
'pn': page,
|
||||
'ps': 20,
|
||||
},
|
||||
options: options,
|
||||
options: isLogin.not ? _options : null,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
ReplyData replyData = ReplyData.fromJson(res.data['data']);
|
||||
@@ -134,28 +128,25 @@ class ReplyHttp {
|
||||
}
|
||||
|
||||
static Future<LoadingState> replyReplyList({
|
||||
required bool isLogin,
|
||||
required int oid,
|
||||
required int root,
|
||||
required int pageNum,
|
||||
required int type,
|
||||
int sort = 1,
|
||||
required String banWordForReply,
|
||||
bool? isCheck,
|
||||
}) async {
|
||||
Options? options = GStorage.userInfo.get('userInfoCache') == null
|
||||
? Options(
|
||||
headers: {HttpHeaders.cookieHeader: "buvid3= ; b_nut= ; sid= "})
|
||||
: null;
|
||||
var res = await Request().get(
|
||||
'${HttpString.apiBaseUrl}${Api.replyReplyList}',
|
||||
Api.replyReplyList,
|
||||
queryParameters: {
|
||||
'oid': oid,
|
||||
'root': root,
|
||||
'pn': pageNum,
|
||||
'type': type,
|
||||
'sort': 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
if (isLogin) 'csrf': await Request.getCsrf(),
|
||||
},
|
||||
options: options,
|
||||
options: isLogin.not ? _options : null,
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
ReplyReplyData replyData = ReplyReplyData.fromJson(res.data['data']);
|
||||
@@ -168,7 +159,11 @@ class ReplyHttp {
|
||||
}
|
||||
return LoadingState.success(replyData);
|
||||
} else {
|
||||
return LoadingState.error(res.data['message']);
|
||||
return LoadingState.error(
|
||||
isCheck == true
|
||||
? '${res.data['code']}${res.data['message']}'
|
||||
: res.data['message'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -174,7 +174,10 @@ class UserHttp {
|
||||
var res = await Request().get(Api.seeYouLater);
|
||||
if (res.data['code'] == 0) {
|
||||
if (res.data['data']['count'] == 0) {
|
||||
return LoadingState.success([]);
|
||||
return LoadingState.success({
|
||||
'list': [],
|
||||
'count': 0,
|
||||
});
|
||||
}
|
||||
List<HotVideoItemModel> list = [];
|
||||
for (var i in res.data['data']['list']) {
|
||||
|
||||
@@ -421,14 +421,18 @@ class VideoHttp {
|
||||
}
|
||||
|
||||
// 投币
|
||||
static Future coinVideo({required String bvid, required int multiply}) async {
|
||||
static Future coinVideo({
|
||||
required String bvid,
|
||||
required int multiply,
|
||||
int selectLike = 0,
|
||||
}) async {
|
||||
var res = await Request().post(
|
||||
Api.coinVideo,
|
||||
queryParameters: {
|
||||
'aid': IdUtils.bv2av(bvid),
|
||||
// 'bvid': bvid,
|
||||
'multiply': multiply,
|
||||
'select_like': 0,
|
||||
'select_like': selectLike,
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
// 'csrf': await Request.getCsrf(),
|
||||
@@ -759,6 +763,7 @@ class VideoHttp {
|
||||
int? root,
|
||||
int? parent,
|
||||
List? pictures,
|
||||
bool? syncToDynamic,
|
||||
}) async {
|
||||
if (message == '') {
|
||||
return {'status': false, 'data': [], 'msg': '请输入评论内容'};
|
||||
@@ -766,10 +771,11 @@ class VideoHttp {
|
||||
Map<String, dynamic> data = {
|
||||
'type': type.index,
|
||||
'oid': oid,
|
||||
'root': root == null || root == 0 ? '' : root,
|
||||
'parent': parent == null || parent == 0 ? '' : parent,
|
||||
if (root != null && root != 0) 'root': root,
|
||||
if (parent != null && parent != 0) 'parent': parent,
|
||||
'message': message,
|
||||
if (pictures != null) 'pictures': jsonEncode(pictures),
|
||||
if (syncToDynamic == true) 'sync_to_dynamic': 1,
|
||||
'csrf': await Request.getCsrf(),
|
||||
};
|
||||
var res = await Request().post(
|
||||
@@ -975,7 +981,6 @@ class VideoHttp {
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
dynamic data = res.data['data'];
|
||||
List subtitlesJson = data['subtitle']['subtitles'];
|
||||
/*
|
||||
[
|
||||
{
|
||||
@@ -993,10 +998,11 @@ class VideoHttp {
|
||||
*/
|
||||
return {
|
||||
'status': true,
|
||||
'data': subtitlesJson,
|
||||
'data': data['subtitle']['subtitles'],
|
||||
'view_points': data['view_points'],
|
||||
// 'last_play_time': data['last_play_time'],
|
||||
'last_play_cid': data['last_play_cid'],
|
||||
'interaction': data['interaction'],
|
||||
};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/utils/cache_manage.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flex_seed_scheme/flex_seed_scheme.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
@@ -177,6 +178,7 @@ class MyApp extends StatelessWidget {
|
||||
// tones: FlexTones.soft(Brightness.dark),
|
||||
);
|
||||
}
|
||||
|
||||
// 图片缓存
|
||||
// PaintingBinding.instance.imageCache.maximumSizeBytes = 1000 << 20;
|
||||
return GetMaterialApp(
|
||||
@@ -229,7 +231,7 @@ class MyApp extends StatelessWidget {
|
||||
bool isDark = false,
|
||||
required FlexSchemeVariant variant,
|
||||
}) {
|
||||
return ThemeData(
|
||||
ThemeData themeData = ThemeData(
|
||||
colorScheme: colorScheme,
|
||||
useMaterial3: true,
|
||||
appBarTheme: AppBarTheme(
|
||||
@@ -282,6 +284,10 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
if (isDark && GStorage.isPureBlackTheme) {
|
||||
themeData = Utils.darkenTheme(themeData);
|
||||
}
|
||||
return themeData;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,9 @@ class BangumiListDataModel {
|
||||
|
||||
BangumiListDataModel.fromJson(Map<String, dynamic> json) {
|
||||
hasNext = json['has_next'];
|
||||
list = json['list'] != null
|
||||
? json['list']
|
||||
.map<BangumiListItemModel>((e) => BangumiListItemModel.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
list = (json['list'] as List?)
|
||||
?.map<BangumiListItemModel>((e) => BangumiListItemModel.fromJson(e))
|
||||
.toList();
|
||||
num = json['num'];
|
||||
size = json['size'];
|
||||
total = json['total'];
|
||||
|
||||
@@ -14,7 +14,7 @@ class DynamicsDataModel {
|
||||
|
||||
DynamicsDataModel.fromJson(Map<String, dynamic> json) {
|
||||
hasMore = json['has_more'];
|
||||
items = json['items']
|
||||
items = (json['items'] as List?)
|
||||
?.map<DynamicItemModel>((e) => DynamicItemModel.fromJson(e))
|
||||
.toList();
|
||||
offset = json['offset'];
|
||||
@@ -364,7 +364,9 @@ class Good {
|
||||
Good.fromJson(Map<String, dynamic> json) {
|
||||
headIcon = json['head_icon'];
|
||||
headText = json['head_text'];
|
||||
items = json['items'].map<GoodItem>((e) => GoodItem.fromJson(e)).toList();
|
||||
items = (json['items'] as List?)
|
||||
?.map<GoodItem>((e) => GoodItem.fromJson(e))
|
||||
.toList();
|
||||
jumpUrl = json['jump_url'];
|
||||
}
|
||||
}
|
||||
@@ -409,11 +411,9 @@ class DynamicDescModel {
|
||||
String? text;
|
||||
|
||||
DynamicDescModel.fromJson(Map<String, dynamic> json) {
|
||||
richTextNodes = json['rich_text_nodes'] != null
|
||||
? json['rich_text_nodes']
|
||||
.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
richTextNodes = (json['rich_text_nodes'] as List?)
|
||||
?.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
|
||||
.toList();
|
||||
text = json['text'];
|
||||
}
|
||||
}
|
||||
@@ -557,12 +557,9 @@ class DynamicDrawModel {
|
||||
|
||||
DynamicDrawModel.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
// ignore: prefer_null_aware_operators
|
||||
items = json['items'] != null
|
||||
? json['items']
|
||||
.map<DynamicDrawItemModel>((e) => DynamicDrawItemModel.fromJson(e))
|
||||
.toList()
|
||||
: null;
|
||||
items = (json['items'] as List?)
|
||||
?.map<DynamicDrawItemModel>((e) => DynamicDrawItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,7 +577,7 @@ class DynamicOpusModel {
|
||||
String? title;
|
||||
DynamicOpusModel.fromJson(Map<String, dynamic> json) {
|
||||
jumpUrl = json['jump_url'];
|
||||
pics = json['pics']
|
||||
pics = (json['pics'] as List?)
|
||||
?.map<OpusPicsModel>((e) => OpusPicsModel.fromJson(e))
|
||||
.toList();
|
||||
summary =
|
||||
@@ -599,8 +596,8 @@ class SummaryModel {
|
||||
String? text;
|
||||
|
||||
SummaryModel.fromJson(Map<String, dynamic> json) {
|
||||
richTextNodes = json['rich_text_nodes']
|
||||
.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
|
||||
richTextNodes = (json['rich_text_nodes'] as List?)
|
||||
?.map<RichTextNodeItem>((e) => RichTextNodeItem.fromJson(e))
|
||||
.toList();
|
||||
text = json['text'];
|
||||
}
|
||||
@@ -638,11 +635,15 @@ class Emoji {
|
||||
});
|
||||
|
||||
String? iconUrl;
|
||||
String? webpUrl;
|
||||
String? gifUrl;
|
||||
double? size;
|
||||
String? text;
|
||||
int? type;
|
||||
Emoji.fromJson(Map<String, dynamic> json) {
|
||||
iconUrl = json['icon_url'];
|
||||
webpUrl = json['webp_url'];
|
||||
gifUrl = json['gif_url'];
|
||||
size = json['size'].toDouble();
|
||||
text = json['text'];
|
||||
type = json['type'];
|
||||
|
||||
@@ -12,9 +12,9 @@ class FollowUpModel {
|
||||
liveUsers = json['live_users'] != null
|
||||
? LiveUsers.fromJson(json['live_users'])
|
||||
: null;
|
||||
upList = json['up_list'] != null
|
||||
? json['up_list'].map<UpItem>((e) => UpItem.fromJson(e)).toList()
|
||||
: [];
|
||||
upList = (json['up_list'] as List?)
|
||||
?.map<UpItem>((e) => UpItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,8 @@ class LiveUsers {
|
||||
LiveUsers.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
group = json['group'];
|
||||
items = json['items']
|
||||
.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
|
||||
items = (json['items'] as List?)
|
||||
?.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ class FansDataModel {
|
||||
|
||||
FansDataModel.fromJson(Map<String, dynamic> json) {
|
||||
total = json['total'];
|
||||
list = json['list']
|
||||
.map<FansItemModel>((e) => FansItemModel.fromJson(e))
|
||||
list = (json['list'] as List?)
|
||||
?.map<FansItemModel>((e) => FansItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ class FollowDataModel {
|
||||
|
||||
FollowDataModel.fromJson(Map<String, dynamic> json) {
|
||||
total = json['total'] ?? 0;
|
||||
list = json['list']
|
||||
.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||
list = (json['list'] as List?)
|
||||
?.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@ class LatestDataModel {
|
||||
url = json['url'];
|
||||
tagName = json['tag_name'];
|
||||
createdAt = json['created_at'];
|
||||
assets = json['assets'] != null
|
||||
? json['assets'].map<AssetItem>((e) => AssetItem.fromJson(e)).toList()
|
||||
: [];
|
||||
assets = (json['assets'] as List?)
|
||||
?.map<AssetItem>((e) => AssetItem.fromJson(e))
|
||||
.toList();
|
||||
body = json['body'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,9 +45,12 @@ class Playurl {
|
||||
|
||||
Playurl.fromJson(Map<String, dynamic> json) {
|
||||
cid = json['cid'];
|
||||
gQnDesc =
|
||||
json['g_qn_desc'].map<GQnDesc>((e) => GQnDesc.fromJson(e)).toList();
|
||||
stream = json['stream'].map<Streams>((e) => Streams.fromJson(e)).toList();
|
||||
gQnDesc = (json['g_qn_desc'] as List?)
|
||||
?.map<GQnDesc>((e) => GQnDesc.fromJson(e))
|
||||
.toList();
|
||||
stream = (json['stream'] as List?)
|
||||
?.map<Streams>((e) => Streams.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,8 +86,9 @@ class Streams {
|
||||
|
||||
Streams.fromJson(Map<String, dynamic> json) {
|
||||
protocolName = json['protocol_name'];
|
||||
format =
|
||||
json['format'].map<FormatItem>((e) => FormatItem.fromJson(e)).toList();
|
||||
format = (json['format'] as List?)
|
||||
?.map<FormatItem>((e) => FormatItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +103,9 @@ class FormatItem {
|
||||
|
||||
FormatItem.fromJson(Map<String, dynamic> json) {
|
||||
formatName = json['format_name'];
|
||||
codec = json['codec'].map<CodecItem>((e) => CodecItem.fromJson(e)).toList();
|
||||
codec = (json['codec'] as List?)
|
||||
?.map<CodecItem>((e) => CodecItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +135,8 @@ class CodecItem {
|
||||
currentQn = json['current_qn'];
|
||||
acceptQn = json['accept_qn'];
|
||||
baseUrl = json['base_url'];
|
||||
urlInfo = json['url_info']
|
||||
.map<UrlInfoItem>((e) => UrlInfoItem.fromJson(e))
|
||||
urlInfo = (json['url_info'] as List?)
|
||||
?.map<UrlInfoItem>((e) => UrlInfoItem.fromJson(e))
|
||||
.toList();
|
||||
hdrQn = json['hdr_n'];
|
||||
dolbyType = json['dolby_type'];
|
||||
|
||||
@@ -27,8 +27,8 @@ class ArchiveListModel {
|
||||
? Map.from(json['tlist']).map((k, v) =>
|
||||
MapEntry<String, TListItemModel>(k, TListItemModel.fromJson(v)))
|
||||
: {};
|
||||
vlist = json['vlist']
|
||||
.map<VListItemModel>((e) => VListItemModel.fromJson(e))
|
||||
vlist = (json['vlist'] as List?)
|
||||
?.map<VListItemModel>((e) => VListItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,9 @@ class MemberSeasonsDataModel {
|
||||
|
||||
MemberSeasonsDataModel.fromJson(Map<String, dynamic> json) {
|
||||
page = json['page'];
|
||||
seasonsList = json['seasons_list'] != null
|
||||
? json['seasons_list']
|
||||
.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
seasonsList = (json['seasons_list'] as List?)
|
||||
?.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,11 +29,9 @@ class MemberSeasonsList {
|
||||
Map? page;
|
||||
|
||||
MemberSeasonsList.fromJson(Map<String, dynamic> json) {
|
||||
archives = json['archives'] != null
|
||||
? json['archives']
|
||||
.map<MemberArchiveItem>((e) => MemberArchiveItem.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
archives = (json['archives'] as List?)
|
||||
?.map<MemberArchiveItem>((e) => MemberArchiveItem.fromJson(e))
|
||||
.toList();
|
||||
meta = MamberMeta.fromJson(json['meta']);
|
||||
page = json['page'];
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class SessionDataModel {
|
||||
int? hasMore;
|
||||
|
||||
SessionDataModel.fromJson(Map<String, dynamic> json) {
|
||||
sessionList = json['session_list']
|
||||
sessionList = (json['session_list'] as List?)
|
||||
?.map<SessionList>((e) => SessionList.fromJson(e))
|
||||
.toList();
|
||||
hasMore = json['has_more'];
|
||||
@@ -175,8 +175,8 @@ class SessionMsgDataModel {
|
||||
List<dynamic>? eInfos;
|
||||
|
||||
SessionMsgDataModel.fromJson(Map<String, dynamic> json) {
|
||||
messages = json['messages']
|
||||
.map<MessageItem>((e) => MessageItem.fromJson(e))
|
||||
messages = (json['messages'] as List?)
|
||||
?.map<MessageItem>((e) => MessageItem.fromJson(e))
|
||||
.toList();
|
||||
hasMore = json['has_more'];
|
||||
minSeqno = json['min_seqno'];
|
||||
|
||||
@@ -12,8 +12,8 @@ class HotSearchModel {
|
||||
List<HotSearchItem>? list;
|
||||
|
||||
HotSearchModel.fromJson(Map<String, dynamic> json) {
|
||||
list = json['list']
|
||||
.map<HotSearchItem>((e) => HotSearchItem.fromJson(e))
|
||||
list = (json['list'] as List?)
|
||||
?.map<HotSearchItem>((e) => HotSearchItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ class SearchVideoModel {
|
||||
|
||||
SearchVideoModel.fromJson(Map<String, dynamic> json) {
|
||||
numResults = (json['numResults'] as num?)?.toInt();
|
||||
list = json['result']
|
||||
list = (json['result'] as List?)
|
||||
?.where((e) => e['available'] == true)
|
||||
?.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||
.map<SearchVideoItemModel>((e) => SearchVideoItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ class SearchUserModel {
|
||||
|
||||
SearchUserModel.fromJson(Map<String, dynamic> json) {
|
||||
numResults = (json['numResults'] as num?)?.toInt();
|
||||
list = json['result']
|
||||
list = (json['result'] as List?)
|
||||
?.map<SearchUserItemModel>((e) => SearchUserItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
@@ -316,12 +316,10 @@ class SearchMBangumiModel {
|
||||
|
||||
SearchMBangumiModel.fromJson(Map<String, dynamic> json) {
|
||||
numResults = (json['numResults'] as num?)?.toInt();
|
||||
list = json['result'] != null
|
||||
? json['result']
|
||||
.map<SearchMBangumiItemModel>(
|
||||
(e) => SearchMBangumiItemModel.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
list = (json['result'] as List?)
|
||||
?.map<SearchMBangumiItemModel>(
|
||||
(e) => SearchMBangumiItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -420,12 +418,9 @@ class SearchArticleModel {
|
||||
|
||||
SearchArticleModel.fromJson(Map<String, dynamic> json) {
|
||||
numResults = (json['numResults'] as num?)?.toInt();
|
||||
list = json['result'] != null
|
||||
? json['result']
|
||||
.map<SearchArticleItemModel>(
|
||||
(e) => SearchArticleItemModel.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
list = (json['result'] as List?)
|
||||
?.map<SearchArticleItemModel>((e) => SearchArticleItemModel.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ class SearchSuggestModel {
|
||||
String? term;
|
||||
|
||||
SearchSuggestModel.fromJson(Map<String, dynamic> json) {
|
||||
tag = json['tag']
|
||||
.map<SearchSuggestItem>(
|
||||
tag = (json['tag'] as List?)
|
||||
?.map<SearchSuggestItem>(
|
||||
(e) => SearchSuggestItem.fromJson(e, json['term']))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ class Item {
|
||||
String? publishTimeText;
|
||||
List<Badge>? badges;
|
||||
Map? season;
|
||||
Map? history;
|
||||
|
||||
Item({
|
||||
this.title,
|
||||
@@ -97,6 +98,7 @@ class Item {
|
||||
this.publishTimeText,
|
||||
this.badges,
|
||||
this.season,
|
||||
this.history,
|
||||
});
|
||||
|
||||
factory Item.fromJson(Map<String, dynamic> json) => _$ItemFromJson(json);
|
||||
|
||||
@@ -48,6 +48,7 @@ Item _$ItemFromJson(Map<String, dynamic> json) => Item(
|
||||
?.map((e) => Badge.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
season: json['season'],
|
||||
history: json['history'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ItemToJson(Item instance) => <String, dynamic>{
|
||||
|
||||
@@ -8,8 +8,8 @@ class BlackListDataModel {
|
||||
int? total;
|
||||
|
||||
BlackListDataModel.fromJson(Map<String, dynamic> json) {
|
||||
list = json['list']
|
||||
.map<BlackListItem>((e) => BlackListItem.fromJson(e))
|
||||
list = (json['list'] as List?)
|
||||
?.map<BlackListItem>((e) => BlackListItem.fromJson(e))
|
||||
.toList();
|
||||
total = json['total'];
|
||||
}
|
||||
|
||||
@@ -15,11 +15,9 @@ class FavDetailData {
|
||||
FavDetailData.fromJson(Map<String, dynamic> json) {
|
||||
info =
|
||||
json['info'] == null ? null : FavFolderItemData.fromJson(json['info']);
|
||||
medias = json['medias'] != null
|
||||
? json['medias']
|
||||
.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
medias = (json['medias'] as List?)
|
||||
?.map<FavDetailItemData>((e) => FavDetailItemData.fromJson(e))
|
||||
.toList();
|
||||
hasMore = json['has_more'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,10 @@ class FavFolderData {
|
||||
|
||||
FavFolderData.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'];
|
||||
list = json['list'] != null
|
||||
? json['list']
|
||||
.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
|
||||
.toList()
|
||||
: [FavFolderItemData()];
|
||||
list = (json['list'] as List?)
|
||||
?.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
|
||||
.toList() ??
|
||||
[FavFolderItemData()];
|
||||
hasMore = json['has_more'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ class HistoryData {
|
||||
|
||||
HistoryData.fromJson(Map<String, dynamic> json) {
|
||||
cursor = json['cursor'] != null ? Cursor.fromJson(json['cursor']) : null;
|
||||
tab = json['tab'] != null
|
||||
? json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList()
|
||||
: [];
|
||||
list = json['list'] != null
|
||||
? json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList()
|
||||
: [];
|
||||
tab = (json['tab'] as List?)
|
||||
?.map<HisTabItem>((e) => HisTabItem.fromJson(e))
|
||||
.toList();
|
||||
list = (json['list'] as List?)
|
||||
?.map<HisListItem>((e) => HisListItem.fromJson(e))
|
||||
.toList();
|
||||
page = json['page'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ class Emote {
|
||||
|
||||
class EmoteMeta {
|
||||
int? size;
|
||||
List<String>? suggest;
|
||||
List? suggest;
|
||||
String? alias;
|
||||
String? gifUrl;
|
||||
|
||||
@@ -245,9 +245,7 @@ class EmoteMeta {
|
||||
|
||||
EmoteMeta.fromJson(Map<String, dynamic> json) {
|
||||
size = json['size'];
|
||||
suggest = json['suggest'] == null
|
||||
? null
|
||||
: List<String>.from(json['suggest'].map((x) => x));
|
||||
suggest = json['suggest'];
|
||||
alias = json['alias'];
|
||||
gifUrl = json['gif_url'];
|
||||
}
|
||||
|
||||
@@ -10,11 +10,9 @@ class SubFolderModelData {
|
||||
factory SubFolderModelData.fromJson(Map<String, dynamic> json) {
|
||||
return SubFolderModelData(
|
||||
count: json['count'],
|
||||
list: json['list'] != null
|
||||
? (json['list'] as List)
|
||||
.map<SubFolderItemData>((i) => SubFolderItemData.fromJson(i))
|
||||
.toList()
|
||||
: null,
|
||||
list: (json['list'] as List?)
|
||||
?.map<SubFolderItemData>((i) => SubFolderItemData.fromJson(i))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,8 +39,8 @@ class ModelResult {
|
||||
ModelResult.fromJson(Map<String, dynamic> json) {
|
||||
resultType = json['result_type'];
|
||||
summary = json['summary'];
|
||||
outline = json['outline']
|
||||
.map<OutlineItem>((e) => OutlineItem.fromJson(e))
|
||||
outline = (json['outline'] as List?)
|
||||
?.map<OutlineItem>((e) => OutlineItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
@@ -56,8 +56,8 @@ class OutlineItem {
|
||||
|
||||
OutlineItem.fromJson(Map<String, dynamic> json) {
|
||||
title = json['title'];
|
||||
partOutline = json['part_outline']
|
||||
.map<PartOutline>((e) => PartOutline.fromJson(e))
|
||||
partOutline = (json['part_outline'] as List?)
|
||||
?.map<PartOutline>((e) => PartOutline.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,10 +84,7 @@ class MediaVideoItemModel {
|
||||
likeState: json["like_state"],
|
||||
favState: json["fav_state"],
|
||||
page: json["page"],
|
||||
// json["pages"] 可能为null
|
||||
pages: json["pages"] == null
|
||||
? []
|
||||
: List<Page>.from(json["pages"].map((x) => Page.fromJson(x))),
|
||||
pages: (json["pages"] as List?)?.map((x) => Page.fromJson(x)).toList(),
|
||||
title: json["title"],
|
||||
type: json["type"],
|
||||
upper: Upper.fromJson(json["upper"]),
|
||||
@@ -149,7 +146,7 @@ class Page {
|
||||
duration: json["duration"],
|
||||
link: json["link"],
|
||||
page: json["page"],
|
||||
metas: List<Meta>.from(json["metas"].map((x) => Meta.fromJson(x))),
|
||||
metas: (json["metas"] as List?)?.map((x) => Meta.fromJson(x)).toList(),
|
||||
from: json["from"],
|
||||
dimension: Dimension.fromJson(json["dimension"]),
|
||||
);
|
||||
|
||||
@@ -49,17 +49,16 @@ class PlayUrlModel {
|
||||
timeLength = json['timelength'];
|
||||
acceptFormat = json['accept_format'];
|
||||
acceptDesc = json['accept_description'];
|
||||
acceptQuality = json['accept_quality'].map<int>((e) => e as int).toList();
|
||||
acceptQuality =
|
||||
(json['accept_quality'] as List?)?.map<int>((e) => e as int).toList();
|
||||
videoCodecid = json['video_codecid'];
|
||||
seekParam = json['seek_param'];
|
||||
seekType = json['seek_type'];
|
||||
dash = json['dash'] != null ? Dash.fromJson(json['dash']) : null;
|
||||
durl = json['durl']?.map<Durl>((e) => Durl.fromJson(e)).toList();
|
||||
supportFormats = json['support_formats'] != null
|
||||
? json['support_formats']
|
||||
.map<FormatItem>((e) => FormatItem.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
durl = (json['durl'] as List?)?.map<Durl>((e) => Durl.fromJson(e)).toList();
|
||||
supportFormats = (json['support_formats'] as List?)
|
||||
?.map<FormatItem>((e) => FormatItem.fromJson(e))
|
||||
.toList();
|
||||
lastPlayTime = json['last_play_time'];
|
||||
lastPlayCid = json['last_play_cid'];
|
||||
}
|
||||
@@ -85,10 +84,12 @@ class Dash {
|
||||
Dash.fromJson(Map<String, dynamic> json) {
|
||||
duration = json['duration'];
|
||||
minBufferTime = json['minBufferTime'];
|
||||
video = json['video'].map<VideoItem>((e) => VideoItem.fromJson(e)).toList();
|
||||
audio = json['audio'] != null
|
||||
? json['audio'].map<AudioItem>((e) => AudioItem.fromJson(e)).toList()
|
||||
: [];
|
||||
video = (json['video'] as List?)
|
||||
?.map<VideoItem>((e) => VideoItem.fromJson(e))
|
||||
.toList();
|
||||
audio = (json['audio'] as List?)
|
||||
?.map<AudioItem>((e) => AudioItem.fromJson(e))
|
||||
.toList();
|
||||
dolby = json['dolby'] != null ? Dolby.fromJson(json['dolby']) : null;
|
||||
flac = json['flac'] != null ? Flac.fromJson(json['flac']) : null;
|
||||
}
|
||||
@@ -288,9 +289,9 @@ class Dolby {
|
||||
|
||||
Dolby.fromJson(Map<String, dynamic> json) {
|
||||
type = json['type'];
|
||||
audio = json['audio'] != null
|
||||
? json['audio'].map<AudioItem>((e) => AudioItem.fromJson(e)).toList()
|
||||
: [];
|
||||
audio = (json['audio'] as List?)
|
||||
?.map<AudioItem>((e) => AudioItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ class ReplyContent {
|
||||
this.pictures, // {}
|
||||
this.vote,
|
||||
this.richText,
|
||||
this.isText,
|
||||
this.topicsMeta,
|
||||
});
|
||||
|
||||
@@ -20,27 +19,19 @@ class ReplyContent {
|
||||
List? pictures;
|
||||
Map? vote;
|
||||
Map? richText;
|
||||
bool? isText;
|
||||
Map? topicsMeta;
|
||||
|
||||
ReplyContent.fromJson(Map<String, dynamic> json) {
|
||||
message = json['message']
|
||||
.replaceAll('>', '>')
|
||||
.replaceAll('"', '"')
|
||||
.replaceAll(''', "'");
|
||||
message = json['message'];
|
||||
atNameToMid = json['at_name_to_mid'] ?? {};
|
||||
members = json['members'] != null
|
||||
? json['members']
|
||||
.map<MemberItemModel>((e) => MemberItemModel.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
members = (json['members'] as List?)
|
||||
?.map<MemberItemModel>((e) => MemberItemModel.fromJson(e))
|
||||
.toList();
|
||||
emote = json['emote'] ?? {};
|
||||
jumpUrl = json['jump_url'] ?? {};
|
||||
pictures = json['pictures'] ?? [];
|
||||
vote = json['vote'] ?? {};
|
||||
richText = json['rich_text'] ?? {};
|
||||
// 不包含@ 笔记 图片的时候,文字可折叠
|
||||
isText = atNameToMid!.isEmpty && vote!.isEmpty && pictures!.isEmpty;
|
||||
topicsMeta = json['topics_meta'] ?? {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,15 +27,15 @@ class ReplyData {
|
||||
json['cursor'] == null ? null : ReplyCursor.fromJson(json['cursor']);
|
||||
config =
|
||||
json['config'] == null ? null : ReplyConfig.fromJson(json['config']);
|
||||
replies = json['replies'] != null
|
||||
? List<ReplyItemModel>.from(json['replies'].map<ReplyItemModel>(
|
||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid'])))
|
||||
: <ReplyItemModel>[];
|
||||
topReplies = json['top_replies'] != null
|
||||
? List<ReplyItemModel>.from(json['top_replies'].map<ReplyItemModel>(
|
||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid'],
|
||||
isTopStatus: true)))
|
||||
: <ReplyItemModel>[];
|
||||
replies = (json['replies'] as List?)
|
||||
?.map<ReplyItemModel>(
|
||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
||||
.toList();
|
||||
topReplies = (json['top_replies'] as List?)
|
||||
?.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
|
||||
item, json['upper']['mid'],
|
||||
isTopStatus: true))
|
||||
.toList();
|
||||
upper = json['upper'] == null ? null : ReplyUpper.fromJson(json['upper']);
|
||||
}
|
||||
}
|
||||
@@ -60,15 +60,15 @@ class ReplyReplyData {
|
||||
ReplyReplyData.fromJson(Map<String, dynamic> json) {
|
||||
page = ReplyPage.fromJson(json['page']);
|
||||
config = ReplyConfig.fromJson(json['config']);
|
||||
replies = json['replies'] != null
|
||||
? List<ReplyItemModel>.from(json['replies'].map<ReplyItemModel>(
|
||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid'])))
|
||||
: <ReplyItemModel>[];
|
||||
topReplies = json['top_replies'] != null
|
||||
? List<ReplyItemModel>.from(json['top_replies'].map<ReplyItemModel>(
|
||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid'],
|
||||
isTopStatus: true)))
|
||||
: <ReplyItemModel>[];
|
||||
replies = (json['replies'] as List?)
|
||||
?.map<ReplyItemModel>(
|
||||
(item) => ReplyItemModel.fromJson(item, json['upper']['mid']))
|
||||
.toList();
|
||||
topReplies = (json['top_replies'] as List?)
|
||||
?.map<ReplyItemModel>((item) => ReplyItemModel.fromJson(
|
||||
item, json['upper']['mid'],
|
||||
isTopStatus: true))
|
||||
.toList();
|
||||
upper = ReplyUpper.fromJson(json['upper']);
|
||||
root = ReplyItemModel.fromJson(json['root'], json['upper']['mid']);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ class EmoteModelData {
|
||||
|
||||
EmoteModelData.fromJson(Map<String, dynamic> json) {
|
||||
setting =
|
||||
json['setting'] != null ? Setting.fromJson(json['setting']) : null;
|
||||
json['setting'] != null ? Setting.fromJson(json['setting']) : null;
|
||||
if (json['packages'] != null) {
|
||||
packages = <Packages>[];
|
||||
json['packages'].forEach((v) {
|
||||
@@ -68,17 +68,17 @@ class Packages {
|
||||
|
||||
Packages(
|
||||
{this.id,
|
||||
this.text,
|
||||
this.url,
|
||||
this.mtime,
|
||||
this.type,
|
||||
this.attr,
|
||||
this.meta,
|
||||
this.emote,
|
||||
this.flags,
|
||||
this.label,
|
||||
this.packageSubTitle,
|
||||
this.refMid});
|
||||
this.text,
|
||||
this.url,
|
||||
this.mtime,
|
||||
this.type,
|
||||
this.attr,
|
||||
this.meta,
|
||||
this.emote,
|
||||
this.flags,
|
||||
this.label,
|
||||
this.packageSubTitle,
|
||||
this.refMid});
|
||||
|
||||
Packages.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
@@ -95,7 +95,7 @@ class Packages {
|
||||
});
|
||||
}
|
||||
flags =
|
||||
json['flags'] != null ? PackagesFlags.fromJson(json['flags']) : null;
|
||||
json['flags'] != null ? PackagesFlags.fromJson(json['flags']) : null;
|
||||
label = json['label'] != null ? Label.fromJson(json['label']) : null;
|
||||
packageSubTitle = json['package_sub_title'];
|
||||
refMid = json['ref_mid'];
|
||||
@@ -189,16 +189,16 @@ class Emote {
|
||||
|
||||
Emote(
|
||||
{this.id,
|
||||
this.packageId,
|
||||
this.text,
|
||||
this.url,
|
||||
this.mtime,
|
||||
this.type,
|
||||
this.attr,
|
||||
this.meta,
|
||||
this.flags,
|
||||
this.activity,
|
||||
this.gifUrl});
|
||||
this.packageId,
|
||||
this.text,
|
||||
this.url,
|
||||
this.mtime,
|
||||
this.type,
|
||||
this.attr,
|
||||
this.meta,
|
||||
this.flags,
|
||||
this.activity,
|
||||
this.gifUrl});
|
||||
|
||||
Emote.fromJson(Map<String, dynamic> json) {
|
||||
id = json['id'];
|
||||
@@ -237,7 +237,7 @@ class Emote {
|
||||
|
||||
class EmoteMeta {
|
||||
int? size;
|
||||
List<String>? suggest;
|
||||
List? suggest;
|
||||
String? alias;
|
||||
String? gifUrl;
|
||||
|
||||
@@ -245,9 +245,7 @@ class EmoteMeta {
|
||||
|
||||
EmoteMeta.fromJson(Map<String, dynamic> json) {
|
||||
size = json['size'];
|
||||
suggest = json['suggest'] == null
|
||||
? null
|
||||
: List<String>.from(json['suggest'].map((x) => x));
|
||||
suggest = json['suggest'];
|
||||
alias = json['alias'];
|
||||
gifUrl = json['gif_url'];
|
||||
}
|
||||
|
||||
@@ -86,10 +86,9 @@ class ReplyItemModel {
|
||||
action = json['action'];
|
||||
member = ReplyMember.fromJson(json['member']);
|
||||
content = ReplyContent.fromJson(json['content']);
|
||||
replies = json['replies'] != null
|
||||
? List<ReplyItemModel>.from(json['replies']
|
||||
.map((item) => ReplyItemModel.fromJson(item, upperMid)))
|
||||
: <ReplyItemModel>[];
|
||||
replies = (json['replies'] as List?)
|
||||
?.map((item) => ReplyItemModel.fromJson(item, upperMid))
|
||||
.toList();
|
||||
assist = json['assist'];
|
||||
upAction = UpAction.fromJson(json['up_action']);
|
||||
invisible = json['invisible'];
|
||||
@@ -98,9 +97,8 @@ class ReplyItemModel {
|
||||
: ReplyControl.fromJson(json['reply_control']);
|
||||
isUp = upperMid.toString() == json['member']['mid'];
|
||||
isTop = isTopStatus;
|
||||
cardLabel = json['card_label'] != null
|
||||
? json['card_label'].map((e) => e['text_content']).toList()
|
||||
: [];
|
||||
cardLabel =
|
||||
(json['card_label'] as List?)?.map((e) => e['text_content']).toList();
|
||||
rcount = json['rcount'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,8 @@ class ReplyCursor {
|
||||
mode = json['mode'];
|
||||
modeText = json['mode_text'];
|
||||
allCount = json['all_count'] ?? 0;
|
||||
supportMode = json['support_mode'].cast<int>();
|
||||
supportMode =
|
||||
(json['support_mode'] as List?)?.map((e) => e as int).toList();
|
||||
name = json['name'];
|
||||
paginationReply = json['pagination_reply'] != null
|
||||
? PaginationReply.fromJson(json['pagination_reply'])
|
||||
|
||||
@@ -46,7 +46,7 @@ class VideoDetailData {
|
||||
List<DescV2>? descV2;
|
||||
int? state;
|
||||
int? duration;
|
||||
Map<String, int>? rights;
|
||||
Map? rights;
|
||||
Owner? owner;
|
||||
Stat? stat;
|
||||
String? argueMsg;
|
||||
@@ -125,13 +125,11 @@ class VideoDetailData {
|
||||
pubdate = json["pubdate"];
|
||||
ctime = json["ctime"];
|
||||
desc = json["desc"];
|
||||
descV2 = json["desc_v2"] == null
|
||||
? []
|
||||
: List<DescV2>.from(json["desc_v2"]!.map((e) => DescV2.fromJson(e)));
|
||||
descV2 =
|
||||
(json["desc_v2"] as List?)?.map((e) => DescV2.fromJson(e)).toList();
|
||||
state = json["state"];
|
||||
duration = json["duration"];
|
||||
rights =
|
||||
Map.from(json["rights"]!).map((k, v) => MapEntry<String, int>(k, v));
|
||||
rights = json["rights"];
|
||||
owner = json["owner"] == null ? null : Owner.fromJson(json["owner"]);
|
||||
stat = json["stat"] == null ? null : Stat.fromJson(json["stat"]);
|
||||
argueMsg = json['argue_info']?['argue_msg'];
|
||||
@@ -145,9 +143,7 @@ class VideoDetailData {
|
||||
isChargeableSeason = json["is_chargeable_season"];
|
||||
isStory = json["is_story"];
|
||||
noCache = json["no_cache"];
|
||||
pages = json["pages"] == null
|
||||
? []
|
||||
: List<Part>.from(json["pages"]!.map((e) => Part.fromJson(e)));
|
||||
pages = (json["pages"] as List?)?.map((e) => Part.fromJson(e)).toList();
|
||||
subtitle =
|
||||
json["subtitle"] == null ? null : Subtitle.fromJson(json["subtitle"]);
|
||||
ugcSeason = json["ugc_season"] != null
|
||||
@@ -161,9 +157,8 @@ class VideoDetailData {
|
||||
: HonorReply.fromJson(json["honor_reply"]);
|
||||
likeIcon = json["like_icon"];
|
||||
needJumpBv = json["need_jump_bv"];
|
||||
staff = json["staff"] == null
|
||||
? null
|
||||
: (json["staff"] as List).map((item) => Staff.fromJson(item)).toList();
|
||||
staff =
|
||||
(json["staff"] as List?)?.map((item) => Staff.fromJson(item)).toList();
|
||||
if (json['redirect_url'] != null) {
|
||||
epId = resolveEpId(json['redirect_url']);
|
||||
}
|
||||
@@ -296,6 +291,7 @@ class Staff {
|
||||
String? name;
|
||||
String? face;
|
||||
Vip? vip;
|
||||
Map? official;
|
||||
|
||||
Staff({
|
||||
this.mid,
|
||||
@@ -311,6 +307,7 @@ class Staff {
|
||||
name = json["name"];
|
||||
face = json["face"];
|
||||
vip = json["vip"] == null ? null : Vip.fromJson(json["vip"]);
|
||||
official = json['official'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -341,9 +338,7 @@ class HonorReply {
|
||||
String toRawJson() => json.encode(toJson());
|
||||
|
||||
HonorReply.fromJson(Map<String, dynamic> json) {
|
||||
honor = json["honor"] == null
|
||||
? []
|
||||
: List<Honor>.from(json["honor"]!.map((x) => Honor.fromJson(x)));
|
||||
honor = (json["honor"] as List?)?.map((x) => Honor.fromJson(x)).toList();
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -563,9 +558,7 @@ class Subtitle {
|
||||
|
||||
Subtitle.fromJson(Map<String, dynamic> json) {
|
||||
allowSubmit = json["allow_submit"];
|
||||
list = json["list"] == null
|
||||
? []
|
||||
: List<dynamic>.from(json["list"]!.map((x) => x));
|
||||
list = json["list"];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
@@ -634,11 +627,9 @@ class UgcSeason {
|
||||
intro = json['intro'];
|
||||
signState = json['sign_state'];
|
||||
attribute = json['attribute'];
|
||||
sections = json['sections'] != null
|
||||
? json['sections']
|
||||
.map<SectionItem>((e) => SectionItem.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
sections = (json['sections'] as List?)
|
||||
?.map<SectionItem>((e) => SectionItem.fromJson(e))
|
||||
.toList();
|
||||
stat = Stat.fromJson(json['stat']);
|
||||
epCount = json['ep_count'];
|
||||
seasonType = json['season_type'];
|
||||
@@ -680,8 +671,8 @@ class SectionItem {
|
||||
id = json['id'];
|
||||
title = json['title'];
|
||||
type = json['type'];
|
||||
episodes = json['episodes']
|
||||
.map<EpisodeItem>((e) => EpisodeItem.fromJson(e))
|
||||
episodes = (json['episodes'] as List?)
|
||||
?.map<EpisodeItem>((e) => EpisodeItem.fromJson(e))
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class _AboutPageState extends State<AboutPage> {
|
||||
onSubmitted: (value) {
|
||||
Get.back();
|
||||
if (value.isNotEmpty) {
|
||||
Get.toNamed('/webview', parameters: {'url': value});
|
||||
Utils.handleWebview(value, inApp: true);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
||||
@@ -186,13 +186,21 @@ class BangumiIntroController extends CommonController {
|
||||
}
|
||||
}
|
||||
|
||||
void coinVideo(int coin) async {
|
||||
var res = await VideoHttp.coinVideo(bvid: bvid, multiply: coin);
|
||||
void coinVideo(int coin, [bool selectLike = false]) async {
|
||||
var res = await VideoHttp.coinVideo(
|
||||
bvid: bvid,
|
||||
multiply: coin,
|
||||
selectLike: selectLike ? 1 : 0,
|
||||
);
|
||||
if (res['status']) {
|
||||
SmartDialog.showToast('投币成功');
|
||||
hasCoin.value = true;
|
||||
dynamic bangumiDetail = (loadingState.value as Success).response;
|
||||
bangumiDetail.stat!['coins'] = bangumiDetail.stat!['coins'] + coin;
|
||||
if (selectLike && hasLike.value.not) {
|
||||
hasLike.value = true;
|
||||
bangumiDetail.stat!['likes'] = bangumiDetail.stat!['likes'] + 1;
|
||||
}
|
||||
loadingState.value = LoadingState.success(bangumiDetail);
|
||||
} else {
|
||||
SmartDialog.showToast(res['msg']);
|
||||
@@ -418,11 +426,7 @@ class BangumiIntroController extends CommonController {
|
||||
Get.find<VideoDetailController>(tag: Get.arguments['heroTag'])
|
||||
..plPlayerController.pause()
|
||||
..makeHeartBeat()
|
||||
..playedTime = null
|
||||
..videoUrl = null
|
||||
..audioUrl = null
|
||||
..vttSubtitlesIndex = null
|
||||
..savedDanmaku = null
|
||||
..onReset()
|
||||
..epId = epId
|
||||
..bvid = bvid
|
||||
..cid.value = cid
|
||||
@@ -600,21 +604,24 @@ class BangumiIntroController extends CommonController {
|
||||
RxInt followStatus = (-1).obs;
|
||||
|
||||
Future queryIsFollowed() async {
|
||||
dynamic result = await Request().get(
|
||||
'https://www.bilibili.com/bangumi/play/ss$seasonId',
|
||||
);
|
||||
dom.Document document = html_parser.parse(result.data);
|
||||
dom.Element? scriptElement = document.querySelector('script#__NEXT_DATA__');
|
||||
if (scriptElement != null) {
|
||||
dynamic scriptContent = jsonDecode(scriptElement.text);
|
||||
isFollowed.value =
|
||||
scriptContent['props']['pageProps']['followState']['isFollowed'];
|
||||
followStatus.value =
|
||||
scriptContent['props']['pageProps']['followState']['followStatus'];
|
||||
// int progress = scriptContent['props']['pageProps']['dehydratedState']
|
||||
// ['queries'][0]['state']['data']['result']
|
||||
// ['play_view_business_info']['user_status']['watch_progress']
|
||||
// ['current_watch_progress'];
|
||||
}
|
||||
try {
|
||||
dynamic result = await Request().get(
|
||||
'https://www.bilibili.com/bangumi/play/ss$seasonId',
|
||||
);
|
||||
dom.Document document = html_parser.parse(result.data);
|
||||
dom.Element? scriptElement =
|
||||
document.querySelector('script#__NEXT_DATA__');
|
||||
if (scriptElement != null) {
|
||||
dynamic scriptContent = jsonDecode(scriptElement.text);
|
||||
isFollowed.value =
|
||||
scriptContent['props']['pageProps']['followState']['isFollowed'];
|
||||
followStatus.value =
|
||||
scriptContent['props']['pageProps']['followState']['followStatus'];
|
||||
// int progress = scriptContent['props']['pageProps']['dehydratedState']
|
||||
// ['queries'][0]['state']['data']['result']
|
||||
// ['play_view_business_info']['user_status']['watch_progress']
|
||||
// ['current_watch_progress'];
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
Expanded(
|
||||
child: GestureDetector(
|
||||
onTap: showIntroDetail,
|
||||
behavior: HitTestBehavior.translucent,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: SizedBox(
|
||||
height: isLandscape ? 115 : 115 / 0.75,
|
||||
child: Column(
|
||||
@@ -450,6 +450,7 @@ class _BangumiInfoState extends State<BangumiInfo>
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 1),
|
||||
child: SizedBox(
|
||||
|
||||
@@ -82,48 +82,48 @@ class _BangumiPageState extends State<BangumiPage>
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Obx(
|
||||
() => Visibility(
|
||||
visible: _bangumiController.isLogin.value,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Obx(
|
||||
() => Text(
|
||||
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${_bangumiController.followCount.value == -1 ? '' : ' ${_bangumiController.followCount.value}'}',
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
() => _bangumiController.isLogin.value
|
||||
? Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Obx(
|
||||
() => Text(
|
||||
'最近${widget.tabType == TabType.bangumi ? '追番' : '追剧'}${_bangumiController.followCount.value == -1 ? '' : ' ${_bangumiController.followCount.value}'}',
|
||||
style:
|
||||
Theme.of(context).textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '刷新',
|
||||
onPressed: () {
|
||||
_bangumiController
|
||||
..followPage = 1
|
||||
..followEnd = false
|
||||
..queryBangumiFollow();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '刷新',
|
||||
onPressed: () {
|
||||
_bangumiController
|
||||
..followPage = 1
|
||||
..followEnd = false
|
||||
..queryBangumiFollow();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Grid.smallCardWidth / 2 / 0.75 +
|
||||
MediaQuery.textScalerOf(context).scale(50),
|
||||
child: Obx(
|
||||
() => _buildFollowBody(
|
||||
_bangumiController.followState.value),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: Grid.smallCardWidth / 2 / 0.75 +
|
||||
MediaQuery.textScalerOf(context).scale(50),
|
||||
child: Obx(
|
||||
() => _buildFollowBody(
|
||||
_bangumiController.followState.value),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:PiliPlus/grpc/app/main/community/reply/v1/reply.pb.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/reply.dart';
|
||||
import 'package:PiliPlus/models/common/reply_type.dart';
|
||||
import 'package:PiliPlus/models/video/reply/data.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
@@ -9,6 +10,7 @@ import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/models/common/reply_sort_type.dart';
|
||||
import 'package:PiliPlus/models/video/reply/item.dart';
|
||||
@@ -27,10 +29,11 @@ abstract class ReplyController extends CommonController {
|
||||
late final bool isLogin = GStorage.userInfo.get('userInfoCache') != null;
|
||||
|
||||
CursorReply? cursor;
|
||||
late Mode mode = Mode.MAIN_LIST_HOT;
|
||||
late Rx<Mode> mode = Mode.MAIN_LIST_HOT.obs;
|
||||
late bool hasUpTop = false;
|
||||
|
||||
late final banWordForReply = GStorage.banWordForReply;
|
||||
late final enableCommAntifraud = GStorage.enableCommAntifraud;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -43,7 +46,7 @@ abstract class ReplyController extends CommonController {
|
||||
}
|
||||
sortType.value = ReplySortType.values[defaultReplySortIndex];
|
||||
if (sortType.value == ReplySortType.time) {
|
||||
mode = Mode.MAIN_LIST_TIME;
|
||||
mode.value = Mode.MAIN_LIST_TIME;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +98,7 @@ abstract class ReplyController extends CommonController {
|
||||
hasUpTop = true;
|
||||
}
|
||||
}
|
||||
if (response.response.topReplies != null) {
|
||||
if ((response.response.topReplies as List?)?.isNotEmpty == true) {
|
||||
replies.insertAll(0, response.response.topReplies);
|
||||
hasUpTop = true;
|
||||
}
|
||||
@@ -117,11 +120,11 @@ abstract class ReplyController extends CommonController {
|
||||
switch (sortType.value) {
|
||||
case ReplySortType.time:
|
||||
sortType.value = ReplySortType.like;
|
||||
mode = Mode.MAIN_LIST_HOT;
|
||||
mode.value = Mode.MAIN_LIST_HOT;
|
||||
break;
|
||||
case ReplySortType.like:
|
||||
sortType.value = ReplySortType.time;
|
||||
mode = Mode.MAIN_LIST_TIME;
|
||||
mode.value = Mode.MAIN_LIST_TIME;
|
||||
break;
|
||||
}
|
||||
nextOffset = '';
|
||||
@@ -204,21 +207,44 @@ abstract class ReplyController extends CommonController {
|
||||
}
|
||||
count.value += 1;
|
||||
loadingState.value = LoadingState.success(response);
|
||||
if (enableCommAntifraud && context.mounted) {
|
||||
checkReply(
|
||||
context,
|
||||
oid ?? replyItem.oid.toInt(),
|
||||
replyItem?.id.toInt(),
|
||||
replyItem?.type.toInt() ??
|
||||
replyType?.index ??
|
||||
ReplyType.video.index,
|
||||
replyInfo.id.toInt(),
|
||||
replyInfo.content.message,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
ReplyData response = loadingState.value is Success
|
||||
? (loadingState.value as Success).response
|
||||
: ReplyData();
|
||||
response.replies ??= <ReplyItemModel>[];
|
||||
ReplyItemModel replyInfo = ReplyItemModel.fromJson(res, '');
|
||||
if (oid != null) {
|
||||
response.replies
|
||||
?.insert(hasUpTop ? 1 : 0, ReplyItemModel.fromJson(res, ''));
|
||||
response.replies?.insert(hasUpTop ? 1 : 0, replyInfo);
|
||||
} else {
|
||||
response.replies?[index].replies ??= <ReplyItemModel>[];
|
||||
response.replies?[index].replies
|
||||
?.add(ReplyItemModel.fromJson(res, ''));
|
||||
response.replies?[index].replies?.add(replyInfo);
|
||||
}
|
||||
count.value += 1;
|
||||
loadingState.value = LoadingState.success(response);
|
||||
if (enableCommAntifraud && context.mounted) {
|
||||
checkReply(
|
||||
context,
|
||||
oid ?? replyItem.oid,
|
||||
replyItem?.rpid,
|
||||
replyItem?.type.toInt() ??
|
||||
replyType?.index ??
|
||||
ReplyType.video.index,
|
||||
replyInfo.rpid ?? 0,
|
||||
replyInfo.content?.message ?? '',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -262,4 +288,186 @@ abstract class ReplyController extends CommonController {
|
||||
loadingState.value = LoadingState.success(response);
|
||||
}
|
||||
}
|
||||
|
||||
// ref https://github.com/freedom-introvert/biliSendCommAntifraud
|
||||
void checkReply(
|
||||
BuildContext context,
|
||||
dynamic oid,
|
||||
dynamic rpid,
|
||||
int replyType,
|
||||
int replyId,
|
||||
String message,
|
||||
) async {
|
||||
void showReplyCheckResult(String message) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('评论检查结果'),
|
||||
content: SelectableText(message),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
if (context.mounted.not) return;
|
||||
// root reply
|
||||
if (rpid == null) {
|
||||
// no cookie check
|
||||
dynamic res = await ReplyHttp.replyList(
|
||||
isLogin: false,
|
||||
oid: oid,
|
||||
nextOffset: '',
|
||||
type: replyType,
|
||||
sort: ReplySortType.time.index,
|
||||
page: 1,
|
||||
banWordForReply: '',
|
||||
);
|
||||
if (context.mounted.not) return;
|
||||
if (res is Error) {
|
||||
SmartDialog.showToast('获取评论主列表时发生错误:${res.errMsg}');
|
||||
return;
|
||||
} else if (res is Success) {
|
||||
ReplyData replies = res.response;
|
||||
int index =
|
||||
replies.replies?.indexWhere((item) => item.rpid == replyId) ?? -1;
|
||||
if (index != -1) {
|
||||
// found
|
||||
if (context.mounted) {
|
||||
showReplyCheckResult(
|
||||
'无账号状态下找到了你的评论,评论正常!\n\n你的评论:$message',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// not found
|
||||
if (context.mounted.not) return;
|
||||
// cookie check
|
||||
dynamic res1 = await ReplyHttp.replyReplyList(
|
||||
isLogin: isLogin,
|
||||
oid: oid,
|
||||
root: rpid ?? replyId,
|
||||
pageNum: 1,
|
||||
type: replyType,
|
||||
banWordForReply: '',
|
||||
);
|
||||
if (context.mounted.not) return;
|
||||
if (res1 is Error) {
|
||||
// not found
|
||||
if (context.mounted) {
|
||||
showReplyCheckResult(
|
||||
'无法找到你的评论。\n\n你的评论:$message',
|
||||
);
|
||||
}
|
||||
} else if (res1 is Success) {
|
||||
// found
|
||||
if (context.mounted.not) return;
|
||||
// no cookie check
|
||||
dynamic res2 = await ReplyHttp.replyReplyList(
|
||||
isLogin: false,
|
||||
oid: oid,
|
||||
root: rpid ?? replyId,
|
||||
pageNum: 1,
|
||||
type: replyType,
|
||||
banWordForReply: '',
|
||||
isCheck: true,
|
||||
);
|
||||
if (context.mounted.not) return;
|
||||
if (res2 is Error) {
|
||||
// not found
|
||||
if (context.mounted) {
|
||||
showReplyCheckResult(
|
||||
res2.errMsg.startsWith('12022')
|
||||
? '你的评论被shadow ban(仅自己可见)!\n\n你的评论: $message'
|
||||
: '评论不可见(${res2.errMsg}): $message',
|
||||
);
|
||||
}
|
||||
} else if (res2 is Success) {
|
||||
// found
|
||||
if (context.mounted) {
|
||||
showReplyCheckResult('''
|
||||
你评论状态有点可疑,虽然无账号翻找评论区获取不到你的评论,但是无账号可通过
|
||||
https://api.bilibili.com/x/v2/reply/reply?oid=$oid&pn=1&ps=20&root=${rpid ?? replyId}&type=$replyType
|
||||
获取你的评论,疑似评论区被戒严或者这是你的视频。
|
||||
|
||||
你的评论:$message''');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int i = 1; true; i++) {
|
||||
if (context.mounted.not) return;
|
||||
dynamic res3 = await ReplyHttp.replyReplyList(
|
||||
isLogin: false,
|
||||
oid: oid,
|
||||
root: rpid ?? replyId,
|
||||
pageNum: i,
|
||||
type: replyType,
|
||||
banWordForReply: '',
|
||||
isCheck: true,
|
||||
);
|
||||
if (res3 is Error) {
|
||||
break;
|
||||
} else if (res3 is Success) {
|
||||
ReplyReplyData data = res3.response;
|
||||
if (data.replies.isNullOrEmpty) {
|
||||
break;
|
||||
}
|
||||
int index =
|
||||
data.replies?.indexWhere((item) => item.rpid == replyId) ?? -1;
|
||||
if (index == -1) {
|
||||
// not found
|
||||
} else {
|
||||
// found
|
||||
if (context.mounted) {
|
||||
showReplyCheckResult(
|
||||
'无账号状态下找到了你的评论,评论正常!\n\n你的评论:$message',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; true; i++) {
|
||||
if (context.mounted.not) return;
|
||||
dynamic res4 = await ReplyHttp.replyReplyList(
|
||||
isLogin: true,
|
||||
oid: oid,
|
||||
root: rpid ?? replyId,
|
||||
pageNum: i,
|
||||
type: replyType,
|
||||
banWordForReply: '',
|
||||
isCheck: true,
|
||||
);
|
||||
if (res4 is Error) {
|
||||
break;
|
||||
} else if (res4 is Success) {
|
||||
ReplyReplyData data = res4.response;
|
||||
if (data.replies.isNullOrEmpty) {
|
||||
break;
|
||||
}
|
||||
int index =
|
||||
data.replies?.indexWhere((item) => item.rpid == replyId) ?? -1;
|
||||
if (index == -1) {
|
||||
// not found
|
||||
} else {
|
||||
// found
|
||||
if (context.mounted) {
|
||||
showReplyCheckResult(
|
||||
'你的评论被shadow ban(仅自己可见)!\n\n你的评论: $message',
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
showReplyCheckResult(
|
||||
'评论不可见: $message',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:PiliPlus/grpc/dm/v1/dm.pb.dart';
|
||||
import 'package:PiliPlus/http/danmaku.dart';
|
||||
import 'package:PiliPlus/plugin/pl_player/controller.dart';
|
||||
import 'package:PiliPlus/utils/storage.dart';
|
||||
|
||||
class PlDanmakuController {
|
||||
PlDanmakuController(
|
||||
@@ -19,8 +18,6 @@ class PlDanmakuController {
|
||||
|
||||
static int segmentLength = 60 * 6 * 1000;
|
||||
|
||||
late final mergeDanmaku = GStorage.mergeDanmaku;
|
||||
|
||||
void initiate(int videoDuration, int progress) {
|
||||
if (videoDuration <= 0) {
|
||||
return;
|
||||
@@ -50,7 +47,7 @@ class PlDanmakuController {
|
||||
final DmSegMobileReply result = await DanmakaHttp.queryDanmaku(
|
||||
cid: cid,
|
||||
segmentIndex: segmentIndex + 1,
|
||||
mergeDanmaku: mergeDanmaku,
|
||||
mergeDanmaku: plPlayerController.mergeDanmaku,
|
||||
);
|
||||
if (result.elems.isNotEmpty) {
|
||||
for (var element in result.elems) {
|
||||
|
||||
@@ -45,7 +45,7 @@ class DynamicDetailController extends ReplyController {
|
||||
oid: oid!,
|
||||
cursor: CursorReq(
|
||||
next: cursor?.next ?? $fixnum.Int64(0),
|
||||
mode: mode,
|
||||
mode: mode.value,
|
||||
),
|
||||
banWordForReply: banWordForReply,
|
||||
)
|
||||
|
||||
@@ -136,7 +136,6 @@ class _DynamicsPageState extends State<DynamicsPage>
|
||||
super.build(context);
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
backgroundColor: Colors.transparent,
|
||||
appBar: AppBar(
|
||||
leading: upPanelPosition == UpPanelPosition.rightDrawer
|
||||
? _createDynamicBtn(false)
|
||||
|
||||
@@ -110,78 +110,84 @@ class AuthorPanel extends StatelessWidget {
|
||||
)
|
||||
],
|
||||
),
|
||||
// const Spacer(),
|
||||
// if (source != 'detail' && item.modules?.moduleTag?.text != null)
|
||||
// Container(
|
||||
// padding:
|
||||
// const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
|
||||
// decoration: BoxDecoration(
|
||||
// color: Theme.of(context).colorScheme.surface,
|
||||
// borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
// border: Border.all(
|
||||
// width: 1.25,
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// ),
|
||||
// child: Text(
|
||||
// item.modules.moduleTag.text,
|
||||
// style: TextStyle(
|
||||
// height: 1,
|
||||
// fontSize: 12,
|
||||
// color: Theme.of(context).colorScheme.primary,
|
||||
// ),
|
||||
// strutStyle: const StrutStyle(
|
||||
// leading: 0,
|
||||
// height: 1,
|
||||
// fontSize: 12,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: item.modules.moduleAuthor.decorate != null
|
||||
child: source != 'detail' && item.modules?.moduleTag?.text != null
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// GestureDetector(
|
||||
// onTap:
|
||||
// item.modules.moduleAuthor.decorate['jump_url'] != null
|
||||
// ? () {
|
||||
// Get.toNamed(
|
||||
// '/webview',
|
||||
// parameters: {
|
||||
// 'url':
|
||||
// '${item.modules.moduleAuthor.decorate['jump_url']}'
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// : null,
|
||||
// child:
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.centerRight,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
height: 32,
|
||||
imageUrl:
|
||||
item.modules.moduleAuthor.decorate['card_url'],
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 4, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(4)),
|
||||
border: Border.all(
|
||||
width: 1.25,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
if ((item.modules.moduleAuthor.decorate?['fan']
|
||||
?['num_str'] as String?)
|
||||
?.isNotEmpty ==
|
||||
true)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 32),
|
||||
child: Text(
|
||||
'${item.modules.moduleAuthor.decorate['fan']['num_str']}',
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
fontFamily: 'digital_id_num',
|
||||
color:
|
||||
(item.modules.moduleAuthor.decorate?['fan']
|
||||
),
|
||||
child: Text(
|
||||
item.modules.moduleTag.text,
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
strutStyle: const StrutStyle(
|
||||
leading: 0,
|
||||
height: 1,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
_moreWidget(context),
|
||||
],
|
||||
)
|
||||
: item.modules.moduleAuthor.decorate != null
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// GestureDetector(
|
||||
// onTap:
|
||||
// item.modules.moduleAuthor.decorate['jump_url'] != null
|
||||
// ? () {
|
||||
// Get.toNamed(
|
||||
// '/webview',
|
||||
// parameters: {
|
||||
// 'url':
|
||||
// '${item.modules.moduleAuthor.decorate['jump_url']}'
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// : null,
|
||||
// child:
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.centerRight,
|
||||
children: [
|
||||
CachedNetworkImage(
|
||||
height: 32,
|
||||
imageUrl: item
|
||||
.modules.moduleAuthor.decorate['card_url'],
|
||||
),
|
||||
if ((item.modules.moduleAuthor.decorate?['fan']
|
||||
?['num_str'] as String?)
|
||||
?.isNotEmpty ==
|
||||
true)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 32),
|
||||
child: Text(
|
||||
'${item.modules.moduleAuthor.decorate['fan']['num_str']}',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 11,
|
||||
fontFamily: 'digital_id_num',
|
||||
color: (item.modules.moduleAuthor
|
||||
.decorate?['fan']
|
||||
?['color'] as String?)
|
||||
?.startsWith('#') ==
|
||||
true
|
||||
@@ -193,16 +199,16 @@ class AuthorPanel extends StatelessWidget {
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
// ),
|
||||
_moreWidget(context),
|
||||
],
|
||||
),
|
||||
// ),
|
||||
_moreWidget(context),
|
||||
],
|
||||
)
|
||||
: _moreWidget(context),
|
||||
)
|
||||
: _moreWidget(context),
|
||||
)
|
||||
],
|
||||
);
|
||||
@@ -306,6 +312,25 @@ class AuthorPanel extends StatelessWidget {
|
||||
},
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
if (GStorage.isLogin)
|
||||
ListTile(
|
||||
title: Text(
|
||||
'举报',
|
||||
style: Theme.of(context).textTheme.titleSmall!.copyWith(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.error_outline_outlined,
|
||||
size: 19,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_showReportDynDialog(context);
|
||||
},
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
if (item.modules.moduleAuthor.mid ==
|
||||
GStorage.userInfo.get('userInfoCache')?.mid &&
|
||||
onRemove != null)
|
||||
@@ -345,25 +370,6 @@ class AuthorPanel extends StatelessWidget {
|
||||
.titleSmall!
|
||||
.copyWith(color: Theme.of(context).colorScheme.error)),
|
||||
),
|
||||
if (GStorage.isLogin)
|
||||
ListTile(
|
||||
title: Text(
|
||||
'举报',
|
||||
style: Theme.of(context).textTheme.titleSmall!.copyWith(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
leading: Icon(
|
||||
Icons.error_outline_outlined,
|
||||
size: 19,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
_showReportDynDialog(context);
|
||||
},
|
||||
minLeadingWidth: 0,
|
||||
),
|
||||
const Divider(thickness: 0.1, height: 1),
|
||||
ListTile(
|
||||
onTap: Get.back,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/utils/extension.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -38,12 +39,20 @@ class DynamicPanel extends StatelessWidget {
|
||||
child: Material(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
color: Theme.of(context).cardColor.withOpacity(0.5),
|
||||
color: Colors.transparent,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: source == 'detail' && item.type != 'DYNAMIC_TYPE_AV'
|
||||
onTap: source == 'detail' &&
|
||||
[
|
||||
'DYNAMIC_TYPE_AV',
|
||||
'DYNAMIC_TYPE_UGC_SEASON',
|
||||
'DYNAMIC_TYPE_PGC_UNION',
|
||||
'DYNAMIC_TYPE_PGC',
|
||||
'DYNAMIC_TYPE_LIVE',
|
||||
'DYNAMIC_TYPE_LIVE_RCMD',
|
||||
].contains(item.type).not
|
||||
? null
|
||||
: () => Utils.pushDynDetail(item, 1),
|
||||
onLongPress: () {
|
||||
|
||||
@@ -161,9 +161,40 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
|
||||
return videoSeasonWidget(item, context, 'archive', floor: floor);
|
||||
// 文章
|
||||
case 'DYNAMIC_TYPE_ARTICLE':
|
||||
return item is ItemOrigModel
|
||||
? articlePanel(item, context, callback, floor: floor)
|
||||
: const SizedBox.shrink();
|
||||
return switch (item) {
|
||||
ItemOrigModel() => articlePanel(item, context, callback, floor: floor),
|
||||
DynamicItemModel() => item.modules?.moduleDynamic?.major?.blocked !=
|
||||
null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (item.modules?.moduleDynamic?.major?.blocked?['title'] !=
|
||||
null)
|
||||
Text(
|
||||
'${item.modules?.moduleDynamic?.major?.blocked!['title']}',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
if (item.modules?.moduleDynamic?.major
|
||||
?.blocked?['hint_message'] !=
|
||||
null)
|
||||
Text(
|
||||
'${item.modules?.moduleDynamic?.major?.blocked!['hint_message']}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
_ => const SizedBox.shrink(),
|
||||
};
|
||||
// return Container(
|
||||
// padding:
|
||||
// const EdgeInsets.only(left: 10, top: 12, right: 10, bottom: 10),
|
||||
@@ -208,8 +239,7 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 15, top: 10, right: 15, bottom: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 8),
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.08),
|
||||
child:
|
||||
forWard(item.orig, context, source, callback, floor: floor + 1),
|
||||
@@ -305,11 +335,13 @@ Widget forWard(item, context, source, callback, {floor = 1}) {
|
||||
case 'DYNAMIC_TYPE_COMMON_SQUARE':
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Get.toNamed('/webview', parameters: {
|
||||
'url': item.modules.moduleDynamic.major.common['jump_url'],
|
||||
'type': 'url',
|
||||
'pageTitle': item.modules.moduleDynamic.major.common['title']
|
||||
});
|
||||
try {
|
||||
String url = item.modules.moduleDynamic.major.common['jump_url'];
|
||||
if (url.contains('bangumi/play') && Utils.viewPgcFromUri(url)) {
|
||||
return;
|
||||
}
|
||||
Utils.handleWebview(url, inApp: true);
|
||||
} catch (_) {}
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
@@ -7,7 +8,7 @@ import 'rich_node_panel.dart';
|
||||
|
||||
Widget livePanel(item, context, {floor = 1}) {
|
||||
dynamic content = item.modules.moduleDynamic.major;
|
||||
TextStyle authorStyle =
|
||||
late final TextStyle authorStyle =
|
||||
TextStyle(color: Theme.of(context).colorScheme.primary);
|
||||
InlineSpan? richNodes = richNode(item, context);
|
||||
return Column(
|
||||
@@ -55,7 +56,18 @@ Widget livePanel(item, context, {floor = 1}) {
|
||||
const SizedBox(height: 6),
|
||||
],
|
||||
GestureDetector(
|
||||
onTap: () {},
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {
|
||||
Get.toNamed('/liveRoom?roomid=${content.live?.id}');
|
||||
},
|
||||
onLongPress: () {
|
||||
Feedback.forLongPress(context);
|
||||
imageSaveDialog(
|
||||
context: context,
|
||||
title: content.live.title,
|
||||
cover: content.live.cover,
|
||||
);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
@@ -104,13 +104,12 @@ InlineSpan? richNode(item, context) {
|
||||
return;
|
||||
}
|
||||
if (url.startsWith('//')) {
|
||||
url = url.replaceFirst('//', 'https://');
|
||||
PiliScheme.routePush(Uri.parse(url));
|
||||
PiliScheme.routePushFromUrl('https:$url');
|
||||
return;
|
||||
}
|
||||
Utils.handleWebview(url.startsWith('//')
|
||||
? "https://${url.split('//').last}"
|
||||
: url);
|
||||
Utils.handleWebview(
|
||||
url.startsWith('//') ? "https://$url" : url,
|
||||
);
|
||||
},
|
||||
child: Text(
|
||||
i.text ?? '',
|
||||
@@ -153,7 +152,7 @@ InlineSpan? richNode(item, context) {
|
||||
spanChildren.add(
|
||||
WidgetSpan(
|
||||
child: NetworkImgLayer(
|
||||
src: i.emoji!.iconUrl,
|
||||
src: i.emoji!.webpUrl ?? i.emoji!.gifUrl ?? i.emoji!.iconUrl,
|
||||
type: 'emote',
|
||||
width: (i.emoji!.size ?? 1) * 20,
|
||||
height: (i.emoji!.size ?? 1) * 20,
|
||||
|
||||
@@ -7,7 +7,7 @@ import '../../http/reply.dart';
|
||||
|
||||
class EmotePanelController extends CommonController
|
||||
with GetTickerProviderStateMixin {
|
||||
late TabController tabController;
|
||||
TabController? tabController;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -29,7 +29,7 @@ class EmotePanelController extends CommonController
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
tabController.dispose();
|
||||
tabController?.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +95,7 @@ class _EmotePanelState extends State<EmotePanel>
|
||||
),
|
||||
TabBar(
|
||||
controller: _emotePanelController.tabController,
|
||||
padding: const EdgeInsets.only(right: 60),
|
||||
dividerColor: Colors.transparent,
|
||||
isScrollable: true,
|
||||
tabs: (loadingState.response as List<Packages>)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:PiliPlus/common/widgets/image_save.dart';
|
||||
import 'package:PiliPlus/common/widgets/video_progress_indicator.dart';
|
||||
import 'package:PiliPlus/models/user/history.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
|
||||
import 'package:PiliPlus/pages/fav_search/controller.dart';
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -15,6 +16,7 @@ import 'package:PiliPlus/models/common/business_type.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
class HistoryItem extends StatelessWidget {
|
||||
final dynamic videoItem;
|
||||
@@ -50,8 +52,15 @@ class HistoryItem extends StatelessWidget {
|
||||
// 'pageTitle': videoItem.title
|
||||
// },
|
||||
// );
|
||||
PiliScheme.routePush(Uri.parse(
|
||||
"https://www.bilibili.com/read/cv${videoItem.history.oid}"));
|
||||
Utils.toDupNamed(
|
||||
'/htmlRender',
|
||||
parameters: {
|
||||
'url': 'https://www.bilibili.com/read/cv${videoItem.history.oid}',
|
||||
'title': '',
|
||||
'id': 'cv${videoItem.history.oid}',
|
||||
'dynamicType': 'read'
|
||||
},
|
||||
);
|
||||
} else if (videoItem.history.business == 'live') {
|
||||
if (videoItem.liveStatus == 1) {
|
||||
// LiveItemModel liveItem = LiveItemModel.fromJson({
|
||||
@@ -185,7 +194,6 @@ class HistoryItem extends StatelessWidget {
|
||||
return Stack(
|
||||
children: [
|
||||
NetworkImgLayer(
|
||||
radius: 12,
|
||||
src: (videoItem.cover != ''
|
||||
? videoItem.cover
|
||||
: videoItem.covers.first),
|
||||
@@ -227,7 +235,7 @@ class HistoryItem extends StatelessWidget {
|
||||
child: Container(
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
color: Colors.black.withOpacity(0.6),
|
||||
),
|
||||
child: SizedBox(
|
||||
@@ -273,20 +281,10 @@ class HistoryItem extends StatelessWidget {
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
child: ClipRect(
|
||||
clipper: _Clipper(),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.only(
|
||||
bottomLeft: Radius.circular(12),
|
||||
bottomRight: Radius.circular(12),
|
||||
),
|
||||
child: LinearProgressIndicator(
|
||||
minHeight: 12,
|
||||
value: videoItem.progress == -1
|
||||
? 100
|
||||
: videoItem.progress / videoItem.duration,
|
||||
),
|
||||
),
|
||||
child: videoProgressIndicator(
|
||||
videoItem.progress == -1
|
||||
? 1
|
||||
: videoItem.progress / videoItem.duration,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -350,59 +348,79 @@ class HistoryItem extends StatelessWidget {
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: PopupMenuButton<String>(
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: '功能菜单',
|
||||
icon: Icon(
|
||||
Icons.more_vert_outlined,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
size: 18,
|
||||
),
|
||||
position: PopupMenuPosition.under,
|
||||
// constraints: const BoxConstraints(maxHeight: 35),
|
||||
onSelected: (String type) {},
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
if (videoItem.history?.business != 'pgc' &&
|
||||
videoItem.badge != '番剧' &&
|
||||
!videoItem.tagName.contains('动画') &&
|
||||
videoItem.history.business != 'live' &&
|
||||
!videoItem.history.business.contains('article'))
|
||||
if (videoItem is HisListItem)
|
||||
SizedBox(
|
||||
width: 29,
|
||||
height: 29,
|
||||
child: PopupMenuButton<String>(
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: '功能菜单',
|
||||
icon: Icon(
|
||||
Icons.more_vert_outlined,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
size: 18,
|
||||
),
|
||||
position: PopupMenuPosition.under,
|
||||
itemBuilder: (BuildContext context) =>
|
||||
<PopupMenuEntry<String>>[
|
||||
if (videoItem.authorMid != null &&
|
||||
videoItem.authorName?.isNotEmpty == true)
|
||||
PopupMenuItem<String>(
|
||||
onTap: () {
|
||||
Get.toNamed(
|
||||
'/member?mid=${videoItem.authorMid}',
|
||||
arguments: {
|
||||
'heroTag': '${videoItem.authorMid}',
|
||||
},
|
||||
);
|
||||
},
|
||||
height: 35,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(MdiIcons.accountCircleOutline, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text(
|
||||
'访问:${videoItem.authorName}',
|
||||
style: TextStyle(fontSize: 13),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (videoItem.history?.business != 'pgc' &&
|
||||
videoItem.badge != '番剧' &&
|
||||
!videoItem.tagName.contains('动画') &&
|
||||
videoItem.history.business != 'live' &&
|
||||
!videoItem.history.business.contains('article'))
|
||||
PopupMenuItem<String>(
|
||||
onTap: () async {
|
||||
var res = await UserHttp.toViewLater(
|
||||
bvid: videoItem.history.bvid);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
height: 35,
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.watch_later_outlined, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text('稍后再看', style: TextStyle(fontSize: 13))
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
onTap: () async {
|
||||
var res = await UserHttp.toViewLater(
|
||||
bvid: videoItem.history.bvid);
|
||||
SmartDialog.showToast(res['msg']);
|
||||
},
|
||||
value: 'pause',
|
||||
onTap: () => ctr!.delHistory(
|
||||
videoItem.kid, videoItem.history.business),
|
||||
height: 35,
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.watch_later_outlined, size: 16),
|
||||
Icon(Icons.close_outlined, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text('稍后再看', style: TextStyle(fontSize: 13))
|
||||
Text('删除记录', style: TextStyle(fontSize: 13))
|
||||
],
|
||||
),
|
||||
),
|
||||
PopupMenuItem<String>(
|
||||
onTap: () => ctr!.delHistory(
|
||||
videoItem.kid, videoItem.history.business),
|
||||
value: 'pause',
|
||||
height: 35,
|
||||
child: const Row(
|
||||
children: [
|
||||
Icon(Icons.close_outlined, size: 16),
|
||||
SizedBox(width: 6),
|
||||
Text('删除记录', style: TextStyle(fontSize: 13))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -410,15 +428,3 @@ class HistoryItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Clipper extends CustomClipper<Rect> {
|
||||
@override
|
||||
Rect getClip(Size size) {
|
||||
return Rect.fromLTWH(0, 8, size.width, size.height - 8);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldReclip(CustomClipper<Rect> oldClipper) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +105,12 @@ class HomeController extends GetxController with GetTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
void querySearchDefault() async {
|
||||
var res = await Request().get(Api.searchDefault);
|
||||
if (res.data['code'] == 0) {
|
||||
defaultSearch.value = res.data['data']['name'];
|
||||
}
|
||||
try {
|
||||
var res = await Request().get(Api.searchDefault);
|
||||
if (res.data['code'] == 0) {
|
||||
defaultSearch.value = res.data['data']['name'];
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
showUserInfoDialog(context) {
|
||||
|
||||
@@ -47,6 +47,7 @@ class _HomePageState extends State<HomePage>
|
||||
if (_homeController.tabs.length > 1) ...[
|
||||
const SizedBox(height: 4),
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: SizedBox(
|
||||
height: 42,
|
||||
child: TabBar(
|
||||
|
||||
@@ -70,7 +70,7 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Image.network(width: 35, height: 35, iconUrl),
|
||||
const SizedBox(height: 2),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(fontSize: 12),
|
||||
|
||||
@@ -55,7 +55,7 @@ class HtmlRenderController extends ReplyController {
|
||||
oid: oid.value,
|
||||
cursor: CursorReq(
|
||||
next: cursor?.next ?? $fixnum.Int64(0),
|
||||
mode: mode,
|
||||
mode: mode.value,
|
||||
),
|
||||
banWordForReply: banWordForReply,
|
||||
)
|
||||
|
||||
@@ -230,9 +230,9 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
tooltip: '用内置浏览器打开',
|
||||
tooltip: '浏览器打开',
|
||||
onPressed: () {
|
||||
Utils.handleWebview(url.startsWith('http') ? url : 'https:$url');
|
||||
Utils.inAppWebview(url.startsWith('http') ? url : 'https:$url');
|
||||
},
|
||||
icon: const Icon(Icons.open_in_browser_outlined, size: 19),
|
||||
),
|
||||
@@ -253,9 +253,9 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
||||
),
|
||||
),
|
||||
PopupMenuItem(
|
||||
onTap: () => {
|
||||
Utils.handleWebview(
|
||||
url.startsWith('http') ? url : 'https:$url')
|
||||
onTap: () {
|
||||
Utils.inAppWebview(
|
||||
url.startsWith('http') ? url : 'https:$url');
|
||||
},
|
||||
child: const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -564,14 +564,17 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
||||
);
|
||||
|
||||
Widget get _buildContent => SliverPadding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
sliver: Obx(
|
||||
() => _htmlRenderCtr.loaded.value
|
||||
? _htmlRenderCtr.response['isJsonContent'] == true
|
||||
? articleContent(
|
||||
context: context,
|
||||
list: _htmlRenderCtr.response['content'],
|
||||
callback: _getImageCallback,
|
||||
? SliverLayoutBuilder(
|
||||
builder: (context, constraints) => articleContent(
|
||||
context: context,
|
||||
list: _htmlRenderCtr.response['content'],
|
||||
callback: _getImageCallback,
|
||||
maxWidth: constraints.crossAxisExtent,
|
||||
),
|
||||
)
|
||||
: SliverToBoxAdapter(
|
||||
child: LayoutBuilder(
|
||||
@@ -583,9 +586,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
|
||||
),
|
||||
),
|
||||
)
|
||||
: SliverToBoxAdapter(
|
||||
child: const SizedBox(),
|
||||
),
|
||||
: SliverToBoxAdapter(child: const SizedBox()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/pages/live/controller.dart';
|
||||
import 'package:PiliPlus/pages/live/widgets/live_item.dart';
|
||||
import 'package:PiliPlus/pages/live/widgets/live_item_follow.dart';
|
||||
import 'package:PiliPlus/utils/feed_back.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
@@ -226,7 +225,7 @@ class _LivePageState extends State<LivePage>
|
||||
);
|
||||
},
|
||||
onLongPress: () {
|
||||
feedBack();
|
||||
Feedback.forLongPress(context);
|
||||
Get.toNamed(
|
||||
'/member?mid=${loadingState.response[index].uid}',
|
||||
arguments: {
|
||||
|
||||
@@ -298,7 +298,7 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
||||
IconButton(
|
||||
tooltip: '浏览器打开',
|
||||
onPressed: () {
|
||||
Utils.handleWebview(
|
||||
Utils.inAppWebview(
|
||||
'https://live.bilibili.com/h5/${_liveRoomController.roomId}',
|
||||
off: true,
|
||||
);
|
||||
@@ -313,28 +313,30 @@ class _LiveRoomPageState extends State<LiveRoomPage>
|
||||
},
|
||||
),
|
||||
),
|
||||
PopScope(
|
||||
canPop: plPlayerController.isFullScreen.value != true,
|
||||
onPopInvokedWithResult: (bool didPop, Object? result) {
|
||||
if (plPlayerController.isFullScreen.value == true) {
|
||||
plPlayerController.triggerFullScreen(status: false);
|
||||
// if (MediaQuery.of(context).orientation ==
|
||||
// Orientation.landscape) {
|
||||
// verticalScreenForTwoSeconds();
|
||||
// }
|
||||
}
|
||||
},
|
||||
child: Listener(
|
||||
onPointerDown: (_) {
|
||||
_node.unfocus();
|
||||
Obx(
|
||||
() => PopScope(
|
||||
canPop: plPlayerController.isFullScreen.value != true,
|
||||
onPopInvokedWithResult: (bool didPop, Object? result) {
|
||||
if (plPlayerController.isFullScreen.value == true) {
|
||||
plPlayerController.triggerFullScreen(status: false);
|
||||
// if (MediaQuery.of(context).orientation ==
|
||||
// Orientation.landscape) {
|
||||
// verticalScreenForTwoSeconds();
|
||||
// }
|
||||
}
|
||||
},
|
||||
child: SizedBox(
|
||||
width: Get.size.width,
|
||||
height: MediaQuery.of(context).orientation ==
|
||||
Orientation.landscape
|
||||
? Get.size.height
|
||||
: Get.size.width * 9 / 16,
|
||||
child: videoPlayerPanel,
|
||||
child: Listener(
|
||||
onPointerDown: (_) {
|
||||
_node.unfocus();
|
||||
},
|
||||
child: SizedBox(
|
||||
width: Get.size.width,
|
||||
height: MediaQuery.of(context).orientation ==
|
||||
Orientation.landscape
|
||||
? Get.size.height
|
||||
: Get.size.width * 9 / 16,
|
||||
child: videoPlayerPanel,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -83,7 +83,6 @@ class _BottomControlState extends State<BottomControl> {
|
||||
margin: const EdgeInsets.symmetric(horizontal: 10),
|
||||
alignment: Alignment.center,
|
||||
child: PopupMenuButton<BoxFit>(
|
||||
onSelected: widget.controller.toggleVideoFit,
|
||||
initialValue: widget.controller.videoFit.value,
|
||||
color: Colors.black.withOpacity(0.8),
|
||||
itemBuilder: (BuildContext context) {
|
||||
@@ -92,6 +91,9 @@ class _BottomControlState extends State<BottomControl> {
|
||||
height: 35,
|
||||
padding: const EdgeInsets.only(left: 30),
|
||||
value: boxFit,
|
||||
onTap: () {
|
||||
widget.controller.toggleVideoFit(boxFit);
|
||||
},
|
||||
child: Text(
|
||||
"${PlPlayerController.videoFitType[boxFit.index]['desc']}",
|
||||
style:
|
||||
@@ -142,9 +144,6 @@ class _BottomControlState extends State<BottomControl> {
|
||||
child: PopupMenuButton<int>(
|
||||
padding: EdgeInsets.zero,
|
||||
initialValue: widget.liveRoomCtr.currentQn,
|
||||
onSelected: (value) {
|
||||
widget.liveRoomCtr.changeQn(value);
|
||||
},
|
||||
child: Text(
|
||||
widget.liveRoomCtr.currentQnDesc.value,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 13),
|
||||
@@ -153,6 +152,9 @@ class _BottomControlState extends State<BottomControl> {
|
||||
return widget.liveRoomCtr.acceptQnList.map((e) {
|
||||
return PopupMenuItem<int>(
|
||||
value: e['code'],
|
||||
onTap: () {
|
||||
widget.liveRoomCtr.changeQn(e['code']);
|
||||
},
|
||||
child: Text(e['desc']),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
@@ -124,37 +124,41 @@ class MainController extends GetxController {
|
||||
}
|
||||
|
||||
Future _queryPMUnread() async {
|
||||
dynamic res = await Request().get(Api.msgUnread);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': ((res.data['data']?['unfollow_unread'] as int?) ?? 0) +
|
||||
((res.data['data']?['follow_unread'] as int?) ?? 0),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
try {
|
||||
dynamic res = await Request().get(Api.msgUnread);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': ((res.data['data']?['unfollow_unread'] as int?) ?? 0) +
|
||||
((res.data['data']?['follow_unread'] as int?) ?? 0),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future _queryMsgFeedUnread() async {
|
||||
if (isLogin.value.not) {
|
||||
return;
|
||||
}
|
||||
dynamic res = await Request().get(Api.msgFeedUnread);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
try {
|
||||
dynamic res = await Request().get(Api.msgFeedUnread);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data'],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
void getUnreadDynamic() async {
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:PiliPlus/common/widgets/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/widgets/tabs.dart';
|
||||
import 'package:PiliPlus/grpc/grpc_client.dart';
|
||||
import 'package:PiliPlus/pages/mine/controller.dart';
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -64,12 +65,19 @@ class _MainAppState extends State<MainApp>
|
||||
|
||||
@override
|
||||
void didPopNext() {
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_mainController.checkUnreadDynamic();
|
||||
_checkDefaultSearch(true);
|
||||
_checkUnread(context.orientation == Orientation.portrait);
|
||||
super.didPopNext();
|
||||
}
|
||||
|
||||
@override
|
||||
void didPushNext() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
super.didPushNext();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
@@ -156,6 +164,7 @@ class _MainAppState extends State<MainApp>
|
||||
await GrpcClient.instance.shutdown();
|
||||
await GStorage.close();
|
||||
EventBus().off(EventName.loginEvent);
|
||||
PiliScheme.listener?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ class _MemberArticleState extends State<MemberArticle>
|
||||
return ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
PiliScheme.routePush(Uri.parse(item.uri ?? ''));
|
||||
PiliScheme.routePushFromUrl(item.uri ?? '');
|
||||
},
|
||||
leading: item.originImageUrls?.isNotEmpty == true
|
||||
? Container(
|
||||
|
||||
@@ -142,7 +142,7 @@ class _MemberFavoriteState extends State<MemberFavorite>
|
||||
});
|
||||
}
|
||||
} else if (item1.type == 21) {
|
||||
PiliScheme.routePush(Uri.parse(item1.link ?? ''));
|
||||
PiliScheme.routePushFromUrl(item1.link ?? '');
|
||||
} else if (item1.type == 11) {
|
||||
Get.toNamed(
|
||||
'/subDetail',
|
||||
|
||||
@@ -118,9 +118,9 @@ class _MemberHomeState extends State<MemberHome>
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
onTap: () {
|
||||
PiliScheme.routePush(Uri.parse(
|
||||
loadingState.response.article.item.first.uri ??
|
||||
''));
|
||||
PiliScheme.routePushFromUrl(
|
||||
loadingState.response.article.item.first.uri ?? '',
|
||||
);
|
||||
},
|
||||
leading: loadingState.response.article.item.first
|
||||
.originImageUrls?.isNotEmpty ==
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/member.dart';
|
||||
import 'package:PiliPlus/http/video.dart';
|
||||
@@ -46,6 +48,14 @@ class MemberControllerNew extends CommonController
|
||||
int? silence;
|
||||
String? endTime;
|
||||
|
||||
late final implTabs = const [
|
||||
'home',
|
||||
'dynamic',
|
||||
'contribute',
|
||||
'favorite',
|
||||
'bangumi',
|
||||
];
|
||||
|
||||
@override
|
||||
bool customHandleResponse(Success response) {
|
||||
Data data = response.response;
|
||||
@@ -68,7 +78,8 @@ class MemberControllerNew extends CommonController
|
||||
':至 ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(data.card!.endTime! * 1000))}';
|
||||
}
|
||||
}
|
||||
if (tab2 != null && tab2!.isNotEmpty) {
|
||||
tab2?.retainWhere((item) => implTabs.contains(item.param));
|
||||
if (tab2?.isNotEmpty == true) {
|
||||
if (!data.tab!.toJson().values.contains(true) &&
|
||||
tab2!.first.param == 'home') {
|
||||
// remove empty home tab
|
||||
@@ -93,8 +104,8 @@ class MemberControllerNew extends CommonController
|
||||
tabs = tab2!.map((item) => Tab(text: item.title ?? '')).toList();
|
||||
tabController = TabController(
|
||||
vsync: this,
|
||||
length: tab2!.length,
|
||||
initialIndex: initialIndex == -1 ? 0 : initialIndex,
|
||||
length: tabs.length,
|
||||
initialIndex: max(0, initialIndex),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,14 +134,17 @@ class _MemberPageNewState extends State<MemberPageNew>
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _buildTab => TabBar(
|
||||
controller: _userController.tabController,
|
||||
tabs: _userController.tabs,
|
||||
onTap: (value) {
|
||||
if (_userController.tabController?.indexIsChanging == false) {
|
||||
_key.currentState?.outerController.animToTop();
|
||||
}
|
||||
},
|
||||
Widget get _buildTab => Material(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: TabBar(
|
||||
controller: _userController.tabController,
|
||||
tabs: _userController.tabs,
|
||||
onTap: (value) {
|
||||
if (_userController.tabController?.indexIsChanging == false) {
|
||||
_key.currentState?.outerController.animToTop();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Widget get _buildBody => SafeArea(
|
||||
@@ -198,9 +201,7 @@ class _MemberPageNewState extends State<MemberPageNew>
|
||||
bottom: needTab && (_userController.tab2?.length ?? -1) > 1
|
||||
? PreferredSize(
|
||||
preferredSize: Size.fromHeight(48),
|
||||
child: Material(
|
||||
child: _buildTab,
|
||||
),
|
||||
child: _buildTab,
|
||||
)
|
||||
: null,
|
||||
actions: [
|
||||
|
||||
@@ -55,7 +55,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
_getInfo() async {
|
||||
Map<String, String> data = {
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'',
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
@@ -329,7 +330,8 @@ class _EditProfilePageState extends State<EditProfilePage> {
|
||||
}) async {
|
||||
Map<String, String> data = {
|
||||
'access_key': GStorage.localCache
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'],
|
||||
.get(LocalCacheKey.accessKey, defaultValue: {})['value'] ??
|
||||
'',
|
||||
'appkey': Constants.appKey,
|
||||
'build': '1462100',
|
||||
'c_locale': 'zh_CN',
|
||||
|
||||
@@ -79,11 +79,11 @@ class UserInfoCard extends StatelessWidget {
|
||||
: images.nightImgurl?.http2https)
|
||||
: images.imgUrl?.http2https;
|
||||
return Hero(
|
||||
tag: imgUrl ?? 'bgTag',
|
||||
tag: imgUrl ?? '',
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.imageView(
|
||||
imgList: [SourceModel(url: imgUrl ?? 'bgTag')],
|
||||
imgList: [SourceModel(url: imgUrl ?? '')],
|
||||
);
|
||||
},
|
||||
child: CachedNetworkImage(
|
||||
@@ -447,22 +447,22 @@ class UserInfoCard extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
|
||||
_buildAvatar(BuildContext context) => Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 2.5,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Hero(
|
||||
tag: card.face ?? 'avatarTag',
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.imageView(
|
||||
imgList: [SourceModel(url: card.face ?? 'avatarTag')],
|
||||
);
|
||||
},
|
||||
_buildAvatar(BuildContext context) => Hero(
|
||||
tag: card.face ?? '',
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.imageView(
|
||||
imgList: [SourceModel(url: card.face ?? '')],
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 2.5,
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: NetworkImgLayer(
|
||||
src: card.face,
|
||||
type: 'avatar',
|
||||
|
||||
@@ -127,7 +127,7 @@ class _MinePageState extends State<MinePage> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _mineController.onLogin,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
||||
@@ -73,7 +73,7 @@ class _AtMePageState extends State<AtMePage> {
|
||||
String? nativeUri =
|
||||
_atMeController.msgFeedAtMeList[i].item?.nativeUri;
|
||||
if (nativeUri != null) {
|
||||
PiliScheme.routePush(Uri.parse(nativeUri));
|
||||
PiliScheme.routePushFromUrl(nativeUri);
|
||||
}
|
||||
// SmartDialog.showToast("跳转至:$nativeUri(暂未实现)");
|
||||
},
|
||||
|
||||
@@ -122,7 +122,7 @@ class LikeMeList extends StatelessWidget {
|
||||
onTap: () {
|
||||
String? nativeUri = msgFeedLikeMeList[i].item?.nativeUri;
|
||||
if (nativeUri != null) {
|
||||
PiliScheme.routePush(Uri.parse(nativeUri));
|
||||
PiliScheme.routePushFromUrl(nativeUri);
|
||||
}
|
||||
// SmartDialog.showToast("跳转至:$nativeUri(暂未实现)");
|
||||
},
|
||||
|
||||
@@ -72,7 +72,7 @@ class _ReplyMePageState extends State<ReplyMePage> {
|
||||
String? nativeUri = _replyMeController
|
||||
.msgFeedReplyMeList[i].item?.nativeUri;
|
||||
if (nativeUri != null) {
|
||||
PiliScheme.routePush(Uri.parse(nativeUri));
|
||||
PiliScheme.routePushFromUrl(nativeUri);
|
||||
}
|
||||
// SmartDialog.showToast("跳转至:$nativeUri(暂未实现)");
|
||||
},
|
||||
|
||||
@@ -184,8 +184,7 @@ class _SysMsgPageState extends State<SysMsgPage> {
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
try {
|
||||
Uri uri = Uri.parse(match[2]!.replaceAll('"', ''));
|
||||
PiliScheme.routePush(uri);
|
||||
PiliScheme.routePushFromUrl(match[2]!.replaceAll('"', ''));
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
@@ -209,8 +208,7 @@ class _SysMsgPageState extends State<SysMsgPage> {
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
try {
|
||||
Uri uri = Uri.parse(match[3]!);
|
||||
PiliScheme.routePush(uri);
|
||||
PiliScheme.routePushFromUrl(match[3]!);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
}
|
||||
@@ -231,8 +229,7 @@ class _SysMsgPageState extends State<SysMsgPage> {
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
try {
|
||||
Uri uri = Uri.parse(match[0]!);
|
||||
PiliScheme.routePush(uri);
|
||||
PiliScheme.routePushFromUrl(match[0]!);
|
||||
} catch (err) {
|
||||
SmartDialog.showToast(err.toString());
|
||||
Utils.copyText(match[0] ?? '');
|
||||
|
||||
@@ -23,6 +23,7 @@ class HotKeyword extends StatelessWidget {
|
||||
SizedBox(
|
||||
width: width! / 2 - 4,
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(3),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: InkWell(
|
||||
|
||||
@@ -31,9 +31,11 @@ class SearchText extends StatelessWidget {
|
||||
onTap: () {
|
||||
onTap?.call(text);
|
||||
},
|
||||
onLongPress: () {
|
||||
onLongPress?.call(text);
|
||||
},
|
||||
onLongPress: onLongPress != null
|
||||
? () {
|
||||
onLongPress!(text);
|
||||
}
|
||||
: null,
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: Padding(
|
||||
padding: padding ??
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/pages/common/common_controller.dart';
|
||||
import 'package:PiliPlus/pages/search_result/controller.dart';
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:PiliPlus/http/search.dart';
|
||||
import 'package:PiliPlus/models/common/search_type.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
class SearchPanelController extends CommonController {
|
||||
SearchPanelController({
|
||||
this.keyword,
|
||||
required this.keyword,
|
||||
required this.searchType,
|
||||
required this.tag,
|
||||
});
|
||||
String? keyword;
|
||||
String keyword;
|
||||
SearchType searchType;
|
||||
// 结果排序方式 搜索类型为视频、专栏及相簿时
|
||||
RxString order = ''.obs;
|
||||
@@ -26,10 +25,14 @@ class SearchPanelController extends CommonController {
|
||||
String tag;
|
||||
int? pubBegin;
|
||||
int? pubEnd;
|
||||
bool? hasJump2Video;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
if (searchType == SearchType.video) {
|
||||
jump2Video();
|
||||
}
|
||||
queryData();
|
||||
}
|
||||
|
||||
@@ -46,7 +49,10 @@ class SearchPanelController extends CommonController {
|
||||
?.insertAll(0, (loadingState.value as Success).response);
|
||||
}
|
||||
loadingState.value = LoadingState.success(response.response.list);
|
||||
if (currentPage == 1) {
|
||||
if (searchType == SearchType.video &&
|
||||
hasJump2Video != true &&
|
||||
currentPage == 1) {
|
||||
hasJump2Video = true;
|
||||
onPushDetail(response.response.list);
|
||||
}
|
||||
} else {
|
||||
@@ -58,48 +64,32 @@ class SearchPanelController extends CommonController {
|
||||
return true;
|
||||
}
|
||||
|
||||
void jump2Video() {
|
||||
if (RegExp(r'^av\d+$', caseSensitive: false).hasMatch(keyword)) {
|
||||
hasJump2Video = true;
|
||||
PiliScheme.videoPush(
|
||||
int.parse(keyword.substring(2)),
|
||||
null,
|
||||
showDialog: false,
|
||||
);
|
||||
} else if (RegExp(r'^bv[a-z\d]{10}$', caseSensitive: false)
|
||||
.hasMatch(keyword)) {
|
||||
hasJump2Video = true;
|
||||
PiliScheme.videoPush(null, keyword, showDialog: false);
|
||||
}
|
||||
}
|
||||
|
||||
void onPushDetail(resultList) async {
|
||||
// 匹配输入内容,如果是AV、BV号且有结果 直接跳转详情页
|
||||
Map matchRes = IdUtils.matchAvorBv(input: keyword);
|
||||
List matchKeys = matchRes.keys.toList();
|
||||
String? bvid;
|
||||
try {
|
||||
bvid = resultList.first.bvid;
|
||||
} catch (_) {
|
||||
bvid = null;
|
||||
}
|
||||
// keyword 可能输入纯数字
|
||||
int? aid;
|
||||
try {
|
||||
aid = resultList.first.aid;
|
||||
} catch (_) {
|
||||
aid = null;
|
||||
}
|
||||
if (matchKeys.isNotEmpty && searchType == SearchType.video ||
|
||||
aid.toString() == keyword) {
|
||||
int cid = await SearchHttp.ab2c(aid: aid, bvid: bvid);
|
||||
if (matchKeys.isNotEmpty &&
|
||||
matchKeys.first == 'BV' &&
|
||||
matchRes[matchKeys.first] == bvid ||
|
||||
matchKeys.isNotEmpty &&
|
||||
matchKeys.first == 'AV' &&
|
||||
matchRes[matchKeys.first] == aid ||
|
||||
aid.toString() == keyword) {
|
||||
Get.toNamed(
|
||||
'/video?bvid=$bvid&cid=$cid',
|
||||
arguments: {
|
||||
'videoItem': resultList.first,
|
||||
'heroTag': Utils.makeHeroTag(bvid),
|
||||
},
|
||||
);
|
||||
}
|
||||
int? aid = int.tryParse(keyword);
|
||||
if (aid != null && resultList.first.aid == aid) {
|
||||
PiliScheme.videoPush(aid, null, showDialog: false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LoadingState> customGetData() => SearchHttp.searchByType(
|
||||
searchType: searchType,
|
||||
keyword: keyword!,
|
||||
keyword: keyword,
|
||||
page: currentPage,
|
||||
order: order.value,
|
||||
duration: searchType.name != 'video' ? null : duration.value,
|
||||
|
||||
@@ -16,7 +16,7 @@ import 'widgets/media_bangumi_panel.dart';
|
||||
import 'widgets/user_panel.dart';
|
||||
|
||||
class SearchPanel extends StatefulWidget {
|
||||
final String? keyword;
|
||||
final String keyword;
|
||||
final SearchType searchType;
|
||||
final String tag;
|
||||
const SearchPanel({
|
||||
@@ -46,7 +46,7 @@ class _SearchPanelState extends State<SearchPanel>
|
||||
searchType: widget.searchType,
|
||||
tag: widget.tag,
|
||||
),
|
||||
tag: widget.searchType.name + widget.keyword!,
|
||||
tag: widget.searchType.name + widget.keyword,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -257,6 +257,7 @@ class ArticlePanelController extends GetxController {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (context) => SingleChildScrollView(
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
@@ -287,13 +288,12 @@ class ArticlePanelController extends GetxController {
|
||||
SmartDialog.showToast("「${item['label']}」的筛选结果");
|
||||
SearchPanelController ctr =
|
||||
Get.find<SearchPanelController>(
|
||||
tag: 'article${searchPanelCtr.keyword!}');
|
||||
tag: 'article${searchPanelCtr.keyword}');
|
||||
ctr.order.value = item['order'];
|
||||
SmartDialog.showLoading(msg: 'loading');
|
||||
await ctr.onRefresh();
|
||||
SmartDialog.dismiss();
|
||||
},
|
||||
onLongPress: (_) {},
|
||||
bgColor: item['value'] == currentOrderFilterval.value
|
||||
? Theme.of(context).colorScheme.secondaryContainer
|
||||
: null,
|
||||
@@ -321,13 +321,12 @@ class ArticlePanelController extends GetxController {
|
||||
SmartDialog.showToast("「${item['label']}」的筛选结果");
|
||||
SearchPanelController ctr =
|
||||
Get.find<SearchPanelController>(
|
||||
tag: 'article${searchPanelCtr.keyword!}');
|
||||
tag: 'article${searchPanelCtr.keyword}');
|
||||
ctr.categoryId = item['categoryId'];
|
||||
SmartDialog.showLoading(msg: 'loading');
|
||||
await ctr.onRefresh();
|
||||
SmartDialog.dismiss();
|
||||
},
|
||||
onLongPress: (_) {},
|
||||
bgColor: item['value'] == currentZoneFilterval.value
|
||||
? Theme.of(context).colorScheme.secondaryContainer
|
||||
: null,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user