diff --git a/lib/common/constants.dart b/lib/common/constants.dart index dda54361..0607206c 100644 --- a/lib/common/constants.dart +++ b/lib/common/constants.dart @@ -15,5 +15,4 @@ class Constants { // 59b43e04ad6965f34319062b478f83dd TV端 static const String appSec = '59b43e04ad6965f34319062b478f83dd'; static const String thirdSign = '04224646d1fea004e79606d3b038c84a'; - static const List publicFavFolder = [0, 2, 22]; } diff --git a/lib/common/pages_bottom_sheet.dart b/lib/common/pages_bottom_sheet.dart index d7872f13..dd8c5490 100644 --- a/lib/common/pages_bottom_sheet.dart +++ b/lib/common/pages_bottom_sheet.dart @@ -3,7 +3,9 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/video_detail_res.dart'; +import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/utils/utils.dart'; import 'package:scrollview_observer/scrollview_observer.dart'; import '../models/common/video_episode_type.dart'; @@ -20,6 +22,8 @@ class EpisodeBottomSheet { final double? sheetHeight; bool isFullScreen = false; final UgcSeason? ugcSeason; + final int? currentEpisodeIndex; + final int? currentIndex; EpisodeBottomSheet({ required this.episodes, @@ -30,6 +34,8 @@ class EpisodeBottomSheet { this.sheetHeight, this.isFullScreen = false, this.ugcSeason, + this.currentEpisodeIndex, + this.currentIndex, }); Widget buildShowContent() { @@ -42,6 +48,8 @@ class EpisodeBottomSheet { sheetHeight: sheetHeight, isFullScreen: isFullScreen, ugcSeason: ugcSeason, + currentEpisodeIndex: currentEpisodeIndex, + currentIndex: currentIndex, ); } @@ -67,6 +75,8 @@ class PagesBottomSheet extends StatefulWidget { this.sheetHeight, this.isFullScreen = false, this.ugcSeason, + this.currentEpisodeIndex, + this.currentIndex, }); final List episodes; @@ -77,41 +87,38 @@ class PagesBottomSheet extends StatefulWidget { final double? sheetHeight; final bool isFullScreen; final UgcSeason? ugcSeason; + final int? currentEpisodeIndex; + final int? currentIndex; @override State createState() => _PagesBottomSheetState(); } -class _PagesBottomSheetState extends State { +class _PagesBottomSheetState extends State + with TickerProviderStateMixin { final ScrollController _listScrollController = ScrollController(); late ListObserverController _listObserverController; final ScrollController _scrollController = ScrollController(); late int currentIndex; + TabController? tabController; + List? _listObserverControllerList; + List? _listScrollControllerList; + final String heroTag = Get.arguments['heroTag']; + VideoDetailController? _videoDetailController; + RxInt isSubscribe = (-1).obs; + bool isVisible = false; @override void initState() { super.initState(); - currentIndex = + currentIndex = widget.currentIndex ?? widget.episodes.indexWhere((dynamic e) => e.cid == widget.currentCid); - _listObserverController = - ListObserverController(controller: _listScrollController); + _scrollToInit(); + _scrollPositionInit(); if (widget.dataType == VideoEpidoesType.videoEpisode) { - _listObserverController.initialIndexModel = ObserverIndexPositionModel( - index: currentIndex, - isFixedHeight: true, - ); + _videoDetailController = Get.find(tag: heroTag); + _getSubscribeStatus(); } - - WidgetsBinding.instance.addPostFrameCallback((_) { - if (widget.dataType != VideoEpidoesType.videoEpisode) { - double itemHeight = (widget.isFullScreen - ? 400 - : Get.size.width - 3 * StyleString.safeSpace) / - 5.2; - double offset = ((currentIndex - 1) / 2).ceil() * itemHeight; - _scrollController.jumpTo(offset); - } - }); } String prefix() { @@ -126,9 +133,117 @@ class _PagesBottomSheetState extends State { return '选集'; } + // 滚动器初始化 + void _scrollToInit() { + /// 单个 + _listObserverController = + ListObserverController(controller: _listScrollController); + + if (widget.dataType == VideoEpidoesType.videoEpisode && + widget.ugcSeason?.sections != null && + widget.ugcSeason!.sections!.length > 1) { + tabController = TabController( + length: widget.ugcSeason!.sections!.length, + vsync: this, + initialIndex: widget.currentEpisodeIndex ?? 0, + ); + + /// 多tab + _listScrollControllerList = List.generate( + widget.ugcSeason!.sections!.length, + (index) { + return ScrollController(); + }, + ); + _listObserverControllerList = List.generate( + widget.ugcSeason!.sections!.length, + (index) { + return ListObserverController( + controller: _listScrollControllerList![index], + ); + }, + ); + } + } + + // 滚动器位置初始化 + void _scrollPositionInit() { + if (widget.dataType == VideoEpidoesType.videoEpisode) { + // 单个 多tab + if (widget.ugcSeason?.sections != null) { + if (widget.ugcSeason!.sections!.length == 1) { + _listObserverController.initialIndexModel = + ObserverIndexPositionModel( + index: currentIndex, + isFixedHeight: true, + ); + } else { + _listObserverControllerList![widget.currentEpisodeIndex!] + .initialIndexModel = ObserverIndexPositionModel( + index: currentIndex, + isFixedHeight: true, + ); + } + } + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.dataType != VideoEpidoesType.videoEpisode) { + double itemHeight = (widget.isFullScreen + ? 400 + : Get.size.width - 3 * StyleString.safeSpace) / + 5.2; + double offset = ((currentIndex - 1) / 2).ceil() * itemHeight; + _scrollController.jumpTo(offset); + } + }); + } + + // 获取订阅状态 + void _getSubscribeStatus() async { + var res = + await VideoHttp.getSubscribeStatus(bvid: _videoDetailController!.bvid); + if (res['status']) { + isSubscribe.value = res['data']['season_fav'] ? 1 : 0; + } + } + + // 更改订阅状态 + void _changeSubscribeStatus() async { + if (isSubscribe.value == -1) { + return; + } + dynamic result = await VideoHttp.seasonFav( + isFav: isSubscribe.value == 1, + seasonId: widget.ugcSeason!.id, + ); + if (result['status']) { + SmartDialog.showToast(isSubscribe.value == 1 ? '取消订阅成功' : '订阅成功'); + isSubscribe.value = isSubscribe.value == 1 ? 0 : 1; + } else { + SmartDialog.showToast(result['msg']); + } + } + + // 更改展开状态 + void _changeVisible() { + setState(() { + isVisible = !isVisible; + }); + } + @override void dispose() { - _listObserverController.controller?.dispose(); + try { + _listObserverController.controller?.dispose(); + _listScrollController.dispose(); + for (var element in _listObserverControllerList!) { + element.controller?.dispose(); + } + for (var element in _listScrollControllerList!) { + element.dispose(); + } + } catch (_) {} super.dispose(); } @@ -145,36 +260,46 @@ class _PagesBottomSheetState extends State { isFullScreen: widget.isFullScreen, ), if (widget.ugcSeason != null) ...[ - UgcSeasonBuild(ugcSeason: widget.ugcSeason!), + UgcSeasonBuild( + ugcSeason: widget.ugcSeason!, + isSubscribe: isSubscribe, + isVisible: isVisible, + changeFucCall: _changeSubscribeStatus, + changeVisible: _changeVisible, + ), ], Expanded( child: Material( child: widget.dataType == VideoEpidoesType.videoEpisode - ? ListViewObserver( - controller: _listObserverController, - child: ListView.builder( - controller: _listScrollController, - itemCount: widget.episodes.length + 1, - itemBuilder: (BuildContext context, int index) { - bool isLastItem = index == widget.episodes.length; - bool isCurrentIndex = currentIndex == index; - return isLastItem - ? SizedBox( - height: - MediaQuery.of(context).padding.bottom + + ? (widget.ugcSeason!.sections!.length == 1 + ? ListViewObserver( + controller: _listObserverController, + child: ListView.builder( + controller: _listScrollController, + itemCount: widget.episodes.length + 1, + itemBuilder: (BuildContext context, int index) { + bool isLastItem = + index == widget.episodes.length; + bool isCurrentIndex = currentIndex == index; + return isLastItem + ? SizedBox( + height: MediaQuery.of(context) + .padding + .bottom + 20, - ) - : EpisodeListItem( - episode: widget.episodes[index], - index: index, - isCurrentIndex: isCurrentIndex, - dataType: widget.dataType, - changeFucCall: widget.changeFucCall, - isFullScreen: widget.isFullScreen, - ); - }, - ), - ) + ) + : EpisodeListItem( + episode: widget.episodes[index], + index: index, + isCurrentIndex: isCurrentIndex, + dataType: widget.dataType, + changeFucCall: widget.changeFucCall, + isFullScreen: widget.isFullScreen, + ); + }, + ), + ) + : buildTabBar()) : Padding( padding: const EdgeInsets.symmetric( horizontal: 12.0), // 设置左右间距为12 @@ -206,6 +331,65 @@ class _PagesBottomSheetState extends State { ); }); } + + Widget buildTabBar() { + return Column( + children: [ + // 背景色 + Container( + color: Theme.of(context).colorScheme.surface, + child: TabBar( + controller: tabController, + isScrollable: true, + indicatorSize: TabBarIndicatorSize.label, + tabAlignment: TabAlignment.start, + splashBorderRadius: BorderRadius.circular(4), + tabs: [ + ...widget.ugcSeason!.sections!.map((SectionItem section) { + return Tab( + text: section.title, + ); + }).toList() + ], + ), + ), + Expanded( + child: TabBarView( + controller: tabController, + children: [ + ...widget.ugcSeason!.sections!.map((SectionItem section) { + final int fIndex = widget.ugcSeason!.sections!.indexOf(section); + return ListViewObserver( + controller: _listObserverControllerList![fIndex], + child: ListView.builder( + controller: _listScrollControllerList![fIndex], + itemCount: section.episodes!.length + 1, + itemBuilder: (BuildContext context, int index) { + final bool isLastItem = index == section.episodes!.length; + return isLastItem + ? SizedBox( + height: + MediaQuery.of(context).padding.bottom + 20, + ) + : EpisodeListItem( + episode: section.episodes![index], // 调整索引 + index: index, // 调整索引 + isCurrentIndex: widget.currentCid == + section.episodes![index].cid, + dataType: widget.dataType, + changeFucCall: widget.changeFucCall, + isFullScreen: widget.isFullScreen, + ); + }, + ), + ); + }).toList() + ], + ), + ), + ], + ); + } } class TitleBar extends StatelessWidget { @@ -507,77 +691,134 @@ class EpisodeGridItem extends StatelessWidget { class UgcSeasonBuild extends StatelessWidget { final UgcSeason ugcSeason; + final RxInt isSubscribe; + final bool isVisible; + final Function changeFucCall; + final Function changeVisible; const UgcSeasonBuild({ Key? key, required this.ugcSeason, + required this.isSubscribe, + required this.isVisible, + required this.changeFucCall, + required this.changeVisible, }) : super(key: key); @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.fromLTRB(12, 0, 12, 8), - color: Theme.of(context).colorScheme.surface, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Divider( - height: 1, - thickness: 1, - color: Theme.of(context).dividerColor.withOpacity(0.1), - ), - const SizedBox(height: 10), - Text( - '合集:${ugcSeason.title}', - style: Theme.of(context).textTheme.titleMedium, - overflow: TextOverflow.ellipsis, - ), - if (ugcSeason.intro != null && ugcSeason.intro != '') ...[ - const SizedBox(height: 4), - Row( + final ThemeData theme = Theme.of(context); + final Color outline = theme.colorScheme.outline; + final Color surface = theme.colorScheme.surface; + final Color primary = theme.colorScheme.primary; + final Color onPrimary = theme.colorScheme.onPrimary; + final Color onInverseSurface = theme.colorScheme.onInverseSurface; + final TextStyle titleMedium = theme.textTheme.titleMedium!; + final TextStyle labelMedium = theme.textTheme.labelMedium!; + final Color dividerColor = theme.dividerColor.withOpacity(0.1); + + return isVisible + ? Container( + padding: const EdgeInsets.fromLTRB(12, 0, 12, 0), + color: surface, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Text(ugcSeason.intro ?? '', - style: TextStyle( - color: Theme.of(context).colorScheme.outline)), + Divider(height: 1, thickness: 1, color: dividerColor), + const SizedBox(height: 10), + Row( + children: [ + Expanded( + child: Text( + '合集:${ugcSeason.title}', + style: titleMedium, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + const SizedBox(width: 10), + Obx( + () => isSubscribe.value == -1 + ? const SizedBox(height: 32) + : SizedBox( + height: 32, + child: FilledButton.tonal( + onPressed: () => changeFucCall.call(), + style: TextButton.styleFrom( + padding: + const EdgeInsets.only(left: 8, right: 8), + foregroundColor: isSubscribe.value == 1 + ? outline + : onPrimary, + backgroundColor: isSubscribe.value == 1 + ? onInverseSurface + : primary, + ), + child: + Text(isSubscribe.value == 1 ? '已订阅' : '订阅'), + ), + ), + ), + ], ), - // SizedBox( - // height: 32, - // child: FilledButton.tonal( - // onPressed: () {}, - // style: ButtonStyle( - // padding: MaterialStateProperty.all(EdgeInsets.zero), - // ), - // child: const Text('订阅'), - // ), - // ), - // const SizedBox(width: 6), + if (ugcSeason.intro != null && ugcSeason.intro != '') ...[ + const SizedBox(height: 4), + Text( + ugcSeason.intro!, + style: TextStyle(color: outline, fontSize: 12), + ), + ], + const SizedBox(height: 4), + Text.rich( + TextSpan( + style: TextStyle( + fontSize: labelMedium.fontSize, color: outline), + children: [ + TextSpan( + text: '${Utils.numFormat(ugcSeason.stat!.view)}播放'), + const TextSpan(text: ' · '), + TextSpan( + text: + '${Utils.numFormat(ugcSeason.stat!.danmaku)}弹幕'), + ], + ), + ), + const SizedBox(height: 14), + Align( + alignment: Alignment.center, + child: Material( + color: surface, + child: InkWell( + onTap: () => changeVisible.call(), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 10, horizontal: 0), + child: Text( + '收起简介', + style: TextStyle(color: primary, fontSize: 12), + ), + ), + ), + ), + ), + Divider(height: 1, thickness: 1, color: dividerColor), ], ), - ], - const SizedBox(height: 4), - Text.rich( - TextSpan( - style: TextStyle( - fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, - color: Theme.of(context).colorScheme.outline, + ) + : Align( + alignment: Alignment.center, + child: InkWell( + onTap: () => changeVisible.call(), + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 10, horizontal: 0), + child: Text( + '展开简介', + style: TextStyle(color: primary, fontSize: 12), + ), ), - children: [ - TextSpan(text: '${Utils.numFormat(ugcSeason.stat!.view)}播放'), - const TextSpan(text: ' · '), - TextSpan(text: '${Utils.numFormat(ugcSeason.stat!.danmaku)}弹幕'), - ], ), - ), - const SizedBox(height: 14), - Divider( - height: 1, - thickness: 1, - color: Theme.of(context).dividerColor.withOpacity(0.1), - ), - ], - ), - ); + ); } } diff --git a/lib/http/api.dart b/lib/http/api.dart index 13fb19c8..40b4fd5d 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -609,4 +609,14 @@ class Api { /// @我的 static const String messageAtAPi = '/x/msgfeed/at?'; + + /// 订阅 + static const String confirmSub = '/x/v3/fav/season/fav'; + + /// 订阅状态 + static const String videoRelation = '/x/web-interface/archive/relation'; + + /// 获取空降区间 + static const String getSkipSegments = + '${HttpString.sponsorBlockBaseUrl}/api/skipSegments'; } diff --git a/lib/http/common.dart b/lib/http/common.dart index d711a7e7..2f5f0e84 100644 --- a/lib/http/common.dart +++ b/lib/http/common.dart @@ -1,3 +1,5 @@ +import 'package:pilipala/models/sponsor_block/segment.dart'; + import 'index.dart'; class CommonHttp { @@ -14,4 +16,31 @@ class CommonHttp { }; } } + + static Future querySkipSegments({required String bvid}) async { + var res = await Request().getWithoutCookie(Api.getSkipSegments, data: { + 'videoID': bvid, + }); + if (res.data is List && res.data.isNotEmpty) { + try { + return { + 'status': true, + 'data': res.data + .map((e) => SegmentDataModel.fromJson(e)) + .toList(), + }; + } catch (err) { + return { + 'status': false, + 'data': [], + 'msg': 'sponsorBlock数据解析失败: $err', + }; + } + } else { + return { + 'status': false, + 'data': [], + }; + } + } } diff --git a/lib/http/constants.dart b/lib/http/constants.dart index b734c279..07d06958 100644 --- a/lib/http/constants.dart +++ b/lib/http/constants.dart @@ -7,6 +7,7 @@ class HttpString { static const String passBaseUrl = 'https://passport.bilibili.com'; static const String messageBaseUrl = 'https://message.bilibili.com'; static const String bangumiBaseUrl = 'https://bili.meark.me'; + static const String sponsorBlockBaseUrl = 'https://www.bsbsb.top'; static const List validateStatusCodes = [ 302, 304, diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index 9f9bc1c1..b33d18df 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -3,6 +3,7 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:pilipala/utils/login.dart'; class ApiInterceptor extends Interceptor { @override @@ -18,6 +19,9 @@ class ApiInterceptor extends Interceptor { void onResponse(Response response, ResponseInterceptorHandler handler) { try { // 在响应之后处理数据 + if (response.data is Map && response.data['code'] == -101) { + LoginUtils.loginOut(); + } } catch (err) { print('ApiInterceptor: $err'); } diff --git a/lib/http/user.dart b/lib/http/user.dart index 63f6bf66..99888aea 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -516,4 +516,34 @@ class UserHttp { }; } } + + // 解析up投稿 + static Future parseUpArchiveVideo({ + required int mid, + required int oid, + required String bvid, + String sortField = 'pubtime', + }) async { + var res = await Request().get( + 'https://www.bilibili.com/list/$mid', + data: { + 'oid': oid, + 'bvid': bvid, + 'sort_field': sortField, + }, + ); + String scriptContent = + extractScriptContents(parse(res.data).body!.outerHtml)[0]; + int startIndex = scriptContent.indexOf('{'); + int endIndex = scriptContent.lastIndexOf('};'); + String jsonContent = scriptContent.substring(startIndex, endIndex + 1); + // 解析JSON字符串为Map + Map jsonData = json.decode(jsonContent); + return { + 'status': true, + 'data': jsonData['resourceList'] + .map((e) => MediaVideoItemModel.fromJson(e)) + .toList() + }; + } } diff --git a/lib/http/video.dart b/lib/http/video.dart index 03209ab7..b5d47fa3 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:developer'; import 'package:dio/dio.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/utils/id_utils.dart'; import '../common/constants.dart'; import '../models/common/reply_type.dart'; import '../models/home/rcmd/result.dart'; @@ -97,6 +98,8 @@ class VideoHttp { for (var i in res.data['data']['items']) { // 屏蔽推广和拉黑用户 if (i['card_goto'] != 'ad_av' && + i['card_goto'] != 'ad_web_s' && + i['card_goto'] != 'ad_web' && (!enableRcmdDynamic ? i['card_goto'] != 'picture' : true) && (i['args'] != null && !blackMidsList.contains(i['args']['up_mid']))) { @@ -558,4 +561,50 @@ class VideoHttp { final List body = res.data['body']; return {'content': content, 'body': body}; } + + static Future> getSubscribeStatus( + {required dynamic bvid}) async { + var res = await Request().get( + Api.videoRelation, + data: { + 'aid': IdUtils.bv2av(bvid), + 'bvid': bvid, + }, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'msg': res.data['message'], + }; + } + } + + static Future seasonFav({ + required bool isFav, + required dynamic seasonId, + }) async { + var res = await Request().post( + isFav ? Api.cancelSub : Api.confirmSub, + data: { + 'platform': 'web', + 'season_id': seasonId, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return { + 'status': true, + }; + } else { + return { + 'status': false, + 'msg': res.data['message'], + }; + } + } } diff --git a/lib/models/sponsor_block/action_type.dart b/lib/models/sponsor_block/action_type.dart new file mode 100644 index 00000000..d89fcf08 --- /dev/null +++ b/lib/models/sponsor_block/action_type.dart @@ -0,0 +1,26 @@ +// 片段类型枚举 +enum ActionType { + skip, + mute, + full, + poi, + chapter, +} + +extension ActionTypeExtension on ActionType { + String get value => [ + 'skip', + 'mute', + 'full', + 'poi', + 'chapter', + ][index]; + + String get label => [ + '跳过', + '静音', + '完整观看', + '亮点', + '章节切换', + ][index]; +} diff --git a/lib/models/sponsor_block/segment.dart b/lib/models/sponsor_block/segment.dart new file mode 100644 index 00000000..7e6a387f --- /dev/null +++ b/lib/models/sponsor_block/segment.dart @@ -0,0 +1,43 @@ +import 'action_type.dart'; +import 'segment_type.dart'; + +class SegmentDataModel { + final SegmentType? category; + final ActionType? actionType; + final List? segment; + final String? uuid; + final num? videoDuration; + final int? locked; + final int? votes; + final String? description; + // 是否已经跳过 + bool isSkip = false; + + SegmentDataModel({ + this.category, + this.actionType, + this.segment, + this.uuid, + this.videoDuration, + this.locked, + this.votes, + this.description, + }); + + factory SegmentDataModel.fromJson(Map json) { + return SegmentDataModel( + category: SegmentType.values.firstWhere( + (e) => e.value == json['category'], + orElse: () => SegmentType.sponsor), + actionType: ActionType.values.firstWhere( + (e) => e.value == json['actionType'], + orElse: () => ActionType.skip), + segment: json['segment'], + uuid: json['UUID'], + videoDuration: json['videoDuration'], + locked: json['locked'], + votes: json['votes'], + description: json['description'], + ); + } +} diff --git a/lib/models/sponsor_block/segment_type.dart b/lib/models/sponsor_block/segment_type.dart new file mode 100644 index 00000000..b4e3075c --- /dev/null +++ b/lib/models/sponsor_block/segment_type.dart @@ -0,0 +1,46 @@ +// 片段类型枚举 +// ignore_for_file: constant_identifier_names + +enum SegmentType { + sponsor, + intro, + outro, + interaction, + selfpromo, + music_offtopic, + preview, + poi_highlight, + filler, + exclusive_access, + chapter, +} + +extension SegmentTypeExtension on SegmentType { + String get value => [ + 'sponsor', + 'intro', + 'outro', + 'interaction', + 'selfpromo', + 'music_offtopic', + 'preview', + 'poi_highlight', + 'filler', + 'exclusive_access', + 'chapter', + ][index]; + + String get label => [ + '赞助', + '开场介绍', + '片尾致谢', + '互动', + '自我推广', + '音乐', + '预览', + '亮点', + '无效填充', + '独家访问', + '章节', + ][index]; +} diff --git a/lib/models/video_detail_res.dart b/lib/models/video_detail_res.dart index 3401d809..ad52a840 100644 --- a/lib/models/video_detail_res.dart +++ b/lib/models/video_detail_res.dart @@ -641,6 +641,7 @@ class EpisodeItem { this.page, this.bvid, this.cover, + this.pages, }); int? seasonId; int? sectionId; @@ -655,6 +656,7 @@ class EpisodeItem { int? pubdate; int? duration; Stat? stat; + List? pages; EpisodeItem.fromJson(Map json) { seasonId = json['season_id']; @@ -670,6 +672,7 @@ class EpisodeItem { pubdate = json['arc']['pubdate']; duration = json['arc']['duration']; stat = Stat.fromJson(json['arc']['stat']); + pages = json['pages'].map((e) => Page.fromJson(e)).toList(); } } @@ -712,3 +715,18 @@ class Vip { status = json['status']; } } + +class Page { + Page({ + this.cid, + this.page, + }); + + int? cid; + int? page; + + Page.fromJson(Map json) { + cid = json['cid']; + page = json['page']; + } +} diff --git a/lib/pages/bangumi/introduction/controller.dart b/lib/pages/bangumi/introduction/controller.dart index cec5e3f6..2e6cd392 100644 --- a/lib/pages/bangumi/introduction/controller.dart +++ b/lib/pages/bangumi/introduction/controller.dart @@ -24,7 +24,7 @@ class BangumiIntroController extends GetxController { // 视频bvid String bvid = Get.parameters['bvid']!; var seasonId = Get.parameters['seasonId'] != null - ? int.parse(Get.parameters['seasonId']!) + ? int.tryParse(Get.parameters['seasonId']!) : null; var epId = Get.parameters['epId'] != null ? int.tryParse(Get.parameters['epId']!) @@ -69,6 +69,7 @@ class BangumiIntroController extends GetxController { @override void onInit() { super.onInit(); + print('bangumi: ${Get.parameters.toString()}'); userInfo = userInfoCache.get('userInfoCache'); userLogin = userInfo != null; if (userLogin && seasonId != null) { diff --git a/lib/pages/fav/widgets/item.dart b/lib/pages/fav/widgets/item.dart index 412b498f..5269d579 100644 --- a/lib/pages/fav/widgets/item.dart +++ b/lib/pages/fav/widgets/item.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/logic_utils.dart'; import 'package:pilipala/utils/utils.dart'; class FavItem extends StatelessWidget { @@ -96,9 +97,7 @@ class VideoContent extends StatelessWidget { ), const Spacer(), Text( - Constants.publicFavFolder.contains(favFolderItem.attr) - ? '公开' - : '私密', + LogicUtils.isPublic(favFolderItem.attr) ? '公开' : '私密', textAlign: TextAlign.start, style: TextStyle( fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, diff --git a/lib/pages/member_archive/controller.dart b/lib/pages/member_archive/controller.dart index 92f95da6..61816ed5 100644 --- a/lib/pages/member_archive/controller.dart +++ b/lib/pages/member_archive/controller.dart @@ -1,14 +1,16 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/http/member.dart'; +import 'package:pilipala/http/search.dart'; import 'package:pilipala/models/member/archive.dart'; import 'package:pilipala/utils/global_data_cache.dart'; +import 'package:pilipala/utils/utils.dart'; class MemberArchiveController extends GetxController { final ScrollController scrollController = ScrollController(); late int mid; int pn = 1; - int count = 0; + RxInt count = 0.obs; RxMap currentOrder = {}.obs; RxList> orderList = [ {'type': 'pubdate', 'label': '最新发布'}, @@ -50,11 +52,11 @@ class MemberArchiveController extends GetxController { if (res['status']) { if (type == 'init') { archivesList.value = res['data'].list.vlist; + count.value = res['data'].page['count']; } if (type == 'onLoad') { archivesList.addAll(res['data'].list.vlist); } - count = res['data'].page['count']; pn += 1; } isLoading.value = false; @@ -76,4 +78,29 @@ class MemberArchiveController extends GetxController { Future onLoad() async { getMemberArchive('onLoad'); } + + Future toViewPlayAll() async { + final VListItemModel firstItem = archivesList.first; + final String bvid = firstItem.bvid!; + final int cid = await SearchHttp.ab2c(bvid: bvid); + final String heroTag = Utils.makeHeroTag(bvid); + late Map sortFieldMap = { + 'pubdate': 'pubtime', + 'click': 'play', + 'fav': 'fav', + }; + Get.toNamed( + '/video?bvid=${firstItem.bvid}&cid=$cid', + arguments: { + 'videoItem': firstItem, + 'heroTag': heroTag, + 'sourceType': 'up_archive', + 'oid': firstItem.aid, + 'favTitle': '${firstItem.owner!.name!} - ${currentOrder['label']!}', + 'favInfo': firstItem, + 'count': count.value, + 'sortField': sortFieldMap[currentOrder['type']], + }, + ); + } } diff --git a/lib/pages/member_archive/view.dart b/lib/pages/member_archive/view.dart index 86ff9940..6963dad6 100644 --- a/lib/pages/member_archive/view.dart +++ b/lib/pages/member_archive/view.dart @@ -135,6 +135,15 @@ class _MemberArchivePageState extends State { ), ], ), + floatingActionButton: Obx( + () => _memberArchivesController.count > 0 + ? FloatingActionButton.extended( + onPressed: _memberArchivesController.toViewPlayAll, + label: const Text('播放全部'), + icon: const Icon(Icons.playlist_play), + ) + : const SizedBox(), + ), ); } } diff --git a/lib/pages/setting/controller.dart b/lib/pages/setting/controller.dart index 0e082152..ca027832 100644 --- a/lib/pages/setting/controller.dart +++ b/lib/pages/setting/controller.dart @@ -61,16 +61,7 @@ class SettingController extends GetxController { ), TextButton( onPressed: () async { - // 清空cookie - await Request.cookieManager.cookieJar.deleteAll(); - Request.dio.options.headers['cookie'] = ''; - - // 清空本地存储的用户标识 - userInfoCache.put('userInfoCache', null); - localCache - .put(LocalCacheKey.accessKey, {'mid': -1, 'value': ''}); - - await LoginUtils.refreshLoginStatus(false); + await LoginUtils.loginOut(); SmartDialog.dismiss().then((value) => Get.back()); }, child: const Text('确认'), diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 53f7d019..e09dd20f 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -6,11 +6,14 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:ns_danmaku/ns_danmaku.dart'; +import 'package:pilipala/http/common.dart'; import 'package:pilipala/http/constants.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/common/search_type.dart'; +import 'package:pilipala/models/sponsor_block/segment.dart'; +import 'package:pilipala/models/sponsor_block/segment_type.dart'; import 'package:pilipala/models/video/later.dart'; import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/models/video/play/url.dart'; @@ -119,6 +122,9 @@ class VideoDetailController extends GetxController List mediaList = []; RxBool isWatchLaterVisible = false.obs; RxString watchLaterTitle = ''.obs; + RxInt watchLaterCount = 0.obs; + List skipSegments = []; + int? lastPosition; @override void onInit() { @@ -170,7 +176,7 @@ class VideoDetailController extends GetxController sourceType.value = argMap['sourceType'] ?? 'normal'; isWatchLaterVisible.value = - sourceType.value == 'watchLater' || sourceType.value == 'fav'; + ['watchLater', 'fav', 'up_archive'].contains(sourceType.value); if (sourceType.value == 'watchLater') { watchLaterTitle.value = '稍后再看'; fetchMediaList(); @@ -179,9 +185,19 @@ class VideoDetailController extends GetxController watchLaterTitle.value = argMap['favTitle']; queryFavVideoList(); } + if (sourceType.value == 'up_archive') { + watchLaterTitle.value = argMap['favTitle']; + watchLaterCount.value = argMap['count']; + queryArchiveVideoList(); + } tabCtr.addListener(() { onTabChanged(); }); + + /// 仅投稿视频skip + if (videoType == SearchType.video) { + querySkipSegments(); + } } showReplyReplyPanel(oid, fRpid, firstFloor, currentReply, loadMore) { @@ -299,6 +315,7 @@ class VideoDetailController extends GetxController plPlayerController.headerControl = headerControl; plPlayerController.subtitles.value = subtitles; + onPositionChanged(); } // 视频链接 @@ -585,7 +602,9 @@ class VideoDetailController extends GetxController } void toggeleWatchLaterVisible(bool val) { - if (sourceType.value == 'watchLater' || sourceType.value == 'fav') { + if (sourceType.value == 'watchLater' || + sourceType.value == 'fav' || + sourceType.value == 'up_archive') { isWatchLaterVisible.value = !isWatchLaterVisible.value; } } @@ -616,8 +635,19 @@ class VideoDetailController extends GetxController changeMediaList: changeMediaList, panelTitle: watchLaterTitle.value, bvid: bvid, - mediaId: Get.arguments['mediaId'], + mediaId: [ + 'watchLater', + 'fav', + ].contains(sourceType.value) + ? Get.arguments['mediaId'] + : Get.arguments['favInfo'].owner.mid, hasMore: mediaList.length != Get.arguments['count'], + type: [ + 'watchLater', + 'fav', + ].contains(sourceType.value) + ? 3 + : 1, ); }); replyReplyBottomSheetCtr?.closed.then((value) { @@ -667,11 +697,73 @@ class VideoDetailController extends GetxController } } + Future queryArchiveVideoList() async { + final Map argMap = Get.arguments; + var favInfo = argMap['favInfo']; + var sortField = argMap['sortField']; + var res = await UserHttp.parseUpArchiveVideo( + mid: favInfo.owner.mid, + oid: oid.value, + bvid: bvid, + sortField: sortField, + ); + if (res['status']) { + mediaList = res['data']; + } + } + // 监听tabBarView切换 void onTabChanged() { isWatchLaterVisible.value = tabCtr.index == 0; } + // 获取sponsorBlock数据 + Future querySkipSegments() async { + var res = await CommonHttp.querySkipSegments(bvid: bvid); + if (res['status']) { + /// TODO 根据segmentType过滤数据 + skipSegments = res['data'] ?? []; + } + } + + // 监听视频进度 + void onPositionChanged() async { + final List sponsorSkipSegments = skipSegments + .where((e) => e.category!.value == SegmentType.sponsor.value) + .toList(); + if (sponsorSkipSegments.isEmpty) { + return; + } + + plPlayerController.videoPlayerController?.stream.position + .listen((Duration position) async { + final int positionMs = position.inSeconds; + + // 如果当前秒与上次处理的秒相同,则直接返回 + if (lastPosition != null && lastPosition! == positionMs) { + return; + } + + lastPosition = positionMs; + for (SegmentDataModel segment in sponsorSkipSegments) { + try { + final segmentStart = segment.segment!.first.toInt(); + final segmentEnd = segment.segment!.last.toInt(); + + /// 只有顺序播放时才skip,跳转时间点不会skip + if (positionMs == segmentStart && !segment.isSkip) { + await plPlayerController.videoPlayerController + ?.seek(Duration(seconds: segmentEnd)); + segment.isSkip = true; + SmartDialog.showToast('已跳过${segment.category!.label}片段'); + } + } catch (err) { + SmartDialog.showToast('skipSegments error: $err'); + } + } + }); + } + @override void onClose() { super.onClose(); diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index fd09fea6..cfd65b2f 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -63,6 +63,7 @@ class VideoIntroController extends GetxController { PersistentBottomSheetController? bottomSheetController; late bool enableRelatedVideo; UgcSeason? ugcSeason; + RxList pages = [].obs; @override void onInit() { @@ -84,18 +85,20 @@ class VideoIntroController extends GetxController { } // 获取视频简介&分p - Future queryVideoIntro() async { + Future queryVideoIntro({cover}) async { var result = await VideoHttp.videoIntro(bvid: bvid); if (result['status']) { videoDetail.value = result['data']!; ugcSeason = result['data']!.ugcSeason; - if (videoDetail.value.pages!.isNotEmpty && lastPlayCid.value == 0) { - lastPlayCid.value = videoDetail.value.pages!.first.cid!; + pages.value = result['data']!.pages!; + lastPlayCid.value = videoDetail.value.cid!; + if (pages.isNotEmpty) { + lastPlayCid.value = pages.first.cid!; } final VideoDetailController videoDetailCtr = Get.find(tag: heroTag); videoDetailCtr.tabs.value = ['简介', '评论 ${result['data']?.stat?.reply}']; - videoDetailCtr.cover.value = result['data'].pic ?? ''; + videoDetailCtr.cover.value = cover ?? result['data'].pic ?? ''; // 获取到粉丝数再返回 await queryUserStat(); } @@ -470,8 +473,7 @@ class VideoIntroController extends GetxController { videoReplyCtr.queryReplyList(type: 'init'); } catch (_) {} this.bvid = bvid; - lastPlayCid.value = cid; - await queryVideoIntro(); + await queryVideoIntro(cover: cover); } void startTimer() { @@ -521,9 +523,8 @@ class VideoIntroController extends GetxController { final List episodesList = sections[i].episodes!; episodes.addAll(episodesList); } - } else if (videoDetail.value.pages != null) { + } else if (pages.isNotEmpty) { isPages = true; - final List pages = videoDetail.value.pages!; episodes.addAll(pages); } @@ -621,10 +622,9 @@ class VideoIntroController extends GetxController { } } } - if (videoDetail.value.pages != null && - videoDetail.value.pages!.length > 1) { + if (pages.length > 1) { dataType = VideoEpidoesType.videoPart; - episodes = videoDetail.value.pages!; + episodes = pages; } DrawerUtils.showRightDialog( diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 2ac4efff..f6b6ed65 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -404,27 +404,18 @@ class _VideoInfoState extends State with TickerProviderStateMixin { Obx( () => SeasonPanel( ugcSeason: widget.videoDetail!.ugcSeason!, - cid: videoIntroController.lastPlayCid.value != 0 - ? videoIntroController.lastPlayCid.value - : widget.videoDetail!.pages!.first.cid, + cid: videoIntroController.lastPlayCid.value, sheetHeight: videoDetailCtr.sheetHeight.value, - changeFuc: (bvid, cid, aid, cover) => - videoIntroController.changeSeasonOrbangu( - bvid, - cid, - aid, - cover, - ), + changeFuc: videoIntroController.changeSeasonOrbangu, videoIntroCtr: videoIntroController, ), ) ], // 合集 videoEpisode - if (widget.videoDetail!.pages != null && - widget.videoDetail!.pages!.length > 1) ...[ + if (videoIntroController.pages.length > 1) ...[ Obx( () => PagesPanel( - pages: widget.videoDetail!.pages!, + pages: videoIntroController.pages, cid: videoIntroController.lastPlayCid.value, sheetHeight: videoDetailCtr.sheetHeight.value, changeFuc: (cid, cover) => diff --git a/lib/pages/video/detail/introduction/widgets/fav_panel.dart b/lib/pages/video/detail/introduction/widgets/fav_panel.dart index 54fefe9d..4a8f57ed 100644 --- a/lib/pages/video/detail/introduction/widgets/fav_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/fav_panel.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; -import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/logic_utils.dart'; import 'package:pilipala/utils/storage.dart'; class FavPanel extends StatefulWidget { @@ -67,14 +67,13 @@ class _FavPanelState extends State { onTap: () => widget.ctr!.onChoose(item.favState != 1, index), dense: true, - leading: Icon( - Constants.publicFavFolder.contains(item.attr) - ? Icons.folder_outlined - : Icons.lock_outline), + leading: Icon(LogicUtils.isPublic(item.attr) + ? Icons.folder_outlined + : Icons.lock_outline), minLeadingWidth: 0, title: Text(item.title!), subtitle: Text( - '${item.mediaCount}个内容 - ${Constants.publicFavFolder.contains(item.attr) ? '公开' : '私密'}', + '${item.mediaCount}个内容 - ${LogicUtils.isPublic(item.attr) ? '公开' : '私密'}', ), trailing: Transform.scale( scale: 0.9, diff --git a/lib/pages/video/detail/introduction/widgets/page_panel.dart b/lib/pages/video/detail/introduction/widgets/page_panel.dart index 81a22176..96786804 100644 --- a/lib/pages/video/detail/introduction/widgets/page_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/page_panel.dart @@ -3,7 +3,6 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/models/video_detail_res.dart'; -import 'package:pilipala/pages/video/detail/index.dart'; import 'package:pilipala/pages/video/detail/introduction/index.dart'; import '../../../../../common/pages_bottom_sheet.dart'; import '../../../../../models/common/video_episode_type.dart'; @@ -32,25 +31,26 @@ class _PagesPanelState extends State { late int cid; late RxInt currentIndex = (-1).obs; final String heroTag = Get.arguments['heroTag']; - late VideoDetailController _videoDetailController; final ScrollController listViewScrollCtr = ScrollController(); - late PersistentBottomSheetController? _bottomSheetController; + PersistentBottomSheetController? _bottomSheetController; @override void initState() { super.initState(); cid = widget.cid; episodes = widget.pages; - _videoDetailController = Get.find(tag: heroTag); - currentIndex.value = episodes.indexWhere((Part e) => e.cid == cid); - scrollToIndex(); - _videoDetailController.cid.listen((int p0) { + updateCurrentIndexAndScroll(); + widget.videoIntroCtr.lastPlayCid.listen((int p0) { cid = p0; - currentIndex.value = episodes.indexWhere((Part e) => e.cid == cid); - scrollToIndex(); + updateCurrentIndexAndScroll(); }); } + void updateCurrentIndexAndScroll() { + currentIndex.value = widget.pages.indexWhere((Part e) => e.cid == cid); + scrollToIndex(); + } + @override void dispose() { listViewScrollCtr.dispose(); @@ -60,7 +60,10 @@ class _PagesPanelState extends State { void changeFucCall(item, i) async { widget.changeFuc?.call(item.cid, item.cover); currentIndex.value = i; - _bottomSheetController?.close(); + cid = item.cid; + if (_bottomSheetController != null) { + _bottomSheetController?.close(); + } scrollToIndex(); } @@ -112,7 +115,7 @@ class _PagesPanelState extends State { widget.videoIntroCtr.bottomSheetController = _bottomSheetController = EpisodeBottomSheet( currentCid: cid, - episodes: episodes, + episodes: widget.pages, changeFucCall: changeFucCall, sheetHeight: widget.sheetHeight, dataType: VideoEpidoesType.videoPart, diff --git a/lib/pages/video/detail/introduction/widgets/season_panel.dart b/lib/pages/video/detail/introduction/widgets/season_panel.dart index f26885a7..e5a9ea84 100644 --- a/lib/pages/video/detail/introduction/widgets/season_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/season_panel.dart @@ -33,6 +33,7 @@ class _SeasonPanelState extends State { final String heroTag = Get.arguments['heroTag']; late VideoDetailController _videoDetailController; late PersistentBottomSheetController? _bottomSheetController; + int currentEpisodeIndex = -1; @override void initState() { @@ -41,13 +42,12 @@ class _SeasonPanelState extends State { _videoDetailController = Get.find(tag: heroTag); /// 根据 cid 找到对应集,找到对应 episodes - /// 有多个episodes时,只显示其中一个 - /// TODO 同时显示多个合集 final List sections = widget.ugcSeason.sections!; for (int i = 0; i < sections.length; i++) { final List episodesList = sections[i].episodes!; for (int j = 0; j < episodesList.length; j++) { if (episodesList[j].cid == cid) { + currentEpisodeIndex = i; episodes = episodesList; continue; } @@ -55,10 +55,10 @@ class _SeasonPanelState extends State { } /// 取对应 season_id 的 episodes - currentIndex.value = episodes.indexWhere((EpisodeItem e) => e.cid == cid); + getCurrentIndex(); _videoDetailController.cid.listen((int p0) { cid = p0; - currentIndex.value = episodes.indexWhere((EpisodeItem e) => e.cid == cid); + getCurrentIndex(); }); } @@ -73,6 +73,23 @@ class _SeasonPanelState extends State { _bottomSheetController?.close(); } + // 获取currentIndex + void getCurrentIndex() { + currentIndex.value = episodes.indexWhere((EpisodeItem e) => e.cid == cid); + final List sections = widget.ugcSeason.sections!; + if (sections.length == 1 && sections.first.type == 1) { + final List episodesList = sections.first.episodes!; + for (int i = 0; i < episodesList.length; i++) { + for (int j = 0; j < episodesList[i].pages!.length; j++) { + if (episodesList[i].pages![j].cid == cid) { + currentIndex.value = i; + continue; + } + } + } + } + } + Widget buildEpisodeListItem( EpisodeItem episode, int index, @@ -125,6 +142,8 @@ class _SeasonPanelState extends State { sheetHeight: widget.sheetHeight, dataType: VideoEpidoesType.videoEpisode, ugcSeason: widget.ugcSeason, + currentEpisodeIndex: currentEpisodeIndex, + currentIndex: currentIndex.value, ).show(context); }, child: Padding( diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 775950f9..a176841e 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -786,7 +786,8 @@ class _VideoDetailPageState extends State Obx( () => Visibility( visible: vdCtr.sourceType.value == 'watchLater' || - vdCtr.sourceType.value == 'fav', + vdCtr.sourceType.value == 'fav' || + vdCtr.sourceType.value == 'up_archive', child: AnimatedPositioned( duration: const Duration(milliseconds: 400), curve: Curves.easeInOut, @@ -818,17 +819,21 @@ class _VideoDetailPageState extends State child: Row(children: [ const Icon(Icons.playlist_play, size: 24), const SizedBox(width: 10), - Text( - vdCtr.watchLaterTitle.value, - style: TextStyle( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, - fontWeight: FontWeight.bold, - letterSpacing: 0.2, + Expanded( + child: Text( + vdCtr.watchLaterTitle.value, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + fontWeight: FontWeight.bold, + letterSpacing: 0.2, + ), ), ), - const Spacer(), + const SizedBox(width: 50), const Icon(Icons.keyboard_arrow_up_rounded, size: 26), ]), ), diff --git a/lib/pages/video/detail/widgets/watch_later_list.dart b/lib/pages/video/detail/widgets/watch_later_list.dart index 8e83af4e..eda6cf3e 100644 --- a/lib/pages/video/detail/widgets/watch_later_list.dart +++ b/lib/pages/video/detail/widgets/watch_later_list.dart @@ -19,8 +19,9 @@ class MediaListPanel extends StatefulWidget { this.changeMediaList, this.panelTitle, this.bvid, - this.mediaId, + required this.mediaId, this.hasMore = false, + required this.type, super.key, }); @@ -29,8 +30,9 @@ class MediaListPanel extends StatefulWidget { final Function? changeMediaList; final String? panelTitle; final String? bvid; - final int? mediaId; + final int mediaId; final bool hasMore; + final int type; @override State createState() => _MediaListPanelState(); @@ -59,8 +61,8 @@ class _MediaListPanelState extends State { void loadMore() async { var res = await UserHttp.getMediaList( - type: 3, - bizId: widget.mediaId!, + type: widget.type, + bizId: widget.mediaId, ps: 20, oid: mediaList.last.id, ); diff --git a/lib/utils/logic_utils.dart b/lib/utils/logic_utils.dart new file mode 100644 index 00000000..3994c222 --- /dev/null +++ b/lib/utils/logic_utils.dart @@ -0,0 +1,6 @@ +class LogicUtils { + // 收藏夹是否公开 + static bool isPublic(int attr) { + return (attr & 1) == 0; + } +} diff --git a/lib/utils/login.dart b/lib/utils/login.dart index a2b36d02..26950382 100644 --- a/lib/utils/login.dart +++ b/lib/utils/login.dart @@ -7,15 +7,20 @@ import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/http/index.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/utils/cookie.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:uuid/uuid.dart'; class LoginUtils { + static Box userInfoCache = GStrorage.userInfo; + static Box localCache = GStrorage.localCache; + static Future refreshLoginStatus(bool status) async { try { // 更改我的页面登录状态 @@ -109,4 +114,14 @@ class LoginUtils { Clipboard.setData(ClipboardData(text: content)); } } + + // 退出登录 + static loginOut() async { + await Request.cookieManager.cookieJar.deleteAll(); + Request.dio.options.headers['cookie'] = ''; + userInfoCache.put('userInfoCache', null); + localCache.put(LocalCacheKey.accessKey, {'mid': -1, 'value': ''}); + GlobalDataCache().userInfo = null; + await refreshLoginStatus(false); + } } diff --git a/pubspec.lock b/pubspec.lock index 35e34ab9..b0839e19 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -533,18 +533,18 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + sha256: "712ce7fab537ba532c8febdb1a8f167b32441e74acd68c3ccb2e36dcb52c4ab2" url: "https://pub.flutter-io.cn" source: hosted - version: "0.9.2+1" + version: "0.9.3" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.flutter-io.cn" source: hosted - version: "0.9.4+1" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: @@ -557,10 +557,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" url: "https://pub.flutter-io.cn" source: hosted - version: "0.9.3+2" + version: "0.9.3+3" fixnum: dependency: transitive description: @@ -842,10 +842,10 @@ packages: dependency: transitive description: name: image_picker_ios - sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" url: "https://pub.flutter-io.cn" source: hosted - version: "0.8.12" + version: "0.8.12+1" image_picker_linux: dependency: transitive description: @@ -1438,10 +1438,10 @@ packages: dependency: "direct main" description: name: scrollview_observer - sha256: fa408bcfd41e19da841eb53fc471f8f952d5ef818b854d2505c4bb3f0c876381 + sha256: "8537ba32e5a15ade301e5c77ae858fd8591695defaad1821eca9eeb4ac28a157" url: "https://pub.flutter-io.cn" source: hosted - version: "1.22.0" + version: "1.23.0" sentry: dependency: transitive description: