mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-06-01 16:48:16 +08:00
@@ -26,63 +26,81 @@ Future<void> autoWrapReportDialog(
|
||||
right: 16,
|
||||
bottom: 10,
|
||||
),
|
||||
content: Form(
|
||||
key: key,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Builder(
|
||||
builder: (context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
child: Builder(
|
||||
builder: (context) => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: 22,
|
||||
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,
|
||||
top: 5,
|
||||
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(),
|
||||
child: Form(
|
||||
key: key,
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
minLines: 2,
|
||||
maxLines: 4,
|
||||
initialValue: reasonDesc,
|
||||
decoration: const InputDecoration(
|
||||
labelText: '为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息',
|
||||
border: OutlineInputBorder(),
|
||||
contentPadding: EdgeInsets.all(10),
|
||||
),
|
||||
onChanged: (value) => reasonDesc = value,
|
||||
validator: (value) =>
|
||||
value.isNullOrEmpty ? '理由不能为空' : null,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (reasonType == 0)
|
||||
ReasonField(
|
||||
onChanged: (value) => reasonDesc = value,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 14, top: 6),
|
||||
child: CheckBoxText(
|
||||
text: '拉黑该用户',
|
||||
onChanged: (value) => banUid = value,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 14, top: 6),
|
||||
child: CheckBoxText(
|
||||
text: '拉黑该用户',
|
||||
onChanged: (value) => banUid = value,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
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 {
|
||||
final String text;
|
||||
final ValueChanged<bool> onChanged;
|
||||
|
||||
@@ -1314,10 +1314,10 @@ class _RenderListTile extends RenderBox
|
||||
Iterable<RenderBox> get children {
|
||||
final RenderBox? title = childForSlot(_ListTileSlot.title);
|
||||
return <RenderBox>[
|
||||
if (leading != null) leading!,
|
||||
if (title != null) title,
|
||||
if (subtitle != null) subtitle!,
|
||||
if (trailing != null) trailing!,
|
||||
?leading,
|
||||
?title,
|
||||
?subtitle,
|
||||
?trailing,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ class HttpError extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return isSliver
|
||||
? SliverToBoxAdapter(child: content(context))
|
||||
: content(context);
|
||||
: SizedBox(width: double.infinity, child: content(context));
|
||||
}
|
||||
|
||||
Widget content(BuildContext context) {
|
||||
|
||||
@@ -479,6 +479,7 @@ class AuthorPanel extends StatelessWidget {
|
||||
mid: moduleAuthor.mid,
|
||||
dynId: item.idStr,
|
||||
reasonType: reasonType,
|
||||
reasonDesc: reasonType == 0 ? reasonDesc : null,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -172,9 +172,7 @@ class _VotePanelState extends State<VotePanel> {
|
||||
CheckBoxText(
|
||||
text: '匿名',
|
||||
selected: anonymity,
|
||||
onChanged: (val) {
|
||||
anonymity = val;
|
||||
},
|
||||
onChanged: (val) => anonymity = val,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:ui' show Offset;
|
||||
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/http/match.dart';
|
||||
import 'package:PiliPlus/models_new/match/match_info/contest.dart';
|
||||
@@ -18,9 +16,6 @@ class MatchInfoController extends CommonDynController {
|
||||
final Rx<LoadingState<MatchContest?>> infoState =
|
||||
LoadingState<MatchContest?>.loading().obs;
|
||||
|
||||
@override
|
||||
Offset get fabOffset => const Offset(0, 2);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
@@ -21,7 +21,7 @@ class MusicDetailController extends CommonDynController {
|
||||
bool get showDynActionBar => Pref.showDynActionBar;
|
||||
|
||||
String get shareUrl =>
|
||||
'https://music.bilibili.com/h5/music-detail?music_id=${musicId}';
|
||||
'https://music.bilibili.com/h5/music-detail?music_id=$musicId';
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
|
||||
|
||||
typedef PopupMenuItemSelected<T> = bool Function(T value);
|
||||
|
||||
class PopupMenuText<T> extends StatefulWidget {
|
||||
final String title;
|
||||
final T initialValue;
|
||||
@@ -24,9 +26,17 @@ class PopupMenuText<T> extends StatefulWidget {
|
||||
class _PopupMenuTextState<T> extends State<PopupMenuText<T>> {
|
||||
late T select = widget.initialValue;
|
||||
|
||||
@override
|
||||
void didUpdateWidget(PopupMenuText<T> oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.initialValue != widget.initialValue) {
|
||||
select = widget.initialValue;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final secondary = Theme.of(context).colorScheme.secondary;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@@ -35,10 +45,11 @@ class _PopupMenuTextState<T> extends State<PopupMenuText<T>> {
|
||||
initialValue: select,
|
||||
onSelected: (value) {
|
||||
if (value == select) return;
|
||||
setState(() {
|
||||
select = value;
|
||||
widget.onSelected(value);
|
||||
});
|
||||
if (!widget.onSelected(value)) {
|
||||
setState(() {
|
||||
select = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
itemBuilder: widget.itemBuilder,
|
||||
child: Row(
|
||||
@@ -49,7 +60,7 @@ class _PopupMenuTextState<T> extends State<PopupMenuText<T>> {
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 14,
|
||||
color: theme.colorScheme.secondary,
|
||||
color: secondary,
|
||||
),
|
||||
strutStyle: const StrutStyle(
|
||||
height: 1,
|
||||
@@ -59,7 +70,7 @@ class _PopupMenuTextState<T> extends State<PopupMenuText<T>> {
|
||||
Icon(
|
||||
MdiIcons.unfoldMoreHorizontal,
|
||||
size: MediaQuery.textScalerOf(context).scale(14),
|
||||
color: theme.colorScheme.secondary,
|
||||
color: secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -57,7 +57,7 @@ class PostPanel extends CommonCollapseSlidePage {
|
||||
static Widget segmentWidget(
|
||||
ThemeData theme, {
|
||||
required PostSegmentModel item,
|
||||
required double currentPos,
|
||||
required double Function() currentPos, // get real-time pos
|
||||
required double videoDuration,
|
||||
}) {
|
||||
Widget segment(bool isFirst) => Builder(
|
||||
@@ -81,7 +81,7 @@ class PostPanel extends CommonCollapseSlidePage {
|
||||
updateSegment(
|
||||
isFirst: isFirst,
|
||||
item: item,
|
||||
value: currentPos,
|
||||
value: currentPos(),
|
||||
);
|
||||
(context as Element).markNeedsBuild();
|
||||
},
|
||||
@@ -377,82 +377,105 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
color: theme.colorScheme.onInverseSurface,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (item.actionType != ActionType.full)
|
||||
PostPanel.segmentWidget(
|
||||
theme,
|
||||
item: item,
|
||||
currentPos: currentPos,
|
||||
videoDuration: videoDuration,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 16,
|
||||
children: [
|
||||
PopupMenuText(
|
||||
title: '分类',
|
||||
initialValue: item.category,
|
||||
onSelected: (e) {
|
||||
item.category = e;
|
||||
List<ActionType> constraintList = e.toActionType;
|
||||
if (!constraintList.contains(item.actionType)) {
|
||||
item.actionType = constraintList.first;
|
||||
}
|
||||
switch (e) {
|
||||
case SegmentType.poi_highlight:
|
||||
PostPanel.updateSegment(
|
||||
isFirst: false,
|
||||
item: item,
|
||||
value: item.segment.first,
|
||||
);
|
||||
break;
|
||||
case SegmentType.exclusive_access:
|
||||
child: Builder(
|
||||
builder: (context) => Column(
|
||||
spacing: 8,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (item.actionType != ActionType.full)
|
||||
PostPanel.segmentWidget(
|
||||
theme,
|
||||
item: item,
|
||||
currentPos: () => currentPos,
|
||||
videoDuration: videoDuration,
|
||||
),
|
||||
Wrap(
|
||||
runSpacing: 8,
|
||||
spacing: 16,
|
||||
children: [
|
||||
PopupMenuText(
|
||||
title: '分类',
|
||||
initialValue: item.category,
|
||||
onSelected: (e) {
|
||||
bool flag = false;
|
||||
if (item.category == SegmentType.exclusive_access ||
|
||||
item.category == SegmentType.poi_highlight) {
|
||||
flag = true;
|
||||
}
|
||||
item.category = e;
|
||||
List<ActionType> constraintList = e.toActionType;
|
||||
if (!constraintList.contains(item.actionType)) {
|
||||
item.actionType = constraintList.first;
|
||||
flag = true;
|
||||
}
|
||||
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(
|
||||
isFirst: true,
|
||||
item: item,
|
||||
value: 0,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
},
|
||||
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) {
|
||||
item.actionType = e;
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
}
|
||||
if (flag) {
|
||||
(context as Element).markNeedsBuild();
|
||||
}
|
||||
return flag;
|
||||
},
|
||||
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(
|
||||
@@ -492,7 +515,9 @@ class _PostPanelState extends CommonCollapseSlidePageState<PostPanel> {
|
||||
await Future.delayed(Duration(milliseconds: delay));
|
||||
}
|
||||
videoCtr.seek(
|
||||
Duration(milliseconds: (item.segment.second * 1000).round()),
|
||||
Duration(
|
||||
milliseconds: (item.segment.second * 1000).round(),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -38,6 +38,7 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
onPressed: () => e.type.action(
|
||||
context: context,
|
||||
controller: _controller,
|
||||
item: e,
|
||||
),
|
||||
icon: e.type.icon,
|
||||
);
|
||||
@@ -57,6 +58,7 @@ class _WhisperPageState extends State<WhisperPage> {
|
||||
onTap: () => e.type.action(
|
||||
context: context,
|
||||
controller: _controller,
|
||||
item: e,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -47,6 +47,7 @@ class _WhisperSecPageState extends State<WhisperSecPage> {
|
||||
onTap: () => e.type.action(
|
||||
context: context,
|
||||
controller: _controller,
|
||||
item: e,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -1906,7 +1906,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
PostPanel.segmentWidget(
|
||||
theme,
|
||||
item: model,
|
||||
currentPos: currentPos,
|
||||
currentPos: () => currentPos,
|
||||
videoDuration: duration,
|
||||
),
|
||||
PopupMenuText(
|
||||
@@ -1916,6 +1916,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
final video = videoCtr.findVideoByQa(value);
|
||||
url = video.baseUrl;
|
||||
qa = video.quality;
|
||||
return false;
|
||||
},
|
||||
itemBuilder: (context) => videoInfo.supportFormats!
|
||||
.map(
|
||||
@@ -1932,9 +1933,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
||||
title: 'webp预设',
|
||||
initialValue: preset,
|
||||
onSelected: (value) {
|
||||
if (preset == value) return;
|
||||
preset = value;
|
||||
(context as Element).markNeedsBuild();
|
||||
return false;
|
||||
},
|
||||
itemBuilder: (context) => WebpPreset.values
|
||||
.map((i) => PopupMenuItem(value: i, child: Text(i.name)))
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import 'dart:io';
|
||||
|
||||
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'
|
||||
show IMSettingType, ThreeDotItemType;
|
||||
import 'package:PiliPlus/pages/common/common_whisper_controller.dart';
|
||||
import 'package:PiliPlus/pages/contact/view.dart';
|
||||
import 'package:PiliPlus/pages/whisper_settings/view.dart';
|
||||
import 'package:PiliPlus/utils/app_scheme.dart';
|
||||
import 'package:floating/floating.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
@@ -172,6 +174,7 @@ extension ThreeDotItemTypeExt on ThreeDotItemType {
|
||||
void action({
|
||||
required BuildContext context,
|
||||
required CommonWhisperController controller,
|
||||
required ThreeDotItem item,
|
||||
}) {
|
||||
switch (this) {
|
||||
case ThreeDotItemType.THREE_DOT_ITEM_TYPE_READ_ALL:
|
||||
@@ -195,15 +198,26 @@ extension ThreeDotItemTypeExt on ThreeDotItemType {
|
||||
),
|
||||
);
|
||||
case ThreeDotItemType.THREE_DOT_ITEM_TYPE_UP_HELPER:
|
||||
Get.toNamed(
|
||||
'/whisperDetail',
|
||||
arguments: {
|
||||
'talkerId': 844424930131966,
|
||||
'name': 'UP主小助手',
|
||||
'face':
|
||||
'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png',
|
||||
},
|
||||
);
|
||||
dynamic talkerId = PiliScheme.uriDigitRegExp
|
||||
.firstMatch(item.url)
|
||||
?.group(1);
|
||||
if (talkerId != null) {
|
||||
talkerId = int.parse(talkerId);
|
||||
Get.toNamed(
|
||||
'/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:
|
||||
Get.to(const ContactPage(isFromSelect: false));
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user