Compare commits

..

38 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
edb5ea7a7a opt search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-07 14:48:43 +08:00
bggRGjQaUbCoE
b4c1568869 tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-07 13:49:12 +08:00
bggRGjQaUbCoE
83e25ec0bf opt sort search
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-07 12:58:25 +08:00
bggRGjQaUbCoE
6d55321699 feat: member cheese
feat: fav pugv

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-07 12:58:19 +08:00
bggRGjQaUbCoE
26a5b7b7a7 opt sync history status
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-06 21:36:11 +08:00
bggRGjQaUbCoE
f663301eae opt history account
Closes #948

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-06 21:25:50 +08:00
bggRGjQaUbCoE
eb9f3cd21c update part media item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-06 20:15:29 +08:00
bggRGjQaUbCoE
05119edacb opt live room
Closes #947

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-06 20:15:04 +08:00
bggRGjQaUbCoE
554e96c820 opt later item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-06 18:26:18 +08:00
bggRGjQaUbCoE
40a19f2766 opt pgc/pugv intro panel
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-06 17:47:53 +08:00
bggRGjQaUbCoE
b723529d7f tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-06 14:29:57 +08:00
My-Responsitories
9f33488248 revert: toSet (#941) 2025-08-05 21:36:21 +08:00
bggRGjQaUbCoE
80a4c8c24d opt change episode
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-05 21:30:52 +08:00
My-Responsitories
170b2aa6d9 opt: GroupPanel (#940)
* opt: GroupPanel

* mod: int? operator
2025-08-05 12:52:11 +00:00
bggRGjQaUbCoE
e2639b6951 opt del later view
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-05 19:08:27 +08:00
bggRGjQaUbCoE
b954c6f893 opt del later view
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-05 18:47:34 +08:00
bggRGjQaUbCoE
104d295389 opt multi del
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-05 18:15:06 +08:00
bggRGjQaUbCoE
3caa684b2e fix del history/later
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-05 16:30:48 +08:00
bggRGjQaUbCoE
af7a1a6ee9 opt context ext
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-05 15:58:48 +08:00
My-Responsitories
add519120c mod: hasLater (#938) 2025-08-05 05:45:49 +00:00
My-Responsitories
01552801f2 opt: select (#937) 2025-08-05 05:41:37 +00:00
bggRGjQaUbCoE
afb09e8a0a opt player
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-05 11:57:46 +08:00
bggRGjQaUbCoE
deb48d1ada opt to live room
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-04 18:58:48 +08:00
bggRGjQaUbCoE
cf84a92808 refa video params
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-04 17:38:10 +08:00
bggRGjQaUbCoE
26ccb92b44 Update README.md
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-04 15:02:03 +08:00
bggRGjQaUbCoE
3fa697a037 remove appbar anim
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-04 14:27:06 +08:00
qpst4
f72c13df62 Fix intent-filter for bilibili://search deep link (#934)
Fix intent-filter for bilibili://search deep link

* update

---------

Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-04 14:07:32 +08:00
My-Responsitories
7b51f15753 opt: multiSelect (#935) 2025-08-04 04:57:37 +00:00
bggRGjQaUbCoE
d246462535 fix heartbeat
Closes #929

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-04 12:23:29 +08:00
bggRGjQaUbCoE
3208661a52 opt pugv
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-04 11:44:17 +08:00
bggRGjQaUbCoE
2e614fa03c upgrade deps
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-03 19:03:19 +08:00
bggRGjQaUbCoE
b7f70ee0b3 tweak
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-03 19:03:13 +08:00
bggRGjQaUbCoE
cb52840bad Release 1.1.4
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-03 15:39:48 +08:00
dom
bd3d6cf34c feat: pugv (#927)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-03 15:25:29 +08:00
bggRGjQaUbCoE
cf835e330b opt change episode
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-03 10:42:26 +08:00
bggRGjQaUbCoE
14fd660ce2 opt history pause tip
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-03 09:47:50 +08:00
bggRGjQaUbCoE
0a8282d3e3 opt save pgc reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-02 22:50:35 +08:00
bggRGjQaUbCoE
574e432e09 fix get live second list
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-08-02 22:03:09 +08:00
212 changed files with 4812 additions and 3941 deletions

View File

@@ -47,6 +47,7 @@
## feat
- [x] 播放课堂视频
- [x] 发起投票
- [x] 发布动态/评论支持`富文本编辑`/`表情显示`/`@用户`
- [x] 修改消息设置

View File

@@ -111,9 +111,10 @@
android:pathPattern="/readlist" />
<data android:host="advertise" android:path="/home" />
<data android:host="clip" />
<data android:host="search" />
<data android:host="search" android:pathPattern=".*" />
<data android:host="stardust-search" />
<data android:host="music" />
<data android:host="cheese" />
<data android:host="bangumi"
android:pathPattern="/season.*" />
<data android:host="bangumi" android:pathPattern="/.*" />
@@ -145,7 +146,6 @@
<data android:host="video" />
<data android:host="story" />
<data android:host="podcast" />
<data android:host="search" />
<data android:host="main" android:path="/favorite" />
<data android:host="pgc" android:path="/theater/match" />
<data android:host="pgc" android:path="/theater/square" />
@@ -160,7 +160,6 @@
<data android:host="history" />
<data android:host="charge" android:path="/rank" />
<data android:host="assistant" />
<data android:host="assistant" />
<data android:host="feedback" />
<data android:host="auth" android:path="/launch" />
</intent-filter>

View File

@@ -0,0 +1,59 @@
import 'package:PiliPlus/pages/common/multi_select/base.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MultiSelectAppBarWidget extends StatelessWidget
implements PreferredSizeWidget {
final MultiSelectBase ctr;
final bool? visible;
final AppBar child;
final List<Widget>? children;
const MultiSelectAppBarWidget({
super.key,
required this.ctr,
this.visible,
this.children,
required this.child,
});
@override
Widget build(BuildContext context) {
if (visible ?? ctr.enableMultiSelect.value) {
return AppBar(
bottom: child.bottom,
leading: IconButton(
tooltip: '取消',
onPressed: ctr.handleSelect,
icon: const Icon(Icons.close_outlined),
),
title: Obx(() => Text('已选: ${ctr.checkedCount}')),
actions: [
TextButton(
style: TextButton.styleFrom(
visualDensity: VisualDensity.compact,
),
onPressed: () => ctr.handleSelect(true),
child: const Text('全选'),
),
...?children,
TextButton(
style: TextButton.styleFrom(
visualDensity: VisualDensity.compact,
),
onPressed: ctr.onRemove,
child: Text(
'移除',
style: TextStyle(color: Get.theme.colorScheme.error),
),
),
const SizedBox(width: 6),
],
);
}
return child;
}
@override
Size get preferredSize => child.preferredSize;
}

View File

@@ -7,6 +7,7 @@ import 'package:PiliPlus/common/widgets/image/nine_grid_view.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:flutter/material.dart';
@@ -98,12 +99,12 @@ Widget imageView(
};
}
void onTap(BuildContext context, int index) {
void onTap(int index) {
if (callback != null) {
callback(picArr.map((item) => item.url).toList(), index);
} else {
onViewImage?.call();
context.imageView(
PageUtils.imageView(
initialPage: index,
imgList: picArr.map(
(item) {
@@ -138,7 +139,7 @@ Widget imageView(
return Hero(
tag: item.url,
child: GestureDetector(
onTap: () => onTap(context, index),
onTap: () => onTap(index),
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,

View File

@@ -48,7 +48,7 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
this.minScale = 1.0,
this.onPageChanged,
this.onDismissed,
this.setStatusBar,
this.setStatusBar = true,
this.onClose,
required this.quality,
});
@@ -57,7 +57,7 @@ class InteractiveviewerGallery<T> extends StatefulWidget {
final ValueChanged<bool>? onClose;
final bool? setStatusBar;
final bool setStatusBar;
/// The sources to show.
final List<SourceModel> sources;
@@ -114,7 +114,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
duration: const Duration(milliseconds: 300),
)..addListener(listener);
if (widget.setStatusBar != false) {
if (widget.setStatusBar) {
setStatusBar();
}
@@ -148,7 +148,7 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
_animationController
..removeListener(listener)
..dispose();
if (widget.setStatusBar != false) {
if (widget.setStatusBar) {
if (Platform.isIOS || Platform.isAndroid) {
SystemChrome.setEnabledSystemUIMode(
mode ?? SystemUiMode.edgeToEdge,

View File

@@ -3,10 +3,10 @@ import 'package:PiliPlus/models/common/avatar_badge_type.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/image_util.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class PendantAvatar extends StatelessWidget {
final BadgeType _badgeType;
@@ -14,7 +14,7 @@ class PendantAvatar extends StatelessWidget {
final double size;
final double badgeSize;
final String? garbPendantImage;
final dynamic roomId;
final int? roomId;
final VoidCallback? onTap;
const PendantAvatar({
@@ -71,7 +71,7 @@ class PendantAvatar extends StatelessWidget {
Positioned(
bottom: 0,
child: InkWell(
onTap: () => Get.toNamed('/liveRoom?roomid=$roomId'),
onTap: () => PageUtils.toLiveRoom(roomId),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
decoration: BoxDecoration(

View File

@@ -0,0 +1,34 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:flutter/material.dart';
Widget selectMask(ThemeData theme, bool checked) {
return AnimatedOpacity(
opacity: checked ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: StyleString.mdRadius,
color: Colors.black.withValues(alpha: 0.6),
),
child: AnimatedScale(
scale: checked ? 1 : 0,
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
child: Container(
width: 34,
height: 34,
decoration: BoxDecoration(
color: theme.colorScheme.surface.withValues(alpha: 0.8),
shape: BoxShape.circle,
),
child: Icon(
Icons.done_all_outlined,
color: theme.colorScheme.primary,
semanticLabel: '取消选择',
),
),
),
),
);
}

View File

@@ -67,7 +67,7 @@ class _SelfSizedHorizontalListState extends State<SelfSizedHorizontalList> {
padding: widget.padding,
scrollDirection: Axis.horizontal,
itemCount: widget.itemCount,
itemBuilder: (c, i) => widget.childBuilder.call(i),
itemBuilder: (c, i) => widget.childBuilder(i),
separatorBuilder: (c, i) => SizedBox(width: widget.gapSize),
),
);

View File

@@ -23,6 +23,7 @@ class StatWidget extends StatelessWidget {
Theme.of(context).colorScheme.outline.withValues(alpha: 0.8);
return Row(
spacing: 2,
mainAxisSize: MainAxisSize.min,
children: [
Icon(
type.iconData,

View File

@@ -14,10 +14,8 @@ import 'package:PiliPlus/models/search/result.dart';
import 'package:PiliPlus/utils/date_util.dart';
import 'package:PiliPlus/utils/duration_util.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
// 视频卡片 - 水平布局
class VideoCardH extends StatelessWidget {
@@ -43,6 +41,11 @@ class VideoCardH extends StatelessWidget {
var typeOrNull = item.type;
if (typeOrNull?.isNotEmpty == true) {
type = typeOrNull!;
if (type == 'ketang') {
badge = '课堂';
} else if (type == 'live_room') {
badge = '直播';
}
}
if (item.isUnionVideo == 1) {
badge = '合作';
@@ -71,13 +74,13 @@ class VideoCardH extends StatelessWidget {
onTap ??
() async {
if (type == 'ketang') {
SmartDialog.showToast('课堂视频暂不支持播放');
PageUtils.viewPugv(seasonId: videoItem.aid);
return;
} else if (type == 'live_room') {
if (videoItem case SearchVideoItemModel item) {
int? roomId = item.id;
if (roomId != null) {
Get.toNamed('/liveRoom?roomid=$roomId');
PageUtils.toLiveRoom(roomId);
}
} else {
SmartDialog.showToast(
@@ -92,6 +95,7 @@ class VideoCardH extends StatelessWidget {
return;
}
}
try {
final int? cid =
videoItem.cid ??
@@ -101,11 +105,10 @@ class VideoCardH extends StatelessWidget {
);
if (cid != null) {
PageUtils.toVideoPage(
'bvid=${videoItem.bvid}&cid=$cid',
arguments: {
'videoItem': videoItem,
'heroTag': Utils.makeHeroTag(videoItem.aid),
},
bvid: videoItem.bvid,
cid: cid,
cover: videoItem.cover,
title: videoItem.title,
);
}
} catch (err) {
@@ -173,13 +176,6 @@ class VideoCardH extends StatelessWidget {
bottom: 6.0,
type: PBadgeType.gray,
),
if (type != 'video')
PBadge(
text: type,
left: 6.0,
bottom: 6.0,
type: PBadgeType.primary,
),
],
);
},

View File

@@ -42,11 +42,11 @@ class VideoCardV extends StatelessWidget {
await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid);
if (cid != null) {
PageUtils.toVideoPage(
'bvid=$bvid&cid=$cid',
arguments: {
'heroTag': heroTag,
'videoItem': videoItem,
},
aid: videoItem.aid,
bvid: bvid,
cid: cid,
cover: videoItem.cover,
title: videoItem.title,
);
}
break;

View File

@@ -22,6 +22,8 @@ class Api {
// https://api.bilibili.com/pgc/player/web/v2/playurl?cid=104236640&bvid=BV13t411n7ex
static const String pgcUrl = '/pgc/player/web/v2/playurl';
static const String pugvUrl = '/pugv/player/web/playurl';
// 字幕
// aid, cid
static const String playInfo = '/x/player/wbi/v2';
@@ -279,6 +281,8 @@ class Api {
// 番剧/剧集明细
static const String pgcInfo = '/pgc/view/web/season';
static const String pugvInfo = '/pugv/view/web/season';
// https://api.bilibili.com/pgc/season/episode/web/info?ep_id=12345678
static const String episodeInfo = '/pgc/season/episode/web/info';
@@ -846,6 +850,12 @@ class Api {
static const String dynReserve = '/x/dynamic/feed/reserve/click';
static const String favPugv = '/pugv/app/web/favorite/page';
static const String addFavPugv = '/pugv/app/web/favorite/add';
static const String delFavPugv = '/pugv/app/web/favorite/del';
static const String favTopicList = '/x/topic/web/fav/list';
static const String addFavTopic = '/x/topic/fav/sub/add';
@@ -916,6 +926,8 @@ class Api {
static const String spaceAudio = '/audio/music-service/web/song/upper';
static const String spaceCheese = '/pugv/app/web/season/page';
static const String dynMention = '/x/polymer/web-dynamic/v1/mention/search';
static const String createVote = '/x/vote/create';

View File

@@ -10,6 +10,7 @@ import 'package:PiliPlus/models_new/fav/fav_folder/list.dart';
import 'package:PiliPlus/models_new/fav/fav_note/list.dart';
import 'package:PiliPlus/models_new/fav/fav_pgc/data.dart';
import 'package:PiliPlus/models_new/fav/fav_topic/data.dart';
import 'package:PiliPlus/models_new/space/space_cheese/data.dart';
import 'package:PiliPlus/models_new/space/space_fav/data.dart';
import 'package:PiliPlus/models_new/sub/sub_detail/data.dart';
import 'package:PiliPlus/utils/accounts.dart';
@@ -142,6 +143,58 @@ class FavHttp {
}
}
static Future<LoadingState<SpaceCheeseData>> favPugv({
required int mid,
required int page,
}) async {
var res = await Request().get(
Api.favPugv,
queryParameters: {
'mid': mid,
'ps': 20,
'pn': page,
'web_location': 333.1387,
},
);
if (res.data['code'] == 0) {
return Success(SpaceCheeseData.fromJson(res.data['data']));
} else {
return Error(res.data['message']);
}
}
static Future addFavPugv(seasonId) async {
var res = await Request().post(
Api.addFavPugv,
data: {
'season_id': seasonId,
'csrf': Accounts.main.csrf,
},
options: Options(contentType: Headers.formUrlEncodedContentType),
);
if (res.data['code'] == 0) {
return {'status': true};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
static Future delFavPugv(seasonId) async {
var res = await Request().post(
Api.delFavPugv,
data: {
'season_id': seasonId,
'csrf': Accounts.main.csrf,
},
options: Options(contentType: Headers.formUrlEncodedContentType),
);
if (res.data['code'] == 0) {
return {'status': true};
} else {
return {'status': false, 'msg': res.data['message']};
}
}
static Future<LoadingState<FavTopicData>> favTopic({
required int page,
}) async {
@@ -312,12 +365,12 @@ class FavHttp {
static Future delNote({
required bool isPublish,
required List noteIds,
required String noteIds,
}) async {
final res = await Request().post(
isPublish ? Api.delPublishNote : Api.delNote,
data: {
isPublish ? 'cvids' : 'note_ids': noteIds.join(','),
isPublish ? 'cvids' : 'note_ids': noteIds,
'csrf': Accounts.main.csrf,
},
options: Options(
@@ -375,10 +428,10 @@ class FavHttp {
}
static Future sortFavFolder({
required List<int?> sort,
required String sort,
}) async {
Map<String, dynamic> data = {
'sort': sort.join(','),
'sort': sort,
'csrf': Accounts.main.csrf,
};
AppSign.appSign(data);
@@ -398,11 +451,11 @@ class FavHttp {
static Future sortFav({
required dynamic mediaId,
required List<String> sort,
required String sort,
}) async {
Map<String, dynamic> data = {
'media_id': mediaId,
'sort': sort.join(','),
'sort': sort,
'csrf': Accounts.main.csrf,
};
AppSign.appSign(data);
@@ -442,12 +495,12 @@ class FavHttp {
}
static Future deleteFolder({
required List<dynamic> mediaIds,
required String mediaIds,
}) async {
var res = await Request().post(
Api.deleteFolder,
data: {
'media_ids': mediaIds.join(','),
'media_ids': mediaIds,
'platform': 'web',
'csrf': Accounts.main.csrf,
},
@@ -537,19 +590,20 @@ class FavHttp {
static Future<LoadingState<List<SpaceFavData>?>> spaceFav({
required int mid,
}) async {
Map<String, String> data = {
'build': '8430300',
final params = {
'build': 8430300,
'version': '8.43.0',
'c_locale': 'zh_CN',
'channel': 'bili',
'channel': 'master',
'mobi_app': 'android',
'platform': 'android',
's_locale': 'zh_CN',
'statistics': Constants.statisticsApp,
'up_mid': mid.toString(),
'up_mid': mid,
};
var res = await Request().get(
Api.spaceFav,
queryParameters: data,
queryParameters: params,
options: Options(
headers: {
'bili-http-engine': 'cronet',
@@ -636,13 +690,13 @@ class FavHttp {
}
}
static Future copyOrMoveFav({
static Future<LoadingState> copyOrMoveFav({
required bool isCopy,
required bool isFav,
required dynamic srcMediaId,
required dynamic tarMediaId,
dynamic mid,
required List resources,
required String resources,
}) async {
var res = await Request().post(
isFav
@@ -656,28 +710,28 @@ class FavHttp {
'src_media_id': ?srcMediaId,
'tar_media_id': tarMediaId,
'mid': ?mid,
'resources': resources.join(','),
'resources': resources,
'platform': 'web',
'csrf': Accounts.main.csrf,
},
options: Options(contentType: Headers.formUrlEncodedContentType),
);
if (res.data['code'] == 0) {
return {'status': true};
return const Success(null);
} else {
return {'status': false, 'msg': res.data['message']};
return Error(res.data['message']);
}
}
static Future allFavFolders(mid) async {
static Future<LoadingState<FavFolderData>> allFavFolders(Object mid) async {
var res = await Request().get(
Api.favFolder,
queryParameters: {'up_mid': mid},
);
if (res.data['code'] == 0) {
return {'status': true, 'data': FavFolderData.fromJson(res.data['data'])};
return Success(FavFolderData.fromJson(res.data['data']));
} else {
return {'status': false, 'msg': res.data['message']};
return Error(res.data['message']);
}
}

View File

@@ -2,6 +2,7 @@ import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/http/api.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/login.dart';
import 'package:PiliPlus/http/ua_type.dart';
import 'package:PiliPlus/models/common/live_search_type.dart';
import 'package:PiliPlus/models_new/live/live_area_list/area_item.dart';
@@ -169,23 +170,25 @@ class LiveHttp {
final params = {
if (isLogin) 'access_key': Accounts.main.accessKey,
'appkey': Constants.appKey,
'channel': 'master',
'actionKey': 'appkey',
'build': '8430300',
'build': 8430300,
'version': '8.43.0',
'c_locale': 'zh_CN',
'device': 'pad',
'device': 'android',
'device_name': 'android',
'device_type': '0',
'fnval': '912',
'disable_rcmd': '0',
'https_url_req': '1',
if (moduleSelect == true) 'module_select': '1',
'mobi_app': 'android_hd',
'device_type': 0,
'fnval': 912,
'disable_rcmd': 0,
'https_url_req': 1,
if (moduleSelect == true) 'module_select': 1,
'mobi_app': 'android',
'network': 'wifi',
'page': pn,
'platform': 'android',
if (isLogin) 'relation_page': '1',
if (isLogin) 'relation_page': 1,
's_locale': 'zh_CN',
'scale': '2',
'scale': 2,
'statistics': Constants.statisticsApp,
'ts': DateTime.now().millisecondsSinceEpoch ~/ 1000,
};
@@ -197,6 +200,23 @@ class LiveHttp {
var res = await Request().get(
Api.liveFeedIndex,
queryParameters: params,
options: Options(
headers: {
'buvid': LoginHttp.buvid,
'fp_local':
'1111111111111111111111111111111111111111111111111111111111111111',
'fp_remote':
'1111111111111111111111111111111111111111111111111111111111111111',
'session_id': '11111111',
'env': 'prod',
'app-key': 'android',
'User-Agent': Constants.userAgentApp,
'x-bili-trace-id': Constants.traceId,
'x-bili-aurora-eid': '',
'x-bili-aurora-zone': '',
'bili-http-engine': 'cronet',
},
),
);
if (res.data['code'] == 0) {
return Success(LiveIndexData.fromJson(res.data['data']));
@@ -233,29 +253,31 @@ class LiveHttp {
if (isLogin) 'access_key': Accounts.main.accessKey,
'appkey': Constants.appKey,
'actionKey': 'appkey',
'channel': 'master',
'area_id': ?areaId,
'parent_area_id': ?parentAreaId,
'build': '8430300',
'build': 8430300,
'version': '8.43.0',
'c_locale': 'zh_CN',
'device': 'pad',
'device': 'android',
'device_name': 'android',
'device_type': '0',
'fnval': '912',
'disable_rcmd': '0',
'https_url_req': '1',
'mobi_app': 'android_hd',
'module_select': '0',
'device_type': 0,
'fnval': 912,
'disable_rcmd': 0,
'https_url_req': 1,
'mobi_app': 'android',
'module_select': 0,
'network': 'wifi',
'page': pn,
'page_size': '20',
'page_size': 20,
'platform': 'android',
'qn': '0',
'qn': 0,
'sort_type': ?sortType,
'tag_version': '1',
'tag_version': 1,
's_locale': 'zh_CN',
'scale': '2',
'scale': 2,
'statistics': Constants.statisticsApp,
'ts': (DateTime.now().millisecondsSinceEpoch ~/ 1000).toString(),
'ts': DateTime.now().millisecondsSinceEpoch ~/ 1000,
};
AppSign.appSign(
params,
@@ -265,6 +287,23 @@ class LiveHttp {
var res = await Request().get(
Api.liveSecondList,
queryParameters: params,
options: Options(
headers: {
'buvid': LoginHttp.buvid,
'fp_local':
'1111111111111111111111111111111111111111111111111111111111111111',
'fp_remote':
'1111111111111111111111111111111111111111111111111111111111111111',
'session_id': '11111111',
'env': 'prod',
'app-key': 'android',
'User-Agent': Constants.userAgentApp,
'x-bili-trace-id': Constants.traceId,
'x-bili-aurora-eid': '',
'x-bili-aurora-zone': '',
'bili-http-engine': 'cronet',
},
),
);
if (res.data['code'] == 0) {
return Success(LiveSecondData.fromJson(res.data['data']));
@@ -280,11 +319,13 @@ class LiveHttp {
if (isLogin) 'access_key': Accounts.main.accessKey,
'appkey': Constants.appKey,
'actionKey': 'appkey',
'build': '8430300',
'build': 8430300,
'channel': 'master',
'version': '8.43.0',
'c_locale': 'zh_CN',
'device': 'pad',
'disable_rcmd': '0',
'mobi_app': 'android_hd',
'device': 'android',
'disable_rcmd': 0,
'mobi_app': 'android',
'platform': 'android',
's_locale': 'zh_CN',
'statistics': Constants.statisticsApp,
@@ -317,11 +358,13 @@ class LiveHttp {
if (isLogin) 'access_key': Accounts.main.accessKey,
'appkey': Constants.appKey,
'actionKey': 'appkey',
'build': '8430300',
'build': 8430300,
'channel': 'master',
'version': '8.43.0',
'c_locale': 'zh_CN',
'device': 'pad',
'disable_rcmd': '0',
'mobi_app': 'android_hd',
'device': 'android',
'disable_rcmd': 0,
'mobi_app': 'android',
'platform': 'android',
's_locale': 'zh_CN',
'statistics': Constants.statisticsApp,
@@ -350,18 +393,20 @@ class LiveHttp {
}
static Future setLiveFavTag({
required List ids,
required String ids,
}) async {
final data = {
'tags': ids.join(','),
'tags': ids,
'access_key': Accounts.main.accessKey,
'appkey': Constants.appKey,
'actionKey': 'appkey',
'build': '8430300',
'build': 8430300,
'channel': 'master',
'version': '8.43.0',
'c_locale': 'zh_CN',
'device': 'pad',
'disable_rcmd': '0',
'mobi_app': 'android_hd',
'device': 'android',
'disable_rcmd': 0,
'mobi_app': 'android',
'platform': 'android',
's_locale': 'zh_CN',
'statistics': Constants.statisticsApp,
@@ -395,14 +440,16 @@ class LiveHttp {
if (isLogin) 'access_key': Accounts.main.accessKey,
'appkey': Constants.appKey,
'actionKey': 'appkey',
'build': '8430300',
'build': 8430300,
'channel': 'master',
'version': '8.43.0',
'c_locale': 'zh_CN',
'device': 'pad',
'disable_rcmd': '0',
'device': 'android',
'disable_rcmd': 0,
'need_entrance': 1,
'parent_id': parentid,
'source_id': 2,
'mobi_app': 'android_hd',
'mobi_app': 'android',
'platform': 'android',
's_locale': 'zh_CN',
'statistics': Constants.statisticsApp,
@@ -436,14 +483,16 @@ class LiveHttp {
if (isLogin) 'access_key': Accounts.main.accessKey,
'appkey': Constants.appKey,
'actionKey': 'appkey',
'build': '8430300',
'build': 8430300,
'channel': 'master',
'version': '8.43.0',
'c_locale': 'zh_CN',
'device': 'pad',
'device': 'android',
'page': page,
'pagesize': 30,
'keyword': keyword,
'disable_rcmd': '0',
'mobi_app': 'android_hd',
'disable_rcmd': 0,
'mobi_app': 'android',
'platform': 'android',
's_locale': 'zh_CN',
'statistics': Constants.statisticsApp,

View File

@@ -20,6 +20,7 @@ import 'package:PiliPlus/models_new/space/space/data.dart';
import 'package:PiliPlus/models_new/space/space_archive/data.dart';
import 'package:PiliPlus/models_new/space/space_article/data.dart';
import 'package:PiliPlus/models_new/space/space_audio/data.dart';
import 'package:PiliPlus/models_new/space/space_cheese/data.dart';
import 'package:PiliPlus/models_new/space/space_opus/data.dart';
import 'package:PiliPlus/models_new/space/space_season_series/item.dart';
import 'package:PiliPlus/models_new/upower_rank/data.dart';
@@ -57,21 +58,22 @@ class MemberHttp {
required int mid,
required int page,
}) async {
Map<String, String> data = {
'build': '8430300',
final params = {
'build': 8430300,
'channel': 'master',
'version': '8.43.0',
'c_locale': 'zh_CN',
'channel': 'bili',
'mobi_app': 'android',
'platform': 'android',
'pn': page.toString(),
'ps': '10',
'pn': page,
'ps': 10,
's_locale': 'zh_CN',
'statistics': Constants.statisticsApp,
'vmid': mid.toString(),
'vmid': mid,
};
var res = await Request().get(
Api.spaceArticle,
queryParameters: data,
queryParameters: params,
options: Options(
headers: {
'bili-http-engine': 'cronet',
@@ -119,25 +121,26 @@ class MemberHttp {
int? seriesId,
includeCursor,
}) async {
Map<String, String> data = {
if (aid != null) 'aid': aid.toString(),
'build': '8430300',
final params = {
'aid': ?aid,
'build': 8430300,
'version': '8.43.0',
'c_locale': 'zh_CN',
'channel': 'bili',
'channel': 'master',
'mobi_app': 'android',
'platform': 'android',
's_locale': 'zh_CN',
'ps': '20',
if (pn != null) 'pn': pn.toString(),
if (next != null) 'next': next.toString(),
if (seasonId != null) 'season_id': seasonId.toString(),
if (seriesId != null) 'series_id': seriesId.toString(),
'qn': type == ContributeType.video ? '80' : '32',
'ps': 20,
'pn': ?pn,
'next': ?next,
'season_id': ?seasonId,
'series_id': ?seriesId,
'qn': type == ContributeType.video ? 80 : 32,
'order': ?order,
'sort': ?sort,
if (includeCursor != null) 'include_cursor': includeCursor.toString(),
'include_cursor': ?includeCursor,
'statistics': Constants.statisticsApp,
'vmid': mid.toString(),
'vmid': mid,
};
var res = await Request().get(
switch (type) {
@@ -148,7 +151,7 @@ class MemberHttp {
ContributeType.bangumi => Api.spaceBangumi,
ContributeType.comic => Api.spaceComic,
},
queryParameters: data,
queryParameters: params,
options: Options(
headers: {
'bili-http-engine': 'cronet',
@@ -184,6 +187,26 @@ class MemberHttp {
}
}
static Future<LoadingState<SpaceCheeseData>> spaceCheese({
required int page,
required mid,
}) async {
var res = await Request().get(
Api.spaceCheese,
queryParameters: {
'pn': page,
'ps': 30,
'mid': mid,
'web_location': 333.1387,
},
);
if (res.data['code'] == 0) {
return Success(SpaceCheeseData.fromJson(res.data['data']));
} else {
return Error(res.data['message']);
}
}
static Future<LoadingState> spaceStory({
required mid,
required aid,
@@ -193,25 +216,26 @@ class MemberHttp {
required contain,
required index,
}) async {
Map<String, String> data = {
'aid': aid.toString(),
'before_size': beforeSize.toString(),
'after_size': afterSize.toString(),
'cid': cid.toString(),
'contain': contain.toString(),
'index': index.toString(),
'build': '8430300',
final params = {
'aid': aid,
'before_size': beforeSize,
'after_size': afterSize,
'cid': cid,
'contain': contain,
'index': index,
'build': 8430300,
'version': '8.43.0',
'c_locale': 'zh_CN',
'channel': 'bili',
'channel': 'master',
'mobi_app': 'android',
'platform': 'android',
's_locale': 'zh_CN',
'statistics': Constants.statisticsApp,
'vmid': mid.toString(),
'vmid': mid,
};
var res = await Request().get(
Api.spaceStory,
queryParameters: data,
queryParameters: params,
options: Options(
headers: {
'bili-http-engine': 'cronet',
@@ -230,20 +254,21 @@ class MemberHttp {
int? mid,
dynamic fromViewAid,
}) async {
Map<String, String> data = {
'build': '8430300',
final params = {
'build': 8430300,
'version': '8.43.0',
'c_locale': 'zh_CN',
'channel': 'bili',
'channel': 'master',
'mobi_app': 'android',
'platform': 'android',
's_locale': 'zh_CN',
'from_view_aid': ?fromViewAid,
'statistics': Constants.statisticsApp,
'vmid': mid.toString(),
'vmid': mid,
};
var res = await Request().get(
Api.space,
queryParameters: data,
queryParameters: params,
options: Options(
headers: {
'bili-http-engine': 'cronet',
@@ -341,7 +366,7 @@ class MemberHttp {
'keyword': ?keyword,
'order': order,
'platform': 'web',
'web_location': '1550101',
'web_location': 1550101,
'order_avoided': orderAvoided,
'dm_img_list': '[]',
'dm_img_str': dmImgStr,
@@ -462,17 +487,16 @@ class MemberHttp {
}
// 查询分组
static Future followUpTags() async {
static Future<LoadingState<List<MemberTagItemModel>>> followUpTags() async {
var res = await Request().get(Api.followUpTag);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data']
.map<MemberTagItemModel>((e) => MemberTagItemModel.fromJson(e))
return Success(
(res.data['data'] as List)
.map((e) => MemberTagItemModel.fromJson(e))
.toList(),
};
);
} else {
return {'status': false, 'msg': res.data['message']};
return Error(res.data['message']);
}
}
@@ -501,7 +525,7 @@ class MemberHttp {
}
// 设置分组
static Future addUsers(List<int?> fids, List<int?> tagids) async {
static Future addUsers(String fids, String tagids) async {
var res = await Request().post(
Api.addUsers,
queryParameters: {
@@ -509,8 +533,8 @@ class MemberHttp {
'{"platform":"web","device":"pc","spmid":"333.1387"}',
},
data: {
'fids': fids.join(','),
'tagids': tagids.join(','),
'fids': fids,
'tagids': tagids,
'csrf': Accounts.main.csrf,
// 'cross_domain': true
},

View File

@@ -533,13 +533,13 @@ class MsgHttp {
}
static Future<LoadingState<List<ImUserInfosData>?>> imUserInfos({
required List uids,
required String uids,
}) async {
final csrf = Accounts.main.csrf;
var res = await Request().get(
Api.imUserInfos,
queryParameters: {
'uids': uids.join(','),
'uids': uids,
'build': 0,
'mobi_app': 'web',
'csrf_token': csrf,

View File

@@ -193,6 +193,24 @@ class SearchHttp {
}
}
static Future<LoadingState<PgcInfoModel>> pugvInfo({
dynamic seasonId,
dynamic epId,
}) async {
var res = await Request().get(
Api.pugvInfo,
queryParameters: {
'season_id': ?seasonId,
'ep_id': ?epId,
},
);
if (res.data['code'] == 0) {
return Success(PgcInfoModel.fromJson(res.data['data']));
} else {
return Error(res.data['message']);
}
}
static Future<LoadingState> episodeInfo({dynamic epId}) async {
var res = await Request().get(
Api.episodeInfo,
@@ -227,7 +245,9 @@ class SearchHttp {
final res = await Request().get(
Api.searchRecommend,
queryParameters: {
'build': '8430300',
'build': 8430300,
'channel': 'master',
'version': '8.43.0',
'c_locale': 'zh_CN',
'mobi_app': 'android',
'platform': 'android',

View File

@@ -13,6 +13,7 @@ import 'package:PiliPlus/models_new/space_setting/data.dart';
import 'package:PiliPlus/models_new/sub/sub/data.dart';
import 'package:PiliPlus/models_new/video/video_tag/data.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/wbi_sign.dart';
import 'package:dio/dio.dart';
@@ -80,6 +81,7 @@ class UserHttp {
required String type,
int? max,
int? viewAt,
Account? account,
}) async {
var res = await Request().get(
Api.historyList,
@@ -89,6 +91,7 @@ class UserHttp {
'max': max ?? 0,
'view_at': viewAt ?? 0,
},
options: Options(extra: {'account': account ?? Accounts.history}),
);
if (res.data['code'] == 0) {
return Success(HistoryData.fromJson(res.data['data']));
@@ -98,22 +101,27 @@ class UserHttp {
}
// 暂停观看历史
static Future pauseHistory(bool switchStatus) async {
static Future pauseHistory(bool switchStatus, {Account? account}) async {
// 暂停switchStatus传true 否则false
account ??= Accounts.history;
var res = await Request().post(
Api.pauseHistory,
queryParameters: {
'switch': switchStatus,
'jsonp': 'jsonp',
'csrf': Accounts.main.csrf,
'csrf': account.csrf,
},
options: Options(extra: {'account': account}),
);
return res;
}
// 观看历史暂停状态
static Future historyStatus() async {
var res = await Request().get(Api.historyStatus);
static Future historyStatus({Account? account}) async {
var res = await Request().get(
Api.historyStatus,
options: Options(extra: {'account': account ?? Accounts.history}),
);
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -122,13 +130,15 @@ class UserHttp {
}
// 清空历史记录
static Future clearHistory() async {
static Future clearHistory({Account? account}) async {
account ??= Accounts.history;
var res = await Request().post(
Api.clearHistory,
queryParameters: {
'jsonp': 'jsonp',
'csrf': Accounts.main.csrf,
'csrf': account.csrf,
},
options: Options(extra: {'account': account}),
);
return res;
}
@@ -151,10 +161,10 @@ class UserHttp {
}
// 移除已观看
static Future toViewDel({required List<int?> aids}) async {
static Future toViewDel({required String aids}) async {
final Map<String, dynamic> params = {
'csrf': Accounts.main.csrf,
'resources': aids.join(','),
'resources': aids,
};
var res = await Request().post(
Api.toViewDel,
@@ -204,15 +214,17 @@ class UserHttp {
}
// 删除历史记录
static Future delHistory(List<String> kidList) async {
static Future delHistory(String kid, {Account? account}) async {
account ??= Accounts.history;
var res = await Request().post(
Api.delHistory,
data: {
'kid': kidList.join(','),
'kid': kid,
'jsonp': 'jsonp',
'csrf': Accounts.main.csrf,
'csrf': account.csrf,
},
options: Options(
extra: {'account': account},
contentType: Headers.formUrlEncodedContentType,
),
);
@@ -241,6 +253,7 @@ class UserHttp {
static Future<LoadingState<HistoryData>> searchHistory({
required int pn,
required String keyword,
Account? account,
}) async {
var res = await Request().get(
Api.searchHistory,
@@ -249,6 +262,7 @@ class UserHttp {
'keyword': keyword,
'business': 'all',
},
options: Options(extra: {'account': account ?? Accounts.history}),
);
if (res.data['code'] == 0) {
return Success(HistoryData.fromJson(res.data['data']));

View File

@@ -1,5 +1,4 @@
import 'dart:convert';
import 'dart:developer';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/http/api.dart';
@@ -8,6 +7,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/login.dart';
import 'package:PiliPlus/http/ua_type.dart';
import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/models/common/video/video_type.dart';
import 'package:PiliPlus/models/home/rcmd/result.dart';
import 'package:PiliPlus/models/model_hot_video_item.dart';
import 'package:PiliPlus/models/model_rec_video_item.dart';
@@ -23,6 +23,7 @@ import 'package:PiliPlus/models_new/video/video_note_list/data.dart';
import 'package:PiliPlus/models_new/video/video_play_info/data.dart';
import 'package:PiliPlus/models_new/video/video_relation/data.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/accounts/account.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/global_data.dart';
import 'package:PiliPlus/utils/id_utils.dart';
@@ -34,7 +35,6 @@ import 'package:flutter/foundation.dart';
/// view层根据 status 判断渲染逻辑
class VideoHttp {
static bool p1080 = Pref.p1080;
// static bool enableRcmdDynamic = Pref.enableRcmdDynamic;
static RegExp zoneRegExp = RegExp(Pref.banWordForZone, caseSensitive: false);
static bool enableFilter = zoneRegExp.pattern.isNotEmpty;
@@ -77,38 +77,38 @@ class VideoHttp {
// 添加额外的loginState变量模拟未登录状态
static Future<LoadingState> rcmdVideoListApp({required int freshIdx}) async {
Map<String, String> data = {
'build': '2001100',
final params = {
'build': 2001100,
'c_locale': 'zh_CN',
'channel': 'master',
'column': '4',
'column': 4,
'device': 'pad',
'device_name': 'android',
'device_type': '0',
'disable_rcmd': '0',
'flush': '5',
'fnval': '976',
'fnver': '0',
'force_host': '2', //使用https
'fourk': '1',
'guidance': '0',
'https_url_req': '0',
'idx': freshIdx.toString(),
'device_type': 0,
'disable_rcmd': 0,
'flush': 5,
'fnval': 976,
'fnver': 0,
'force_host': 2, //使用https
'fourk': 1,
'guidance': 0,
'https_url_req': 0,
'idx': freshIdx,
'mobi_app': 'android_hd',
'network': 'wifi',
'platform': 'android',
'player_net': '1',
'player_net': 1,
'pull': freshIdx == 0 ? 'true' : 'false',
'qn': '32',
'recsys_mode': '0',
'qn': 32,
'recsys_mode': 0,
's_locale': 'zh_CN',
'splash_id': '',
'statistics': Constants.statistics,
'voice_balance': '0',
'voice_balance': 0,
};
var res = await Request().get(
Api.recommendListApp,
queryParameters: data,
queryParameters: params,
options: Options(
headers: {
'buvid': LoginHttp.buvid,
@@ -194,7 +194,8 @@ class VideoHttp {
int? qn,
dynamic epid,
dynamic seasonId,
bool? forcePgcApi,
required bool tryLook,
required VideoType videoType,
}) async {
final params = await WbiSign.makSign({
'avid': ?avid,
@@ -212,33 +213,34 @@ class VideoHttp {
'isGaiaAvoided': true,
'web_location': 1315873,
// 免登录查看1080p
if (!Accounts.get(AccountType.video).isLogin && p1080) 'try_look': 1,
if (tryLook) 'try_look': 1,
});
late final usePgcApi =
forcePgcApi == true || Accounts.get(AccountType.video).isLogin;
try {
var res = await Request().get(
epid != null && usePgcApi ? Api.pgcUrl : Api.ugcUrl,
videoType.api,
queryParameters: params,
);
if (res.data['code'] == 0) {
late PlayUrlModel data;
if (epid != null && usePgcApi) {
data = PlayUrlModel.fromJson(res.data['result']['video_info'])
..lastPlayTime = res
.data['result']['play_view_business_info']['user_status']['watch_progress']['current_watch_progress'];
} else {
data = PlayUrlModel.fromJson(res.data['data']);
switch (videoType) {
case VideoType.ugc:
data = PlayUrlModel.fromJson(res.data['data']);
case VideoType.pugv:
var result = res.data['data'];
data = PlayUrlModel.fromJson(result)
..lastPlayTime =
result?['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress'];
case VideoType.pgc:
var result = res.data['result'];
data = PlayUrlModel.fromJson(result['video_info'])
..lastPlayTime =
result?['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress'];
}
return {
'status': true,
'data': data,
};
return {'status': true, 'data': data};
} else {
if (epid != null && !usePgcApi && forcePgcApi != true) {
if (epid != null && videoType == VideoType.ugc) {
return videoUrl(
avid: avid,
bvid: bvid,
@@ -246,7 +248,8 @@ class VideoHttp {
qn: qn,
epid: epid,
seasonId: seasonId,
forcePgcApi: true,
tryLook: tryLook,
videoType: VideoType.pgc,
);
}
return {
@@ -465,7 +468,7 @@ class VideoHttp {
'id': id,
'reason_id': ?reasonId,
'feedback_id': ?feedbackId,
'build': '1',
'build': 1,
'mobi_app': 'android',
},
);
@@ -493,7 +496,7 @@ class VideoHttp {
'id': id,
'reason_id': ?reasonId,
'feedback_id': ?feedbackId,
'build': '1',
'build': 1,
'mobi_app': 'android',
},
);
@@ -542,7 +545,6 @@ class VideoHttp {
data: data,
options: Options(contentType: Headers.formUrlEncodedContentType),
);
log(res.toString());
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
@@ -564,7 +566,6 @@ class VideoHttp {
'csrf': Accounts.main.csrf,
},
);
log(res.toString());
if (res.data['code'] == 0) {
return {'status': true};
} else {
@@ -620,7 +621,7 @@ class VideoHttp {
await Request().post(
Api.roomEntryAction,
queryParameters: {
'csrf': Accounts.main.csrf,
'csrf': Accounts.heartbeat.csrf,
},
data: {
'room_id': roomId,
@@ -638,31 +639,34 @@ class VideoHttp {
queryParameters: {
'aid': ?aid,
'type': ?type,
'csrf': Accounts.main.csrf,
'csrf': Accounts.heartbeat.csrf,
},
);
}
// 视频播放进度
static Future heartBeat({
aid,
bvid,
cid,
progress,
epid,
seasonId,
subType,
required VideoType videoType,
}) async {
final isPugv = videoType == VideoType.pugv;
await Request().post(
Api.heartBeat,
queryParameters: {
'bvid': bvid,
if (isPugv) 'aid': ?aid else 'bvid': ?bvid,
'cid': cid,
'epid': ?epid,
'sid': ?seasonId,
if (epid != null) 'type': 4,
'type': videoType.type,
'sub_type': ?subType,
'played_time': progress,
'csrf': Accounts.main.csrf,
'csrf': Accounts.heartbeat.csrf,
},
);
}
@@ -671,15 +675,18 @@ class VideoHttp {
required int desc,
required dynamic oid,
required dynamic upperMid,
Account? account,
}) async {
account ??= Accounts.history;
await Request().post(
Api.mediaListHistory,
queryParameters: {
'desc': desc,
'oid': oid,
'upper_mid': upperMid,
'csrf': Accounts.main.csrf,
'csrf': account.csrf,
},
options: Options(extra: {'account': account}),
);
}
@@ -736,13 +743,13 @@ class VideoHttp {
}
static Future pgcUpdate({
required List seasonId,
required dynamic status,
required String seasonId,
required int status,
}) async {
var res = await Request().post(
Api.pgcUpdate,
data: {
'season_id': seasonId.join(','),
'season_id': seasonId,
'status': status,
'csrf': Accounts.main.csrf,
},

View File

@@ -12,8 +12,8 @@ import 'package:PiliPlus/services/loggeer.dart';
import 'package:PiliPlus/services/service_locator.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/cache_manage.dart';
import 'package:PiliPlus/utils/data.dart';
import 'package:PiliPlus/utils/date_util.dart';
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
@@ -124,7 +124,7 @@ Commit Hash: ${BuildConfig.commitHash}''';
systemNavigationBarContrastEnforced: false,
),
);
Data.init();
RequestUtils.syncHistoryStatus();
PiliScheme.init();
}
@@ -145,15 +145,14 @@ class MyApp extends StatelessWidget {
FlutterDisplayMode.supported.then((value) {
modes = value;
var storageDisplay = GStorage.setting.get(SettingBoxKey.displayMode);
DisplayMode f = DisplayMode.auto;
DisplayMode? displayMode;
if (storageDisplay != null) {
f = modes.firstWhere(
displayMode = modes.firstWhereOrNull(
(e) => e.toString() == storageDisplay,
orElse: () => f,
);
}
DisplayMode preferred = modes.toList().firstWhere((el) => el == f);
FlutterDisplayMode.setPreferredMode(preferred);
displayMode ??= DisplayMode.auto;
FlutterDisplayMode.setPreferredMode(displayMode);
});
}

View File

@@ -1,7 +1,7 @@
enum EpisodeType {
part('分P'),
season('合集'),
pgc('');
pgc('');
final String title;
const EpisodeType(this.title);

View File

@@ -1,4 +1,5 @@
import 'package:PiliPlus/pages/fav/article/view.dart';
import 'package:PiliPlus/pages/fav/cheese/view.dart';
import 'package:PiliPlus/pages/fav/note/view.dart';
import 'package:PiliPlus/pages/fav/pgc/view.dart';
import 'package:PiliPlus/pages/fav/topic/view.dart';
@@ -11,7 +12,8 @@ enum FavTabType {
cinema('追剧', FavPgcPage(type: 2)),
article('专栏', FavArticlePage()),
note('笔记', FavNotePage()),
topic('话题', FavTopicPage());
topic('话题', FavTopicPage()),
cheese('课堂', FavCheesePage());
final String title;
final Widget page;

View File

@@ -10,11 +10,6 @@ enum HistoryBusinessType {
// 文章
article('article');
// 隐藏时长
static const hiddenDurationType = {'live', 'article-list', 'article'};
// 右上
static const showBadge = {'pgc', 'article-list', 'article'};
final String type;
const HistoryBusinessType(this.type);
}

View File

@@ -4,7 +4,17 @@ enum MemberTabType {
dynamic('动态'),
contribute('投稿'),
favorite('收藏'),
bangumi('番剧');
bangumi('番剧'),
cheese('课堂');
static bool contains(String type) {
for (var e in MemberTabType.values) {
if (e.name == type) {
return true;
}
}
return false;
}
final String title;
const MemberTabType(this.title);

View File

@@ -0,0 +1,27 @@
enum ArticleOrderType {
totalrank('综合排序'),
pubdate('最新发布'),
click('最多点击'),
attention('最多喜欢'),
scores('最多评论');
String get order => name;
final String label;
const ArticleOrderType(this.label);
}
enum ArticleZoneType {
all('全部分区', 0),
douga('动画', 2),
game('游戏', 1),
cinephile('影视', 28),
life('生活', 3),
interest('兴趣', 29),
novel('轻小说', 16),
tech('科技', 17),
note('笔记', 41);
final String label;
final int categoryId;
const ArticleZoneType(this.label, this.categoryId);
}

View File

@@ -0,0 +1,22 @@
enum UserOrderType {
def('默认排序', 0, ''),
fansDesc('粉丝数由高到低', 0, 'fans'),
fansAsc('粉丝数由低到高', 1, 'fans'),
levelDesc('Lv等级由高到低', 0, 'level'),
levelAsc('Lv等级由低到高', 1, 'level');
final String label;
final int orderSort;
final String order;
const UserOrderType(this.label, this.orderSort, this.order);
}
enum UserType {
all('全部用户'),
up('UP主'),
common('普通用户'),
verified('认证用户');
final String label;
const UserType(this.label);
}

View File

@@ -0,0 +1,49 @@
enum VideoPubTimeType {
all('不限'),
day('最近一天'),
week('最近一周'),
halfYear('最近半年');
final String label;
const VideoPubTimeType(this.label);
}
enum VideoDurationType {
all('全部时长'),
tenMins('0-10分钟'),
halfHour('0-30分钟'),
hour('30-60分钟'),
hourPlus('60分钟+');
final String label;
const VideoDurationType(this.label);
}
enum VideoZoneType {
all('全部'),
douga('动画', tids: 1),
anime('番剧', tids: 13),
guochuang('国创', tids: 167),
music('音乐', tids: 3),
dance('舞蹈', tids: 129),
game('游戏', tids: 4),
knowledge('知识', tids: 36),
tech('科技', tids: 188),
sports('运动', tids: 234),
car('汽车', tids: 223),
life('生活', tids: 160),
food('美食', tids: 221),
animal('动物', tids: 217),
kichiku('鬼畜', tids: 119),
fashion('时尚', tids: 115),
info('资讯', tids: 202),
ent('娱乐', tids: 5),
cinephile('影视', tids: 181),
documentary('记录', tids: 177),
movie('电影', tids: 23),
tv('电视', tids: 11);
final String label;
final int? tids;
const VideoZoneType(this.label, {this.tids});
}

View File

@@ -0,0 +1,26 @@
import 'package:PiliPlus/http/api.dart';
enum VideoType {
ugc(
type: 3,
api: Api.ugcUrl,
),
pgc(
type: 4,
api: Api.pgcUrl,
),
pugv(
type: 10,
replyType: 33,
api: Api.pugvUrl,
);
final int type;
final String api;
final int replyType;
const VideoType({
required this.api,
required this.type,
this.replyType = 1,
});
}

View File

@@ -1,7 +1,4 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
class MemberTagItemModel with MultiSelectData {
class MemberTagItemModel {
MemberTagItemModel({
this.count,
this.name,

View File

@@ -1,8 +1,7 @@
import 'package:PiliPlus/models/model_owner.dart';
import 'package:PiliPlus/models/model_rec_video_item.dart';
import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
import 'package:PiliPlus/pages/common/multi_select/base.dart';
// 稍后再看, 排行榜等网页返回也使用该类
class HotVideoItemModel extends BaseRecVideoItemModel with MultiSelectData {

View File

@@ -170,23 +170,25 @@ abstract class BaseItem {
BaseItem.fromJson(Map<String, dynamic> json) {
id = json['id'];
baseUrl = json['baseUrl'];
final backupUrls = (json['backupUrl'] as List?)?.cast<String>() ?? [];
baseUrl = json['baseUrl'] ?? json['base_url'];
final backupUrls =
((json['backupUrl'] ?? json['backup_url']) as List?)?.cast<String>() ??
<String>[];
backupUrl = backupUrls.isNotEmpty
? backupUrls.firstWhere(
(i) => !_isMCDNorPCDN(i),
orElse: () => backupUrls.first,
)
: null;
bandWidth = json['bandWidth'];
bandWidth = json['bandWidth'] ?? json['bandwidth'];
mimeType = json['mime_type'];
codecs = json['codecs'];
width = json['width'];
height = json['height'];
frameRate = json['frameRate'];
frameRate = json['frameRate'] ?? json['frame_rate'];
sar = json['sar'];
startWithSap = json['startWithSap'];
segmentBase = json['segmentBase'];
startWithSap = json['startWithSap'] ?? json['start_with_sap'];
segmentBase = json['segmentBase'] ?? json['segment_base'];
codecid = json['codecid'];
}
}

View File

@@ -1,5 +1,4 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
import 'package:PiliPlus/pages/common/multi_select/base.dart';
class MentionItem with MultiSelectData {
String? face;

View File

@@ -2,8 +2,7 @@ import 'package:PiliPlus/models/model_owner.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/cnt_info.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/ogv.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/ugc.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
import 'package:PiliPlus/pages/common/multi_select/base.dart';
class FavDetailItemModel with MultiSelectData {
int? id;

View File

@@ -1,5 +1,4 @@
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
import 'package:PiliPlus/pages/common/multi_select/base.dart';
class FavNoteItemModel with MultiSelectData {
FavNoteItemModel({

View File

@@ -11,8 +11,7 @@ import 'package:PiliPlus/models_new/fav/fav_pgc/rights.dart';
import 'package:PiliPlus/models_new/fav/fav_pgc/section.dart';
import 'package:PiliPlus/models_new/fav/fav_pgc/series.dart';
import 'package:PiliPlus/models_new/fav/fav_pgc/stat.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
import 'package:PiliPlus/pages/common/multi_select/base.dart';
class FavPgcItemModel with MultiSelectData {
int? seasonId;

View File

@@ -1,6 +1,5 @@
import 'package:PiliPlus/models_new/history/history.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
import 'package:PiliPlus/pages/common/multi_select/base.dart';
class HistoryItemModel with MultiSelectData {
String? title;

View File

@@ -4,8 +4,7 @@ import 'package:PiliPlus/models_new/later/owner.dart';
import 'package:PiliPlus/models_new/later/page.dart';
import 'package:PiliPlus/models_new/later/rights.dart';
import 'package:PiliPlus/models_new/later/stat.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart'
show MultiSelectData;
import 'package:PiliPlus/pages/common/multi_select/base.dart';
class LaterItemModel with MultiSelectData {
int? aid;

View File

@@ -1,22 +1,24 @@
import 'package:PiliPlus/models_new/media_list/media_list.dart';
class MediaListData {
List<MediaListItemModel>? mediaList;
List<MediaListItemModel> mediaList;
bool? hasMore;
int? totalCount;
String? nextStartKey;
MediaListData({
this.mediaList,
required this.mediaList,
this.hasMore,
this.totalCount,
this.nextStartKey,
});
factory MediaListData.fromJson(Map<String, dynamic> json) => MediaListData(
mediaList: (json['media_list'] as List<dynamic>?)
?.map((e) => MediaListItemModel.fromJson(e as Map<String, dynamic>))
.toList(),
mediaList:
(json['media_list'] as List<dynamic>?)
?.map((e) => MediaListItemModel.fromJson(e as Map<String, dynamic>))
.toList() ??
<MediaListItemModel>[],
hasMore: json['has_more'] as bool?,
totalCount: json['total_count'] as int?,
nextStartKey: json['next_start_key'] as String?,

View File

@@ -1,15 +1,14 @@
import 'package:PiliPlus/models/model_owner.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/cnt_info.dart';
import 'package:PiliPlus/models_new/media_list/badge.dart';
import 'package:PiliPlus/models_new/media_list/coin.dart';
import 'package:PiliPlus/models_new/media_list/ogv_info.dart';
import 'package:PiliPlus/models_new/media_list/page.dart';
import 'package:PiliPlus/models_new/media_list/rights.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/models_new/video/video_detail/episode.dart';
class MediaListItemModel {
class MediaListItemModel extends BaseEpisodeItem {
@override
int? get id => aid;
int? aid;
int? offset;
int? index;
String? intro;
@@ -17,32 +16,29 @@ class MediaListItemModel {
int? tid;
int? copyRight;
CntInfo? cntInfo;
String? cover;
int? duration;
int? pubtime;
int? likeState;
int? favState;
int? page;
List<Page>? pages;
String? title;
int? type;
Owner? upper;
String? link;
String? bvid;
String? shortLink;
Rights? rights;
dynamic elecInfo;
Coin? coin;
OgvInfo? ogvInfo;
double? progressPercent;
Badge? badge;
bool? forbidFav;
int? moreType;
int? businessOid;
int? cid;
@override
int? get cid => pages?.firstOrNull?.id;
MediaListItemModel({
this.aid,
super.aid,
this.offset,
this.index,
this.intro,
@@ -50,29 +46,29 @@ class MediaListItemModel {
this.tid,
this.copyRight,
this.cntInfo,
this.cover,
super.cover,
this.duration,
this.pubtime,
this.likeState,
this.favState,
this.page,
this.pages,
this.title,
super.title,
this.type,
this.upper,
this.link,
this.bvid,
super.bvid,
this.shortLink,
this.rights,
this.elecInfo,
this.coin,
this.ogvInfo,
this.progressPercent,
this.badge,
super.badge,
this.forbidFav,
this.moreType,
this.businessOid,
this.cid,
super.cid,
});
MediaListItemModel.fromJson(Map<String, dynamic> json) {
@@ -106,10 +102,9 @@ class MediaListItemModel {
? null
: OgvInfo.fromJson(json['ogv_info']);
progressPercent = (json['progress_percent'] as num?)?.toDouble();
badge = json['badge'] == null ? null : Badge.fromJson(json['badge']);
badge = json['badge']?['text'];
forbidFav = json['forbid_fav'] as bool?;
moreType = json['more_type'] as int?;
businessOid = json['business_oid'] as int?;
cid = pages.getOrNull((page ?? 1) - 1)?.id;
}
}

View File

@@ -0,0 +1,26 @@
class Brief {
List<Img>? img;
Brief({
this.img,
});
factory Brief.fromJson(Map<String, dynamic> json) => Brief(
img: (json['img'] as List?)?.map((e) => Img.fromJson(e)).toList(),
);
}
class Img {
num aspectRatio;
String? url;
Img({
required this.aspectRatio,
this.url,
});
factory Img.fromJson(Map<String, dynamic> json) => Img(
aspectRatio: json['aspect_ratio'] ?? 1,
url: json['url'] as String?,
);
}

View File

@@ -0,0 +1,20 @@
class Cooperator {
int? mid;
String? avatar;
String? nickName;
String? role;
Cooperator({
this.mid,
this.avatar,
this.nickName,
this.role,
});
factory Cooperator.fromJson(Map<String, dynamic> json) => Cooperator(
mid: json['mid'] as int?,
avatar: json['avatar'] as String?,
nickName: json['nick_name'] as String?,
role: json['role'] as String?,
);
}

View File

@@ -8,18 +8,16 @@ import 'package:PiliPlus/models_new/video/video_detail/episode.dart'
class EpisodeItem extends BaseEpisodeItem {
BadgeInfo? badgeInfo;
int? badgeType;
String? cover;
Dimension? dimension;
int? duration;
int? duration; // pgc: millisec , pugv: sec
bool? enableVt;
int? epId;
String? from;
bool? isViewHide;
String? link;
String? longTitle;
int? pubTime;
int? pv;
String? releaseDate;
// String? releaseDate;
Rights? rights;
int? sectionType;
String? shareCopy;
@@ -31,6 +29,7 @@ class EpisodeItem extends BaseEpisodeItem {
int? status;
String? subtitle;
String? vid;
int? play;
EpisodeItem({
super.aid,
@@ -39,11 +38,11 @@ class EpisodeItem extends BaseEpisodeItem {
this.badgeType,
super.bvid,
super.cid,
this.cover,
super.cover,
this.dimension,
this.duration,
this.enableVt,
this.epId,
super.epId,
this.from,
super.id,
this.isViewHide,
@@ -51,7 +50,7 @@ class EpisodeItem extends BaseEpisodeItem {
this.longTitle,
this.pubTime,
this.pv,
this.releaseDate,
// this.releaseDate,
this.rights,
this.sectionType,
this.shareCopy,
@@ -64,6 +63,7 @@ class EpisodeItem extends BaseEpisodeItem {
this.subtitle,
super.title,
this.vid,
this.play,
});
factory EpisodeItem.fromJson(Map<String, dynamic> json) => EpisodeItem(
@@ -87,9 +87,9 @@ class EpisodeItem extends BaseEpisodeItem {
isViewHide: json['is_view_hide'] as bool?,
link: json['link'] as String?,
longTitle: json['long_title'] as String?,
pubTime: json['pub_time'] as int?,
pubTime: json['pub_time'] ?? json['release_date'],
pv: json['pv'] as int?,
releaseDate: json['release_date'] as String?,
// releaseDate: json['release_date'] as String?,
rights: json['rights'] == null
? null
: Rights.fromJson(json['rights'] as Map<String, dynamic>),
@@ -106,5 +106,6 @@ class EpisodeItem extends BaseEpisodeItem {
subtitle: json['subtitle'] as String?,
title: json['title'] as String?,
vid: json['vid'] as String?,
play: json['play'] as int?,
);
}

View File

@@ -1,5 +1,7 @@
import 'package:PiliPlus/models_new/pgc/pgc_info_model/activity.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/area.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/brief.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/cooperator.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/episode.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/icon_font.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/new_ep.dart';
@@ -53,6 +55,8 @@ class PgcInfoModel {
int? type;
UpInfo? upInfo;
UserStatus? userStatus;
List<Cooperator>? cooperators;
Brief? brief;
PgcInfoModel({
this.activity,
@@ -94,6 +98,8 @@ class PgcInfoModel {
this.type,
this.upInfo,
this.userStatus,
this.cooperators,
this.brief,
});
factory PgcInfoModel.fromJson(Map<String, dynamic> json) => PgcInfoModel(
@@ -164,5 +170,11 @@ class PgcInfoModel {
userStatus: json['user_status'] == null
? null
: UserStatus.fromJson(json['user_status'] as Map<String, dynamic>),
cooperators: (json['cooperators'] as List?)
?.map((e) => Cooperator.fromJson(e))
.toList(),
brief: json['brief'] == null
? null
: Brief.fromJson(json['brief'] as Map<String, dynamic>),
);
}

View File

@@ -10,6 +10,7 @@ class UserStatus {
int? payPackPaid;
int? sponsor;
UserProgress? progress;
int? favored;
UserStatus({
this.areaLimit,
@@ -21,6 +22,7 @@ class UserStatus {
this.payPackPaid,
this.sponsor,
this.progress,
this.favored,
});
factory UserStatus.fromJson(Map<String, dynamic> json) => UserStatus(
@@ -35,5 +37,6 @@ class UserStatus {
progress: json['progress'] == null
? null
: UserProgress.fromJson(json['progress']),
favored: json['favored'] as int?,
);
}

View File

@@ -0,0 +1,19 @@
import 'package:PiliPlus/models_new/space/space_cheese/item.dart';
import 'package:PiliPlus/models_new/space/space_cheese/page.dart';
class SpaceCheeseData {
List<SpaceCheeseItem>? items;
SpaceCheesePage? page;
SpaceCheeseData({this.items, this.page});
factory SpaceCheeseData.fromJson(Map<String, dynamic> json) =>
SpaceCheeseData(
items: (json['items'] as List<dynamic>?)
?.map((e) => SpaceCheeseItem.fromJson(e as Map<String, dynamic>))
.toList(),
page: json['page'] == null
? null
: SpaceCheesePage.fromJson(json['page'] as Map<String, dynamic>),
);
}

View File

@@ -0,0 +1,48 @@
class SpaceCheeseItem {
bool? cooperated;
String? cooperationMark;
String? cover;
int? epCount;
String? link;
List<String>? marks;
int? page;
int? play;
int? seasonId;
String? status;
String? subtitle;
String? title;
String? ctime;
SpaceCheeseItem({
this.cooperated,
this.cooperationMark,
this.cover,
this.epCount,
this.link,
this.marks,
this.page,
this.play,
this.seasonId,
this.status,
this.subtitle,
this.title,
this.ctime,
});
factory SpaceCheeseItem.fromJson(Map<String, dynamic> json) =>
SpaceCheeseItem(
cooperated: json['cooperated'] as bool?,
cooperationMark: json['cooperation_mark'] as String?,
cover: json['cover'] as String?,
epCount: json['ep_count'] as int?,
link: json['link'] as String?,
marks: (json['marks'] as List?)?.cast(),
page: json['page'] as int?,
play: json['play'] as int?,
seasonId: json['season_id'] as int?,
status: json['status'] as String?,
subtitle: json['subtitle'] as String?,
title: json['title'] as String?,
ctime: json['ctime'] as String?,
);
}

View File

@@ -0,0 +1,16 @@
class SpaceCheesePage {
bool? next;
int? num;
int? size;
int? total;
SpaceCheesePage({this.next, this.num, this.size, this.total});
factory SpaceCheesePage.fromJson(Map<String, dynamic> json) =>
SpaceCheesePage(
next: json['next'] as bool?,
num: json['num'] as int?,
size: json['size'] as int?,
total: json['total'] as int?,
);
}

View File

@@ -1,21 +1,25 @@
import 'package:PiliPlus/models_new/video/video_detail/arc.dart';
import 'package:PiliPlus/models_new/video/video_detail/page.dart';
abstract class BaseEpisodeItem {
class BaseEpisodeItem {
int? id;
int? aid;
int? cid;
int? epId;
String? bvid;
String? badge;
String? title;
String? cover;
BaseEpisodeItem({
this.id,
this.aid,
this.cid,
this.epId,
this.bvid,
this.badge,
this.title,
this.cover,
});
}
@@ -26,6 +30,8 @@ class EpisodeItem extends BaseEpisodeItem {
Arc? arc;
Part? page;
List<Part>? pages;
@override
String? get cover => arc?.pic;
EpisodeItem({
this.seasonId,

View File

@@ -1,7 +1,7 @@
import 'package:PiliPlus/models_new/video/video_detail/dimension.dart';
import 'package:PiliPlus/models_new/video/video_detail/episode.dart';
class Part {
int? cid;
class Part extends BaseEpisodeItem {
int? page;
String? from;
String? pagePart;
@@ -11,10 +11,9 @@ class Part {
Dimension? dimension;
int? ctime;
String? firstFrame;
String? badge;
Part({
this.cid,
super.cid,
this.page,
this.from,
this.pagePart,
@@ -24,7 +23,7 @@ class Part {
this.dimension,
this.ctime,
this.firstFrame,
this.badge,
super.badge,
});
factory Part.fromJson(Map<String, dynamic> json) => Part(

View File

@@ -1,18 +1,18 @@
class Choice {
int? id;
import 'package:PiliPlus/models_new/video/video_detail/episode.dart';
class Choice extends BaseEpisodeItem {
String? platformAction;
String? nativeAction;
String? condition;
int? cid;
String? option;
int? isDefault;
Choice({
this.id,
super.id,
this.platformAction,
this.nativeAction,
this.condition,
this.cid,
super.cid,
this.option,
this.isDefault,
});

View File

@@ -276,7 +276,7 @@ Commit Hash: ${BuildConfig.commitHash}''',
);
Accounts.account
.putAll(res)
.whenComplete(() => Accounts.refresh())
.whenComplete(Accounts.refresh)
.whenComplete(() {
MineController.anonymity.value =
!Accounts.get(

View File

@@ -5,7 +5,6 @@ import 'package:PiliPlus/http/dynamics.dart';
import 'package:PiliPlus/http/fav.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/common/account_type.dart';
import 'package:PiliPlus/models/dynamics/article_content_model.dart'
show ArticleContentModel;
import 'package:PiliPlus/models/dynamics/result.dart';
@@ -14,6 +13,7 @@ import 'package:PiliPlus/models_new/article/article_info/data.dart';
import 'package:PiliPlus/models_new/article/article_view/data.dart';
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/url_utils.dart';
import 'package:flutter/rendering.dart' show ScrollDirection;
@@ -165,7 +165,7 @@ class ArticleController extends CommonDynController<MainListReply> {
}
if (isLoaded.value) {
queryData();
if (Accounts.get(AccountType.heartbeat).isLogin && !Pref.historyPause) {
if (Accounts.heartbeat.isLogin && !Pref.historyPause) {
VideoHttp.historyReport(aid: commentId, type: 5);
}
}
@@ -177,15 +177,13 @@ class ArticleController extends CommonDynController<MainListReply> {
}
@override
Future<LoadingState<MainListReply>> customGetData() {
return ReplyGrpc.mainList(
type: commentType,
oid: commentId,
mode: mode.value,
cursorNext: cursorNext,
offset: paginationReply?.nextOffset,
);
}
Future<LoadingState<MainListReply>> customGetData() => ReplyGrpc.mainList(
type: commentType,
oid: commentId,
mode: mode.value,
cursorNext: cursorNext,
offset: paginationReply?.nextOffset,
);
Future<void> onFav() async {
final favorite = stats.value?.favorite;
@@ -197,11 +195,10 @@ class ArticleController extends CommonDynController<MainListReply> {
: await FavHttp.communityAction(opusId: id, action: isFav ? 4 : 3);
if (res['status']) {
favorite?.status = !isFav;
var count = favorite?.count ?? 0;
if (isFav) {
favorite?.count = count - 1;
favorite?.count--;
} else {
favorite?.count = count + 1;
favorite?.count++;
}
stats.refresh();
SmartDialog.showToast('${isFav ? '取消' : ''}收藏成功');
@@ -219,11 +216,10 @@ class ArticleController extends CommonDynController<MainListReply> {
);
if (res['status']) {
like?.status = !isLike;
int count = like?.count ?? 0;
if (isLike) {
like?.count = count - 1;
like?.count--;
} else {
like?.count = count + 1;
like?.count++;
}
stats.refresh();
SmartDialog.showToast(!isLike ? '点赞成功' : '取消赞');

View File

@@ -20,8 +20,8 @@ import 'package:PiliPlus/pages/article/widgets/opus_content.dart';
import 'package:PiliPlus/pages/common/dyn/common_dyn_page.dart';
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/date_util.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/image_util.dart';
@@ -35,7 +35,7 @@ import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:get/get.dart' hide ContextExtensionss;
import 'package:html/parser.dart' as parser;
class ArticlePage extends CommonDynPage {
@@ -302,7 +302,7 @@ class _ArticlePageState extends CommonDynPageState<ArticlePage> {
final pic = pics[index];
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => context.imageView(
onTap: () => PageUtils.imageView(
quality: 60,
imgList: pics
.map((e) => SourceModel(url: e.url!))

View File

@@ -1,6 +1,6 @@
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/image_util.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
@@ -54,7 +54,7 @@ Widget htmlRender({
if (callback != null) {
callback([imgUrl], 0);
} else {
context.imageView(
PageUtils.imageView(
imgList: [SourceModel(url: imgUrl)],
quality: 60,
);

View File

@@ -12,6 +12,7 @@ import 'package:PiliPlus/pages/dynamics/widgets/vote.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/image_util.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cached_network_svg_image/cached_network_svg_image.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
@@ -181,7 +182,7 @@ class OpusContent extends StatelessWidget {
if (callback != null) {
callback!([pic.url!], 0);
} else {
context.imageView(
PageUtils.imageView(
imgList: [SourceModel(url: pic.url!)],
quality: 60,
);

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/http/fav.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/common/video/source_type.dart';
import 'package:PiliPlus/models_new/fav/fav_folder/data.dart';
import 'package:PiliPlus/models_new/video/video_detail/data.dart';
import 'package:PiliPlus/models_new/video/video_detail/stat_detail.dart';
@@ -21,9 +22,8 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
abstract class CommonIntroController extends GetxController {
String heroTag = Get.arguments['heroTag'];
String bvid = Get.parameters['bvid']!;
late final String heroTag;
late String bvid;
// 是否点赞
final RxBool hasLike = false.obs;
@@ -49,7 +49,7 @@ abstract class CommonIntroController extends GetxController {
final Rx<VideoDetailData> videoDetail = VideoDetailData().obs;
Future<void> queryVideoIntro();
void queryVideoIntro();
bool prevPlay();
bool nextPlay();
@@ -59,11 +59,17 @@ abstract class CommonIntroController extends GetxController {
late final RxString total = '1'.obs;
Timer? timer;
final RxInt cid = int.parse(Get.parameters['cid']!).obs;
late final RxInt cid;
@override
void onInit() {
super.onInit();
final args = Get.arguments;
heroTag = args['heroTag'];
bvid = args['bvid'];
cid = RxInt(args['cid']);
hasLater.value = args['sourceType'] == SourceType.watchLater;
queryVideoIntro();
startTimer();
}
@@ -264,7 +270,7 @@ abstract class CommonIntroController extends GetxController {
Future<void> viewLater() async {
var res = await (hasLater.value
? UserHttp.toViewDel(aids: [IdUtils.bv2av(bvid)])
? UserHttp.toViewDel(aids: IdUtils.bv2av(bvid).toString())
: await UserHttp.toViewLater(bvid: bvid));
if (res['status']) hasLater.value = !hasLater.value;
SmartDialog.showToast(res['msg']);

View File

@@ -1,88 +0,0 @@
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_search_controller.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
abstract class CommonSearchPage extends StatefulWidget {
const CommonSearchPage({super.key});
}
abstract class CommonSearchPageState<S extends CommonSearchPage, R, T>
extends State<S> {
CommonSearchController<R, T> get controller;
List<Widget>? extraActions;
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
actions: [
IconButton(
tooltip: '搜索',
onPressed: controller.onRefresh,
icon: const Icon(Icons.search_outlined, size: 22),
),
...?extraActions,
const SizedBox(width: 10),
],
title: TextField(
autofocus: true,
focusNode: controller.focusNode,
controller: controller.editController,
textInputAction: TextInputAction.search,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
hintText: '搜索',
border: InputBorder.none,
suffixIcon: IconButton(
tooltip: '清空',
icon: const Icon(Icons.clear, size: 22),
onPressed: () => controller
..loadingState.value = LoadingState.loading()
..onClear()
..focusNode.requestFocus(),
),
),
onSubmitted: (value) => controller.onRefresh(),
),
),
body: SafeArea(
top: false,
bottom: false,
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: controller.scrollController,
slivers: [
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(() => _buildBody(controller.loadingState.value)),
),
],
),
),
);
}
Widget _buildBody(LoadingState<List<T>?> loadingState) {
return switch (loadingState) {
Loading() => const HttpError(),
Success(:var response) =>
response?.isNotEmpty == true
? buildList(response!)
: HttpError(
onReload: controller.onReload,
),
Error(:var errMsg) => HttpError(
errMsg: errMsg,
onReload: controller.onReload,
),
};
}
Widget buildList(List<T> list);
}

View File

@@ -7,11 +7,12 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart';
import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPlus/pages/video/reply_reply/view.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/num_util.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get.dart' hide ContextExtensionss;
abstract class CommonDynPage extends StatefulWidget {
const CommonDynPage({super.key});

View File

@@ -0,0 +1,144 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:get/get.dart';
mixin MultiSelectData {
bool? checked;
}
abstract class MultiSelectBase<T> {
RxBool get enableMultiSelect;
RxBool get allSelected;
int get checkedCount;
void onSelect(T item, [bool disableSelect = true]);
void handleSelect([bool checked = false, bool disableSelect = true]);
void onRemove();
}
mixin CommonMultiSelectMixin<T extends MultiSelectData>
implements MultiSelectBase<T> {
@override
late final RxBool enableMultiSelect = false.obs;
@override
late final allSelected = false.obs;
Rx<LoadingState<List<T>?>> get loadingState;
late final RxInt rxCount = 0.obs;
@override
int get checkedCount => rxCount.value;
Iterable<T> get allChecked =>
loadingState.value.data!.where((v) => v.checked == true);
@override
void onSelect(T item, [bool disableSelect = true]) {
List<T> list = loadingState.value.data!;
item.checked = !(item.checked ?? false);
if (item.checked!) {
rxCount.value++;
} else {
rxCount.value--;
}
loadingState.refresh();
if (disableSelect) {
if (checkedCount == 0) {
enableMultiSelect.value = false;
}
} else {
allSelected.value = checkedCount == list.length;
}
}
@override
void handleSelect([bool checked = false, bool disableSelect = true]) {
if (loadingState.value.isSuccess) {
final list = loadingState.value.data;
if (list?.isNotEmpty == true) {
for (var item in list!) {
item.checked = checked;
}
loadingState.refresh();
rxCount.value = checked ? list.length : 0;
}
}
if (disableSelect && !checked) {
enableMultiSelect.value = false;
}
}
}
mixin DeleteItemMixin<R, T extends MultiSelectData>
on CommonListController<R, T>, CommonMultiSelectMixin<T> {
Future<void> afterDelete(Set<T> removeList) async {
final list = loadingState.value.data!;
if (removeList.length == list.length) {
list.clear();
} else if (removeList.length == 1) {
list.remove(removeList.first);
} else {
list.removeWhere(removeList.contains);
}
if (list.isNotEmpty || isEnd) {
loadingState.refresh();
} else {
onReload();
}
if (enableMultiSelect.value) {
rxCount.value = 0;
enableMultiSelect.value = false;
}
}
}
// abstract class SetMultiSelectController<R, T, I>
// extends CommonListController<R, T>
// with MultiSelectMixin<T>, SetCommonMultiSelectMixin<T, I> {}
// mixin SetCommonMultiSelectMixin<T, R> on MultiSelectMixin<T> {
// Rx<LoadingState<List<T>?>> get loadingState;
// RxSet<R> get selected;
// @override
// int get checkedCount => selected.length;
// R getId(T item);
// @override
// void onSelect(T item, [bool disableSelect = true]) {
// final id = getId(item);
// if (selected.contains(id)) {
// selected.remove(id);
// } else {
// selected.add(id);
// }
// loadingState.refresh();
// if (disableSelect) {
// if (checkedCount == 0) {
// enableMultiSelect.value = false;
// }
// } else {
// allSelected.value = checkedCount == loadingState.value.data!.length;
// }
// }
// @override
// void handleSelect([bool checked = false, bool disableSelect = true]) {
// if (loadingState.value.isSuccess) {
// final list = loadingState.value.data;
// if (list?.isNotEmpty == true) {
// if (checked) {
// selected.addAll(list!.map(getId));
// } else {
// selected.clear();
// }
// loadingState.refresh();
// }
// }
// if (disableSelect && !checked) {
// enableMultiSelect.value = false;
// }
// }
// }

View File

@@ -0,0 +1,6 @@
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/common/multi_select/base.dart';
abstract class MultiSelectController<R, T extends MultiSelectData>
extends CommonListController<R, T>
with CommonMultiSelectMixin<T>, DeleteItemMixin {}

View File

@@ -1,43 +0,0 @@
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:get/get.dart';
mixin MultiSelectData {
bool? checked;
}
abstract class MultiSelectController<R, T extends MultiSelectData>
extends CommonListController<R, T> {
late final RxBool enableMultiSelect = false.obs;
late final RxInt checkedCount = 0.obs;
late final allSelected = false.obs;
void onSelect(T item, [bool disableSelect = true]) {
List<T> list = loadingState.value.data!;
item.checked = !(item.checked ?? false);
checkedCount.value = list.where((item) => item.checked == true).length;
loadingState.refresh();
if (disableSelect) {
if (checkedCount.value == 0) {
enableMultiSelect.value = false;
}
} else {
allSelected.value = checkedCount.value == list.length;
}
}
void handleSelect([bool checked = false, bool disableSelect = true]) {
if (loadingState.value.isSuccess) {
List<T>? list = loadingState.value.data;
if (list?.isNotEmpty == true) {
for (T item in list!) {
item.checked = checked;
}
loadingState.refresh();
checkedCount.value = checked ? list.length : 0;
}
}
if (disableSelect && !checked) {
enableMultiSelect.value = false;
}
}
}

View File

@@ -5,12 +5,13 @@ import 'dart:math' show max;
import 'package:PiliPlus/http/msg.dart';
import 'package:PiliPlus/models/common/publish_panel_type.dart';
import 'package:PiliPlus/models_new/upload_bfs/data.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:chat_bottom_container/chat_bottom_container.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:get/get.dart' hide ContextExtensionss;
import 'package:image_picker/image_picker.dart';
abstract class CommonPublishPage<T> extends StatefulWidget {
@@ -255,5 +256,5 @@ abstract class CommonPublishPageState<T extends CommonPublishPage>
enablePublish.value = value.trim().isNotEmpty;
}
void onSave() {}
void onSave();
}

View File

@@ -11,7 +11,7 @@ import 'package:PiliPlus/models_new/emote/emote.dart' as e;
import 'package:PiliPlus/models_new/live/live_emote/emoticon.dart';
import 'package:PiliPlus/pages/common/publish/common_publish_page.dart';
import 'package:PiliPlus/pages/dynamics_mention/view.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@@ -75,7 +75,7 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
GestureDetector(
onTap: () async {
controller.keepChatPanel();
await context.imageView(
await PageUtils.imageView(
imgList: pathList
.map(
(path) => SourceModel(
@@ -228,22 +228,23 @@ abstract class CommonRichTextPubPageState<T extends CommonRichTextPubPage>
"biz_id": "",
});
case RichTextType.vote:
list.add({
"raw_text": e.rawText,
"type": 4,
"biz_id": e.id,
});
list.add({
"raw_text": ' ',
"type": 1,
"biz_id": "",
});
list
..add({
"raw_text": e.rawText,
"type": 4,
"biz_id": e.id,
})
..add({
"raw_text": ' ',
"type": 1,
"biz_id": "",
});
}
}
return list;
}
double _mentionOffset = 0;
late double _mentionOffset = 0;
Future<void> onMention([bool fromClick = false]) async {
controller.keepChatPanel();
final res = await DynMentionPanel.onDynMention(

View File

@@ -0,0 +1,122 @@
import 'package:PiliPlus/common/widgets/appbar/appbar.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/search/common_search_controller.dart';
import 'package:PiliPlus/pages/common/multi_select/base.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
abstract class CommonSearchPage extends StatefulWidget {
const CommonSearchPage({super.key});
}
abstract class CommonSearchPageState<S extends CommonSearchPage, R, T>
extends State<S> {
CommonSearchController<R, T> get controller;
List<Widget>? get extraActions => null;
List<Widget>? get multiSelectChildren => null;
@override
Widget build(BuildContext context) {
if (controller case MultiSelectBase multiCtr) {
return Obx(() {
final enableMultiSelect = multiCtr.enableMultiSelect.value;
return PopScope(
canPop: !enableMultiSelect,
onPopInvokedWithResult: (didPop, result) {
if (enableMultiSelect) {
multiCtr.handleSelect();
}
},
child: _build(true),
);
});
}
return _build(false);
}
Widget _build(bool multiSelect) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildBar(multiSelect),
body: SafeArea(
top: false,
bottom: false,
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: controller.scrollController,
slivers: [
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(() => _buildBody(controller.loadingState.value)),
),
],
),
),
);
}
PreferredSizeWidget _buildBar(bool multiSelect) {
final AppBar bar = AppBar(
actions: [
IconButton(
tooltip: '搜索',
onPressed: controller.onRefresh,
icon: const Icon(Icons.search_outlined, size: 22),
),
...?extraActions,
const SizedBox(width: 10),
],
title: TextField(
autofocus: true,
focusNode: controller.focusNode,
controller: controller.editController,
textInputAction: TextInputAction.search,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
hintText: '搜索',
border: InputBorder.none,
suffixIcon: IconButton(
tooltip: '清空',
icon: const Icon(Icons.clear, size: 22),
onPressed: () => controller
..loadingState.value = LoadingState.loading()
..onClear()
..focusNode.requestFocus(),
),
),
onSubmitted: (value) => controller.onRefresh(),
),
);
if (multiSelect) {
return MultiSelectAppBarWidget(
ctr: controller as MultiSelectBase,
children: multiSelectChildren,
child: bar,
);
}
return bar;
}
Widget _buildBody(LoadingState<List<T>?> loadingState) {
return switch (loadingState) {
Loading() => const HttpError(),
Success(:var response) =>
response?.isNotEmpty == true
? buildList(response!)
: HttpError(
onReload: controller.onReload,
),
Error(:var errMsg) => HttpError(
errMsg: errMsg,
onReload: controller.onReload,
),
};
}
Widget buildList(List<T> list);
}

View File

@@ -1,4 +1,4 @@
import 'package:PiliPlus/pages/common/common_slide_page.dart';
import 'package:PiliPlus/pages/common/slide/common_slide_page.dart';
import 'package:flutter/material.dart';
abstract class CommonCollapseSlidePage extends CommonSlidePage {

View File

@@ -1,4 +1,3 @@
import 'dart:async';
import 'dart:convert';
import 'package:PiliPlus/grpc/bilibili/community/service/dm/v1.pb.dart';
@@ -14,13 +13,15 @@ import 'package:get/get.dart';
class PlDanmaku extends StatefulWidget {
final int cid;
final PlPlayerController playerController;
final bool? isPipMode;
final bool isPipMode;
final bool isFullScreen;
const PlDanmaku({
super.key,
required this.cid,
required this.playerController,
this.isPipMode,
this.isPipMode = false,
required this.isFullScreen,
});
@override
@@ -33,8 +34,6 @@ class _PlDanmakuState extends State<PlDanmaku> {
late PlDanmakuController _plDanmakuController;
DanmakuController? _controller;
int latestAddedPosition = -1;
bool? _isFullScreen;
StreamSubscription? _listenerFS;
@override
void initState() {
@@ -53,20 +52,27 @@ class _PlDanmakuState extends State<PlDanmaku> {
playerController
..addStatusLister(playerListener)
..addPositionListener(videoPositionListen);
_listenerFS = playerController.isFullScreen.listen((isFullScreen) {
if (isFullScreen != _isFullScreen) {
_isFullScreen = isFullScreen;
if (_controller != null) {
_controller!.updateOption(
_controller!.option.copyWith(
fontSize: _getFontSize(isFullScreen),
),
);
}
}
});
}
@override
void didUpdateWidget(PlDanmaku oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.isPipMode != widget.isPipMode ||
oldWidget.isFullScreen != widget.isFullScreen) {
_updateFontSize();
}
}
void _updateFontSize() {
_controller?.updateOption(
_controller!.option.copyWith(fontSize: _fontSize),
);
}
double get _fontSize => !widget.isFullScreen || widget.isPipMode
? 15 * playerController.danmakuFontScale
: 15 * playerController.danmakuFontScaleFS;
// 播放器状态监听
void playerListener(PlayerStatus? status) {
if (status == PlayerStatus.playing) {
@@ -77,15 +83,11 @@ class _PlDanmakuState extends State<PlDanmaku> {
}
void videoPositionListen(Duration position) {
if (!playerController.enableShowDanmaku.value) {
if (_controller == null || !playerController.enableShowDanmaku.value) {
return;
}
if (_controller == null) {
return;
}
if (!playerController.showDanmaku && widget.isPipMode != true) {
if (!playerController.showDanmaku && !widget.isPipMode) {
return;
}
@@ -138,7 +140,6 @@ class _PlDanmakuState extends State<PlDanmaku> {
@override
void dispose() {
_listenerFS?.cancel();
playerController
..removePositionListener(videoPositionListen)
..removeStatusLister(playerListener);
@@ -146,45 +147,35 @@ class _PlDanmakuState extends State<PlDanmaku> {
super.dispose();
}
double _getFontSize(bool isFullScreen) =>
!isFullScreen || widget.isPipMode == true
? 15 * playerController.danmakuFontScale
: 15 * playerController.danmakuFontScaleFS;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, box) {
// double initDuration = box.maxWidth / 12;
return Obx(
() => AnimatedOpacity(
opacity: playerController.enableShowDanmaku.value ? 1 : 0,
duration: const Duration(milliseconds: 100),
child: DanmakuScreen(
createdController: (DanmakuController e) {
playerController.danmakuController = _controller = e;
},
option: DanmakuOption(
fontSize: _getFontSize(playerController.isFullScreen.value),
fontWeight: playerController.fontWeight,
area: playerController.showArea,
opacity: playerController.danmakuOpacity,
hideTop: playerController.blockTypes.contains(5),
hideScroll: playerController.blockTypes.contains(2),
hideBottom: playerController.blockTypes.contains(4),
duration:
playerController.danmakuDuration /
playerController.playbackSpeed,
staticDuration:
playerController.danmakuStaticDuration /
playerController.playbackSpeed,
strokeWidth: playerController.strokeWidth,
lineHeight: playerController.danmakuLineHeight,
),
),
return Obx(
() => AnimatedOpacity(
opacity: playerController.enableShowDanmaku.value ? 1 : 0,
duration: const Duration(milliseconds: 100),
child: DanmakuScreen(
createdController: (DanmakuController e) {
playerController.danmakuController = _controller = e;
},
option: DanmakuOption(
fontSize: _fontSize,
fontWeight: playerController.fontWeight,
area: playerController.showArea,
opacity: playerController.danmakuOpacity,
hideTop: playerController.blockTypes.contains(5),
hideScroll: playerController.blockTypes.contains(2),
hideBottom: playerController.blockTypes.contains(4),
duration:
playerController.danmakuDuration /
playerController.playbackSpeed,
staticDuration:
playerController.danmakuStaticDuration /
playerController.playbackSpeed,
strokeWidth: playerController.strokeWidth,
lineHeight: playerController.danmakuLineHeight,
),
);
},
),
),
);
}
}

View File

@@ -10,6 +10,7 @@ import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/dynamics/controller.dart';
import 'package:PiliPlus/pages/save_panel/view.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/date_util.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/feed_back.dart';
@@ -19,7 +20,7 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:get/get.dart' hide ContextExtensionss;
class AuthorPanel extends StatelessWidget {
final DynamicItemModel item;
@@ -98,7 +99,7 @@ class AuthorPanel extends StatelessWidget {
item.modules.moduleAuthor!.vip != null &&
item.modules.moduleAuthor!.vip!.status > 0 &&
item.modules.moduleAuthor!.vip!.type == 2
? context.vipColor
? theme.colorScheme.vipColor
: theme.colorScheme.onSurface,
fontSize: theme.textTheme.titleSmall!.fontSize,
),

View File

@@ -6,9 +6,9 @@ import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
import 'package:PiliPlus/pages/dynamics/widgets/blocked_item.dart';
import 'package:PiliPlus/pages/dynamics/widgets/content_panel.dart';
import 'package:PiliPlus/pages/dynamics/widgets/module_panel.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class DynamicPanel extends StatelessWidget {
final DynamicItemModel item;
@@ -80,8 +80,7 @@ class DynamicPanel extends StatelessWidget {
),
),
);
if (isSave ||
(isDetail && Get.context!.orientation == Orientation.landscape)) {
if (isSave || (isDetail && context.isLandscape)) {
return child;
}
return DecoratedBox(

View File

@@ -1,8 +1,8 @@
import 'package:PiliPlus/common/widgets/image/image_save.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
Widget livePanel(
ThemeData theme,
@@ -20,7 +20,7 @@ Widget livePanel(
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => Get.toNamed('/liveRoom?roomid=${content.live?.id}'),
onTap: () => PageUtils.toLiveRoom(content.live?.id),
onLongPress: () {
Feedback.forLongPress(context);
imageSaveDialog(

View File

@@ -3,8 +3,8 @@ import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
Widget livePanelSub(
ThemeData theme,
@@ -24,7 +24,7 @@ Widget livePanelSub(
Padding(
padding: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace),
child: GestureDetector(
onTap: () => Get.toNamed('/liveRoom?roomid=${content.roomId}'),
onTap: () => PageUtils.toLiveRoom(content.roomId),
child: LayoutBuilder(
builder: (context, box) {
double width = box.maxWidth;

View File

@@ -10,9 +10,7 @@ import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/dynamics/widgets/vote.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@@ -231,10 +229,8 @@ TextSpan? richNode(
int? cid = await SearchHttp.ab2c(bvid: i.rid);
if (cid != null) {
PageUtils.toVideoPage(
'bvid=${i.rid}&cid=$cid',
arguments: {
'heroTag': Utils.makeHeroTag(i.rid),
},
bvid: i.rid,
cid: cid,
);
}
} catch (err) {
@@ -276,7 +272,7 @@ TextSpan? richNode(
recognizer: TapGestureRecognizer()
..onTap = () {
void onView(List<OpusPicModel> list) {
Get.context!.imageView(
PageUtils.imageView(
imgList: list
.map((e) => SourceModel(url: e.src!))
.toList(),

View File

@@ -5,6 +5,7 @@ import 'package:PiliPlus/models/dynamics/up.dart';
import 'package:PiliPlus/pages/dynamics/controller.dart';
import 'package:PiliPlus/pages/live_follow/view.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -144,7 +145,7 @@ class _UpPanelState extends State<UpPanel> {
feedBack();
switch (data) {
case LiveUserItem():
Get.toNamed('/liveRoom?roomid=${data.roomId}');
PageUtils.toLiveRoom(data.roomId);
case UpItem():
_onSelect(data);
break;

View File

@@ -4,10 +4,11 @@ import 'package:PiliPlus/common/widgets/dialog/report.dart';
import 'package:PiliPlus/http/dynamics.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/dynamics/vote_model.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/date_util.dart';
import 'package:PiliPlus/utils/num_util.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get.dart' hide ContextExtensionss;
class VotePanel extends StatefulWidget {
final VoteInfo voteInfo;

View File

@@ -24,6 +24,7 @@ import 'package:PiliPlus/pages/dynamics_select_topic/view.dart';
import 'package:PiliPlus/pages/emote/controller.dart';
import 'package:PiliPlus/pages/emote/view.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/date_util.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/request_utils.dart';
@@ -31,7 +32,7 @@ import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart' hide DraggableScrollableSheet;
import 'package:flutter/services.dart' show LengthLimitingTextInputFormatter;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:get/get.dart' hide ContextExtensionss;
class CreateDynPanel extends CommonRichTextPubPage {
const CreateDynPanel({

View File

@@ -97,7 +97,7 @@ class _CreateVotePageState extends State<CreateVotePage> {
..add(
_buildInput(
theme,
key: ValueKey(e.hashCode),
key: ObjectKey(e),
showDel: showDel,
onDel: () {
FocusManager.instance.primaryFocus?.unfocus();

View File

@@ -9,6 +9,7 @@ import 'package:PiliPlus/pages/dynamics/widgets/author_panel.dart';
import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart';
import 'package:PiliPlus/pages/dynamics_detail/controller.dart';
import 'package:PiliPlus/pages/dynamics_repost/view.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/num_util.dart';
@@ -18,7 +19,7 @@ import 'package:PiliPlus/utils/storage_key.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:get/get.dart' hide ContextExtensionss;
class DynamicDetailPage extends CommonDynPage {
const DynamicDetailPage({super.key});

View File

@@ -10,10 +10,11 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/dynamic/dyn_mention/group.dart';
import 'package:PiliPlus/pages/dynamics_mention/controller.dart';
import 'package:PiliPlus/pages/dynamics_mention/widgets/item.dart';
import 'package:PiliPlus/pages/search/controller.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:stream_transform/stream_transform.dart';
import 'package:get/get.dart' hide ContextExtensionss;
class DynMentionPanel extends StatefulWidget {
const DynMentionPanel({
@@ -57,10 +58,11 @@ class DynMentionPanel extends StatefulWidget {
State<DynMentionPanel> createState() => _DynMentionPanelState();
}
class _DynMentionPanelState extends State<DynMentionPanel> {
class _DynMentionPanelState extends State<DynMentionPanel>
with SearchKeywordMixin {
final _controller = Get.put(DynMentionController());
final StreamController<String> _ctr = StreamController<String>();
late StreamSubscription<String> _sub;
@override
Duration get duration => const Duration(milliseconds: 300);
@override
void initState() {
@@ -68,26 +70,25 @@ class _DynMentionPanelState extends State<DynMentionPanel> {
if (_controller.loadingState.value is Error) {
_controller.onReload();
}
_sub = _ctr.stream
.debounce(const Duration(milliseconds: 300), trailing: true)
.listen((value) {
_controller
..enableClear.value = value.isNotEmpty
..onRefresh().whenComplete(
() => WidgetsBinding.instance.addPostFrameCallback(
(_) => widget.scrollController?.jumpToTop(),
),
);
});
subInit();
}
@override
void dispose() {
_sub.cancel();
_ctr.close();
subDispose();
super.dispose();
}
@override
ValueChanged<String> get onKeywordChanged =>
(value) => _controller
..enableClear.value = value.isNotEmpty
..onRefresh().whenComplete(
() => WidgetsBinding.instance.addPostFrameCallback(
(_) => widget.scrollController?.jumpToTop(),
),
);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@@ -113,7 +114,7 @@ class _DynMentionPanelState extends State<DynMentionPanel> {
child: TextField(
focusNode: _controller.focusNode,
controller: _controller.controller,
onChanged: _ctr.add,
onChanged: ctr!.add,
decoration: InputDecoration(
border: const OutlineInputBorder(
gapPadding: 0,

View File

@@ -8,10 +8,11 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart';
import 'package:PiliPlus/pages/dynamics_select_topic/controller.dart';
import 'package:PiliPlus/pages/dynamics_select_topic/widgets/item.dart';
import 'package:PiliPlus/pages/search/controller.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:stream_transform/stream_transform.dart';
import 'package:get/get.dart' hide ContextExtensionss;
class SelectTopicPanel extends StatefulWidget {
const SelectTopicPanel({
@@ -55,10 +56,11 @@ class SelectTopicPanel extends StatefulWidget {
State<SelectTopicPanel> createState() => _SelectTopicPanelState();
}
class _SelectTopicPanelState extends State<SelectTopicPanel> {
class _SelectTopicPanelState extends State<SelectTopicPanel>
with SearchKeywordMixin {
final _controller = Get.put(SelectTopicController());
final StreamController<String> _ctr = StreamController<String>();
late StreamSubscription<String> _sub;
@override
Duration get duration => const Duration(milliseconds: 300);
@override
void initState() {
@@ -66,26 +68,25 @@ class _SelectTopicPanelState extends State<SelectTopicPanel> {
if (_controller.loadingState.value is Error) {
_controller.onReload();
}
_sub = _ctr.stream
.debounce(const Duration(milliseconds: 300), trailing: true)
.listen((value) {
_controller
..enableClear.value = value.isNotEmpty
..onRefresh().whenComplete(
() => WidgetsBinding.instance.addPostFrameCallback(
(_) => widget.scrollController?.jumpToTop(),
),
);
});
subInit();
}
@override
void dispose() {
_sub.cancel();
_ctr.close();
subDispose();
super.dispose();
}
@override
ValueChanged<String> get onKeywordChanged =>
(value) => _controller
..enableClear.value = value.isNotEmpty
..onRefresh().whenComplete(
() => WidgetsBinding.instance.addPostFrameCallback(
(_) => widget.scrollController?.jumpToTop(),
),
);
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@@ -109,7 +110,7 @@ class _SelectTopicPanelState extends State<SelectTopicPanel> {
child: TextField(
focusNode: _controller.focusNode,
controller: _controller.controller,
onChanged: _ctr.add,
onChanged: ctr!.add,
decoration: InputDecoration(
border: const OutlineInputBorder(
gapPadding: 0,

View File

@@ -20,7 +20,7 @@ import 'package:PiliPlus/models_new/pgc/pgc_info_model/episode.dart' as pgc;
import 'package:PiliPlus/models_new/video/video_detail/episode.dart' as ugc;
import 'package:PiliPlus/models_new/video/video_detail/page.dart';
import 'package:PiliPlus/models_new/video/video_relation/data.dart';
import 'package:PiliPlus/pages/common/common_slide_page.dart';
import 'package:PiliPlus/pages/common/slide/common_slide_page.dart';
import 'package:PiliPlus/pages/video/controller.dart';
import 'package:PiliPlus/pages/video/introduction/ugc/controller.dart';
import 'package:PiliPlus/pages/video/introduction/ugc/widgets/page.dart';
@@ -75,7 +75,7 @@ class EpisodePanel extends CommonSlidePage {
final int initialTabIndex;
final bool? isSupportReverse;
final bool? isReversed;
final Function onChangeEpisode;
final ValueChanged<ugc.BaseEpisodeItem> onChangeEpisode;
final VoidCallback? onReverse;
final VoidCallback? onClose;
@@ -330,7 +330,7 @@ class _EpisodePanelState extends CommonSlidePageState<EpisodePanel> {
Widget _buildEpisodeItem({
required ThemeData theme,
required dynamic episode,
required ugc.BaseEpisodeItem episode,
required int index,
required int length,
required bool isCurrentIndex,
@@ -367,7 +367,12 @@ class _EpisodePanelState extends CommonSlidePageState<EpisodePanel> {
bvid = item.bvid;
title = item.showTitle ?? item.title!;
cover = item.cover;
duration = item.duration == null ? null : item.duration! ~/ 1000;
if (item.from == 'pugv') {
duration = item.duration;
view = item.play;
} else {
duration = item.duration == null ? null : item.duration! ~/ 1000;
}
pubdate = item.pubTime;
break;
}
@@ -379,7 +384,7 @@ class _EpisodePanelState extends CommonSlidePageState<EpisodePanel> {
type: MaterialType.transparency,
child: InkWell(
onTap: () {
if (episode.badge != null && episode.badge == "会员") {
if (episode.badge == "会员") {
UserInfoData? userInfo = GStorage.userInfo.get('userInfoCache');
int vipStatus = userInfo?.vipStatus ?? 0;
if (vipStatus != 1) {
@@ -392,14 +397,7 @@ class _EpisodePanelState extends CommonSlidePageState<EpisodePanel> {
if (!widget.showTitle) {
_currentItemIndex = index;
}
final isEpisode = episode is ugc.BaseEpisodeItem;
widget.onChangeEpisode(
episode is pgc.EpisodeItem ? episode.epId : null,
isEpisode ? episode.bvid : widget.bvid,
episode.cid,
isEpisode ? episode.aid : widget.aid,
cover,
);
widget.onChangeEpisode(episode);
if (widget.type == EpisodeType.season) {
try {
Get.find<VideoDetailController>(

View File

@@ -0,0 +1,40 @@
import 'package:PiliPlus/http/fav.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/space/space_cheese/data.dart';
import 'package:PiliPlus/models_new/space/space_cheese/item.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class FavCheeseController
extends CommonListController<SpaceCheeseData, SpaceCheeseItem> {
final mid = Accounts.main.mid;
@override
void onInit() {
super.onInit();
queryData();
}
@override
List<SpaceCheeseItem>? getDataList(SpaceCheeseData response) {
isEnd = response.page?.next == false;
return response.items;
}
@override
Future<LoadingState<SpaceCheeseData>> customGetData() =>
FavHttp.favPugv(mid: mid, page: page);
Future<void> onRemove(int index, int? sid) async {
var res = await FavHttp.delFavPugv(sid);
if (res['status']) {
loadingState
..value.data!.removeAt(index)
..refresh();
SmartDialog.showToast('已取消收藏');
} else {
SmartDialog.showToast(res['msg']);
}
}
}

View File

@@ -0,0 +1,87 @@
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/space/space_cheese/item.dart';
import 'package:PiliPlus/pages/fav/cheese/controller.dart';
import 'package:PiliPlus/pages/member_cheese/widgets/item.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class FavCheesePage extends StatefulWidget {
const FavCheesePage({super.key});
@override
State<FavCheesePage> createState() => _FavCheesePageState();
}
class _FavCheesePageState extends State<FavCheesePage>
with AutomaticKeepAliveClientMixin {
final FavCheeseController _controller = Get.put(FavCheeseController());
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
final ThemeData theme = Theme.of(context);
return refreshIndicator(
onRefresh: _controller.onRefresh,
child: CustomScrollView(
controller: _controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverPadding(
padding: EdgeInsets.only(
top: 7,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: Obx(
() => _buildBody(theme, _controller.loadingState.value),
),
),
],
),
);
}
Widget _buildBody(
ThemeData theme,
LoadingState<List<SpaceCheeseItem>?> loadingState,
) {
return switch (loadingState) {
Loading() => linearLoading,
Success(:var response) =>
response?.isNotEmpty == true
? SliverGrid(
gridDelegate: Grid.videoCardHDelegate(context),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == response.length - 1) {
_controller.onLoadMore();
}
final item = response[index];
return MemberCheeseItem(
item: item,
onRemove: () => showConfirmDialog(
context: context,
title: '确定取消收藏该课堂?',
onConfirm: () =>
_controller.onRemove(index, item.seasonId),
),
);
},
childCount: response!.length,
),
)
: HttpError(onReload: _controller.onReload),
Error(:var errMsg) => HttpError(
errMsg: errMsg,
onReload: _controller.onReload,
),
};
}
}

View File

@@ -115,7 +115,7 @@ class _FavNoteChildPageState extends State<FavNoteChildPage>
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
onPressed: () {
if (_favNoteController.checkedCount.value != 0) {
if (_favNoteController.checkedCount != 0) {
showConfirmDialog(
context: context,
title: '确定删除已选中的笔记吗?',

View File

@@ -1,7 +1,7 @@
import 'package:PiliPlus/http/fav.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models_new/fav/fav_note/list.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/pages/common/multi_select/multi_select_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class FavNoteController
@@ -34,24 +34,17 @@ class FavNoteController
: FavHttp.noteList(page: page);
}
@override
Future<void> onRemove() async {
List<FavNoteItemModel> dataList = loadingState.value.data!;
Set<FavNoteItemModel> removeList = dataList
.where((item) => item.checked == true)
.toSet();
final removeList = allChecked.toSet();
final res = await FavHttp.delNote(
isPublish: isPublish,
noteIds: removeList
.map((item) => isPublish ? item.cvid : item.noteId)
.toList(),
.join(','),
);
if (res['status']) {
List<FavNoteItemModel> remainList = dataList
.toSet()
.difference(removeList)
.toList();
loadingState.value = Success(remainList);
enableMultiSelect.value = false;
afterDelete(removeList);
SmartDialog.showToast('删除成功');
} else {
SmartDialog.showToast(res['msg']);
@@ -59,7 +52,7 @@ class FavNoteController
}
void onDisable() {
if (checkedCount.value != 0) {
if (checkedCount != 0) {
handleSelect();
}
enableMultiSelect.value = false;

View File

@@ -128,8 +128,7 @@ class _FavPgcChildPageState extends State<FavPgcChildPage>
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (_favPgcController.checkedCount.value !=
0) {
if (_favPgcController.checkedCount != 0) {
_favPgcController.onUpdateList(
item.followStatus,
);

View File

@@ -3,7 +3,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models_new/fav/fav_pgc/data.dart';
import 'package:PiliPlus/models_new/fav/fav_pgc/list.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/pages/common/multi_select/multi_select_controller.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:flutter/foundation.dart' show kDebugMode;
import 'package:flutter/material.dart';
@@ -48,7 +48,7 @@ class FavPgcController
);
void onDisable() {
if (checkedCount.value != 0) {
if (checkedCount != 0) {
handleSelect();
}
enableMultiSelect.value = false;
@@ -65,33 +65,30 @@ class FavPgcController
SmartDialog.showToast(result['msg']);
}
@override
void onRemove() {
assert(false, 'call onUpdateList');
}
Future<void> onUpdateList(int followStatus) async {
List<FavPgcItemModel> dataList = loadingState.value.data!;
Set<FavPgcItemModel> updateList = dataList
.where((item) => item.checked == true)
.toSet();
final removeList = allChecked.toSet();
final res = await VideoHttp.pgcUpdate(
seasonId: updateList.map((item) => item.seasonId).toList(),
seasonId: removeList.map((item) => item.seasonId).join(','),
status: followStatus,
);
if (res['status']) {
List<FavPgcItemModel> remainList = dataList
.toSet()
.difference(updateList)
.toList();
loadingState.value = Success(remainList);
enableMultiSelect.value = false;
try {
final ctr = Get.find<FavPgcController>(tag: '$type$followStatus');
if (ctr.loadingState.value.isSuccess) {
ctr.loadingState
..value.data!.insertAll(
0,
updateList.map((item) => item..checked = null),
removeList.map((item) => item..checked = null),
)
..refresh();
ctr.allSelected.value = false;
}
afterDelete(removeList);
} catch (e) {
if (kDebugMode) debugPrint('fav pgc onUpdate: $e');
}
@@ -101,7 +98,7 @@ class FavPgcController
Future<void> onUpdate(int index, int followStatus, int? seasonId) async {
var result = await VideoHttp.pgcUpdate(
seasonId: [seasonId],
seasonId: seasonId.toString(),
status: followStatus,
);
if (result['status']) {

View File

@@ -4,7 +4,7 @@ import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models_new/fav/fav_pgc/list.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/pages/common/multi_select/base.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:flutter/material.dart';
@@ -18,7 +18,7 @@ class FavPgcItem extends StatelessWidget {
});
final FavPgcItemModel item;
final MultiSelectController ctr;
final MultiSelectBase ctr;
final VoidCallback onSelect;
final VoidCallback onUpdateStatus;

View File

@@ -3,6 +3,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/fav_type.dart';
import 'package:PiliPlus/models_new/fav/fav_folder/list.dart';
import 'package:PiliPlus/pages/fav/article/controller.dart';
import 'package:PiliPlus/pages/fav/cheese/controller.dart';
import 'package:PiliPlus/pages/fav/topic/controller.dart';
import 'package:PiliPlus/pages/fav/video/controller.dart';
import 'package:PiliPlus/pages/fav_folder_sort/view.dart';
@@ -141,6 +142,9 @@ class _FavPageState extends State<FavPage> with SingleTickerProviderStateMixin {
.animToTop();
case FavTabType.topic:
Get.find<FavTopicController>().scrollController.animToTop();
case FavTabType.cheese:
Get.find<FavCheeseController>().scrollController
.animToTop();
default:
}
}

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/http/fav.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/fav_order_type.dart';
@@ -5,24 +6,83 @@ import 'package:PiliPlus/models/common/video/source_type.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/data.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/media.dart';
import 'package:PiliPlus/models_new/fav/fav_folder/list.dart';
import 'package:PiliPlus/pages/common/multi_select_controller.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/pages/common/multi_select/base.dart';
import 'package:PiliPlus/pages/common/multi_select/multi_select_controller.dart';
import 'package:PiliPlus/pages/fav_sort/view.dart';
import 'package:PiliPlus/services/account_service.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show ValueChanged;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
mixin BaseFavController
on
CommonListController<FavDetailData, FavDetailItemModel>,
DeleteItemMixin<FavDetailData, FavDetailItemModel> {
bool get isOwner;
int get mediaId;
ValueChanged<int>? updateCount;
void onViewFav(FavDetailItemModel item, int? index);
Future<void> onCancelFav(int index, int id, int type) async {
var result = await FavHttp.favVideo(
resources: '$id:$type',
delIds: mediaId.toString(),
);
if (result['status']) {
loadingState
..value.data!.removeAt(index)
..refresh();
updateCount?.call(1);
SmartDialog.showToast('取消收藏');
} else {
SmartDialog.showToast(result['msg']);
}
}
@override
void onRemove() {
showConfirmDialog(
context: Get.context!,
content: '确认删除所选收藏吗?',
title: '提示',
onConfirm: () async {
final removeList = allChecked.toSet();
var result = await FavHttp.favVideo(
resources: removeList
.map((item) => '${item.id}:${item.type}')
.join(','),
delIds: mediaId.toString(),
);
if (result['status']) {
updateCount?.call(removeList.length);
afterDelete(removeList);
SmartDialog.showToast('取消收藏');
} else {
SmartDialog.showToast(result['msg']);
}
},
);
}
}
class FavDetailController
extends MultiSelectController<FavDetailData, FavDetailItemModel> {
extends MultiSelectController<FavDetailData, FavDetailItemModel>
with BaseFavController {
@override
late int mediaId;
late String heroTag;
final Rx<FavFolderInfo> folderInfo = FavFolderInfo().obs;
final Rx<bool?> isOwner = Rx<bool?>(null);
final Rx<bool?> _isOwner = Rx<bool?>(null);
final Rx<FavOrderType> order = FavOrderType.mtime.obs;
@override
bool get isOwner => _isOwner.value ?? false;
AccountService accountService = Get.find<AccountService>();
@override
@@ -58,28 +118,16 @@ class FavDetailController
if (isRefresh) {
FavDetailData data = response.response;
folderInfo.value = data.info!;
isOwner.value = data.info?.mid == accountService.mid;
_isOwner.value = data.info?.mid == accountService.mid;
}
return false;
}
Future<void> onCancelFav(int index, int id, int type) async {
var result = await FavHttp.favVideo(
resources: '$id:$type',
delIds: mediaId.toString(),
);
if (result['status']) {
folderInfo
..value.mediaCount -= 1
@override
ValueChanged<int>? get updateCount =>
(count) => folderInfo
..value.mediaCount -= count
..refresh();
loadingState
..value.data!.removeAt(index)
..refresh();
SmartDialog.showToast('取消收藏');
} else {
SmartDialog.showToast(result['msg']);
}
}
@override
Future<LoadingState<FavDetailData>> customGetData() =>
@@ -90,64 +138,6 @@ class FavDetailController
order: order.value,
);
void onDelChecked(BuildContext context) {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('确认删除所选收藏吗?'),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
),
),
),
TextButton(
onPressed: () async {
Get.back();
List<FavDetailItemModel> list = loadingState.value.data!
.where((e) => e.checked == true)
.toList();
var result = await FavHttp.favVideo(
resources: list
.map((item) => '${item.id}:${item.type}')
.join(','),
delIds: mediaId.toString(),
);
if (result['status']) {
List<FavDetailItemModel> dataList = loadingState.value.data!;
List<FavDetailItemModel> remainList = dataList
.toSet()
.difference(list.toSet())
.toList();
folderInfo
..value.mediaCount -= list.length
..refresh();
if (remainList.isNotEmpty) {
loadingState.value = Success(remainList);
} else {
onReload();
}
SmartDialog.showToast('取消收藏');
checkedCount.value = 0;
enableMultiSelect.value = false;
} else {
SmartDialog.showToast(result['msg']);
}
},
child: const Text('确认'),
),
],
);
},
);
}
void toViewPlayAll() {
if (loadingState.value.isSuccess) {
List<FavDetailItemModel>? list = loadingState.value.data;
@@ -157,24 +147,7 @@ class FavDetailController
if (element.ugc?.firstCid == null) {
continue;
} else {
if (element.bvid != list.first.bvid) {
SmartDialog.showToast('已跳过不支持播放的视频');
}
final folderInfo = this.folderInfo.value;
PageUtils.toVideoPage(
'bvid=${element.bvid}&cid=${element.ugc!.firstCid}',
arguments: {
'videoItem': element,
'heroTag': Utils.makeHeroTag(element.bvid),
'sourceType': SourceType.fav,
'mediaId': folderInfo.id,
'oid': element.id,
'favTitle': folderInfo.title,
'count': folderInfo.mediaCount,
'desc': true,
'isOwner': isOwner.value ?? false,
},
);
onViewFav(element, null);
break;
}
}
@@ -224,4 +197,25 @@ class FavDetailController
Get.to(FavSortPage(favDetailController: this));
}
}
@override
void onViewFav(FavDetailItemModel item, int? index) {
final folder = folderInfo.value;
PageUtils.toVideoPage(
bvid: item.bvid,
cid: item.ugc!.firstCid!,
cover: item.cover,
title: item.title,
extraArguments: {
'sourceType': SourceType.fav,
'mediaId': folder.id,
'oid': item.id,
'favTitle': folder.title,
'count': folder.mediaCount,
'desc': true,
if (index != null) 'isContinuePlaying': index != 0,
'isOwner': isOwner,
},
);
}
}

View File

@@ -1,4 +1,3 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/video_card_h.dart';
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
@@ -8,7 +7,6 @@ import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/fav.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/fav_order_type.dart';
import 'package:PiliPlus/models/common/video/source_type.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/data.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/media.dart';
import 'package:PiliPlus/models_new/fav/fav_folder/list.dart';
@@ -17,7 +15,6 @@ import 'package:PiliPlus/pages/fav_detail/controller.dart';
import 'package:PiliPlus/pages/fav_detail/widget/fav_video_card.dart';
import 'package:PiliPlus/utils/fav_util.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/request_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -113,7 +110,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
Obx(
() {
return Text(
'已选: ${_favDetailController.checkedCount.value}',
'已选: ${_favDetailController.checkedCount}',
style: const TextStyle(fontSize: 15),
);
},
@@ -156,7 +153,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
'mediaId': int.parse(mediaId),
'title': folderInfo.title,
'count': folderInfo.mediaCount,
'isOwner': _favDetailController.isOwner.value ?? false,
'isOwner': _favDetailController.isOwner,
},
);
},
@@ -198,7 +195,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (context) {
final isOwner = _favDetailController.isOwner.value ?? false;
final isOwner = _favDetailController.isOwner;
final folderInfo = _favDetailController.folderInfo.value;
return [
if (isOwner) ...[
@@ -252,7 +249,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
context: context,
title: '确定删除该收藏夹?',
onConfirm: () =>
FavHttp.deleteFolder(mediaIds: [mediaId]).then((res) {
FavHttp.deleteFolder(mediaIds: mediaId).then((res) {
if (res['status']) {
SmartDialog.showToast('删除成功');
Get.back(result: true);
@@ -327,7 +324,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
style: TextButton.styleFrom(
visualDensity: VisualDensity.compact,
),
onPressed: () => _favDetailController.onDelChecked(context),
onPressed: _favDetailController.onRemove,
child: Text(
'删除',
style: TextStyle(color: theme.colorScheme.error),
@@ -373,7 +370,7 @@ class _FavDetailPageState extends State<FavDetailPage> {
right: 6,
top: 6,
child: Obx(() {
if (_favDetailController.isOwner.value != false) {
if (_favDetailController.isOwner) {
return const SizedBox.shrink();
}
bool isFav = folderInfo.favState == 1;
@@ -485,112 +482,11 @@ class _FavDetailPageState extends State<FavDetailPage> {
),
);
}
final isOwner = _favDetailController.isOwner.value ?? false;
FavDetailItemModel item = response[index];
return Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: FavVideoCardH(
item: item,
onDelFav: isOwner
? () => _favDetailController.onCancelFav(
index,
item.id!,
item.type!,
)
: null,
onViewFav: () {
final folderInfo =
_favDetailController.folderInfo.value;
PageUtils.toVideoPage(
'bvid=${item.bvid}&cid=${item.ugc?.firstCid}',
arguments: {
'videoItem': item,
'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': SourceType.fav,
'mediaId': folderInfo.id,
'oid': item.id,
'favTitle': folderInfo.title,
'count': folderInfo.mediaCount,
'desc': true,
'isContinuePlaying': index != 0,
'isOwner': isOwner,
},
);
},
onTap: enableMultiSelect
? () => _favDetailController.onSelect(item)
: null,
onLongPress: isOwner
? () {
if (!enableMultiSelect) {
_favDetailController
.enableMultiSelect
.value =
true;
_favDetailController.onSelect(item);
}
}
: null,
),
),
Positioned(
top: 5,
left: 12,
bottom: 5,
child: IgnorePointer(
child: LayoutBuilder(
builder: (context, constraints) =>
AnimatedOpacity(
opacity: item.checked == true ? 1 : 0,
duration: const Duration(milliseconds: 200),
child: Container(
alignment: Alignment.center,
height: constraints.maxHeight,
width:
constraints.maxHeight *
StyleString.aspectRatio,
decoration: BoxDecoration(
borderRadius: StyleString.mdRadius,
color: Colors.black.withValues(
alpha: 0.6,
),
),
child: SizedBox(
width: 34,
height: 34,
child: AnimatedScale(
scale: item.checked == true ? 1 : 0,
duration: const Duration(
milliseconds: 250,
),
curve: Curves.easeInOut,
child: IconButton(
style: ButtonStyle(
padding: WidgetStateProperty.all(
EdgeInsets.zero,
),
backgroundColor:
WidgetStatePropertyAll(
theme.colorScheme.surface
.withValues(alpha: 0.8),
),
),
onPressed: null,
icon: Icon(
Icons.done_all_outlined,
color: theme.colorScheme.primary,
),
),
),
),
),
),
),
),
),
],
return FavVideoCardH(
item: item,
index: index,
ctr: _favDetailController,
);
},
childCount: response!.length + 1,

View File

@@ -3,10 +3,12 @@ import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/image/image_save.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/select_mask.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/common/stat_type.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/media.dart';
import 'package:PiliPlus/pages/fav_detail/controller.dart';
import 'package:PiliPlus/utils/date_util.dart';
import 'package:PiliPlus/utils/duration_util.dart';
import 'package:PiliPlus/utils/page_utils.dart';
@@ -16,55 +18,60 @@ import 'package:get/get.dart';
// 收藏视频卡片 - 水平布局
class FavVideoCardH extends StatelessWidget {
final FavDetailItemModel item;
final GestureTapCallback? onTap;
final GestureLongPressCallback? onLongPress;
final VoidCallback? onDelFav;
final VoidCallback? onViewFav;
final bool? isSort;
final int? index;
final BaseFavController? ctr;
const FavVideoCardH({
super.key,
required this.item,
this.onDelFav,
this.onTap,
this.onLongPress,
this.onViewFav,
this.isSort,
});
this.index,
this.ctr,
}) : assert(ctr == null || index != null);
bool get isSort => ctr == null;
@override
Widget build(BuildContext context) {
final isOwner = !isSort && ctr!.isOwner;
late final enableMultiSelect = ctr?.enableMultiSelect.value ?? false;
final theme = Theme.of(context);
return Material(
type: MaterialType.transparency,
child: InkWell(
onTap: isSort == true
onTap: isSort
? null
: onTap ??
() {
if (!const [0, 16].contains(item.attr)) {
Get.toNamed('/member?mid=${item.upper?.mid}');
return;
}
: enableMultiSelect
? () => ctr!.onSelect(item)
: () {
if (!const [0, 16].contains(item.attr)) {
Get.toNamed('/member?mid=${item.upper?.mid}');
return;
}
// pgc
if (item.type == 24) {
PageUtils.viewPgc(
seasonId: item.ogv!.seasonId,
epId: item.id,
);
return;
}
// pgc
if (item.type == 24) {
PageUtils.viewPgc(
seasonId: item.ogv!.seasonId,
epId: item.id,
);
return;
}
onViewFav?.call();
},
onLongPress: isSort == true
ctr!.onViewFav(item, index);
},
onLongPress: isSort
? null
: onLongPress ??
() => imageSaveDialog(
title: item.title,
cover: item.cover,
bvid: item.bvid,
),
: isOwner && !enableMultiSelect
? () {
ctr!
..enableMultiSelect.value = true
..onSelect(item);
}
: () => imageSaveDialog(
title: item.title,
cover: item.cover,
bvid: item.bvid,
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
@@ -100,13 +107,20 @@ class FavVideoCardH extends StatelessWidget {
bottom: null,
left: null,
),
if (!isSort)
Positioned.fill(
child: selectMask(
theme,
item.checked == true,
),
),
],
);
},
),
),
const SizedBox(width: 10),
content(context),
content(context, theme, isOwner),
],
),
),
@@ -114,8 +128,7 @@ class FavVideoCardH extends StatelessWidget {
);
}
Widget content(BuildContext context) {
final theme = Theme.of(context);
Widget content(BuildContext context, ThemeData theme, isOwner) {
return Expanded(
child: Stack(
clipBehavior: Clip.none,
@@ -170,7 +183,7 @@ class FavVideoCardH extends StatelessWidget {
),
],
),
if (onDelFav != null)
if (isOwner)
Positioned(
right: 0,
bottom: -8,
@@ -197,7 +210,7 @@ class FavVideoCardH extends StatelessWidget {
TextButton(
onPressed: () {
Get.back();
onDelFav!();
ctr!.onCancelFav(index!, item.id!, item.type!);
},
child: const Text('确定取消'),
),

View File

@@ -33,7 +33,7 @@ class _FavFolderSortPageState extends State<FavFolderSortPage> {
TextButton(
onPressed: () async {
var res = await FavHttp.sortFavFolder(
sort: sortList.map((item) => item.id).toList(),
sort: sortList.map((item) => item.id).join(','),
);
if (res['status']) {
SmartDialog.showToast('排序完成');

View File

@@ -1,16 +1,25 @@
import 'package:PiliPlus/http/fav.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/fav_order_type.dart';
import 'package:PiliPlus/models/common/video/source_type.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/data.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/media.dart';
import 'package:PiliPlus/pages/common/common_search_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:PiliPlus/pages/common/multi_select/base.dart';
import 'package:PiliPlus/pages/common/search/common_search_controller.dart';
import 'package:PiliPlus/pages/fav_detail/controller.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:get/get.dart';
class FavSearchController
extends CommonSearchController<FavDetailData, FavDetailItemModel> {
extends CommonSearchController<FavDetailData, FavDetailItemModel>
with
CommonMultiSelectMixin<FavDetailItemModel>,
DeleteItemMixin,
BaseFavController {
int type = Get.arguments['type'];
@override
int mediaId = Get.arguments['mediaId'];
@override
bool isOwner = Get.arguments['isOwner'];
dynamic count = Get.arguments['count'];
dynamic title = Get.arguments['title'];
@@ -36,17 +45,20 @@ class FavSearchController
return response.medias;
}
Future<void> onCancelFav(int index, int id, int? type) async {
var result = await FavHttp.favVideo(
resources: '$id:$type',
addIds: '',
delIds: mediaId.toString(),
);
if (result['status']) {
loadingState
..value.data!.removeAt(index)
..refresh();
SmartDialog.showToast('取消收藏');
}
}
@override
void onViewFav(FavDetailItemModel item, int? index) => PageUtils.toVideoPage(
bvid: item.bvid,
cid: item.ugc!.firstCid!,
cover: item.cover,
title: item.title,
extraArguments: {
'sourceType': SourceType.fav,
'mediaId': mediaId,
'oid': item.id,
'favTitle': title,
'count': count,
'desc': true,
'isContinuePlaying': true,
},
);
}

View File

@@ -1,12 +1,10 @@
import 'package:PiliPlus/models/common/fav_order_type.dart';
import 'package:PiliPlus/models/common/video/source_type.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/data.dart';
import 'package:PiliPlus/models_new/fav/fav_detail/media.dart';
import 'package:PiliPlus/pages/common/common_search_page.dart';
import 'package:PiliPlus/pages/common/search/common_search_page.dart';
import 'package:PiliPlus/pages/fav_detail/widget/fav_video_card.dart';
import 'package:PiliPlus/pages/fav_search/controller.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -69,27 +67,8 @@ class _FavSearchPageState
final item = list[index];
return FavVideoCardH(
item: item,
onDelFav: controller.isOwner == true
? () => controller.onCancelFav(
index,
item.id!,
item.type,
)
: null,
onViewFav: () => PageUtils.toVideoPage(
'bvid=${item.bvid}&cid=${item.ugc?.firstCid}',
arguments: {
'videoItem': item,
'heroTag': Utils.makeHeroTag(item.bvid),
'sourceType': SourceType.fav,
'mediaId': controller.mediaId,
'oid': item.id,
'favTitle': controller.title,
'count': controller.count,
'desc': true,
'isContinuePlaying': true,
},
),
index: index,
ctr: controller,
);
},
),

View File

@@ -79,7 +79,7 @@ class _FavSortPageState extends State<FavSortPage> {
}
var res = await FavHttp.sortFav(
mediaId: _favDetailController.mediaId,
sort: sort,
sort: sort.join(','),
);
if (res['status']) {
SmartDialog.showToast('排序完成');
@@ -136,10 +136,7 @@ class _FavSortPageState extends State<FavSortPage> {
return SizedBox(
key: Key(item.id.toString()),
height: 98,
child: FavVideoCardH(
isSort: true,
item: item,
),
child: FavVideoCardH(item: item),
);
},
);

View File

@@ -32,11 +32,10 @@ class FollowController extends GetxController with GetTickerProviderStateMixin {
Future<void> queryFollowUpTags() async {
var res = await MemberHttp.followUpTags();
if (res['status']) {
if (res.isSuccess) {
tabs
..clear()
..addAll(res['data'])
..insert(0, MemberTagItemModel(name: '全部关注'));
..assign(MemberTagItemModel(name: '全部关注'))
..addAll(res.data);
int initialIndex = 0;
if (tabController != null) {
initialIndex = tabController!.index.clamp(0, tabs.length - 1);
@@ -49,7 +48,7 @@ class FollowController extends GetxController with GetTickerProviderStateMixin {
);
followState.value = Success(tabs.hashCode);
} else {
followState.value = Error(res['msg']);
followState.value = res;
}
}

View File

@@ -2,7 +2,7 @@ import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models_new/follow/data.dart';
import 'package:PiliPlus/models_new/follow/list.dart';
import 'package:PiliPlus/pages/common/common_search_controller.dart';
import 'package:PiliPlus/pages/common/search/common_search_controller.dart';
class FollowSearchController
extends CommonSearchController<FollowData, FollowItemModel> {

View File

@@ -1,6 +1,6 @@
import 'package:PiliPlus/models_new/follow/data.dart';
import 'package:PiliPlus/models_new/follow/list.dart';
import 'package:PiliPlus/pages/common/common_search_page.dart';
import 'package:PiliPlus/pages/common/search/common_search_page.dart';
import 'package:PiliPlus/pages/follow/widgets/follow_item.dart';
import 'package:PiliPlus/pages/follow_search/controller.dart';
import 'package:PiliPlus/utils/utils.dart';

View File

@@ -2,18 +2,19 @@ import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/member/tags.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/feed_back.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class GroupPanel extends StatefulWidget {
final int? mid;
final int mid;
final List? tags;
final ScrollController? scrollController;
const GroupPanel({
super.key,
this.mid,
required this.mid,
this.tags,
this.scrollController,
});
@@ -23,9 +24,9 @@ class GroupPanel extends StatefulWidget {
}
class _GroupPanelState extends State<GroupPanel> {
LoadingState<List<MemberTagItemModel>> loadingState =
LoadingState<List<MemberTagItemModel>>.loading();
LoadingState<List<MemberTagItemModel>> loadingState = LoadingState.loading();
RxBool showDefaultBtn = true.obs;
late final Set<int> tags = widget.tags?.cast<int>().toSet() ?? {};
@override
void initState() {
@@ -36,19 +37,8 @@ class _GroupPanelState extends State<GroupPanel> {
void _query() {
MemberHttp.followUpTags().then((res) {
if (mounted) {
if (res['status']) {
List<MemberTagItemModel> tagsList =
(res['data'] as List<MemberTagItemModel>)
..removeWhere((item) => item.tagid == 0)
..map((item) {
return item.checked =
widget.tags?.contains(item.tagid) == true;
}).toList();
showDefaultBtn.value = !tagsList.any((e) => e.checked == true);
loadingState = Success(tagsList);
} else {
loadingState = Error(res['msg']);
}
loadingState = res..dataOrNull.removeFirstWhere((e) => e.tagid == 0);
showDefaultBtn.value = tags.isEmpty;
setState(() {});
}
});
@@ -60,25 +50,14 @@ class _GroupPanelState extends State<GroupPanel> {
return;
}
feedBack();
// 是否有选中的 有选中的带id没选使用默认0
List<MemberTagItemModel> tagsList = loadingState.data;
final bool anyHasChecked = tagsList.any(
(MemberTagItemModel e) => e.checked == true,
);
late List<int> tagidList;
if (anyHasChecked) {
final List<MemberTagItemModel> checkedList = tagsList
.where((MemberTagItemModel e) => e.checked == true)
.toList();
tagidList = checkedList.map<int>((e) => e.tagid!).toList();
} else {
tagidList = [0];
}
// 保存
final res = await MemberHttp.addUsers([widget.mid], tagidList);
final res = await MemberHttp.addUsers(
widget.mid.toString(),
tags.isEmpty ? '0' : tags.join(','),
);
SmartDialog.showToast(res['msg']);
if (res['status']) {
Get.back(result: tagidList);
Get.back(result: tags);
}
}
@@ -95,11 +74,16 @@ class _GroupPanelState extends State<GroupPanel> {
child: Builder(
builder: (context) {
void onTap() {
item.checked = !item.checked!;
final tagid = item.tagid!;
if (tags.contains(tagid)) {
tags.remove(tagid);
item.count--;
} else {
tags.add(tagid);
item.count++;
}
(context as Element).markNeedsBuild();
showDefaultBtn.value = !response.any(
(e) => e.checked == true,
);
showDefaultBtn.value = tags.isEmpty;
}
return ListTile(
@@ -107,15 +91,15 @@ class _GroupPanelState extends State<GroupPanel> {
dense: true,
leading: const Icon(Icons.group_outlined),
minLeadingWidth: 0,
title: Text(item.name ?? ''),
title: Text('${item.name} (${item.count})'),
subtitle: item.tip?.isNotEmpty == true
? Text(item.tip!)
: null,
trailing: Transform.scale(
scale: 0.9,
child: Checkbox(
value: item.checked,
onChanged: (bool? checkValue) => onTap(),
value: tags.contains(item.tagid),
onChanged: (_) => onTap(),
),
),
);

Some files were not shown because too many files have changed in this diff Show More