From ca42b295d7c4a6851af60cf56122fba7a7c52996 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Sat, 28 Sep 2024 14:47:58 +0800 Subject: [PATCH] feat: report user --- lib/http/api.dart | 2 + lib/http/constants.dart | 1 + lib/http/member.dart | 27 ++++++ lib/pages/member/view.dart | 174 +++++++++++++++++++++++++++++++++++++ 4 files changed, 204 insertions(+) diff --git a/lib/http/api.dart b/lib/http/api.dart index 61131545..461c398a 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -611,4 +611,6 @@ class Api { static const String unfavFolder = '/x/v3/fav/folder/unfav'; static const String videoTags = '/x/tag/archive/tags'; + + static const String reportMember = '/ajax/report/add'; } diff --git a/lib/http/constants.dart b/lib/http/constants.dart index b4b9945b..a4a12a96 100644 --- a/lib/http/constants.dart +++ b/lib/http/constants.dart @@ -7,6 +7,7 @@ class HttpString { static const String passBaseUrl = 'https://passport.bilibili.com'; static const String messageBaseUrl = 'https://message.bilibili.com'; static const String dynamicShareBaseUrl = 'https://t.bilibili.com'; + static const String spaceBaseUrl = 'https://space.bilibili.com'; static const List validateStatusCodes = [ 302, 304, diff --git a/lib/http/member.dart b/lib/http/member.dart index ff1cab73..04a441ca 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -1,4 +1,8 @@ +import 'package:PiliPalaX/http/constants.dart'; import 'package:PiliPalaX/http/loading_state.dart'; +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart' hide FormData; import '../models/dynamics/result.dart'; import '../models/follow/result.dart'; @@ -12,6 +16,29 @@ import '../utils/wbi_sign.dart'; import 'index.dart'; class MemberHttp { + static Future reportMember( + dynamic mid, { + String? reason, + int? reasonV2, + }) async { + var res = await Request().post( + HttpString.spaceBaseUrl + Api.reportMember, + data: FormData.fromMap( + { + 'mid': mid, + 'reason': reason, + if (reasonV2 != null) 'reason_v2': reasonV2, + 'csrf': await Request.getCsrf(), + }, + ), + ); + debugPrint('report: ${res.data}'); + return { + 'status': res.data['status'], + 'msg': res.data['message'] ?? res.data['data'], + }; + } + static Future memberInfo({ int? mid, String token = '', diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 5a2f0659..186044f8 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:PiliPalaX/http/member.dart'; +import 'package:PiliPalaX/http/user.dart'; +import 'package:PiliPalaX/models/member/info.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -144,6 +147,36 @@ class _MemberPageState extends State ], ), ), + if (_memberController.userInfo != null) ...[ + const PopupMenuDivider(), + PopupMenuItem( + onTap: () { + showDialog( + context: context, + builder: (_) => Dialog( + child: ReportPanel( + memberInfo: _memberController.memberInfo.value, + )), + ); + }, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.error_outline, + size: 19, + color: Theme.of(context).colorScheme.error, + ), + const SizedBox(width: 10), + Text( + '举报', + style: TextStyle( + color: Theme.of(context).colorScheme.error), + ), + ], + ), + ), + ], ], ), const SizedBox(width: 4), @@ -489,3 +522,144 @@ class _MemberPageState extends State ); } } + +class ReportPanel extends StatefulWidget { + const ReportPanel({super.key, required this.memberInfo}); + + final MemberInfoModel memberInfo; + + @override + State createState() => _ReportPanelState(); +} + +class _ReportPanelState extends State { + final List _reasonList = List.generate(3, (_) => false).toList(); + final Set _reason = {}; + int? _reasonV2; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '举报: ${widget.memberInfo.name}', + style: const TextStyle(fontSize: 18), + ), + const SizedBox(height: 4), + Text('uid: ${widget.memberInfo.mid}'), + const SizedBox(height: 10), + const Text('举报内容(必选,可多选)'), + ...List.generate( + 3, + (index) => _checkBoxWidget( + _reasonList[index], + (value) { + setState(() => _reasonList[index] = value); + if (value) { + _reason.add(index + 1); + } else { + _reason.remove(index + 1); + } + }, + ['头像违规', '昵称违规', '签名违规'][index], + ), + ).toList(), + const Text('举报理由(单选,非必选)'), + ...List.generate( + 5, + (index) => _radioWidget( + index, + _reasonV2, + (value) { + setState(() => _reasonV2 = value); + }, + ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index], + ), + ).toList(), + const SizedBox(height: 10), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: Get.back, + child: Text( + '取消', + style: + TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + TextButton( + onPressed: () async { + if (_reason.isEmpty) { + SmartDialog.showToast('至少选择一项作为举报内容'); + } else { + Get.back(); + dynamic result = await MemberHttp.reportMember( + widget.memberInfo.mid, + reason: _reason.join(','), + reasonV2: _reasonV2 != null ? _reasonV2! + 1 : null, + ); + if (result['msg'] is String && result['msg'].isNotEmpty) { + SmartDialog.showToast(result['msg']); + } else { + SmartDialog.showToast('举报失败'); + } + } + }, + child: const Text('确定'), + ), + ], + ), + ], + ), + ), + ); + } +} + +Widget _checkBoxWidget( + bool defValue, + ValueChanged onChanged, + String title, +) { + return InkWell( + onTap: () => onChanged(!defValue), + child: Row( + children: [ + Checkbox( + value: defValue, + onChanged: onChanged, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + Text(title), + ], + ), + ); +} + +Widget _radioWidget( + int value, + int? groupValue, + ValueChanged onChanged, + String title, +) { + return InkWell( + onTap: () => onChanged(value), + child: Row( + children: [ + Radio( + value: value, + groupValue: groupValue, + onChanged: onChanged, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + Text(title), + ], + ), + ); +}