split report dialog (#460)

This commit is contained in:
My-Responsitories
2025-03-16 13:34:04 +08:00
committed by GitHub
parent 0b8e95477c
commit 7854c5e6b9
4 changed files with 242 additions and 185 deletions

View File

@@ -1,15 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
Widget radioWidget<T>({ class RadioWidget<T> extends StatelessWidget {
required T value, final T value;
T? groupValue, final T? groupValue;
required ValueChanged onChanged, final ValueChanged<T?> onChanged;
required String title, final String title;
EdgeInsetsGeometry? padding, final EdgeInsetsGeometry? padding;
}) {
Widget child() => Row( const RadioWidget({
super.key,
required this.value,
this.groupValue,
required this.onChanged,
required this.title,
this.padding,
});
Widget _child() => Row(
children: [ children: [
Radio( Radio<T>(
value: value, value: value,
groupValue: groupValue, groupValue: groupValue,
onChanged: onChanged, onChanged: onChanged,
@@ -18,13 +27,67 @@ Widget radioWidget<T>({
Text(title), Text(title),
], ],
); );
return InkWell(
onTap: () => onChanged(value), @override
child: padding != null Widget build(BuildContext context) {
? Padding( return InkWell(
padding: padding, onTap: () => onChanged(value),
child: child(), child: padding != null
) ? Padding(
: child(), padding: padding!,
); child: _child(),
)
: _child(),
);
}
}
class WrapRadioOptionsGroup<T> extends StatelessWidget {
final String groupTitle;
final Map<T, String> options;
final T? selectedValue;
final ValueChanged<T?> onChanged;
final EdgeInsetsGeometry? itemPadding;
const WrapRadioOptionsGroup({
super.key,
required this.groupTitle,
required this.options,
required this.selectedValue,
required this.onChanged,
this.itemPadding,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (groupTitle.isNotEmpty)
Padding(
padding: const EdgeInsets.only(left: 22),
child: Text(
groupTitle,
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Wrap(
children: options.entries.map((entry) {
return IntrinsicWidth(
child: RadioWidget<T>(
value: entry.key,
groupValue: selectedValue,
onChanged: onChanged,
title: entry.value,
padding: itemPadding ?? const EdgeInsets.only(right: 10),
),
);
}).toList(),
),
),
],
);
}
} }

View File

@@ -1,7 +1,6 @@
import 'package:PiliPlus/common/widgets/radio_widget.dart'; import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/utils/extension.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -16,186 +15,181 @@ void autoWrapReportDialog(
late final key = GlobalKey<FormState>(); late final key = GlobalKey<FormState>();
showDialog( showDialog(
context: context, context: context,
builder: (context) { builder: (context) => StatefulBuilder(
return AlertDialog( builder: (context, setState) {
title: const Text('举报'), return AlertDialog(
titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22), title: const Text('举报'),
contentPadding: const EdgeInsets.symmetric(vertical: 5), titlePadding: const EdgeInsets.only(left: 22, top: 16, right: 22),
actionsPadding: const EdgeInsets.only(left: 16, right: 16, bottom: 10), contentPadding: const EdgeInsets.symmetric(vertical: 5),
content: Builder( actionsPadding:
builder: (context) { const EdgeInsets.only(left: 16, right: 16, bottom: 10),
return Column( content: Form(
key: key,
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Flexible( Flexible(
child: Material( child: SingleChildScrollView(
child: SingleChildScrollView( child: AnimatedSize(
child: AnimatedSize( duration: const Duration(milliseconds: 200),
duration: const Duration(milliseconds: 200), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ const Padding(
Padding( padding: EdgeInsets.only(
padding: const EdgeInsets.only( left: 22,
left: 22, right: 22,
right: 22, bottom: 5,
bottom: 5,
),
child: const Text('请选择举报的理由:'),
), ),
...options.entries.map<Widget>((title) { child: Text('请选择举报的理由:'),
return Column( ),
crossAxisAlignment: CrossAxisAlignment.start, ...options.entries.map(
children: [ (entry) => WrapRadioOptionsGroup<int>(
if (title.key.isNotEmpty) groupTitle: entry.key,
Padding( options: entry.value,
padding: const EdgeInsets.only(left: 22), selectedValue: reasonType,
child: Text( onChanged: (value) =>
title.key, setState(() => reasonType = value),
style: TextStyle( ),
color: Theme.of(context) ),
.colorScheme if (reasonType == 0)
.outline), ReasonField(
), onChanged: (value) => reasonDesc = value),
), ],
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12),
child: Wrap(
children: title.value.entries.map((opt) {
return IntrinsicWidth(
child: radioWidget<int>(
value: opt.key,
groupValue: reasonType,
title: opt.value,
padding: const EdgeInsets.only(
right: 10),
onChanged: (value) {
reasonType = value;
if (context.mounted) {
(context as Element?)
?.markNeedsBuild();
}
},
),
);
}).toList(),
),
),
],
);
}),
if (reasonType == 0)
Padding(
padding: const EdgeInsets.only(
left: 22,
top: 5,
right: 22,
),
child: Form(
key: key,
child: TextFormField(
autofocus: true,
minLines: 4,
maxLines: 4,
initialValue: reasonDesc,
inputFormatters: [
LengthLimitingTextInputFormatter(60),
],
decoration: const InputDecoration(
labelText:
'为帮助审核人员更快处理,请补充问题类型和出现位置等详细信息',
border: OutlineInputBorder(),
contentPadding: EdgeInsets.all(10),
),
onChanged: (value) => reasonDesc = value,
validator: (value) {
if (value.isNullOrEmpty) {
return '理由不能为空';
}
return null;
},
),
),
),
],
),
), ),
), ),
), ),
), ),
GestureDetector( BanUserCheckbox(onChanged: (value) => banUid = value),
onTap: () {
banUid = !banUid;
(context as Element?)?.markNeedsBuild();
},
child: Padding(
padding: const EdgeInsets.only(left: 18, top: 10),
child: Row(
children: [
Icon(
size: 22,
banUid
? Icons.check_box_outlined
: Icons.check_box_outline_blank,
color: banUid
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
),
Text(
' 拉黑该用户',
style: TextStyle(
color: banUid
? Theme.of(context).colorScheme.primary
: null,
),
),
],
),
),
),
], ],
);
},
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
), ),
), ),
TextButton( actions: [
onPressed: () async { TextButton(
if (reasonType == null || onPressed: Get.back,
(reasonType == 0 && key.currentState?.validate() != true)) { child: Text(
return; '取消',
} style: TextStyle(color: Theme.of(context).colorScheme.outline),
SmartDialog.showLoading(); ),
try { ),
final data = await onSuccess(reasonType!, reasonDesc, banUid); TextButton(
SmartDialog.dismiss(); onPressed: () async {
if (data['code'] == 0) { if (reasonType == null ||
Get.back(); (reasonType == 0 && key.currentState?.validate() != true)) {
SmartDialog.showToast('举报成功'); return;
} else {
SmartDialog.showToast(data['message']);
} }
} catch (e) { SmartDialog.showLoading();
SmartDialog.dismiss(); try {
SmartDialog.showToast('提交失败:$e'); final data = await onSuccess(reasonType!, reasonDesc, banUid);
} SmartDialog.dismiss();
}, if (data['code'] == 0) {
child: const Text('确定'), Get.back();
), SmartDialog.showToast('举报成功');
], } else {
); SmartDialog.showToast(data['message']);
}, }
} catch (e) {
SmartDialog.dismiss();
SmartDialog.showToast('提交失败:$e');
}
},
child: const Text('确定'),
),
],
);
},
),
); );
} }
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: (value) {
widget.onChanged(value);
},
validator: widget._validator,
),
);
}
}
class BanUserCheckbox extends StatefulWidget {
final ValueChanged<bool> onChanged;
const BanUserCheckbox({super.key, required this.onChanged});
@override
State<BanUserCheckbox> createState() => _BanUserCheckboxState();
}
class _BanUserCheckboxState extends State<BanUserCheckbox> {
bool _banUid = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() => _banUid = !_banUid);
widget.onChanged(_banUid);
},
child: Padding(
padding: const EdgeInsets.only(left: 18, top: 10),
child: Row(
children: [
Icon(
size: 22,
_banUid
? Icons.check_box_outlined
: Icons.check_box_outline_blank,
color: _banUid
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
),
Text(
' 拉黑该用户',
style: TextStyle(
color: _banUid ? Theme.of(context).colorScheme.primary : null,
),
),
],
),
),
);
}
}
class ReportOptions { class ReportOptions {
// from https://s1.hdslb.com/bfs/seed/jinkela/comment-h5/static/js/605.chunks.js // from https://s1.hdslb.com/bfs/seed/jinkela/comment-h5/static/js/605.chunks.js
static Map<String, Map<int, String>> get commentReport => { static Map<String, Map<int, String>> get commentReport => {

View File

@@ -582,7 +582,7 @@ class _ReportPanelState extends State<ReportPanel> {
const Text('举报理由(单选,非必选)'), const Text('举报理由(单选,非必选)'),
...List.generate( ...List.generate(
5, 5,
(index) => radioWidget<int>( (index) => RadioWidget<int>(
value: index, value: index,
groupValue: _reasonV2, groupValue: _reasonV2,
onChanged: (value) { onChanged: (value) {

View File

@@ -407,7 +407,7 @@ class Utils {
child: Builder( child: Builder(
builder: (context) => Column( builder: (context) => Column(
children: List.generate(list.length, (index) { children: List.generate(list.length, (index) {
return radioWidget( return RadioWidget(
padding: const EdgeInsets.only(left: 14), padding: const EdgeInsets.only(left: 14),
title: list[index].title ?? '', title: list[index].title ?? '',
groupValue: checkedId, groupValue: checkedId,