Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-07-21 22:14:38 +08:00
parent dae8df840a
commit 43843ca721
10 changed files with 204 additions and 217 deletions

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
),
],
),

View File

@@ -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?)

View File

@@ -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?)

View File

@@ -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,
),
),
],

View File

@@ -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 {
),
),
],
),
);
},
);
},
),
),
),
],
],
),
);
}
}

View File

@@ -1305,7 +1305,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
bool showIntro = true,
VoidCallback? onTap,
}) {
List<String> tabs = [
final tabs = [
if (showIntro)
videoDetailController.isFileSource ? '离线视频' : introText ?? '简介',
if (videoDetailController.showReply) '评论',
@@ -1322,53 +1322,61 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
);
}
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<VideoDetailPageV>
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),
],
),
),

View File

@@ -1330,14 +1330,14 @@ class HeaderControlState extends State<HeaderControl>
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<HeaderControl>
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<HeaderControl>
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<HeaderControl>
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<HeaderControl>
child: Slider(
min: 0,
max: 1,
divisions: 100,
value: subtitleBgOpacity,
onChanged: updateOpacity,
),

View File

@@ -108,7 +108,7 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
}
void updateFontSizeFS(double val) {
DanmakuOptions.danmakuFontScaleFS = val;
DanmakuOptions.danmakuFontScaleFS = val.toPrecision(2);
setState(() {});
if (isFullScreen) {
setOptions();
@@ -116,7 +116,7 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
}
void updateFontSize(double val) {
DanmakuOptions.danmakuFontScale = val;
DanmakuOptions.danmakuFontScale = val.toPrecision(2);
setState(() {});
if (!isFullScreen) {
setOptions();
@@ -136,7 +136,7 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
}
void updateOpacity(double val) {
plPlayerController.danmakuOpacity.value = val;
plPlayerController.danmakuOpacity.value = val.toPrecision(2);
setState(() {});
}
@@ -286,7 +286,9 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
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<T extends StatefulWidget> on State<T> {
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<T extends StatefulWidget> on State<T> {
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<T extends StatefulWidget> on State<T> {
max: 5,
value: DanmakuOptions.danmakuStrokeWidth,
divisions: 10,
label: DanmakuOptions.danmakuStrokeWidth
.toStringAsFixed(0),
label: DanmakuOptions.danmakuStrokeWidth.toString(),
onChanged: updateStrokeWidth,
),
),