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, color: color,
), ),
), ),
title: UnconstrainedBox( title: Align(
alignment: Alignment.centerLeft, alignment: .topLeft,
child: Container( child: Container(
width: 100, width: 100,
height: 11, height: 11,

View File

@@ -17,8 +17,8 @@ class WhisperItemSkeleton extends StatelessWidget {
color: color, color: color,
), ),
), ),
title: UnconstrainedBox( title: Align(
alignment: Alignment.centerLeft, alignment: .topLeft,
child: Container( child: Container(
width: 100, width: 100,
height: 11, height: 11,

View File

@@ -39,6 +39,7 @@ class LiveCardVApp extends StatelessWidget {
onLongPress: onLongPress, onLongPress: onLongPress,
onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress,
child: Column( child: Column(
crossAxisAlignment: .start,
children: [ children: [
AspectRatio( AspectRatio(
aspectRatio: Style.aspectRatio, aspectRatio: Style.aspectRatio,
@@ -56,11 +57,7 @@ class LiveCardVApp extends StatelessWidget {
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
child: AnimatedOpacity( child: videoStat(),
opacity: 1,
duration: const Duration(milliseconds: 200),
child: videoStat(),
),
), ),
], ],
), ),
@@ -159,7 +156,6 @@ class LiveCardVApp extends StatelessWidget {
Widget liveContent(ThemeData theme) { Widget liveContent(ThemeData theme) {
return Expanded( return Expanded(
flex: 1,
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(5, 8, 5, 4), padding: const EdgeInsets.fromLTRB(5, 8, 5, 4),
child: Column( child: Column(
@@ -173,18 +169,15 @@ class LiveCardVApp extends StatelessWidget {
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
Align( Text(
alignment: .topLeft, item.uname.toString(),
child: Text( textAlign: TextAlign.start,
item.uname.toString(), style: TextStyle(
textAlign: TextAlign.start, fontSize: theme.textTheme.labelMedium!.fontSize,
style: TextStyle( color: theme.colorScheme.outline,
fontSize: theme.textTheme.labelMedium!.fontSize,
color: theme.colorScheme.outline,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
], ],
), ),

View File

@@ -28,6 +28,7 @@ class LiveCardVFollow extends StatelessWidget {
onLongPress: onLongPress, onLongPress: onLongPress,
onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress,
child: Column( child: Column(
crossAxisAlignment: .start,
children: [ children: [
AspectRatio( AspectRatio(
aspectRatio: Style.aspectRatio, aspectRatio: Style.aspectRatio,
@@ -48,11 +49,7 @@ class LiveCardVFollow extends StatelessWidget {
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
child: AnimatedOpacity( child: videoStat(),
opacity: 1,
duration: const Duration(milliseconds: 200),
child: videoStat(context),
),
), ),
], ],
); );
@@ -69,7 +66,6 @@ class LiveCardVFollow extends StatelessWidget {
Widget liveContent(BuildContext context) { Widget liveContent(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
return Expanded( return Expanded(
flex: 1,
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(5, 8, 5, 4), padding: const EdgeInsets.fromLTRB(5, 8, 5, 4),
child: Column( child: Column(
@@ -77,7 +73,7 @@ class LiveCardVFollow extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'${liveItem.title}', liveItem.title.toString(),
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
letterSpacing: 0.3, letterSpacing: 0.3,
@@ -85,21 +81,15 @@ class LiveCardVFollow extends StatelessWidget {
maxLines: 2, maxLines: 2,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
Row( Text(
children: [ liveItem.uname.toString(),
Expanded( textAlign: TextAlign.start,
child: Text( style: TextStyle(
'${liveItem.uname}', fontSize: theme.textTheme.labelMedium!.fontSize,
textAlign: TextAlign.start, color: theme.colorScheme.outline,
style: TextStyle( ),
fontSize: theme.textTheme.labelMedium!.fontSize, maxLines: 1,
color: theme.colorScheme.outline, overflow: TextOverflow.ellipsis,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
],
), ),
], ],
), ),
@@ -107,7 +97,7 @@ class LiveCardVFollow extends StatelessWidget {
); );
} }
Widget videoStat(BuildContext context) { Widget videoStat() {
return Container( return Container(
height: 50, height: 50,
padding: const EdgeInsets.only(top: 26, left: 10, right: 10), padding: const EdgeInsets.only(top: 26, left: 10, right: 10),
@@ -126,7 +116,7 @@ class LiveCardVFollow extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'${liveItem.areaName}', liveItem.areaName.toString(),
style: const TextStyle(fontSize: 11, color: Colors.white), style: const TextStyle(fontSize: 11, color: Colors.white),
), ),
if (liveItem.textSmall case final textSmall?) if (liveItem.textSmall case final textSmall?)

View File

@@ -28,7 +28,7 @@ class LiveCardVSearch extends StatelessWidget {
onLongPress: onLongPress, onLongPress: onLongPress,
onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
AspectRatio( AspectRatio(
aspectRatio: Style.aspectRatio, aspectRatio: Style.aspectRatio,
@@ -49,11 +49,7 @@ class LiveCardVSearch extends StatelessWidget {
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
child: AnimatedOpacity( child: videoStat(),
opacity: 1,
duration: const Duration(milliseconds: 200),
child: videoStat(context),
),
), ),
], ],
); );
@@ -63,7 +59,7 @@ class LiveCardVSearch extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(5, 8, 5, 4), padding: const EdgeInsets.fromLTRB(5, 8, 5, 4),
child: Text( child: Text(
'${item.title}', item.title.toString(),
textAlign: TextAlign.start, textAlign: TextAlign.start,
style: const TextStyle( style: const TextStyle(
letterSpacing: 0.3, letterSpacing: 0.3,
@@ -78,7 +74,7 @@ class LiveCardVSearch extends StatelessWidget {
); );
} }
Widget videoStat(BuildContext context) { Widget videoStat() {
return Container( return Container(
height: 50, height: 50,
padding: const EdgeInsets.only(top: 26, left: 10, right: 10), padding: const EdgeInsets.only(top: 26, left: 10, right: 10),
@@ -97,7 +93,7 @@ class LiveCardVSearch extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'${item.name}', item.name.toString(),
style: const TextStyle(fontSize: 11, color: Colors.white), style: const TextStyle(fontSize: 11, color: Colors.white),
), ),
if (item.watchedShow?.textLarge case final textLarge?) if (item.watchedShow?.textLarge case final textLarge?)

View File

@@ -26,7 +26,7 @@ class LiveItem extends StatelessWidget {
onLongPress: onLongPress, onLongPress: onLongPress,
onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: .start,
children: [ children: [
AspectRatio( AspectRatio(
aspectRatio: Style.aspectRatio, aspectRatio: Style.aspectRatio,
@@ -47,13 +47,9 @@ class LiveItem extends StatelessWidget {
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
child: AnimatedOpacity( child: liveStat(
opacity: 1, liveItem.online,
duration: const Duration(milliseconds: 200), liveItem.cateName,
child: liveStat(
liveItem.online,
liveItem.cateName,
),
), ),
), ),
], ],

View File

@@ -25,44 +25,47 @@ class AiConclusionPanel extends CommonSlidePage {
Key? key, Key? key,
bool tap = true, bool tap = true,
}) { }) {
return CustomScrollView( final outline = res.outline;
key: key, final hasOutline = outline != null && outline.isNotEmpty;
shrinkWrap: !tap, return SelectionArea(
physics: const AlwaysScrollableScrollPhysics(), child: CustomScrollView(
slivers: [ key: key,
if (res.summary?.isNotEmpty == true) ...[ shrinkWrap: !tap,
SliverToBoxAdapter( physics: const AlwaysScrollableScrollPhysics(),
child: Padding( slivers: [
padding: const EdgeInsets.symmetric(horizontal: 14), if (res.summary?.isNotEmpty == true) ...[
child: SelectableText(
res.summary!,
style: const TextStyle(fontSize: 15, height: 1.5),
scrollPhysics: const NeverScrollableScrollPhysics(),
),
),
),
if (res.outline?.isNotEmpty == true)
SliverToBoxAdapter( SliverToBoxAdapter(
child: Divider( child: Padding(
height: 20, padding: const EdgeInsets.symmetric(horizontal: 14),
color: theme.dividerColor.withValues(alpha: 0.1), child: Text(
thickness: 6, res.summary!,
style: const TextStyle(fontSize: 15, height: 1.5),
),
), ),
), ),
], if (hasOutline)
if (res.outline?.isNotEmpty == true) SliverToBoxAdapter(
SliverPadding( child: Divider(
padding: EdgeInsets.only( height: 20,
left: 14, color: theme.dividerColor.withValues(alpha: 0.1),
right: 14, thickness: 6,
bottom: !tap ? 0 : MediaQuery.viewPaddingOf(context).bottom + 100, ),
), ),
sliver: SliverList.builder( ],
itemCount: res.outline!.length, if (hasOutline)
itemBuilder: (context, index) { SliverPadding(
final item = res.outline![index]; padding: EdgeInsets.only(
return SelectionArea( left: 14,
child: Column( 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, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (index != 0) const SizedBox(height: 10), 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, bool showIntro = true,
VoidCallback? onTap, VoidCallback? onTap,
}) { }) {
List<String> tabs = [ final tabs = [
if (showIntro) if (showIntro)
videoDetailController.isFileSource ? '离线视频' : introText ?? '简介', videoDetailController.isFileSource ? '离线视频' : introText ?? '简介',
if (videoDetailController.showReply) '评论', if (videoDetailController.showReply) '评论',
@@ -1322,53 +1322,61 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
); );
} }
final flag = !needIndicator || tabs.length == 1; Widget tabBar() {
Widget tabBar() => TabBar( final flag = !needIndicator || tabs.length == 1;
labelColor: flag ? themeData.colorScheme.onSurface : null, return TabBar(
indicator: flag ? const BoxDecoration() : null, padding: .zero,
padding: EdgeInsets.zero, dividerHeight: 0,
controller: videoDetailController.tabCtr, labelPadding: .zero,
labelStyle: dividerColor: Colors.transparent,
TabBarTheme.of(context).labelStyle?.copyWith(fontSize: 13) ?? controller: videoDetailController.tabCtr,
const TextStyle(fontSize: 13), indicator: flag ? const BoxDecoration() : null,
labelPadding: const EdgeInsets.symmetric(horizontal: 10.0), labelColor: flag ? themeData.colorScheme.onSurface : null,
dividerColor: Colors.transparent, labelStyle:
dividerHeight: 0, TabBarTheme.of(context).labelStyle?.copyWith(fontSize: 13) ??
onTap: (value) { const TextStyle(fontSize: 13),
void animToTop() { onTap: (value) {
if (onTap != null) { void animToTop() {
onTap(); if (onTap != null) {
return; 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) { if (flag) {
animToTop(); animToTop();
} else if (!videoDetailController.tabCtr.indexIsChanging) { } else if (!videoDetailController.tabCtr.indexIsChanging) {
animToTop(); animToTop();
} }
}, },
tabs: tabs.map((text) { tabs: tabs.map((text) {
if (text == '评论') { if (text == '评论') {
return Obx(() { return Obx(() {
final count = _videoReplyController.count.value; final count = _videoReplyController.count.value;
return Tab(
child: Text(
'评论${count == -1 ? '' : ' ${NumUtils.numFormat(count)}'}',
softWrap: false,
overflow: .visible,
),
);
});
} else {
return Tab( return Tab(
text: '评论${count == -1 ? '' : ' ${NumUtils.numFormat(count)}'}', child: Text(text, softWrap: false, overflow: .visible),
); );
}); }
} else { }).toList(),
return Tab(text: text); );
} }
}).toList(),
);
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
@@ -1385,68 +1393,62 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
if (tabs.isEmpty) if (tabs.isEmpty)
const Spacer() const Spacer()
else else
Flexible( Expanded(
flex: tabs.length == 3 ? 2 : 1, child: Align(
child: tabBar(), alignment: .centerLeft,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 96.0 * tabs.length),
child: tabBar(),
),
),
), ),
Flexible( SizedBox(
flex: 1, height: 32,
child: Center( child: TextButton(
child: Row( style: const ButtonStyle(
mainAxisAlignment: MainAxisAlignment.end, padding: WidgetStatePropertyAll(.zero),
children: [ ),
SizedBox( onPressed: videoDetailController.showShootDanmakuSheet,
height: 32, child: Text(
child: TextButton( '发弹幕',
style: const ButtonStyle( style: TextStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero), fontSize: 12,
), color: themeData.colorScheme.onSurfaceVariant,
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.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) { void updateFontScaleFS(double val) {
plPlayerController plPlayerController
..subtitleFontScaleFS = val ..subtitleFontScaleFS = val.toPrecision(2)
..updateSubtitleStyle(); ..updateSubtitleStyle();
setState(() {}); setState(() {});
} }
void updateFontScale(double val) { void updateFontScale(double val) {
plPlayerController plPlayerController
..subtitleFontScale = val ..subtitleFontScale = val.toPrecision(2)
..updateSubtitleStyle(); ..updateSubtitleStyle();
setState(() {}); setState(() {});
} }
@@ -1387,7 +1387,7 @@ class HeaderControlState extends State<HeaderControl>
min: 0.5, min: 0.5,
max: 2.5, max: 2.5,
value: subtitleFontScale, value: subtitleFontScale,
divisions: 20, divisions: 200,
label: label:
'${(subtitleFontScale * 100).toStringAsFixed(1)}%', '${(subtitleFontScale * 100).toStringAsFixed(1)}%',
onChanged: updateFontScale, onChanged: updateFontScale,
@@ -1416,7 +1416,7 @@ class HeaderControlState extends State<HeaderControl>
min: 0.5, min: 0.5,
max: 2.5, max: 2.5,
value: subtitleFontScaleFS, value: subtitleFontScaleFS,
divisions: 20, divisions: 200,
label: label:
'${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%', '${(subtitleFontScaleFS * 100).toStringAsFixed(1)}%',
onChanged: updateFontScaleFS, onChanged: updateFontScaleFS,
@@ -1530,7 +1530,9 @@ class HeaderControlState extends State<HeaderControl>
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('背景不透明度 ${(subtitleBgOpacity * 100).toInt()}%'), Text(
'背景不透明度 ${(subtitleBgOpacity * 100).toStringAsFixed(1)}%',
),
resetBtn(theme, '67%', () => updateOpacity(0.67)), resetBtn(theme, '67%', () => updateOpacity(0.67)),
], ],
), ),
@@ -1546,6 +1548,7 @@ class HeaderControlState extends State<HeaderControl>
child: Slider( child: Slider(
min: 0, min: 0,
max: 1, max: 1,
divisions: 100,
value: subtitleBgOpacity, value: subtitleBgOpacity,
onChanged: updateOpacity, onChanged: updateOpacity,
), ),

View File

@@ -108,7 +108,7 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
} }
void updateFontSizeFS(double val) { void updateFontSizeFS(double val) {
DanmakuOptions.danmakuFontScaleFS = val; DanmakuOptions.danmakuFontScaleFS = val.toPrecision(2);
setState(() {}); setState(() {});
if (isFullScreen) { if (isFullScreen) {
setOptions(); setOptions();
@@ -116,7 +116,7 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
} }
void updateFontSize(double val) { void updateFontSize(double val) {
DanmakuOptions.danmakuFontScale = val; DanmakuOptions.danmakuFontScale = val.toPrecision(2);
setState(() {}); setState(() {});
if (!isFullScreen) { if (!isFullScreen) {
setOptions(); setOptions();
@@ -136,7 +136,7 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
} }
void updateOpacity(double val) { void updateOpacity(double val) {
plPlayerController.danmakuOpacity.value = val; plPlayerController.danmakuOpacity.value = val.toPrecision(2);
setState(() {}); setState(() {});
} }
@@ -286,7 +286,9 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('显示区域 ${DanmakuOptions.danmakuShowArea * 100}%'), Text(
'显示区域 ${(DanmakuOptions.danmakuShowArea * 100).toStringAsFixed(1)}%',
),
resetBtn(theme, '50.0%', () => updateShowArea(0.5)), resetBtn(theme, '50.0%', () => updateShowArea(0.5)),
], ],
), ),
@@ -312,7 +314,9 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('不透明度 ${plPlayerController.danmakuOpacity * 100}%'), Text(
'不透明度 ${(plPlayerController.danmakuOpacity * 100).toStringAsFixed(1)}%',
),
resetBtn(theme, '100.0%', () => updateOpacity(1.0)), resetBtn(theme, '100.0%', () => updateOpacity(1.0)),
], ],
), ),
@@ -330,7 +334,8 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
max: 1, max: 1,
value: plPlayerController.danmakuOpacity.value, value: plPlayerController.danmakuOpacity.value,
divisions: 100, divisions: 100,
label: '${plPlayerController.danmakuOpacity * 100}%', label:
'${(plPlayerController.danmakuOpacity * 100).toStringAsFixed(1)}%',
onChanged: updateOpacity, onChanged: updateOpacity,
), ),
), ),
@@ -384,8 +389,7 @@ mixin HeaderMixin<T extends StatefulWidget> on State<T> {
max: 5, max: 5,
value: DanmakuOptions.danmakuStrokeWidth, value: DanmakuOptions.danmakuStrokeWidth,
divisions: 10, divisions: 10,
label: DanmakuOptions.danmakuStrokeWidth label: DanmakuOptions.danmakuStrokeWidth.toString(),
.toStringAsFixed(0),
onChanged: updateStrokeWidth, onChanged: updateStrokeWidth,
), ),
), ),