Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-09-04 22:08:32 +08:00
parent 172389b12b
commit 7e1e42181c
13 changed files with 219 additions and 194 deletions

View File

@@ -26,63 +26,81 @@ Future<void> autoWrapReportDialog(
right: 16, right: 16,
bottom: 10, bottom: 10,
), ),
content: Form( content: Column(
key: key, mainAxisSize: MainAxisSize.min,
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min, children: [
crossAxisAlignment: CrossAxisAlignment.start, Flexible(
children: [ child: SingleChildScrollView(
Flexible( child: AnimatedSize(
child: SingleChildScrollView( duration: const Duration(milliseconds: 200),
child: AnimatedSize( child: Builder(
duration: const Duration(milliseconds: 200), builder: (context) => Column(
child: Builder( crossAxisAlignment: CrossAxisAlignment.start,
builder: (context) => Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, const Padding(
children: [ padding: EdgeInsets.only(
const Padding( left: 22,
padding: EdgeInsets.only( right: 22,
bottom: 5,
),
child: Text('请选择举报的理由:'),
),
RadioGroup(
onChanged: (value) {
reasonType = value;
(context as Element).markNeedsBuild();
},
groupValue: reasonType,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: options.entries.map((entry) {
return WrapRadioOptionsGroup<int>(
groupTitle: entry.key,
options: entry.value,
);
}).toList(),
),
),
if (reasonType == 0)
Padding(
padding: const EdgeInsets.only(
left: 22, left: 22,
top: 5,
right: 22, right: 22,
bottom: 5,
), ),
child: Text('请选择举报的理由:'), child: Form(
), key: key,
RadioGroup( child: TextFormField(
onChanged: (value) { autofocus: true,
reasonType = value; minLines: 2,
(context as Element).markNeedsBuild(); maxLines: 4,
}, initialValue: reasonDesc,
groupValue: reasonType, decoration: const InputDecoration(
child: Column( labelText: '为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息',
crossAxisAlignment: CrossAxisAlignment.start, border: OutlineInputBorder(),
children: options.entries.map((entry) { contentPadding: EdgeInsets.all(10),
return WrapRadioOptionsGroup<int>( ),
groupTitle: entry.key, onChanged: (value) => reasonDesc = value,
options: entry.value, validator: (value) =>
); value.isNullOrEmpty ? '理由不能为空' : null,
}).toList(), ),
), ),
), ),
if (reasonType == 0) ],
ReasonField(
onChanged: (value) => reasonDesc = value,
),
],
),
), ),
), ),
), ),
), ),
Padding( ),
padding: const EdgeInsets.only(left: 14, top: 6), Padding(
child: CheckBoxText( padding: const EdgeInsets.only(left: 14, top: 6),
text: '拉黑该用户', child: CheckBoxText(
onChanged: (value) => banUid = value, text: '拉黑该用户',
), onChanged: (value) => banUid = value,
), ),
], ),
), ],
), ),
actions: [ actions: [
TextButton( TextButton(
@@ -121,46 +139,6 @@ Future<void> autoWrapReportDialog(
); );
} }
class ReasonField extends StatefulWidget {
final ValueChanged<String> onChanged;
String? _validator(String? value) => value.isNullOrEmpty ? '理由不能为空' : null;
const ReasonField({super.key, required this.onChanged});
@override
State<ReasonField> createState() => _ReasonFieldState();
}
class _ReasonFieldState extends State<ReasonField> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 22, top: 5, right: 22),
child: TextFormField(
controller: _controller,
autofocus: true,
minLines: 4,
maxLines: 4,
decoration: const InputDecoration(
labelText: '为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(10),
),
onChanged: widget.onChanged,
validator: widget._validator,
),
);
}
}
class CheckBoxText extends StatefulWidget { class CheckBoxText extends StatefulWidget {
final String text; final String text;
final ValueChanged<bool> onChanged; final ValueChanged<bool> onChanged;

View File

@@ -1314,10 +1314,10 @@ class _RenderListTile extends RenderBox
Iterable<RenderBox> get children { Iterable<RenderBox> get children {
final RenderBox? title = childForSlot(_ListTileSlot.title); final RenderBox? title = childForSlot(_ListTileSlot.title);
return <RenderBox>[ return <RenderBox>[
if (leading != null) leading!, ?leading,
if (title != null) title, ?title,
if (subtitle != null) subtitle!, ?subtitle,
if (trailing != null) trailing!, ?trailing,
]; ];
} }

View File

@@ -19,7 +19,7 @@ class HttpError extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return isSliver return isSliver
? SliverToBoxAdapter(child: content(context)) ? SliverToBoxAdapter(child: content(context))
: content(context); : SizedBox(width: double.infinity, child: content(context));
} }
Widget content(BuildContext context) { Widget content(BuildContext context) {

View File

@@ -479,6 +479,7 @@ class AuthorPanel extends StatelessWidget {
mid: moduleAuthor.mid, mid: moduleAuthor.mid,
dynId: item.idStr, dynId: item.idStr,
reasonType: reasonType, reasonType: reasonType,
reasonDesc: reasonType == 0 ? reasonDesc : null,
); );
}, },
); );

