✨ Report abuse
This commit is contained in:
		| @@ -440,5 +440,10 @@ | |||||||
|   "termRelated": "Related Terms", |   "termRelated": "Related Terms", | ||||||
|   "appDetails": "App Details", |   "appDetails": "App Details", | ||||||
|   "projectWebsite": "Project Website", |   "projectWebsite": "Project Website", | ||||||
|   "iAmNotRobot": "I'm not a Robot" |   "iAmNotRobot": "I'm not a Robot", | ||||||
|  |   "report": "Report", | ||||||
|  |   "reportAbuse": "Report abuse", | ||||||
|  |   "reportAbuseResource": "Resource identifier", | ||||||
|  |   "reportAbuseReason": "Report reason", | ||||||
|  |   "reportSubmitted": "Report submitted, thank you for your contribution. We will send a notification about the result of the report within 24 hours for you." | ||||||
| } | } | ||||||
|   | |||||||
| @@ -436,5 +436,10 @@ | |||||||
|   "termRelated": "相关条款", |   "termRelated": "相关条款", | ||||||
|   "projectWebsite": "项目网站", |   "projectWebsite": "项目网站", | ||||||
|   "appDetails": "应用详情", |   "appDetails": "应用详情", | ||||||
|   "iAmNotRobot": "我不是机器人" |   "iAmNotRobot": "我不是机器人", | ||||||
|  |   "report": "举报", | ||||||
|  |   "reportAbuse": "举报滥用", | ||||||
|  |   "reportAbuseResource": "举报的资源", | ||||||
|  |   "reportAbuseReason": "举报的原因", | ||||||
|  |   "reportSubmitted": "举报已提交,感谢你的贡献。我们将通过通知在 24 小时内通知该举报的处理结果。" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -41,30 +41,21 @@ class _BootstrapperShellState extends State<BootstrapperShell> { | |||||||
|  |  | ||||||
|   final Completer _bootCompleter = Completer(); |   final Completer _bootCompleter = Completer(); | ||||||
|  |  | ||||||
|   late final List<({String label, Future<void> Function() action})> _periods = [ |   Future<void> _checkForUpdate() async { | ||||||
|     ( |  | ||||||
|       label: 'bsLoadingTheme', |  | ||||||
|       action: () async { |  | ||||||
|         await context.read<ThemeSwitcher>().restoreTheme(); |  | ||||||
|       }, |  | ||||||
|     ), |  | ||||||
|     ( |  | ||||||
|       label: 'bsCheckForUpdate', |  | ||||||
|       action: () async { |  | ||||||
|     if (PlatformInfo.isWeb) return; |     if (PlatformInfo.isWeb) return; | ||||||
|     try { |     try { | ||||||
|       final prefs = await SharedPreferences.getInstance(); |       final prefs = await SharedPreferences.getInstance(); | ||||||
|       final info = await PackageInfo.fromPlatform(); |       final info = await PackageInfo.fromPlatform(); | ||||||
|       final localVersionString = '${info.version}+${info.buildNumber}'; |       final localVersionString = '${info.version}+${info.buildNumber}'; | ||||||
|           final resp = await GetConnect().get( |       final resp = await GetConnect( | ||||||
|  |         timeout: const Duration(seconds: 60), | ||||||
|  |       ).get( | ||||||
|         'https://git.solsynth.dev/api/v1/repos/hydrogen/solian/tags?page=1&limit=1', |         'https://git.solsynth.dev/api/v1/repos/hydrogen/solian/tags?page=1&limit=1', | ||||||
|       ); |       ); | ||||||
|       final remoteVersionString = |       final remoteVersionString = | ||||||
|           (resp.body as List).firstOrNull?['name'] ?? '0.0.0+0'; |           (resp.body as List).firstOrNull?['name'] ?? '0.0.0+0'; | ||||||
|           final remoteVersion = |       final remoteVersion = Version.parse(remoteVersionString.split('+').first); | ||||||
|               Version.parse(remoteVersionString.split('+').first); |       final localVersion = Version.parse(localVersionString.split('+').first); | ||||||
|           final localVersion = |  | ||||||
|               Version.parse(localVersionString.split('+').first); |  | ||||||
|       final remoteBuildNumber = |       final remoteBuildNumber = | ||||||
|           int.tryParse(remoteVersionString.split('+').last) ?? 0; |           int.tryParse(remoteVersionString.split('+').last) ?? 0; | ||||||
|       final localBuildNumber = |       final localBuildNumber = | ||||||
| @@ -74,11 +65,6 @@ class _BootstrapperShellState extends State<BootstrapperShell> { | |||||||
|           (remoteVersion == localVersion && |           (remoteVersion == localVersion && | ||||||
|               remoteBuildNumber > localBuildNumber) || |               remoteBuildNumber > localBuildNumber) || | ||||||
|           (remoteVersionString != localVersionString && strictUpdate)) { |           (remoteVersionString != localVersionString && strictUpdate)) { | ||||||
|             setState(() { |  | ||||||
|               _isErrored = true; |  | ||||||
|               _subtitle = 'bsCheckForUpdateDesc'.tr; |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|         if (PlatformInfo.isAndroid) { |         if (PlatformInfo.isAndroid) { | ||||||
|           context |           context | ||||||
|               .showConfirmDialog( |               .showConfirmDialog( | ||||||
| @@ -100,10 +86,10 @@ class _BootstrapperShellState extends State<BootstrapperShell> { | |||||||
|             } |             } | ||||||
|           }); |           }); | ||||||
|         } else { |         } else { | ||||||
|               setState(() { |           context.showInfoDialog( | ||||||
|                 _isErrored = true; |             'updateAvailable'.tr, | ||||||
|                 _subtitle = 'bsCheckForUpdateDesc'.tr; |             'bsCheckForUpdateDesc'.tr, | ||||||
|               }); |           ); | ||||||
|         } |         } | ||||||
|       } else if (remoteVersionString != localVersionString) { |       } else if (remoteVersionString != localVersionString) { | ||||||
|         _bootCompleter.future.then((_) { |         _bootCompleter.future.then((_) { | ||||||
| @@ -115,6 +101,13 @@ class _BootstrapperShellState extends State<BootstrapperShell> { | |||||||
|     } catch (e) { |     } catch (e) { | ||||||
|       context.showErrorDialog('Unable to check update: $e'); |       context.showErrorDialog('Unable to check update: $e'); | ||||||
|     } |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   late final List<({String label, Future<void> Function() action})> _periods = [ | ||||||
|  |     ( | ||||||
|  |       label: 'bsLoadingTheme', | ||||||
|  |       action: () async { | ||||||
|  |         await context.read<ThemeSwitcher>().restoreTheme(); | ||||||
|       }, |       }, | ||||||
|     ), |     ), | ||||||
|     ( |     ( | ||||||
| @@ -214,6 +207,7 @@ class _BootstrapperShellState extends State<BootstrapperShell> { | |||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|     _runPeriods(); |     _runPeriods(); | ||||||
|  |     _checkForUpdate(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   | |||||||
| @@ -146,7 +146,10 @@ class _SignUpScreenState extends State<SignUpScreen> { | |||||||
|             const Gap(8), |             const Gap(8), | ||||||
|             CheckboxListTile( |             CheckboxListTile( | ||||||
|               value: _isTermAccepted, |               value: _isTermAccepted, | ||||||
|               title: Text('termAccept'.tr), |               title: Text( | ||||||
|  |                 'termAccept'.tr, | ||||||
|  |                 style: const TextStyle(height: 1.2), | ||||||
|  |               ).paddingOnly(bottom: 4), | ||||||
|               shape: const RoundedRectangleBorder( |               shape: const RoundedRectangleBorder( | ||||||
|                 borderRadius: BorderRadius.all( |                 borderRadius: BorderRadius.all( | ||||||
|                   Radius.circular(8), |                   Radius.circular(8), | ||||||
|   | |||||||
| @@ -74,9 +74,13 @@ class LinkExpansion extends StatelessWidget { | |||||||
|                                 ), |                                 ), | ||||||
|                               ).paddingOnly(right: 8), |                               ).paddingOnly(right: 8), | ||||||
|                             if (snapshot.data!.siteName != null) |                             if (snapshot.data!.siteName != null) | ||||||
|                               Text( |                               Expanded( | ||||||
|  |                                 child: Text( | ||||||
|                                   snapshot.data!.siteName!, |                                   snapshot.data!.siteName!, | ||||||
|                                   style: Theme.of(context).textTheme.labelLarge, |                                   style: Theme.of(context).textTheme.labelLarge, | ||||||
|  |                                   maxLines: 1, | ||||||
|  |                                   overflow: TextOverflow.ellipsis, | ||||||
|  |                                 ), | ||||||
|                               ), |                               ), | ||||||
|                           ], |                           ], | ||||||
|                         ).paddingOnly( |                         ).paddingOnly( | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import 'package:solian/platform.dart'; | |||||||
| import 'package:solian/providers/auth.dart'; | import 'package:solian/providers/auth.dart'; | ||||||
| import 'package:solian/router.dart'; | import 'package:solian/router.dart'; | ||||||
| import 'package:solian/screens/posts/post_editor.dart'; | import 'package:solian/screens/posts/post_editor.dart'; | ||||||
|  | import 'package:solian/widgets/reports/abuse_report.dart'; | ||||||
|  |  | ||||||
| class PostAction extends StatefulWidget { | class PostAction extends StatefulWidget { | ||||||
|   final Post item; |   final Post item; | ||||||
| @@ -149,6 +150,23 @@ class _PostActionState extends State<PostAction> { | |||||||
|                     Navigator.pop(context); |                     Navigator.pop(context); | ||||||
|                   }, |                   }, | ||||||
|                 ), |                 ), | ||||||
|  |                 ListTile( | ||||||
|  |                   contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|  |                   leading: const Icon(Icons.report), | ||||||
|  |                   title: Text('report'.tr), | ||||||
|  |                   onTap: () { | ||||||
|  |                     showDialog( | ||||||
|  |                       context: context, | ||||||
|  |                       builder: (context) => AbuseReportDialog( | ||||||
|  |                         resourceId: 'post:${widget.item.id}', | ||||||
|  |                       ), | ||||||
|  |                     ).then((status) { | ||||||
|  |                       if (status == true) { | ||||||
|  |                         Navigator.pop(context); | ||||||
|  |                       } | ||||||
|  |                     }); | ||||||
|  |                   }, | ||||||
|  |                 ), | ||||||
|                 if (!widget.noReact) |                 if (!widget.noReact) | ||||||
|                   ListTile( |                   ListTile( | ||||||
|                     contentPadding: const EdgeInsets.symmetric(horizontal: 24), |                     contentPadding: const EdgeInsets.symmetric(horizontal: 24), | ||||||
|   | |||||||
							
								
								
									
										107
									
								
								lib/widgets/reports/abuse_report.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								lib/widgets/reports/abuse_report.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,107 @@ | |||||||
|  | import 'package:flutter/material.dart'; | ||||||
|  | import 'package:gap/gap.dart'; | ||||||
|  | import 'package:get/get.dart'; | ||||||
|  | import 'package:solian/exceptions/request.dart'; | ||||||
|  | import 'package:solian/exts.dart'; | ||||||
|  | import 'package:solian/providers/auth.dart'; | ||||||
|  |  | ||||||
|  | class AbuseReportDialog extends StatefulWidget { | ||||||
|  |   final String? resourceId; | ||||||
|  |  | ||||||
|  |   const AbuseReportDialog({super.key, this.resourceId}); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   State<AbuseReportDialog> createState() => _AbuseReportDialogState(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | class _AbuseReportDialogState extends State<AbuseReportDialog> { | ||||||
|  |   final TextEditingController _resourceController = TextEditingController(); | ||||||
|  |   final TextEditingController _reasonController = TextEditingController(); | ||||||
|  |  | ||||||
|  |   bool _isBusy = false; | ||||||
|  |  | ||||||
|  |   Future<void> _submit() async { | ||||||
|  |     final auth = Get.find<AuthProvider>(); | ||||||
|  |     if (!auth.isAuthorized.value) return; | ||||||
|  |     final client = await auth.configureClient('id'); | ||||||
|  |  | ||||||
|  |     setState(() => _isBusy = true); | ||||||
|  |  | ||||||
|  |     final resp = await client.post('/reports/abuse', { | ||||||
|  |       'resource': _resourceController.text, | ||||||
|  |       'reason': _reasonController.text, | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     setState(() => _isBusy = false); | ||||||
|  |  | ||||||
|  |     if (resp.statusCode != 200) { | ||||||
|  |       context.showErrorDialog(RequestException(resp)); | ||||||
|  |     } else { | ||||||
|  |       context.showSnackbar('reportSubmitted'.tr); | ||||||
|  |       Navigator.pop(context, true); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     if (widget.resourceId != null) { | ||||||
|  |       _resourceController.text = widget.resourceId!; | ||||||
|  |     } | ||||||
|  |     super.initState(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _resourceController.dispose(); | ||||||
|  |     _reasonController.dispose(); | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget build(BuildContext context) { | ||||||
|  |     return AlertDialog( | ||||||
|  |       title: Text('reportAbuse'.tr), | ||||||
|  |       content: Column( | ||||||
|  |         mainAxisSize: MainAxisSize.min, | ||||||
|  |         children: [ | ||||||
|  |           const Gap(4), | ||||||
|  |           TextField( | ||||||
|  |             controller: _resourceController, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               border: const OutlineInputBorder(), | ||||||
|  |               labelText: 'reportAbuseResource'.tr, | ||||||
|  |               enabled: widget.resourceId == null, | ||||||
|  |               isDense: true, | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |           const Gap(12), | ||||||
|  |           TextField( | ||||||
|  |             controller: _reasonController, | ||||||
|  |             textInputAction: TextInputAction.newline, | ||||||
|  |             minLines: 1, | ||||||
|  |             maxLines: 3, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               border: const OutlineInputBorder(), | ||||||
|  |               labelText: 'reportAbuseReason'.tr, | ||||||
|  |               isDense: true, | ||||||
|  |             ), | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|  |       ), | ||||||
|  |       actions: [ | ||||||
|  |         TextButton( | ||||||
|  |           onPressed: _isBusy | ||||||
|  |               ? null | ||||||
|  |               : () { | ||||||
|  |                   Navigator.pop(context); | ||||||
|  |                 }, | ||||||
|  |           child: Text('cancel'.tr), | ||||||
|  |         ), | ||||||
|  |         TextButton( | ||||||
|  |           onPressed: _isBusy ? null : () => _submit(), | ||||||
|  |           child: Text('okay'.tr), | ||||||
|  |         ), | ||||||
|  |       ], | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user