✨ Report abuse
This commit is contained in:
		| @@ -440,5 +440,10 @@ | ||||
|   "termRelated": "Related Terms", | ||||
|   "appDetails": "App Details", | ||||
|   "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": "相关条款", | ||||
|   "projectWebsite": "项目网站", | ||||
|   "appDetails": "应用详情", | ||||
|   "iAmNotRobot": "我不是机器人" | ||||
|   "iAmNotRobot": "我不是机器人", | ||||
|   "report": "举报", | ||||
|   "reportAbuse": "举报滥用", | ||||
|   "reportAbuseResource": "举报的资源", | ||||
|   "reportAbuseReason": "举报的原因", | ||||
|   "reportSubmitted": "举报已提交,感谢你的贡献。我们将通过通知在 24 小时内通知该举报的处理结果。" | ||||
| } | ||||
|   | ||||
| @@ -41,6 +41,68 @@ class _BootstrapperShellState extends State<BootstrapperShell> { | ||||
|  | ||||
|   final Completer _bootCompleter = Completer(); | ||||
|  | ||||
|   Future<void> _checkForUpdate() async { | ||||
|     if (PlatformInfo.isWeb) return; | ||||
|     try { | ||||
|       final prefs = await SharedPreferences.getInstance(); | ||||
|       final info = await PackageInfo.fromPlatform(); | ||||
|       final localVersionString = '${info.version}+${info.buildNumber}'; | ||||
|       final resp = await GetConnect( | ||||
|         timeout: const Duration(seconds: 60), | ||||
|       ).get( | ||||
|         'https://git.solsynth.dev/api/v1/repos/hydrogen/solian/tags?page=1&limit=1', | ||||
|       ); | ||||
|       final remoteVersionString = | ||||
|           (resp.body as List).firstOrNull?['name'] ?? '0.0.0+0'; | ||||
|       final remoteVersion = Version.parse(remoteVersionString.split('+').first); | ||||
|       final localVersion = Version.parse(localVersionString.split('+').first); | ||||
|       final remoteBuildNumber = | ||||
|           int.tryParse(remoteVersionString.split('+').last) ?? 0; | ||||
|       final localBuildNumber = | ||||
|           int.tryParse(localVersionString.split('+').last) ?? 0; | ||||
|       final strictUpdate = prefs.getBool('check_update_strictly') ?? false; | ||||
|       if (remoteVersion > localVersion || | ||||
|           (remoteVersion == localVersion && | ||||
|               remoteBuildNumber > localBuildNumber) || | ||||
|           (remoteVersionString != localVersionString && strictUpdate)) { | ||||
|         if (PlatformInfo.isAndroid) { | ||||
|           context | ||||
|               .showConfirmDialog( | ||||
|             'updateAvailable'.tr, | ||||
|             'updateAvailableDesc'.trParams({ | ||||
|               'from': localVersionString, | ||||
|               'to': remoteVersionString, | ||||
|             }), | ||||
|           ) | ||||
|               .then((result) { | ||||
|             if (result) { | ||||
|               final model = UpdateModel( | ||||
|                 'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk', | ||||
|                 'solian-app-arm64-v8a-release.apk', | ||||
|                 'ic_launcher', | ||||
|                 'https://testflight.apple.com/join/YJ0lmN6O', | ||||
|               ); | ||||
|               AzhonAppUpdate.update(model); | ||||
|             } | ||||
|           }); | ||||
|         } else { | ||||
|           context.showInfoDialog( | ||||
|             'updateAvailable'.tr, | ||||
|             'bsCheckForUpdateDesc'.tr, | ||||
|           ); | ||||
|         } | ||||
|       } else if (remoteVersionString != localVersionString) { | ||||
|         _bootCompleter.future.then((_) { | ||||
|           context.showSnackbar('updateMayAvailable'.trParams({ | ||||
|             'version': remoteVersionString, | ||||
|           })); | ||||
|         }); | ||||
|       } | ||||
|     } catch (e) { | ||||
|       context.showErrorDialog('Unable to check update: $e'); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   late final List<({String label, Future<void> Function() action})> _periods = [ | ||||
|     ( | ||||
|       label: 'bsLoadingTheme', | ||||
| @@ -48,75 +110,6 @@ class _BootstrapperShellState extends State<BootstrapperShell> { | ||||
|         await context.read<ThemeSwitcher>().restoreTheme(); | ||||
|       }, | ||||
|     ), | ||||
|     ( | ||||
|       label: 'bsCheckForUpdate', | ||||
|       action: () async { | ||||
|         if (PlatformInfo.isWeb) return; | ||||
|         try { | ||||
|           final prefs = await SharedPreferences.getInstance(); | ||||
|           final info = await PackageInfo.fromPlatform(); | ||||
|           final localVersionString = '${info.version}+${info.buildNumber}'; | ||||
|           final resp = await GetConnect().get( | ||||
|             'https://git.solsynth.dev/api/v1/repos/hydrogen/solian/tags?page=1&limit=1', | ||||
|           ); | ||||
|           final remoteVersionString = | ||||
|               (resp.body as List).firstOrNull?['name'] ?? '0.0.0+0'; | ||||
|           final remoteVersion = | ||||
|               Version.parse(remoteVersionString.split('+').first); | ||||
|           final localVersion = | ||||
|               Version.parse(localVersionString.split('+').first); | ||||
|           final remoteBuildNumber = | ||||
|               int.tryParse(remoteVersionString.split('+').last) ?? 0; | ||||
|           final localBuildNumber = | ||||
|               int.tryParse(localVersionString.split('+').last) ?? 0; | ||||
|           final strictUpdate = prefs.getBool('check_update_strictly') ?? false; | ||||
|           if (remoteVersion > localVersion || | ||||
|               (remoteVersion == localVersion && | ||||
|                   remoteBuildNumber > localBuildNumber) || | ||||
|               (remoteVersionString != localVersionString && strictUpdate)) { | ||||
|             setState(() { | ||||
|               _isErrored = true; | ||||
|               _subtitle = 'bsCheckForUpdateDesc'.tr; | ||||
|             }); | ||||
|  | ||||
|             if (PlatformInfo.isAndroid) { | ||||
|               context | ||||
|                   .showConfirmDialog( | ||||
|                 'updateAvailable'.tr, | ||||
|                 'updateAvailableDesc'.trParams({ | ||||
|                   'from': localVersionString, | ||||
|                   'to': remoteVersionString, | ||||
|                 }), | ||||
|               ) | ||||
|                   .then((result) { | ||||
|                 if (result) { | ||||
|                   final model = UpdateModel( | ||||
|                     'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk', | ||||
|                     'solian-app-arm64-v8a-release.apk', | ||||
|                     'ic_launcher', | ||||
|                     'https://testflight.apple.com/join/YJ0lmN6O', | ||||
|                   ); | ||||
|                   AzhonAppUpdate.update(model); | ||||
|                 } | ||||
|               }); | ||||
|             } else { | ||||
|               setState(() { | ||||
|                 _isErrored = true; | ||||
|                 _subtitle = 'bsCheckForUpdateDesc'.tr; | ||||
|               }); | ||||
|             } | ||||
|           } else if (remoteVersionString != localVersionString) { | ||||
|             _bootCompleter.future.then((_) { | ||||
|               context.showSnackbar('updateMayAvailable'.trParams({ | ||||
|                 'version': remoteVersionString, | ||||
|               })); | ||||
|             }); | ||||
|           } | ||||
|         } catch (e) { | ||||
|           context.showErrorDialog('Unable to check update: $e'); | ||||
|         } | ||||
|       }, | ||||
|     ), | ||||
|     ( | ||||
|       label: 'bsCheckingServer', | ||||
|       action: () async { | ||||
| @@ -214,6 +207,7 @@ class _BootstrapperShellState extends State<BootstrapperShell> { | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     _runPeriods(); | ||||
|     _checkForUpdate(); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   | ||||
| @@ -146,7 +146,10 @@ class _SignUpScreenState extends State<SignUpScreen> { | ||||
|             const Gap(8), | ||||
|             CheckboxListTile( | ||||
|               value: _isTermAccepted, | ||||
|               title: Text('termAccept'.tr), | ||||
|               title: Text( | ||||
|                 'termAccept'.tr, | ||||
|                 style: const TextStyle(height: 1.2), | ||||
|               ).paddingOnly(bottom: 4), | ||||
|               shape: const RoundedRectangleBorder( | ||||
|                 borderRadius: BorderRadius.all( | ||||
|                   Radius.circular(8), | ||||
|   | ||||
| @@ -74,9 +74,13 @@ class LinkExpansion extends StatelessWidget { | ||||
|                                 ), | ||||
|                               ).paddingOnly(right: 8), | ||||
|                             if (snapshot.data!.siteName != null) | ||||
|                               Text( | ||||
|                                 snapshot.data!.siteName!, | ||||
|                                 style: Theme.of(context).textTheme.labelLarge, | ||||
|                               Expanded( | ||||
|                                 child: Text( | ||||
|                                   snapshot.data!.siteName!, | ||||
|                                   style: Theme.of(context).textTheme.labelLarge, | ||||
|                                   maxLines: 1, | ||||
|                                   overflow: TextOverflow.ellipsis, | ||||
|                                 ), | ||||
|                               ), | ||||
|                           ], | ||||
|                         ).paddingOnly( | ||||
|   | ||||
| @@ -12,6 +12,7 @@ import 'package:solian/platform.dart'; | ||||
| import 'package:solian/providers/auth.dart'; | ||||
| import 'package:solian/router.dart'; | ||||
| import 'package:solian/screens/posts/post_editor.dart'; | ||||
| import 'package:solian/widgets/reports/abuse_report.dart'; | ||||
|  | ||||
| class PostAction extends StatefulWidget { | ||||
|   final Post item; | ||||
| @@ -149,6 +150,23 @@ class _PostActionState extends State<PostAction> { | ||||
|                     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) | ||||
|                   ListTile( | ||||
|                     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