diff --git a/lib/common/skeleton/msg_feed_top.dart b/lib/common/skeleton/msg_feed_top.dart index 012b7f19f..5d2a15c83 100644 --- a/lib/common/skeleton/msg_feed_top.dart +++ b/lib/common/skeleton/msg_feed_top.dart @@ -17,8 +17,8 @@ class MsgFeedTopSkeleton extends StatelessWidget { color: color, ), ), - title: UnconstrainedBox( - alignment: Alignment.centerLeft, + title: Align( + alignment: .topLeft, child: Container( width: 100, height: 11, diff --git a/lib/common/skeleton/whisper_item.dart b/lib/common/skeleton/whisper_item.dart index dfb9275c1..de6ceb3ab 100644 --- a/lib/common/skeleton/whisper_item.dart +++ b/lib/common/skeleton/whisper_item.dart @@ -17,8 +17,8 @@ class WhisperItemSkeleton extends StatelessWidget { color: color, ), ), - title: UnconstrainedBox( - alignment: Alignment.centerLeft, + title: Align( + alignment: .topLeft, child: Container( width: 100, height: 11, diff --git a/lib/pages/live/widgets/live_item_app.dart b/lib/pages/live/widgets/live_item_app.dart index c63514b0f..3835a97fd 100644 --- a/lib/pages/live/widgets/live_item_app.dart +++ b/lib/pages/live/widgets/live_item_app.dart @@ -39,6 +39,7 @@ class LiveCardVApp extends StatelessWidget { onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Column( + crossAxisAlignment: .start, children: [ AspectRatio( aspectRatio: Style.aspectRatio, @@ -56,11 +57,7 @@ class LiveCardVApp extends StatelessWidget { left: 0, right: 0, bottom: 0, - child: AnimatedOpacity( - opacity: 1, - duration: const Duration(milliseconds: 200), - child: videoStat(), - ), + child: videoStat(), ), ], ), @@ -159,7 +156,6 @@ class LiveCardVApp extends StatelessWidget { Widget liveContent(ThemeData theme) { return Expanded( - flex: 1, child: Padding( padding: const EdgeInsets.fromLTRB(5, 8, 5, 4), child: Column( @@ -173,18 +169,15 @@ class LiveCardVApp extends StatelessWidget { maxLines: 2, overflow: TextOverflow.ellipsis, ), - Align( - alignment: .topLeft, - child: Text( - item.uname.toString(), - textAlign: TextAlign.start, - style: TextStyle( - fontSize: theme.textTheme.labelMedium!.fontSize, - color: theme.colorScheme.outline, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + Text( + item.uname.toString(), + textAlign: TextAlign.start, + style: TextStyle( + fontSize: theme.textTheme.labelMedium!.fontSize, + color: theme.colorScheme.outline, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ], ), diff --git a/lib/pages/live_follow/widgets/live_item_follow.dart b/lib/pages/live_follow/widgets/live_item_follow.dart index 90463c8cf..d5ac60bc0 100644 --- a/lib/pages/live_follow/widgets/live_item_follow.dart +++ b/lib/pages/live_follow/widgets/live_item_follow.dart @@ -28,6 +28,7 @@ class LiveCardVFollow extends StatelessWidget { onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Column( + crossAxisAlignment: .start, children: [ AspectRatio( aspectRatio: Style.aspectRatio, @@ -48,11 +49,7 @@ class LiveCardVFollow extends StatelessWidget { left: 0, right: 0, bottom: 0, - child: AnimatedOpacity( - opacity: 1, - duration: const Duration(milliseconds: 200), - child: videoStat(context), - ), + child: videoStat(), ), ], ); @@ -69,7 +66,6 @@ class LiveCardVFollow extends StatelessWidget { Widget liveContent(BuildContext context) { final theme = Theme.of(context); return Expanded( - flex: 1, child: Padding( padding: const EdgeInsets.fromLTRB(5, 8, 5, 4), child: Column( @@ -77,7 +73,7 @@ class LiveCardVFollow extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '${liveItem.title}', + liveItem.title.toString(), textAlign: TextAlign.start, style: const TextStyle( letterSpacing: 0.3, @@ -85,21 +81,15 @@ class LiveCardVFollow extends StatelessWidget { maxLines: 2, overflow: TextOverflow.ellipsis, ), - Row( - children: [ - Expanded( - child: Text( - '${liveItem.uname}', - textAlign: TextAlign.start, - style: TextStyle( - fontSize: theme.textTheme.labelMedium!.fontSize, - color: theme.colorScheme.outline, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], + Text( + liveItem.uname.toString(), + textAlign: TextAlign.start, + style: TextStyle( + fontSize: theme.textTheme.labelMedium!.fontSize, + color: theme.colorScheme.outline, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ], ), @@ -107,7 +97,7 @@ class LiveCardVFollow extends StatelessWidget { ); } - Widget videoStat(BuildContext context) { + Widget videoStat() { return Container( height: 50, padding: const EdgeInsets.only(top: 26, left: 10, right: 10), @@ -126,7 +116,7 @@ class LiveCardVFollow extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '${liveItem.areaName}', + liveItem.areaName.toString(), style: const TextStyle(fontSize: 11, color: Colors.white), ), if (liveItem.textSmall case final textSmall?) diff --git a/lib/pages/live_search/widgets/live_search_room.dart b/lib/pages/live_search/widgets/live_search_room.dart index 314bb2953..818a3cca9 100644 --- a/lib/pages/live_search/widgets/live_search_room.dart +++ b/lib/pages/live_search/widgets/live_search_room.dart @@ -28,7 +28,7 @@ class LiveCardVSearch extends StatelessWidget { onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ AspectRatio( aspectRatio: Style.aspectRatio, @@ -49,11 +49,7 @@ class LiveCardVSearch extends StatelessWidget { left: 0, right: 0, bottom: 0, - child: AnimatedOpacity( - opacity: 1, - duration: const Duration(milliseconds: 200), - child: videoStat(context), - ), + child: videoStat(), ), ], ); @@ -63,7 +59,7 @@ class LiveCardVSearch extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(5, 8, 5, 4), child: Text( - '${item.title}', + item.title.toString(), textAlign: TextAlign.start, style: const TextStyle( letterSpacing: 0.3, @@ -78,7 +74,7 @@ class LiveCardVSearch extends StatelessWidget { ); } - Widget videoStat(BuildContext context) { + Widget videoStat() { return Container( height: 50, padding: const EdgeInsets.only(top: 26, left: 10, right: 10), @@ -97,7 +93,7 @@ class LiveCardVSearch extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - '${item.name}', + item.name.toString(), style: const TextStyle(fontSize: 11, color: Colors.white), ), if (item.watchedShow?.textLarge case final textLarge?) diff --git a/lib/pages/search_panel/live/widgets/item.dart b/lib/pages/search_panel/live/widgets/item.dart index 1e2794e1f..225206745 100644 --- a/lib/pages/search_panel/live/widgets/item.dart +++ b/lib/pages/search_panel/live/widgets/item.dart @@ -26,7 +26,7 @@ class LiveItem extends StatelessWidget { onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ AspectRatio( aspectRatio: Style.aspectRatio, @@ -47,13 +47,9 @@ class LiveItem extends StatelessWidget { left: 0, right: 0, bottom: 0, - child: AnimatedOpacity( - opacity: 1, - duration: const Duration(milliseconds: 200), - child: liveStat( - liveItem.online, - liveItem.cateName, - ), + child: liveStat( + liveItem.online, + liveItem.cateName, ), ), ], diff --git a/lib/pages/video/ai_conclusion/view.dart b/lib/pages/video/ai_conclusion/view.dart index f60afd189..5b4744b84 100644 --- a/lib/pages/video/ai_conclusion/view.dart +++ b/lib/pages/video/ai_conclusion/view.dart @@ -25,44 +25,47 @@ class AiConclusionPanel extends CommonSlidePage { Key? key, bool tap = true, }) { - return CustomScrollView( - key: key, - shrinkWrap: !tap, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - if (res.summary?.isNotEmpty == true) ...[ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 14), - child: SelectableText( - res.summary!, - style: const TextStyle(fontSize: 15, height: 1.5), - scrollPhysics: const NeverScrollableScrollPhysics(), - ), - ), - ), - if (res.outline?.isNotEmpty == true) + final outline = res.outline; + final hasOutline = outline != null && outline.isNotEmpty; + return SelectionArea( + child: CustomScrollView( + key: key, + shrinkWrap: !tap, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + if (res.summary?.isNotEmpty == true) ...[ SliverToBoxAdapter( - child: Divider( - height: 20, - color: theme.dividerColor.withValues(alpha: 0.1), - thickness: 6, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 14), + child: Text( + res.summary!, + style: const TextStyle(fontSize: 15, height: 1.5), + ), ), ), - ], - if (res.outline?.isNotEmpty == true) - SliverPadding( - padding: EdgeInsets.only( - left: 14, - right: 14, - bottom: !tap ? 0 : MediaQuery.viewPaddingOf(context).bottom + 100, - ), - sliver: SliverList.builder( - itemCount: res.outline!.length, - itemBuilder: (context, index) { - final item = res.outline![index]; - return SelectionArea( - child: Column( + if (hasOutline) + SliverToBoxAdapter( + child: Divider( + height: 20, + color: theme.dividerColor.withValues(alpha: 0.1), + thickness: 6, + ), + ), + ], + if (hasOutline) + SliverPadding( + padding: EdgeInsets.only( + left: 14, + right: 14, + bottom: !tap + ? 0 + : MediaQuery.viewPaddingOf(context).bottom + 100, + ), + sliver: SliverList.builder( + itemCount: outline.length, + itemBuilder: (context, index) { + final item = outline[index]; + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (index != 0) const SizedBox(height: 10), @@ -120,12 +123,12 @@ class AiConclusionPanel extends CommonSlidePage { ), ), ], - ), - ); - }, + ); + }, + ), ), - ), - ], + ], + ), ); } } diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index 33665db7d..e50c96860 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -1305,7 +1305,7 @@ class _VideoDetailPageVState extends State bool showIntro = true, VoidCallback? onTap, }) { - List tabs = [ + final tabs = [ if (showIntro) videoDetailController.isFileSource ? '离线视频' : introText ?? '简介', if (videoDetailController.showReply) '评论', @@ -1322,53 +1322,61 @@ class _VideoDetailPageVState extends State ); } - final flag = !needIndicator || tabs.length == 1; - Widget tabBar() => TabBar( - labelColor: flag ? themeData.colorScheme.onSurface : null, - indicator: flag ? const BoxDecoration() : null, - padding: EdgeInsets.zero, - controller: videoDetailController.tabCtr, - labelStyle: - TabBarTheme.of(context).labelStyle?.copyWith(fontSize: 13) ?? - const TextStyle(fontSize: 13), - labelPadding: const EdgeInsets.symmetric(horizontal: 10.0), - dividerColor: Colors.transparent, - dividerHeight: 0, - onTap: (value) { - void animToTop() { - if (onTap != null) { - onTap(); - return; + Widget tabBar() { + final flag = !needIndicator || tabs.length == 1; + return TabBar( + padding: .zero, + dividerHeight: 0, + labelPadding: .zero, + dividerColor: Colors.transparent, + controller: videoDetailController.tabCtr, + indicator: flag ? const BoxDecoration() : null, + labelColor: flag ? themeData.colorScheme.onSurface : null, + labelStyle: + TabBarTheme.of(context).labelStyle?.copyWith(fontSize: 13) ?? + const TextStyle(fontSize: 13), + onTap: (value) { + void animToTop() { + if (onTap != null) { + onTap(); + return; + } + String text = tabs[value]; + if (videoDetailController.isFileSource || + text == '简介' || + text == '相关视频') { + videoDetailController.introScrollCtr?.animToTop(); + } else if (text.startsWith('评论')) { + _videoReplyController.animateToTop(); + } } - String text = tabs[value]; - if (videoDetailController.isFileSource || - text == '简介' || - text == '相关视频') { - videoDetailController.introScrollCtr?.animToTop(); - } else if (text.startsWith('评论')) { - _videoReplyController.animateToTop(); - } - } - if (flag) { - animToTop(); - } else if (!videoDetailController.tabCtr.indexIsChanging) { - animToTop(); - } - }, - tabs: tabs.map((text) { - if (text == '评论') { - return Obx(() { - final count = _videoReplyController.count.value; + if (flag) { + animToTop(); + } else if (!videoDetailController.tabCtr.indexIsChanging) { + animToTop(); + } + }, + tabs: tabs.map((text) { + if (text == '评论') { + return Obx(() { + final count = _videoReplyController.count.value; + return Tab( + child: Text( + '评论${count == -1 ? '' : ' ${NumUtils.numFormat(count)}'}', + softWrap: false, + overflow: .visible, + ), + ); + }); + } else { return Tab( - text: '评论${count == -1 ? '' : ' ${NumUtils.numFormat(count)}'}', + child: Text(text, softWrap: false, overflow: .visible), ); - }); - } else { - return Tab(text: text); - } - }).toList(), - ); + } + }).toList(), + ); + } return DecoratedBox( decoration: BoxDecoration( @@ -1385,68 +1393,62 @@ class _VideoDetailPageVState extends State if (tabs.isEmpty) const Spacer() else - Flexible( - flex: tabs.length == 3 ? 2 : 1, - child: tabBar(), + Expanded( + child: Align( + alignment: .centerLeft, + child: ConstrainedBox( + constraints: BoxConstraints(maxWidth: 96.0 * tabs.length), + child: tabBar(), + ), + ), ), - Flexible( - flex: 1, - child: Center( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SizedBox( - height: 32, - child: TextButton( - style: const ButtonStyle( - padding: WidgetStatePropertyAll(EdgeInsets.zero), - ), - onPressed: videoDetailController.showShootDanmakuSheet, - child: Text( - '发弹幕', - style: TextStyle( - fontSize: 12, - color: themeData.colorScheme.onSurfaceVariant, - ), - ), - ), - ), - SizedBox( - width: 38, - height: 38, - child: Obx( - () { - final ctr = videoDetailController.plPlayerController; - final enableShowDanmaku = ctr.enableShowDanmaku.value; - return IconButton( - onPressed: () { - final newVal = !enableShowDanmaku; - ctr.enableShowDanmaku.value = newVal; - if (!ctr.tempPlayerConf) { - GStorage.setting.put( - SettingBoxKey.enableShowDanmaku, - newVal, - ); - } - }, - icon: Icon( - size: 22, - enableShowDanmaku - ? CustomIcons.dm_on - : CustomIcons.dm_off, - color: enableShowDanmaku - ? themeData.colorScheme.secondary - : themeData.colorScheme.outline, - ), - ); - }, - ), - ), - const SizedBox(width: 14), - ], + SizedBox( + height: 32, + child: TextButton( + style: const ButtonStyle( + padding: WidgetStatePropertyAll(.zero), + ), + onPressed: videoDetailController.showShootDanmakuSheet, + child: Text( + '发弹幕', + style: TextStyle( + fontSize: 12, + color: themeData.colorScheme.onSurfaceVariant, + ), ), ), ), + SizedBox.square( + dimension: 38, + child: Obx( + () { + final ctr = videoDetailController.plPlayerController; + final enableShowDanmaku = ctr.enableShowDanmaku.value; + return IconButton( + onPressed: () { + final newVal = !enableShowDanmaku; + ctr.enableShowDanmaku.value = newVal; + if (!ctr.tempPlayerConf) { + GStorage.setting.put( + SettingBoxKey.enableShowDanmaku, + newVal, + ); + } + }, + icon: Icon( + size: 22, + enableShowDanmaku + ? CustomIcons.dm_on + : CustomIcons.dm_off, + color: enableShowDanmaku + ? themeData.colorScheme.secondary + : themeData.colorScheme.outline, + ), + ); + }, + ), + ), + const SizedBox(width: 14), ], ), ), diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index be1eed0c9..bb80f043b 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -1330,14 +1330,14 @@ class HeaderControlState extends State void updateFontScaleFS(double val) { plPlayerController - ..subtitleFontScaleFS = val + ..subtitleFontScaleFS = val.toPrecision(2) ..updateSubtitleStyle(); setState(() {}); } void updateFontScale(double val) { plPlayerController - ..subtitleFontScale = val + ..subtitleFontScale = val.toPrecision(2) ..updateSubtitleStyle(); setState(() {}); } @@ -1387,7 +1387,7 @@ class HeaderControlState extends State min: 0.5, max: 2.5, value: subtitleFontScale, - divisions: 20, + divisions: 200, label: '${(subtitleFontScale * 100).toStringAsFixed(1)}%', onChanged: updateFontScale, @@ -1416,7 +1416,7 @@ class HeaderControlState extends State min: 0.5, max: 2.5, value: subtitleFontScaleFS, - divisions: 20, + divisions: 200, label: '${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%', onChanged: updateFontScaleFS, @@ -1530,7 +1530,9 @@ class HeaderControlState extends State Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('背景不透明度 ${(subtitleBgOpacity * 100).toInt()}%'), + Text( + '背景不透明度 ${(subtitleBgOpacity * 100).toStringAsFixed(1)}%', + ), resetBtn(theme, '67%', () => updateOpacity(0.67)), ], ), @@ -1546,6 +1548,7 @@ class HeaderControlState extends State child: Slider( min: 0, max: 1, + divisions: 100, value: subtitleBgOpacity, onChanged: updateOpacity, ), diff --git a/lib/pages/video/widgets/header_mixin.dart b/lib/pages/video/widgets/header_mixin.dart index 3706caf01..cbce50c6e 100644 --- a/lib/pages/video/widgets/header_mixin.dart +++ b/lib/pages/video/widgets/header_mixin.dart @@ -108,7 +108,7 @@ mixin HeaderMixin on State { } void updateFontSizeFS(double val) { - DanmakuOptions.danmakuFontScaleFS = val; + DanmakuOptions.danmakuFontScaleFS = val.toPrecision(2); setState(() {}); if (isFullScreen) { setOptions(); @@ -116,7 +116,7 @@ mixin HeaderMixin on State { } void updateFontSize(double val) { - DanmakuOptions.danmakuFontScale = val; + DanmakuOptions.danmakuFontScale = val.toPrecision(2); setState(() {}); if (!isFullScreen) { setOptions(); @@ -136,7 +136,7 @@ mixin HeaderMixin on State { } void updateOpacity(double val) { - plPlayerController.danmakuOpacity.value = val; + plPlayerController.danmakuOpacity.value = val.toPrecision(2); setState(() {}); } @@ -286,7 +286,9 @@ mixin HeaderMixin on State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('显示区域 ${DanmakuOptions.danmakuShowArea * 100}%'), + Text( + '显示区域 ${(DanmakuOptions.danmakuShowArea * 100).toStringAsFixed(1)}%', + ), resetBtn(theme, '50.0%', () => updateShowArea(0.5)), ], ), @@ -312,7 +314,9 @@ mixin HeaderMixin on State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('不透明度 ${plPlayerController.danmakuOpacity * 100}%'), + Text( + '不透明度 ${(plPlayerController.danmakuOpacity * 100).toStringAsFixed(1)}%', + ), resetBtn(theme, '100.0%', () => updateOpacity(1.0)), ], ), @@ -330,7 +334,8 @@ mixin HeaderMixin on State { max: 1, value: plPlayerController.danmakuOpacity.value, divisions: 100, - label: '${plPlayerController.danmakuOpacity * 100}%', + label: + '${(plPlayerController.danmakuOpacity * 100).toStringAsFixed(1)}%', onChanged: updateOpacity, ), ), @@ -384,8 +389,7 @@ mixin HeaderMixin on State { max: 5, value: DanmakuOptions.danmakuStrokeWidth, divisions: 10, - label: DanmakuOptions.danmakuStrokeWidth - .toStringAsFixed(0), + label: DanmakuOptions.danmakuStrokeWidth.toString(), onChanged: updateStrokeWidth, ), ),