diff --git a/lib/main.dart b/lib/main.dart index 027d7236..73bc71fd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -292,7 +292,7 @@ class MyApp extends StatelessWidget { } class _CustomHttpOverrides extends HttpOverrides { - static final badCertificateCallback = + final badCertificateCallback = BuildConfig.isDebug || GStorage.badCertificateCallback; @override diff --git a/lib/pages/bangumi/introduction/widgets/intro_detail.dart b/lib/pages/bangumi/introduction/widgets/intro_detail.dart index a4f21714..0f29c03a 100644 --- a/lib/pages/bangumi/introduction/widgets/intro_detail.dart +++ b/lib/pages/bangumi/introduction/widgets/intro_detail.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/pages/common/common_slide_page.dart'; import 'package:PiliPlus/pages/search/widgets/search_text.dart'; +import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/material.dart'; import 'package:PiliPlus/common/widgets/stat/danmu.dart'; import 'package:PiliPlus/common/widgets/stat/view.dart'; @@ -22,15 +23,46 @@ class IntroDetail extends CommonSlidePage { } class _IntroDetailState extends CommonSlidePageState { + late bool _isInit = true; late final TextStyle smallTitle = TextStyle( fontSize: 12, color: Theme.of(context).colorScheme.onSurface, ); + @override + void initState() { + super.initState(); + if (enableSlide && GStorage.collapsibleVideoPage) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _isInit = false; + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + if (enableSlide && GStorage.collapsibleVideoPage && _isInit) { + return CustomScrollView( + physics: const NeverScrollableScrollPhysics(), + ); + } + + return enableSlide + ? Padding( + padding: EdgeInsets.only(top: padding), + child: buildPage, + ) + : buildPage; + } + @override Widget get buildPage { - return Padding( - padding: const EdgeInsets.only(left: 14, right: 14), + return Material( + color: Theme.of(context).colorScheme.surface, child: Column( children: [ GestureDetector( @@ -61,95 +93,96 @@ class _IntroDetailState extends CommonSlidePageState { } @override - Widget get buildList => SingleChildScrollView( + Widget get buildList => ListView( controller: ScrollController(), physics: const AlwaysScrollableScrollPhysics(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText( - widget.bangumiDetail!.title, - style: const TextStyle( - fontSize: 16, - ), - ), - const SizedBox(height: 4), - Row( - children: [ - statView( - context: context, - theme: 'gray', - view: widget.bangumiDetail!.stat!['views'], - size: 'medium', - ), - const SizedBox(width: 6), - statDanMu( - context: context, - theme: 'gray', - danmu: widget.bangumiDetail!.stat!['danmakus'], - size: 'medium', - ), - ], - ), - const SizedBox(height: 4), - Row( - children: [ - Text( - widget.bangumiDetail!.areas!.first['name'], - style: smallTitle, - ), - const SizedBox(width: 6), - Text( - widget.bangumiDetail!.publish!['pub_time_show'], - style: smallTitle, - ), - const SizedBox(width: 6), - Text( - widget.bangumiDetail!.newEp!['desc'], - style: smallTitle, - ), - ], - ), - const SizedBox(height: 20), - Text( - '简介:', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 4), - SelectableText( - '${widget.bangumiDetail!.evaluate!}', - style: smallTitle.copyWith(fontSize: 13), - ), - const SizedBox(height: 20), - Text( - '声优:', - style: Theme.of(context).textTheme.titleMedium, - ), - const SizedBox(height: 4), - SelectableText( - widget.bangumiDetail.actors, - style: smallTitle.copyWith(fontSize: 13), - ), - if (widget.videoTags is List && widget.videoTags.isNotEmpty) ...[ - const SizedBox(height: 10), - Wrap( - spacing: 8, - runSpacing: 8, - children: (widget.videoTags as List) - .map( - (item) => SearchText( - fontSize: 13, - text: item['tag_name'], - onTap: (_) => Get.toNamed('/searchResult', - parameters: {'keyword': item['tag_name']}), - onLongPress: (_) => Utils.copyText(item['tag_name']), - ), - ) - .toList(), - ) - ], - SizedBox(height: MediaQuery.of(context).padding.bottom + 20) - ], + padding: EdgeInsets.only( + left: 14, + right: 14, + bottom: MediaQuery.paddingOf(context).bottom + 80, ), + children: [ + SelectableText( + widget.bangumiDetail!.title, + style: const TextStyle( + fontSize: 16, + ), + ), + const SizedBox(height: 4), + Row( + children: [ + statView( + context: context, + theme: 'gray', + view: widget.bangumiDetail!.stat!['views'], + size: 'medium', + ), + const SizedBox(width: 6), + statDanMu( + context: context, + theme: 'gray', + danmu: widget.bangumiDetail!.stat!['danmakus'], + size: 'medium', + ), + ], + ), + const SizedBox(height: 4), + Row( + children: [ + Text( + widget.bangumiDetail!.areas!.first['name'], + style: smallTitle, + ), + const SizedBox(width: 6), + Text( + widget.bangumiDetail!.publish!['pub_time_show'], + style: smallTitle, + ), + const SizedBox(width: 6), + Text( + widget.bangumiDetail!.newEp!['desc'], + style: smallTitle, + ), + ], + ), + const SizedBox(height: 20), + Text( + '简介:', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 4), + SelectableText( + '${widget.bangumiDetail!.evaluate!}', + style: smallTitle.copyWith(fontSize: 14), + ), + const SizedBox(height: 20), + Text( + '声优:', + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 4), + SelectableText( + widget.bangumiDetail.actors, + style: smallTitle.copyWith(fontSize: 14), + ), + if (widget.videoTags is List && widget.videoTags.isNotEmpty) ...[ + const SizedBox(height: 10), + Wrap( + spacing: 8, + runSpacing: 8, + children: (widget.videoTags as List) + .map( + (item) => SearchText( + fontSize: 13, + text: item['tag_name'], + onTap: (_) => Get.toNamed('/searchResult', + parameters: {'keyword': item['tag_name']}), + onLongPress: (_) => Utils.copyText(item['tag_name']), + ), + ) + .toList(), + ) + ], + ], ); } diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index 6da1c663..140a76d0 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -395,8 +395,7 @@ class VideoDetailController extends GetxController showMediaListPanel(context) { if (mediaList.isNotEmpty) { childKey.currentState?.showBottomSheet( - shape: const RoundedRectangleBorder(), - backgroundColor: Theme.of(context).colorScheme.surface, + backgroundColor: Colors.transparent, (context) => MediaListPanel( mediaList: mediaList, changeMediaList: (bvid, cid, aid, cover) { diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index 2008d5d0..fc03443b 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -19,8 +19,10 @@ class IntroDetail extends StatelessWidget { @override Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.only(left: 14, right: 14), + return Material( + color: Theme.of(context).colorScheme.surface, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), child: Column( children: [ InkWell( @@ -133,7 +135,9 @@ class IntroDetail extends StatelessWidget { ), ) ], - )); + ), + ), + ); } InlineSpan buildContent(BuildContext context, content) { diff --git a/lib/pages/video/detail/view_point/view_points_page.dart b/lib/pages/video/detail/view_point/view_points_page.dart index 436fc1a7..0f227796 100644 --- a/lib/pages/video/detail/view_point/view_points_page.dart +++ b/lib/pages/video/detail/view_point/view_points_page.dart @@ -109,13 +109,15 @@ class _ViewPointsPageState extends CommonSlidePageState { ); @override - Widget get buildList => SingleChildScrollView( + Widget get buildList => ListView( controller: ScrollController(), physics: const AlwaysScrollableScrollPhysics(), - child: Column( - children: [ - ...List.generate(videoDetailController.viewPointList.length * 2 - 1, - (rawIndex) { + padding: + EdgeInsets.only(bottom: MediaQuery.paddingOf(context).bottom + 80), + children: [ + ...List.generate( + videoDetailController.viewPointList.length * 2 - 1, + (rawIndex) { if (rawIndex % 2 == 1) { return Divider( height: 1, @@ -191,9 +193,8 @@ class _ViewPointsPageState extends CommonSlidePageState { ), ), ); - }), - SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom), - ], - ), + }, + ), + ], ); } diff --git a/lib/pages/video/detail/view_v.dart b/lib/pages/video/detail/view_v.dart index 3b117333..5578f875 100644 --- a/lib/pages/video/detail/view_v.dart +++ b/lib/pages/video/detail/view_v.dart @@ -2130,8 +2130,7 @@ class _VideoDetailPageVState extends State showIntroDetail(videoDetail, videoTags) { videoDetailController.childKey.currentState?.showBottomSheet( - shape: const RoundedRectangleBorder(), - backgroundColor: Theme.of(context).colorScheme.surface, + backgroundColor: Colors.transparent, (context) => videoDetail is BangumiInfoModel ? bangumi.IntroDetail( bangumiDetail: videoDetail, diff --git a/lib/pages/video/detail/widgets/ai_detail.dart b/lib/pages/video/detail/widgets/ai_detail.dart index e0fdce22..597b1100 100644 --- a/lib/pages/video/detail/widgets/ai_detail.dart +++ b/lib/pages/video/detail/widgets/ai_detail.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/pages/common/common_slide_page.dart'; import 'package:PiliPlus/pages/video/detail/controller.dart'; +import 'package:PiliPlus/utils/storage.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -21,6 +22,38 @@ class AiDetail extends CommonSlidePage { } class _AiDetailState extends CommonSlidePageState { + late bool _isInit = true; + + @override + void initState() { + super.initState(); + if (enableSlide && GStorage.collapsibleVideoPage) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() { + _isInit = false; + }); + } + }); + } + } + + @override + Widget build(BuildContext context) { + if (enableSlide && GStorage.collapsibleVideoPage && _isInit) { + return CustomScrollView( + physics: const NeverScrollableScrollPhysics(), + ); + } + + return enableSlide + ? Padding( + padding: EdgeInsets.only(top: padding), + child: buildPage, + ) + : buildPage; + } + InlineSpan buildContent(BuildContext context, content) { List descV2 = content.descV2; // type @@ -89,12 +122,11 @@ class _AiDetailState extends CommonSlidePageState { } @override - Widget get buildPage => Container( + Widget get buildPage => Material( color: Theme.of(context).colorScheme.surface, - padding: const EdgeInsets.symmetric(horizontal: 14), child: Column( children: [ - InkWell( + GestureDetector( onTap: Get.back, child: Container( height: 35, @@ -119,34 +151,46 @@ class _AiDetailState extends CommonSlidePageState { ); @override - Widget get buildList => SingleChildScrollView( + Widget get buildList => CustomScrollView( controller: ScrollController(), physics: const AlwaysScrollableScrollPhysics(), - child: Column( - children: [ - if (widget.modelResult.summary?.isNotEmpty == true) ...[ - SelectableText( - '总结: ${widget.modelResult.summary}', - style: const TextStyle( - fontSize: 15, - height: 1.5, + slivers: [ + if (widget.modelResult.summary?.isNotEmpty == true) ...[ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: SelectableText( + '总结: ${widget.modelResult.summary}', + style: const TextStyle( + fontSize: 15, + height: 1.5, + ), ), ), - if (widget.modelResult.outline?.isNotEmpty == true) - Divider( + ), + if (widget.modelResult.outline?.isNotEmpty == true) + SliverToBoxAdapter( + child: Divider( height: 20, color: Theme.of(context).dividerColor.withOpacity(0.1), thickness: 6, - ) - ], - if (widget.modelResult.outline?.isNotEmpty == true) - ListView.builder( - shrinkWrap: true, + ), + ), + ], + if (widget.modelResult.outline?.isNotEmpty == true) + SliverPadding( + padding: EdgeInsets.only( + left: 14, + right: 14, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: SliverList.builder( itemCount: widget.modelResult.outline!.length, - physics: const NeverScrollableScrollPhysics(), itemBuilder: (context, index) { return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ + if (index != 0) const SizedBox(height: 10), SelectableText( widget.modelResult.outline![index].title!, style: const TextStyle( @@ -159,86 +203,59 @@ class _AiDetailState extends CommonSlidePageState { if (widget.modelResult.outline![index].partOutline ?.isNotEmpty == true) - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: widget - .modelResult.outline![index].partOutline!.length, - itemBuilder: (context, i) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Wrap( + ...widget.modelResult.outline![index].partOutline!.map( + (item) => Wrap( + children: [ + SelectableText.rich( + TextSpan( + style: TextStyle( + fontSize: 14, + color: + Theme.of(context).colorScheme.onSurface, + height: 1.5, + ), children: [ - SelectableText.rich( - TextSpan( - style: TextStyle( - fontSize: 13, - color: Theme.of(context) - .colorScheme - .onSurface, - height: 1.5, - ), - children: [ - TextSpan( - text: Utils.tampToSeektime(widget - .modelResult - .outline![index] - .partOutline![i] - .timestamp!), - style: TextStyle( - color: Theme.of(context) - .colorScheme - .primary, - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - // 跳转到指定位置 - try { - Get.find( - tag: Get.arguments[ - 'heroTag']) - .plPlayerController - .seekTo( - Duration( - seconds: - Utils.duration( - Utils.tampToSeektime(widget - .modelResult - .outline![ - index] - .partOutline![ - i] - .timestamp!) - .toString(), - ), - ), - ); - } catch (_) {} - }, - ), - const TextSpan(text: ' '), - TextSpan( - text: widget - .modelResult - .outline![index] - .partOutline![i] - .content!), - ], + TextSpan( + text: + Utils.tampToSeektime(item.timestamp!), + style: TextStyle( + color: Theme.of(context) + .colorScheme + .primary, ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // 跳转到指定位置 + try { + Get.find( + tag: Get + .arguments['heroTag']) + .plPlayerController + .seekTo( + Duration( + seconds: Utils.duration( + Utils.tampToSeektime( + item.timestamp!) + .toString(), + ), + ), + ); + } catch (_) {} + }, ), + const TextSpan(text: ' '), + TextSpan(text: item.content!), ], ), - ], - ); - }, + ), + ], + ), ), - const SizedBox(height: 20), ], ); }, - ) - ], - ), + ), + ), + ], ); } diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 375f3f73..ef212492 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -101,24 +101,23 @@ class _HeaderControlState extends State { void showSettingSheet() { Utils.showFSSheet( isFullScreen: () => isFullScreen, - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - child: Material( - color: Colors.transparent, - child: MediaQuery.removePadding( - context: context, - removeBottom: true, + child: Builder( + builder: (context) => Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Material( + color: Colors.transparent, child: ListView( + padding: EdgeInsets.only(bottom: 0), children: [ const SizedBox(height: 14), // ListTile( @@ -586,166 +585,155 @@ class _HeaderControlState extends State { bottom: 12 + MediaQuery.paddingOf(context).bottom, ), padding: const EdgeInsets.only(left: 14, right: 14), - child: SingleChildScrollView( - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 0, horizontal: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 10), - const Center(child: Text('定时关闭', style: titleStyle)), - const SizedBox(height: 10), - ...[ - ...[ - ...scheduleTimeChoices, - if (scheduleTimeChoices - .contains( - shutdownTimerService.scheduledExitInMinutes) - .not) - shutdownTimerService.scheduledExitInMinutes, - ]..sort(), - -1, - ].map( - (choice) => ListTile( - dense: true, - onTap: () { - if (choice == -1) { - showDialog( - context: context, - builder: (context) { - String duration = ''; - return AlertDialog( - title: const Text('自定义时长'), - content: TextField( - autofocus: true, - onChanged: (value) => duration = value, - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.allow( - RegExp(r'\d+')), - ], - decoration: const InputDecoration( - suffixText: 'min'), + child: ListView( + padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20), + children: [ + const SizedBox(height: 10), + const Center(child: Text('定时关闭', style: titleStyle)), + const SizedBox(height: 10), + ...[ + ...[ + ...scheduleTimeChoices, + if (scheduleTimeChoices + .contains(shutdownTimerService.scheduledExitInMinutes) + .not) + shutdownTimerService.scheduledExitInMinutes, + ]..sort(), + -1, + ].map( + (choice) => ListTile( + dense: true, + onTap: () { + if (choice == -1) { + showDialog( + context: context, + builder: (context) { + String duration = ''; + return AlertDialog( + title: const Text('自定义时长'), + content: TextField( + autofocus: true, + onChanged: (value) => duration = value, + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'\d+')), + ], + decoration: + const InputDecoration(suffixText: 'min'), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline), ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .outline), - ), - ), - TextButton( - onPressed: () { - Get.back(); - int choice = - int.tryParse(duration) ?? 0; - shutdownTimerService - .scheduledExitInMinutes = choice; - shutdownTimerService - .startShutdownTimer(); - setState(() {}); - }, - child: Text('确定'), - ), - ], - ); - }, + ), + TextButton( + onPressed: () { + Get.back(); + int choice = int.tryParse(duration) ?? 0; + shutdownTimerService + .scheduledExitInMinutes = choice; + shutdownTimerService.startShutdownTimer(); + setState(() {}); + }, + child: Text('确定'), + ), + ], ); - } else { - Get.back(); - shutdownTimerService.scheduledExitInMinutes = - choice; - shutdownTimerService.startShutdownTimer(); - } - }, - contentPadding: const EdgeInsets.only(), - title: Text(choice == -1 - ? '自定义' - : choice == 0 - ? "禁用" - : "$choice分钟后"), - trailing: shutdownTimerService.scheduledExitInMinutes == - choice + }, + ); + } else { + Get.back(); + shutdownTimerService.scheduledExitInMinutes = choice; + shutdownTimerService.startShutdownTimer(); + } + }, + contentPadding: const EdgeInsets.only(), + title: Text(choice == -1 + ? '自定义' + : choice == 0 + ? "禁用" + : "$choice分钟后"), + trailing: + shutdownTimerService.scheduledExitInMinutes == choice ? Icon( Icons.done, color: Theme.of(context).colorScheme.primary, ) : null, - ), + ), + ), + const SizedBox(height: 6), + const Center( + child: SizedBox( + width: 125, + child: Divider(height: 1), + ), + ), + const SizedBox(height: 10), + ListTile( + dense: true, + onTap: () { + shutdownTimerService.waitForPlayingCompleted = + !shutdownTimerService.waitForPlayingCompleted; + setState(() {}); + }, + contentPadding: const EdgeInsets.only(), + title: const Text("额外等待视频播放完毕", style: titleStyle), + trailing: Transform.scale( + alignment: + Alignment.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大 + scale: 0.8, + child: Switch( + thumbIcon: WidgetStateProperty.resolveWith( + (Set states) { + if (states.isNotEmpty && + states.first == WidgetState.selected) { + return const Icon(Icons.done); + } + return null; + }), + value: shutdownTimerService.waitForPlayingCompleted, + onChanged: (value) => setState(() => + shutdownTimerService.waitForPlayingCompleted = value), ), - const SizedBox(height: 6), - const Center( - child: SizedBox( - width: 125, - child: Divider(height: 1), - ), - ), - const SizedBox(height: 10), - ListTile( - dense: true, + ), + ), + const SizedBox(height: 10), + Row( + children: [ + const Text('倒计时结束:', style: titleStyle), + const Spacer(), + ActionRowLineItem( onTap: () { - shutdownTimerService.waitForPlayingCompleted = - !shutdownTimerService.waitForPlayingCompleted; + shutdownTimerService.exitApp = false; setState(() {}); + // Get.back(); }, - contentPadding: const EdgeInsets.only(), - title: const Text("额外等待视频播放完毕", style: titleStyle), - trailing: Transform.scale( - alignment: Alignment - .centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大 - scale: 0.8, - child: Switch( - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.isNotEmpty && - states.first == WidgetState.selected) { - return const Icon(Icons.done); - } - return null; - }), - value: shutdownTimerService.waitForPlayingCompleted, - onChanged: (value) => setState(() => - shutdownTimerService.waitForPlayingCompleted = - value), - ), - ), + text: " 暂停视频 ", + selectStatus: !shutdownTimerService.exitApp, ), - const SizedBox(height: 10), - Row( - children: [ - const Text('倒计时结束:', style: titleStyle), - const Spacer(), - ActionRowLineItem( - onTap: () { - shutdownTimerService.exitApp = false; - setState(() {}); - // Get.back(); - }, - text: " 暂停视频 ", - selectStatus: !shutdownTimerService.exitApp, - ), - const Spacer(), - // const SizedBox(width: 10), - ActionRowLineItem( - onTap: () { - shutdownTimerService.exitApp = true; - setState(() {}); - // Get.back(); - }, - text: " 退出APP ", - selectStatus: shutdownTimerService.exitApp, - ) - ], - ), - const SizedBox(height: 10), + const Spacer(), + // const SizedBox(width: 10), + ActionRowLineItem( + onTap: () { + shutdownTimerService.exitApp = true; + setState(() {}); + // Get.back(); + }, + text: " 退出APP ", + selectStatus: shutdownTimerService.exitApp, + ) ], ), - ), + const SizedBox(height: 10), + ], ), ); }, @@ -779,49 +767,48 @@ class _HeaderControlState extends State { Utils.showFSSheet( isFullScreen: () => isFullScreen, - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - child: Column( - children: [ - SizedBox( - height: 45, - child: GestureDetector( - onTap: () { - SmartDialog.showToast( - '标灰画质需要bilibili会员(已是会员?请关闭无痕模式);4k和杜比视界播放效果可能不佳'); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('选择画质', style: titleStyle), - SizedBox(width: buttonSpace), - Icon( - Icons.info_outline, - size: 16, - color: Theme.of(context).colorScheme.outline, - ) - ], + child: Builder( + builder: (context) => Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Column( + children: [ + SizedBox( + height: 45, + child: GestureDetector( + onTap: () { + SmartDialog.showToast( + '标灰画质需要bilibili会员(已是会员?请关闭无痕模式);4k和杜比视界播放效果可能不佳'); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('选择画质', style: titleStyle), + SizedBox(width: buttonSpace), + Icon( + Icons.info_outline, + size: 16, + color: Theme.of(context).colorScheme.outline, + ) + ], + ), ), ), - ), - Expanded( - child: Material( - color: Colors.transparent, - child: Scrollbar( - child: MediaQuery.removePadding( - context: context, - removeBottom: true, + Expanded( + child: Material( + color: Colors.transparent, + child: Scrollbar( child: ListView( + padding: EdgeInsets.only(bottom: 0), children: [ for (int i = 0; i < totalQaSam; i++) ...[ ListTile( @@ -870,8 +857,8 @@ class _HeaderControlState extends State { ), ), ), - ), - ], + ], + ), ), ), ); @@ -883,30 +870,29 @@ class _HeaderControlState extends State { final List audio = videoInfo.dash!.audio!; Utils.showFSSheet( isFullScreen: () => isFullScreen, - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - child: Column( - children: [ - const SizedBox( - height: 45, - child: Center(child: Text('选择音质', style: titleStyle))), - Expanded( - child: Material( - color: Colors.transparent, - child: MediaQuery.removePadding( - context: context, - removeBottom: true, + child: Builder( + builder: (context) => Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Column( + children: [ + const SizedBox( + height: 45, + child: Center(child: Text('选择音质', style: titleStyle))), + Expanded( + child: Material( + color: Colors.transparent, child: ListView( + padding: EdgeInsets.only(bottom: 0), children: [ for (final AudioItem i in audio) ...[ ListTile( @@ -949,8 +935,8 @@ class _HeaderControlState extends State { ), ), ), - ), - ], + ], + ), ), ), ); @@ -974,30 +960,29 @@ class _HeaderControlState extends State { Utils.showFSSheet( isFullScreen: () => isFullScreen, - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - child: Column( - children: [ - const SizedBox( - height: 45, - child: Center(child: Text('选择解码格式', style: titleStyle))), - Expanded( - child: Material( - color: Colors.transparent, - child: MediaQuery.removePadding( - context: context, - removeBottom: true, + child: Builder( + builder: (context) => Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Column( + children: [ + const SizedBox( + height: 45, + child: Center(child: Text('选择解码格式', style: titleStyle))), + Expanded( + child: Material( + color: Colors.transparent, child: ListView( + padding: EdgeInsets.only(bottom: 0), children: [ for (var i in list) ...[ ListTile( @@ -1031,8 +1016,8 @@ class _HeaderControlState extends State { ), ), ), - ), - ], + ], + ), ), ), ); @@ -1099,600 +1084,596 @@ class _HeaderControlState extends State { bottom: MediaQuery.paddingOf(context).bottom + 12, ), padding: const EdgeInsets.only(left: 14, right: 14), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 45, - child: Center(child: Text('弹幕/字幕设置', style: titleStyle)), + child: ListView( + padding: EdgeInsets.only(bottom: 0), + children: [ + const SizedBox( + height: 45, + child: Center(child: Text('弹幕/字幕设置', style: titleStyle)), + ), + const SizedBox(height: 10), + Row( + children: [ + Text('智能云屏蔽 $danmakuWeight 级'), + const Spacer(), + TextButton( + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + minimumSize: Size.zero, + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + onPressed: () => { + Get.back(), + Get.toNamed('/danmakuBlock', + arguments: widget.controller) + }, + child: Text("屏蔽管理(${widget.controller.filterCount})")), + ], + ), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, ), - const SizedBox(height: 10), - Row( + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 10, + value: danmakuWeight.toDouble(), + divisions: 10, + label: '$danmakuWeight', + onChanged: (double val) { + danmakuWeight = val.toInt(); + widget.controller + ..danmakuWeight = danmakuWeight + ..putDanmakuSettings(); + setState(() {}); + }, + ), + ), + ), + const Text('按类型屏蔽'), + Padding( + padding: const EdgeInsets.only(top: 12, bottom: 18), + child: Row( children: [ - Text('智能云屏蔽 $danmakuWeight 级'), - const Spacer(), - TextButton( - style: TextButton.styleFrom( - padding: EdgeInsets.zero, - minimumSize: Size.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - onPressed: () => { - Get.back(), - Get.toNamed('/danmakuBlock', - arguments: widget.controller) - }, - child: Text("屏蔽管理(${widget.controller.filterCount})")), + for (final Map i in blockTypesList) ...[ + ActionRowLineItem( + onTap: () async { + final bool isChoose = blockTypes.contains(i['value']); + if (isChoose) { + blockTypes.remove(i['value']); + } else { + blockTypes.add(i['value']); + } + widget.controller + ..blockTypes = blockTypes + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith( + hideTop: blockTypes.contains(5), + hideBottom: blockTypes.contains(4), + hideScroll: blockTypes.contains(2), + // 添加或修改其他需要修改的选项属性 + ), + ); + } catch (_) {} + }, + text: i['label'], + selectStatus: blockTypes.contains(i['value']), + ), + const SizedBox(width: 10), + ] ], ), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 10, - value: danmakuWeight.toDouble(), - divisions: 10, - label: '$danmakuWeight', - onChanged: (double val) { - danmakuWeight = val.toInt(); - widget.controller - ..danmakuWeight = danmakuWeight - ..putDanmakuSettings(); - setState(() {}); - }, - ), - ), - ), - const Text('按类型屏蔽'), - Padding( - padding: const EdgeInsets.only(top: 12, bottom: 18), - child: Row( - children: [ - for (final Map i in blockTypesList) ...[ - ActionRowLineItem( - onTap: () async { - final bool isChoose = - blockTypes.contains(i['value']); - if (isChoose) { - blockTypes.remove(i['value']); - } else { - blockTypes.add(i['value']); - } - widget.controller - ..blockTypes = blockTypes - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith( - hideTop: blockTypes.contains(5), - hideBottom: blockTypes.contains(4), - hideScroll: blockTypes.contains(2), - // 添加或修改其他需要修改的选项属性 - ), - ); - } catch (_) {} - }, - text: i['label'], - selectStatus: blockTypes.contains(i['value']), - ), - const SizedBox(width: 10), - ] - ], - ), - ), - const Text('显示区域'), - Padding( - padding: const EdgeInsets.only(top: 12), - child: Row( - children: [ - for (final Map i in showAreas) ...[ - ActionRowLineItem( - onTap: () { - showArea = i['value']; - widget.controller - ..showArea = showArea - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option - .copyWith(area: i['value']), - ); - } catch (_) {} - }, - text: i['label'], - selectStatus: showArea == i['value'], - ), - const SizedBox(width: 10), - ] - ], - ), - ), - SetSwitchItem( - title: '海量弹幕', - contentPadding: EdgeInsets.all(0), - titleStyle: TextStyle(fontSize: 14), - defaultVal: massiveMode, - setKey: SettingBoxKey.danmakuMassiveMode, - onChanged: (value) { - massiveMode = value; - widget.controller.massiveMode = value; - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith(massiveMode: value), - ); - } catch (_) {} - }, - ), - Text('不透明度 ${opacityVal * 100}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 1, - value: opacityVal, - divisions: 10, - label: '${opacityVal * 100}%', - onChanged: (double val) { - opacityVal = val; - widget.controller - ..opacityVal = opacityVal - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith(opacity: val), - ); - } catch (_) {} - }, - ), - ), - ), - Text('字体粗细 ${fontWeight + 1}(可能无法精确调节)'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 8, - value: fontWeight.toDouble(), - divisions: 8, - label: '${fontWeight + 1}', - onChanged: (double val) { - fontWeight = val.toInt(); - widget.controller - ..fontWeight = fontWeight - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option - .copyWith(fontWeight: fontWeight), - ); - } catch (_) {} - }, - ), - ), - ), - Text('描边粗细 $strokeWidth'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 3, - value: strokeWidth, - divisions: 6, - label: '$strokeWidth', - onChanged: (double val) { - strokeWidth = val; - widget.controller - ..strokeWidth = val - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith(strokeWidth: val), - ); - } catch (_) {} - }, - ), - ), - ), - Text('字体大小 ${(fontSizeVal * 100).toStringAsFixed(1)}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0.5, - max: 2.5, - value: fontSizeVal, - divisions: 20, - label: '${(fontSizeVal * 100).toStringAsFixed(1)}%', - onChanged: (double val) { - fontSizeVal = val; - widget.controller - ..fontSizeVal = fontSizeVal - ..putDanmakuSettings(); - setState(() {}); - if (widget.controller.isFullScreen.value == false) { + ), + const Text('显示区域'), + Padding( + padding: const EdgeInsets.only(top: 12), + child: Row( + children: [ + for (final Map i in showAreas) ...[ + ActionRowLineItem( + onTap: () { + showArea = i['value']; + widget.controller + ..showArea = showArea + ..putDanmakuSettings(); + setState(() {}); try { danmakuController?.updateOption( - danmakuController.option.copyWith( - fontSize: (15 * fontSizeVal).toDouble(), - ), + danmakuController.option + .copyWith(area: i['value']), ); } catch (_) {} - } - }, - ), + }, + text: i['label'], + selectStatus: showArea == i['value'], + ), + const SizedBox(width: 10), + ] + ], + ), + ), + SetSwitchItem( + title: '海量弹幕', + contentPadding: EdgeInsets.all(0), + titleStyle: TextStyle(fontSize: 14), + defaultVal: massiveMode, + setKey: SettingBoxKey.danmakuMassiveMode, + onChanged: (value) { + massiveMode = value; + widget.controller.massiveMode = value; + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith(massiveMode: value), + ); + } catch (_) {} + }, + ), + Text('不透明度 ${opacityVal * 100}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 1, + value: opacityVal, + divisions: 10, + label: '${opacityVal * 100}%', + onChanged: (double val) { + opacityVal = val; + widget.controller + ..opacityVal = opacityVal + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith(opacity: val), + ); + } catch (_) {} + }, ), ), - Text('全屏字体大小 ${(fontSizeFSVal * 100).toStringAsFixed(1)}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, + ), + Text('字体粗细 ${fontWeight + 1}(可能无法精确调节)'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0.5, - max: 2.5, - value: fontSizeFSVal, - divisions: 20, - label: '${(fontSizeFSVal * 100).toStringAsFixed(1)}%', - onChanged: (double val) { - fontSizeFSVal = val; - widget.controller - ..fontSizeFSVal = fontSizeFSVal - ..putDanmakuSettings(); - setState(() {}); - if (widget.controller.isFullScreen.value == true) { - try { - danmakuController?.updateOption( - danmakuController.option.copyWith( - fontSize: (15 * fontSizeFSVal).toDouble(), - ), - ); - } catch (_) {} - } - }, - ), + child: Slider( + min: 0, + max: 8, + value: fontWeight.toDouble(), + divisions: 8, + label: '${fontWeight + 1}', + onChanged: (double val) { + fontWeight = val.toInt(); + widget.controller + ..fontWeight = fontWeight + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option + .copyWith(fontWeight: fontWeight), + ); + } catch (_) {} + }, ), ), - Text('弹幕时长 $danmakuDurationVal 秒'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, + ), + Text('描边粗细 $strokeWidth'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 1, - max: 4, - value: pow(danmakuDurationVal, 1 / 4) as double, - divisions: 60, - label: danmakuDurationVal.toString(), - onChanged: (double val) { - danmakuDurationVal = - (pow(val, 4) as double).toPrecision(2); - widget.controller - ..danmakuDurationVal = danmakuDurationVal - ..putDanmakuSettings(); - setState(() {}); + child: Slider( + min: 0, + max: 3, + value: strokeWidth, + divisions: 6, + label: '$strokeWidth', + onChanged: (double val) { + strokeWidth = val; + widget.controller + ..strokeWidth = val + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith(strokeWidth: val), + ); + } catch (_) {} + }, + ), + ), + ), + Text('字体大小 ${(fontSizeVal * 100).toStringAsFixed(1)}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0.5, + max: 2.5, + value: fontSizeVal, + divisions: 20, + label: '${(fontSizeVal * 100).toStringAsFixed(1)}%', + onChanged: (double val) { + fontSizeVal = val; + widget.controller + ..fontSizeVal = fontSizeVal + ..putDanmakuSettings(); + setState(() {}); + if (widget.controller.isFullScreen.value == false) { try { danmakuController?.updateOption( danmakuController.option.copyWith( - duration: danmakuDurationVal ~/ - widget.controller.playbackSpeed), - ); - } catch (_) {} - }, - ), - ), - ), - Text('弹幕行高 $danmakuLineHeight'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 1.0, - max: 3.0, - value: danmakuLineHeight, - // label: '$danmakuLineHeight', - onChanged: (double val) { - danmakuLineHeight = val.toPrecision(1); - widget.controller - ..danmakuLineHeight = danmakuLineHeight - ..putDanmakuSettings(); - setState(() {}); - try { - danmakuController?.updateOption( - danmakuController.option.copyWith( - lineHeight: danmakuLineHeight, + fontSize: (15 * fontSizeVal).toDouble(), ), ); } catch (_) {} - }, - ), + } + }, ), ), - Text('字幕字体大小 ${(subtitleFontScale * 100).toStringAsFixed(1)}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, + ), + Text('全屏字体大小 ${(fontSizeFSVal * 100).toStringAsFixed(1)}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0.5, - max: 2.5, - value: subtitleFontScale, - divisions: 20, - label: '${(subtitleFontScale * 100).toStringAsFixed(1)}%', - onChanged: (double val) { - subtitleFontScale = val; - widget.controller - ..subtitleFontScale = subtitleFontScale - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), + child: Slider( + min: 0.5, + max: 2.5, + value: fontSizeFSVal, + divisions: 20, + label: '${(fontSizeFSVal * 100).toStringAsFixed(1)}%', + onChanged: (double val) { + fontSizeFSVal = val; + widget.controller + ..fontSizeFSVal = fontSizeFSVal + ..putDanmakuSettings(); + setState(() {}); + if (widget.controller.isFullScreen.value == true) { + try { + danmakuController?.updateOption( + danmakuController.option.copyWith( + fontSize: (15 * fontSizeFSVal).toDouble(), + ), + ); + } catch (_) {} + } + }, ), ), - Text( - '全屏字幕字体大小 ${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, + ), + Text('弹幕时长 $danmakuDurationVal 秒'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0.5, - max: 2.5, - value: subtitleFontScaleFS, - divisions: 20, - label: - '${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%', - onChanged: (double val) { - subtitleFontScaleFS = val; - widget.controller - ..subtitleFontScaleFS = subtitleFontScaleFS - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), + child: Slider( + min: 1, + max: 4, + value: pow(danmakuDurationVal, 1 / 4) as double, + divisions: 60, + label: danmakuDurationVal.toString(), + onChanged: (double val) { + danmakuDurationVal = + (pow(val, 4) as double).toPrecision(2); + widget.controller + ..danmakuDurationVal = danmakuDurationVal + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith( + duration: danmakuDurationVal ~/ + widget.controller.playbackSpeed), + ); + } catch (_) {} + }, ), ), - Text('字幕左右边距 $subtitlePaddingH'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, + ), + Text('弹幕行高 $danmakuLineHeight'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 100, - value: subtitlePaddingH.toDouble(), - divisions: 100, - label: '$subtitlePaddingH', - onChanged: (double val) { - subtitlePaddingH = val.round(); - widget.controller - ..subtitlePaddingH = subtitlePaddingH - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), + child: Slider( + min: 1.0, + max: 3.0, + value: danmakuLineHeight, + // label: '$danmakuLineHeight', + onChanged: (double val) { + danmakuLineHeight = val.toPrecision(1); + widget.controller + ..danmakuLineHeight = danmakuLineHeight + ..putDanmakuSettings(); + setState(() {}); + try { + danmakuController?.updateOption( + danmakuController.option.copyWith( + lineHeight: danmakuLineHeight, + ), + ); + } catch (_) {} + }, ), ), - Text('字幕底部边距 $subtitlePaddingB'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, + ), + Text('字幕字体大小 ${(subtitleFontScale * 100).toStringAsFixed(1)}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 100, - value: subtitlePaddingB.toDouble(), - divisions: 100, - label: '$subtitlePaddingB', - onChanged: (double val) { - subtitlePaddingB = val.round(); - widget.controller - ..subtitlePaddingB = subtitlePaddingB - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), + child: Slider( + min: 0.5, + max: 2.5, + value: subtitleFontScale, + divisions: 20, + label: '${(subtitleFontScale * 100).toStringAsFixed(1)}%', + onChanged: (double val) { + subtitleFontScale = val; + widget.controller + ..subtitleFontScale = subtitleFontScale + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, ), ), - Text('字幕背景不透明度 ${(subtitleBgOpaticy * 100).toInt()}%'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, + ), + Text( + '全屏字幕字体大小 ${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: - const RoundSliderThumbShape(enabledThumbRadius: 6.0), - ), - child: Slider( - min: 0, - max: 1, - value: subtitleBgOpaticy, - // label: '${(subtitleBgOpaticy * 100).toInt()}%', - onChanged: (double val) { - subtitleBgOpaticy = val.toPrecision(2); - widget.controller - ..subtitleBgOpaticy = subtitleBgOpaticy - ..updateSubtitleStyle() - ..putDanmakuSettings(); - setState(() {}); - }, - ), + child: Slider( + min: 0.5, + max: 2.5, + value: subtitleFontScaleFS, + divisions: 20, + label: '${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%', + onChanged: (double val) { + subtitleFontScaleFS = val; + widget.controller + ..subtitleFontScaleFS = subtitleFontScaleFS + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, ), ), - ], - ), + ), + Text('字幕左右边距 $subtitlePaddingH'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 100, + value: subtitlePaddingH.toDouble(), + divisions: 100, + label: '$subtitlePaddingH', + onChanged: (double val) { + subtitlePaddingH = val.round(); + widget.controller + ..subtitlePaddingH = subtitlePaddingH + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, + ), + ), + ), + Text('字幕底部边距 $subtitlePaddingB'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 100, + value: subtitlePaddingB.toDouble(), + divisions: 100, + label: '$subtitlePaddingB', + onChanged: (double val) { + subtitlePaddingB = val.round(); + widget.controller + ..subtitlePaddingB = subtitlePaddingB + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, + ), + ), + ), + Text('字幕背景不透明度 ${(subtitleBgOpaticy * 100).toInt()}%'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: + const RoundSliderThumbShape(enabledThumbRadius: 6.0), + ), + child: Slider( + min: 0, + max: 1, + value: subtitleBgOpaticy, + // label: '${(subtitleBgOpaticy * 100).toInt()}%', + onChanged: (double val) { + subtitleBgOpaticy = val.toPrecision(2); + widget.controller + ..subtitleBgOpaticy = subtitleBgOpaticy + ..updateSubtitleStyle() + ..putDanmakuSettings(); + setState(() {}); + }, + ), + ), + ), + ], ), ); }), @@ -1703,30 +1684,29 @@ class _HeaderControlState extends State { void showSetRepeat() async { Utils.showFSSheet( isFullScreen: () => isFullScreen, - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - margin: EdgeInsets.only( - left: 12, - top: 12, - right: 12, - bottom: 12 + MediaQuery.paddingOf(context).bottom, - ), - child: Column( - children: [ - const SizedBox( - height: 45, - child: Center(child: Text('选择播放顺序', style: titleStyle))), - Expanded( - child: Material( - color: Colors.transparent, - child: MediaQuery.removePadding( - context: context, - removeBottom: true, + child: Builder( + builder: (context) => Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + margin: EdgeInsets.only( + left: 12, + top: 12, + right: 12, + bottom: 12 + MediaQuery.paddingOf(context).bottom, + ), + child: Column( + children: [ + const SizedBox( + height: 45, + child: Center(child: Text('选择播放顺序', style: titleStyle))), + Expanded( + child: Material( + color: Colors.transparent, child: ListView( + padding: EdgeInsets.only(bottom: 0), children: [ for (final PlayRepeat i in PlayRepeat.values) ...[ ListTile( @@ -1750,8 +1730,8 @@ class _HeaderControlState extends State { ), ), ), - ), - ], + ], + ), ), ), ); diff --git a/lib/pages/video/detail/widgets/watch_later_list.dart b/lib/pages/video/detail/widgets/watch_later_list.dart index a850267d..063af953 100644 --- a/lib/pages/video/detail/widgets/watch_later_list.dart +++ b/lib/pages/video/detail/widgets/watch_later_list.dart @@ -64,38 +64,41 @@ class _MediaListPanelState extends CommonSlidePageState { @override Widget get buildPage { - return Column( - children: [ - AppBar( - toolbarHeight: 45, - automaticallyImplyLeading: false, - titleSpacing: 16, - title: Text(widget.panelTitle ?? '稍后再看'), - actions: [ - Obx( - () => mediumButton( - tooltip: desc.value ? '顺序播放' : '倒序播放', - icon: desc.value - ? MdiIcons.sortAscending - : MdiIcons.sortDescending, - onPressed: () { - widget.onReverse(); - desc.value = !desc.value; - }, + return Material( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + AppBar( + toolbarHeight: 45, + automaticallyImplyLeading: false, + titleSpacing: 16, + title: Text(widget.panelTitle ?? '稍后再看'), + actions: [ + Obx( + () => mediumButton( + tooltip: desc.value ? '顺序播放' : '倒序播放', + icon: desc.value + ? MdiIcons.sortAscending + : MdiIcons.sortDescending, + onPressed: () { + widget.onReverse(); + desc.value = !desc.value; + }, + ), ), - ), - mediumButton( - tooltip: '关闭', - icon: Icons.close, - onPressed: Get.back, - ), - const SizedBox(width: 14), - ], - ), - Expanded( - child: enableSlide ? slideList() : buildList, - ), - ], + mediumButton( + tooltip: '关闭', + icon: Icons.close, + onPressed: Get.back, + ), + const SizedBox(width: 14), + ], + ), + Expanded( + child: enableSlide ? slideList() : buildList, + ), + ], + ), ); } diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 29877e6d..8fab2973 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -78,273 +78,265 @@ class _WhisperPageState extends State { _whisperController.onRefresh(), ]); }, - child: SingleChildScrollView( + child: ListView( + padding: EdgeInsets.only(bottom: 80), controller: _scrollController, - child: Column( - children: [ - LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - // 在这里根据父级容器的约束条件构建小部件树 - return Padding( - padding: const EdgeInsets.only(left: 20, right: 20), - child: SizedBox( - height: 90, - child: Obx( - () => Row( - children: Iterable.generate( - _whisperController.msgFeedTop.length) - .map((idx) { - return Expanded( - child: GestureDetector( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Badge( - isLabelVisible: _whisperController - .msgFeedTop[idx]['value'] > - 0, - label: Text( - " ${_whisperController.msgFeedTop[idx]['value']} "), - alignment: Alignment.topRight, - child: CircleAvatar( - radius: 22, - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, - child: Icon( - _whisperController.msgFeedTop[idx] - ['icon'], - size: 20, - color: - Theme.of(context).colorScheme.primary, - ), + children: [ + LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + // 在这里根据父级容器的约束条件构建小部件树 + return Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: SizedBox( + height: 90, + child: Obx( + () => Row( + children: Iterable.generate( + _whisperController.msgFeedTop.length) + .map((idx) { + return Expanded( + child: GestureDetector( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Badge( + isLabelVisible: _whisperController + .msgFeedTop[idx]['value'] > + 0, + label: Text( + " ${_whisperController.msgFeedTop[idx]['value']} "), + alignment: Alignment.topRight, + child: CircleAvatar( + radius: 22, + backgroundColor: Theme.of(context) + .colorScheme + .onInverseSurface, + child: Icon( + _whisperController.msgFeedTop[idx]['icon'], + size: 20, + color: + Theme.of(context).colorScheme.primary, ), ), - const SizedBox(height: 6), - Text(_whisperController.msgFeedTop[idx]['name'], - style: const TextStyle(fontSize: 13)) - ], - ), - onTap: () { - if (!_whisperController.msgFeedTop[idx] - ['enabled']) { - SmartDialog.showToast('已禁用'); - return; - } - setState(() { - _whisperController.msgFeedTop[idx]['value'] = 0; - }); - Get.toNamed( - _whisperController.msgFeedTop[idx]['route']); - }, - )); - }).toList(), - ), + ), + const SizedBox(height: 6), + Text(_whisperController.msgFeedTop[idx]['name'], + style: const TextStyle(fontSize: 13)) + ], + ), + onTap: () { + if (!_whisperController.msgFeedTop[idx] + ['enabled']) { + SmartDialog.showToast('已禁用'); + return; + } + setState(() { + _whisperController.msgFeedTop[idx]['value'] = 0; + }); + Get.toNamed( + _whisperController.msgFeedTop[idx]['route']); + }, + )); + }).toList(), ), ), - ); - }), - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.data != null) { - // TODO: refactor - if (snapshot.data is! Map) { - return HttpError( - isSliver: false, - callback: () => setState(() { - _futureBuilderFuture = - _whisperController.querySessionList('init'); - }), - ); - } - Map data = snapshot.data as Map; - if (data['status']) { - List sessionList = - _whisperController.sessionList; - return Obx( - () => sessionList.isEmpty - ? const SizedBox() - : ListView.separated( - itemCount: sessionList.length, - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemBuilder: (context, int i) { - dynamic content = - sessionList[i].lastMsg?.content; - if (content == null || content == "") { - content = '不支持的消息类型'; - } else { - dynamic msg = content['text'] ?? - content['content'] ?? - content['title'] ?? - content['reply_content']; - if (msg == null) { - if (content['imageType'] != null) { - msg = '[图片消息]'; - } + ), + ); + }), + FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.data != null) { + // TODO: refactor + if (snapshot.data is! Map) { + return HttpError( + isSliver: false, + callback: () => setState(() { + _futureBuilderFuture = + _whisperController.querySessionList('init'); + }), + ); + } + Map data = snapshot.data as Map; + if (data['status']) { + List sessionList = + _whisperController.sessionList; + return Obx( + () => sessionList.isEmpty + ? const SizedBox() + : ListView.separated( + itemCount: sessionList.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, int i) { + dynamic content = + sessionList[i].lastMsg?.content; + if (content == null || content == "") { + content = '不支持的消息类型'; + } else { + dynamic msg = content['text'] ?? + content['content'] ?? + content['title'] ?? + content['reply_content']; + if (msg == null) { + if (content['imageType'] != null) { + msg = '[图片消息]'; } - content = msg ?? content.toString(); } - return ListTile( - tileColor: sessionList[i].topTs == 0 - ? null - : Theme.of(context) - .colorScheme - .onInverseSurface, - onLongPress: () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - clipBehavior: Clip.hardEdge, - contentPadding: - const EdgeInsets.symmetric( - vertical: 12), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - dense: true, - onTap: () { - Get.back(); - _whisperController - .onSetTop(i); - }, - title: Text( - sessionList[i].topTs == 0 - ? '置顶' - : '移除置顶', - style: const TextStyle( - fontSize: 14), - ), + content = msg ?? content.toString(); + } + return ListTile( + tileColor: sessionList[i].topTs == 0 + ? null + : Theme.of(context) + .colorScheme + .onInverseSurface, + onLongPress: () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + clipBehavior: Clip.hardEdge, + contentPadding: + const EdgeInsets.symmetric( + vertical: 12), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + dense: true, + onTap: () { + Get.back(); + _whisperController + .onSetTop(i); + }, + title: Text( + sessionList[i].topTs == 0 + ? '置顶' + : '移除置顶', + style: const TextStyle( + fontSize: 14), ), - ListTile( - dense: true, - onTap: () { - Get.back(); - _whisperController - .onRemove(i); - }, - title: const Text( - '删除', - style: - TextStyle(fontSize: 14), - ), + ), + ListTile( + dense: true, + onTap: () { + Get.back(); + _whisperController + .onRemove(i); + }, + title: const Text( + '删除', + style: + TextStyle(fontSize: 14), ), - ], - ), - ); - }, - ); - }, - onTap: () { - setState(() { - sessionList[i].unreadCount = 0; - }); - Get.toNamed( - '/whisperDetail', - parameters: { - 'talkerId': - '${sessionList[i].talkerId}', - 'name': sessionList[i] - .accountInfo - ?.name ?? - '', - 'face': sessionList[i] + ), + ], + ), + ); + }, + ); + }, + onTap: () { + setState(() { + sessionList[i].unreadCount = 0; + }); + Get.toNamed( + '/whisperDetail', + parameters: { + 'talkerId': + '${sessionList[i].talkerId}', + 'name': + sessionList[i].accountInfo?.name ?? + '', + 'face': + sessionList[i].accountInfo?.face ?? + '', + if (sessionList[i].accountInfo?.mid != + null) + 'mid': + '${sessionList[i].accountInfo?.mid}', + }, + ); + }, + leading: Builder(builder: (context) { + Widget buildAvatar() => NetworkImgLayer( + width: 45, + height: 45, + type: 'avatar', + src: sessionList[i] .accountInfo ?.face ?? - '', - if (sessionList[i].accountInfo?.mid != - null) - 'mid': - '${sessionList[i].accountInfo?.mid}', - }, - ); - }, - leading: Builder(builder: (context) { - Widget buildAvatar() => NetworkImgLayer( - width: 45, - height: 45, - type: 'avatar', - src: sessionList[i] - .accountInfo - ?.face ?? - "", - ); - return sessionList[i].unreadCount != null - ? Badge( - isLabelVisible: - sessionList[i].unreadCount! > - 0, - label: Text( - " ${sessionList[i].unreadCount} "), - alignment: Alignment.topRight, - child: buildAvatar(), - ) - : buildAvatar(); - }), - title: Text( - sessionList[i].accountInfo?.name ?? ""), - subtitle: Text( - '$content', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context) - .textTheme - .labelMedium! - .copyWith( - color: Theme.of(context) - .colorScheme - .outline), - ), - trailing: - sessionList[i].lastMsg?.timestamp != - null - ? Text( - Utils.dateFormat( - sessionList[i] - .lastMsg! - .timestamp, - formatType: "day"), - style: Theme.of(context) - .textTheme - .labelSmall! - .copyWith( - color: Theme.of(context) - .colorScheme - .outline), - ) - : null, - ); - }, - separatorBuilder: - (BuildContext context, int index) { - return Divider( - indent: 72, - endIndent: 20, - height: 6, - color: Colors.grey.withOpacity(0.1), - ); - }, - ), - ); - } else { - // 请求错误 - return Center( - child: Text(data['msg'] ?? '请求异常'), - ); - } + "", + ); + return sessionList[i].unreadCount != null + ? Badge( + isLabelVisible: + sessionList[i].unreadCount! > 0, + label: Text( + " ${sessionList[i].unreadCount} "), + alignment: Alignment.topRight, + child: buildAvatar(), + ) + : buildAvatar(); + }), + title: Text( + sessionList[i].accountInfo?.name ?? ""), + subtitle: Text( + '$content', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context) + .textTheme + .labelMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .outline), + ), + trailing: sessionList[i].lastMsg?.timestamp != + null + ? Text( + Utils.dateFormat( + sessionList[i].lastMsg!.timestamp, + formatType: "day"), + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith( + color: Theme.of(context) + .colorScheme + .outline), + ) + : null, + ); + }, + separatorBuilder: + (BuildContext context, int index) { + return Divider( + indent: 72, + endIndent: 20, + height: 6, + color: Colors.grey.withOpacity(0.1), + ); + }, + ), + ); } else { - // 骨架屏 - return const SizedBox(); + // 请求错误 + return Center( + child: Text(data['msg'] ?? '请求异常'), + ); } - }, - ) - ], - ), + } else { + // 骨架屏 + return const SizedBox(); + } + }, + ) + ], ), ), );