View File

@@ -172,9 +172,7 @@ class _VotePanelState extends State<VotePanel> {
CheckBoxText( CheckBoxText(
text: '匿名', text: '匿名',
selected: anonymity, selected: anonymity,
onChanged: (val) { onChanged: (val) => anonymity = val,
anonymity = val;
},
), ),
], ],
); );

View File

@@ -1,5 +1,3 @@
import 'dart:ui' show Offset;
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/match.dart'; import 'package:PiliPlus/http/match.dart';
import 'package:PiliPlus/models_new/match/match_info/contest.dart'; import 'package:PiliPlus/models_new/match/match_info/contest.dart';
@@ -18,9 +16,6 @@ class MatchInfoController extends CommonDynController {
final Rx<LoadingState<MatchContest?>> infoState = final Rx<LoadingState<MatchContest?>> infoState =
LoadingState<MatchContest?>.loading().obs; LoadingState<MatchContest?>.loading().obs;
@override
Offset get fabOffset => const Offset(0, 2);
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();

View File

@@ -21,7 +21,7 @@ class MusicDetailController extends CommonDynController {
bool get showDynActionBar => Pref.showDynActionBar; bool get showDynActionBar => Pref.showDynActionBar;
String get shareUrl => String get shareUrl =>
'https://music.bilibili.com/h5/music-detail?music_id=${musicId}'; 'https://music.bilibili.com/h5/music-detail?music_id=$musicId';
@override @override
void onInit() { void onInit() {

View File

@@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
typedef PopupMenuItemSelected<T> = bool Function(T value);
class PopupMenuText<T> extends StatefulWidget { class PopupMenuText<T> extends StatefulWidget {
final String title; final String title;
final T initialValue; final T initialValue;
@@ -24,9 +26,17 @@ class PopupMenuText<T> extends StatefulWidget {
class _PopupMenuTextState<T> extends State<PopupMenuText<T>> { class _PopupMenuTextState<T> extends State<PopupMenuText<T>> {
late T select = widget.initialValue; late T select = widget.initialValue;
@override
void didUpdateWidget(PopupMenuText<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initialValue != widget.initialValue) {
select = widget.initialValue;
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final secondary = Theme.of(context).colorScheme.secondary;
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@@ -35,10 +45,11 @@ class _PopupMenuTextState<T> extends State<PopupMenuText<T>> {
initialValue: select, initialValue: select,
onSelected: (value) { onSelected: (value) {
if (value == select) return; if (value == select) return;
setState(() { if (!widget.onSelected(value)) {
select = value; setState(() {
widget.onSelected(value); select = value;
}); });
}
}, },
itemBuilder: widget.itemBuilder, itemBuilder: widget.itemBuilder,
child: Row( child: Row(
@@ -49,7 +60,7 @@ class _PopupMenuTextState<T> extends State<PopupMenuText<T>> {
style: TextStyle( style: TextStyle(
height: 1, height: 1,
fontSize: 14, fontSize: 14,
color: theme.colorScheme.secondary, color: secondary,
), ),
strutStyle: const StrutStyle( strutStyle: const StrutStyle(
height: 1, height: 1,
@@ -59,7 +70,7 @@ class _PopupMenuTextState<T> extends State<PopupMenuText<T>> {
Icon( Icon(
MdiIcons.unfoldMoreHorizontal, MdiIcons.unfoldMoreHorizontal,
size: MediaQuery.textScalerOf(context).scale(14), size: MediaQuery.textScalerOf(context).scale(14),
color: theme.colorScheme.secondary, color: secondary,
), ),
], ],
), ),

View File

@@ -57,7 +57,7 @@ class PostPanel extends CommonCollapseSlidePage {
static Widget segmentWidget( static Widget segmentWidget(
ThemeData theme, { ThemeData theme, {
required PostSegmentModel item, required PostSegmentModel item,
required double currentPos, required double Function() currentPos, // get real-time pos
required double videoDuration, required double videoDuration,
}) { }) {
Widget segment(bool isFirst) => Builder( Widget segment(bool isFirst) => Builder(
@@ -81,7 +81,7 @@ class PostPanel extends CommonCollapseSlidePage {
updateSegment( updateSegment(
isFirst: isFirst, isFirst: isFirst,
item: item, item: item,
value: currentPos, value: currentPos(),
); );
(context as Element).markNeedsBuild(); (context as Element).markNeedsBuild();
}, },
@@ -377,82 +377,105 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
color: theme.colorScheme.onInverseSurface, color: theme.colorScheme.onInverseSurface,
borderRadius: const BorderRadius.all(Radius.circular(12)), borderRadius: const BorderRadius.all(Radius.circular(12)),
), ),
child: Column( child: Builder(
mainAxisSize: MainAxisSize.min, builder: (context) => Column(
crossAxisAlignment: CrossAxisAlignment.stretch, spacing: 8,
children: [ mainAxisSize: MainAxisSize.min,
if (item.actionType != ActionType.full) crossAxisAlignment: CrossAxisAlignment.stretch,
PostPanel.segmentWidget( children: [
theme, if (item.actionType != ActionType.full)
item: item, PostPanel.segmentWidget(
currentPos: currentPos, theme,
videoDuration: videoDuration, item: item,
), currentPos: () => currentPos,
Wrap( videoDuration: videoDuration,
runSpacing: 8, ),
spacing: 16, Wrap(
children: [ runSpacing: 8,
PopupMenuText( spacing: 16,
title: '分类', children: [
initialValue: item.category, PopupMenuText(
onSelected: (e) { title: '分类',
item.category = e; initialValue: item.category,
List<ActionType> constraintList = e.toActionType; onSelected: (e) {
if (!constraintList.contains(item.actionType)) { bool flag = false;
item.actionType = constraintList.first; if (item.category == SegmentType.exclusive_access ||
} item.category == SegmentType.poi_highlight) {
switch (e) { flag = true;
case SegmentType.poi_highlight: }
PostPanel.updateSegment( item.category = e;
isFirst: false, List<ActionType> constraintList = e.toActionType;
item: item, if (!constraintList.contains(item.actionType)) {
value: item.segment.first, item.actionType = constraintList.first;
); flag = true;
break; }
case SegmentType.exclusive_access: switch (e) {
case SegmentType.poi_highlight:
PostPanel.updateSegment(
isFirst: false,
item: item,
value: item.segment.first,
);
break;
case SegmentType.exclusive_access:
PostPanel.updateSegment(
isFirst: true,
item: item,
value: 0,
);
break;
default:
}
if (flag) {
(context as Element).markNeedsBuild();
}
return flag;
},
itemBuilder: (context) => SegmentType.values
.map(
(e) =>
PopupMenuItem(value: e, child: Text(e.title)),
)
.toList(),
getSelectTitle: (category) => category.title,
),
PopupMenuText(
title: '行为类别',
initialValue: item.actionType,
onSelected: (e) {
bool flag = false;
if (item.actionType == ActionType.full) {
flag = true;
}
item.actionType = e;
if (e == ActionType.full) {
flag = true;
PostPanel.updateSegment( PostPanel.updateSegment(
isFirst: true, isFirst: true,
item: item, item: item,
value: 0, value: 0,
); );
break; }
default: if (flag) {
} (context as Element).markNeedsBuild();
}, }
itemBuilder: (context) => SegmentType.values return flag;
.map( },
(e) => PopupMenuItem(value: e, child: Text(e.title)), itemBuilder: (context) => ActionType.values
) .map(
.toList(), (e) => PopupMenuItem(
getSelectTitle: (category) => category.title, enabled: item.category.toActionType.contains(e),
), value: e,
PopupMenuText( child: Text(e.title),
title: '行为类别', ),
initialValue: item.actionType, )
onSelected: (e) { .toList(),
item.actionType = e; getSelectTitle: (i) => i.title,
if (e == ActionType.full) { ),
PostPanel.updateSegment( ],
isFirst: true, ),
item: item, ],
value: 0, ),
);
}
},
itemBuilder: (context) => ActionType.values
.map(
(e) => PopupMenuItem(
enabled: item.category.toActionType.contains(e),
value: e,
child: Text(e.title),
),
)
.toList(),
getSelectTitle: (i) => i.title,
),
],
),
],
), ),
), ),
Positioned( Positioned(
@@ -492,7 +515,9 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
await Future.delayed(Duration(milliseconds: delay)); await Future.delayed(Duration(milliseconds: delay));
} }
videoCtr.seek( videoCtr.seek(
Duration(milliseconds: (item.segment.second * 1000).round()), Duration(
milliseconds: (item.segment.second * 1000).round(),
),
); );
} }
}, },

View File

@@ -38,6 +38,7 @@ class _WhisperPageState extends State<WhisperPage> {
onPressed: () => e.type.action( onPressed: () => e.type.action(
context: context, context: context,
controller: _controller, controller: _controller,
item: e,
), ),
icon: e.type.icon, icon: e.type.icon,
); );
@@ -57,6 +58,7 @@ class _WhisperPageState extends State<WhisperPage> {
onTap: () => e.type.action( onTap: () => e.type.action(
context: context, context: context,
controller: _controller, controller: _controller,
item: e,
), ),
child: Row( child: Row(
children: [ children: [

View File

@@ -47,6 +47,7 @@ class _WhisperSecPageState extends State<WhisperSecPage> {
onTap: () => e.type.action( onTap: () => e.type.action(
context: context, context: context,
controller: _controller, controller: _controller,
item: e,
), ),
child: Row( child: Row(
children: [ children: [

View File

@@ -1906,7 +1906,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
PostPanel.segmentWidget( PostPanel.segmentWidget(
theme, theme,
item: model, item: model,
currentPos: currentPos, currentPos: () => currentPos,
videoDuration: duration, videoDuration: duration,
), ),
PopupMenuText( PopupMenuText(
@@ -1916,6 +1916,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
final video = videoCtr.findVideoByQa(value); final video = videoCtr.findVideoByQa(value);
url = video.baseUrl; url = video.baseUrl;
qa = video.quality; qa = video.quality;
return false;
}, },
itemBuilder: (context) => videoInfo.supportFormats! itemBuilder: (context) => videoInfo.supportFormats!
.map( .map(
@@ -1932,9 +1933,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
title: 'webp预设', title: 'webp预设',
initialValue: preset, initialValue: preset,
onSelected: (value) { onSelected: (value) {
if (preset == value) return;
preset = value; preset = value;
(context as Element).markNeedsBuild(); return false;
}, },
itemBuilder: (context) => WebpPreset.values itemBuilder: (context) => WebpPreset.values
.map((i) => PopupMenuItem(value: i, child: Text(i.name))) .map((i) => PopupMenuItem(value: i, child: Text(i.name)))

View File

@@ -1,11 +1,13 @@
import 'dart:io'; import 'dart:io';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pb.dart' show ThreeDotItem;
import 'package:PiliPlus/grpc/bilibili/app/im/v1.pbenum.dart' import 'package:PiliPlus/grpc/bilibili/app/im/v1.pbenum.dart'
show IMSettingType, ThreeDotItemType; show IMSettingType, ThreeDotItemType;
import 'package:PiliPlus/pages/common/common_whisper_controller.dart'; import 'package:PiliPlus/pages/common/common_whisper_controller.dart';
import 'package:PiliPlus/pages/contact/view.dart'; import 'package:PiliPlus/pages/contact/view.dart';
import 'package:PiliPlus/pages/whisper_settings/view.dart'; import 'package:PiliPlus/pages/whisper_settings/view.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:floating/floating.dart'; import 'package:floating/floating.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@@ -172,6 +174,7 @@ extension ThreeDotItemTypeExt on ThreeDotItemType {
void action({ void action({
required BuildContext context, required BuildContext context,
required CommonWhisperController controller, required CommonWhisperController controller,
required ThreeDotItem item,
}) { }) {
switch (this) { switch (this) {
case ThreeDotItemType.THREE_DOT_ITEM_TYPE_READ_ALL: case ThreeDotItemType.THREE_DOT_ITEM_TYPE_READ_ALL:
@@ -195,15 +198,26 @@ extension ThreeDotItemTypeExt on ThreeDotItemType {
), ),
); );
case ThreeDotItemType.THREE_DOT_ITEM_TYPE_UP_HELPER: case ThreeDotItemType.THREE_DOT_ITEM_TYPE_UP_HELPER:
Get.toNamed( dynamic talkerId = PiliScheme.uriDigitRegExp
'/whisperDetail', .firstMatch(item.url)
arguments: { ?.group(1);
'talkerId': 844424930131966, if (talkerId != null) {
'name': 'UP主小助手', talkerId = int.parse(talkerId);
'face': Get.toNamed(
'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png', '/whisperDetail',
}, arguments: {
); 'talkerId': talkerId,
'name': item.title,
'face': switch (talkerId) {
844424930131966 =>
'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png',
844424930131964 =>
'https://i0.hdslb.com/bfs/im_new/58eda511672db078466e7ab8db22a95c1503684976.png',
_ => item.icon,
},
},
);
}
case ThreeDotItemType.THREE_DOT_ITEM_TYPE_CONTACTS: case ThreeDotItemType.THREE_DOT_ITEM_TYPE_CONTACTS:
Get.to(const ContactPage(isFromSelect: false)); Get.to(const ContactPage(isFromSelect: false));
default: default: