185 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
| import 'package:easy_localization/easy_localization.dart';
 | |
| import 'package:flutter/material.dart';
 | |
| import 'package:flutter_hooks/flutter_hooks.dart';
 | |
| import 'package:gap/gap.dart';
 | |
| import 'package:hooks_riverpod/hooks_riverpod.dart';
 | |
| import 'package:island/pods/network.dart';
 | |
| import 'package:island/widgets/alert.dart';
 | |
| import 'package:island/widgets/content/sheet.dart';
 | |
| import 'package:material_symbols_icons/symbols.dart';
 | |
| 
 | |
| class AbuseReportSheet extends HookConsumerWidget {
 | |
|   final String resourceIdentifier;
 | |
|   final String? initialReason;
 | |
| 
 | |
|   const AbuseReportSheet({
 | |
|     super.key,
 | |
|     required this.resourceIdentifier,
 | |
|     this.initialReason,
 | |
|   });
 | |
| 
 | |
|   @override
 | |
|   Widget build(BuildContext context, WidgetRef ref) {
 | |
|     final reasonController = useTextEditingController(
 | |
|       text: initialReason ?? '',
 | |
|     );
 | |
|     final selectedType = useState<int>(0);
 | |
|     final isSubmitting = useState<bool>(false);
 | |
| 
 | |
|     final reportTypes = [
 | |
|       {'value': 0, 'label': 'abuseReportTypeCopyright'.tr()},
 | |
|       {'value': 1, 'label': 'abuseReportTypeHarassment'.tr()},
 | |
|       {'value': 2, 'label': 'abuseReportTypeImpersonation'.tr()},
 | |
|       {'value': 3, 'label': 'abuseReportTypeOffensiveContent'.tr()},
 | |
|       {'value': 4, 'label': 'abuseReportTypeSpam'.tr()},
 | |
|       {'value': 5, 'label': 'abuseReportTypePrivacyViolation'.tr()},
 | |
|       {'value': 6, 'label': 'abuseReportTypeIllegalContent'.tr()},
 | |
|       {'value': 7, 'label': 'abuseReportTypeOther'.tr()},
 | |
|     ];
 | |
| 
 | |
|     Future<void> submitReport() async {
 | |
|       isSubmitting.value = true;
 | |
| 
 | |
|       try {
 | |
|         final client = ref.read(apiClientProvider);
 | |
|         await client.post(
 | |
|           '/pass/safety/reports',
 | |
|           data: {
 | |
|             'resource_identifier': resourceIdentifier,
 | |
|             'type': selectedType.value,
 | |
|             'reason': reasonController.text.trim(),
 | |
|           },
 | |
|         );
 | |
| 
 | |
|         if (context.mounted) {
 | |
|           Navigator.of(context).pop();
 | |
|           showDialog(
 | |
|             context: context,
 | |
|             builder:
 | |
|                 (contextDialog) => AlertDialog(
 | |
|                   icon: const Icon(
 | |
|                     Icons.check_circle,
 | |
|                     color: Colors.green,
 | |
|                     size: 36,
 | |
|                   ),
 | |
|                   title: Text('abuseReportSuccessTitle'.tr()),
 | |
|                   content: Text('abuseReportSuccess'.tr()),
 | |
|                   actions: [
 | |
|                     TextButton(
 | |
|                       onPressed: () {
 | |
|                         Navigator.of(contextDialog).pop();
 | |
|                         Navigator.of(context).pop();
 | |
|                       },
 | |
|                       child: const Text('OK'),
 | |
|                     ),
 | |
|                   ],
 | |
|                 ),
 | |
|           );
 | |
|         }
 | |
|       } catch (err) {
 | |
|         showErrorAlert(err);
 | |
|       } finally {
 | |
|         isSubmitting.value = false;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return SheetScaffold(
 | |
|       titleText: 'abuseReportTitle'.tr(),
 | |
|       child: SingleChildScrollView(
 | |
|         padding: EdgeInsets.all(16),
 | |
|         child: Column(
 | |
|           crossAxisAlignment: CrossAxisAlignment.start,
 | |
|           children: [
 | |
|             // Information text
 | |
|             Container(
 | |
|               padding: const EdgeInsets.all(12),
 | |
|               decoration: BoxDecoration(
 | |
|                 color: Theme.of(context).colorScheme.surfaceVariant,
 | |
|                 borderRadius: BorderRadius.circular(8),
 | |
|               ),
 | |
|               child: Row(
 | |
|                 children: [
 | |
|                   Icon(
 | |
|                     Symbols.info,
 | |
|                     size: 20,
 | |
|                     color: Theme.of(context).colorScheme.onSurfaceVariant,
 | |
|                   ),
 | |
|                   const Gap(8),
 | |
|                   Expanded(
 | |
|                     child: Text(
 | |
|                       'abuseReportDescription'.tr(),
 | |
|                       style: Theme.of(context).textTheme.bodySmall?.copyWith(
 | |
|                         color: Theme.of(context).colorScheme.onSurfaceVariant,
 | |
|                       ),
 | |
|                     ),
 | |
|                   ),
 | |
|                 ],
 | |
|               ),
 | |
|             ),
 | |
|             const Gap(24),
 | |
| 
 | |
|             // Report type selection
 | |
|             Text(
 | |
|               'abuseReportType'.tr(),
 | |
|               style: Theme.of(
 | |
|                 context,
 | |
|               ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
 | |
|             ),
 | |
|             const Gap(12),
 | |
|             ...reportTypes.map((type) {
 | |
|               return RadioListTile<int>(
 | |
|                 value: type['value'] as int,
 | |
|                 groupValue: selectedType.value,
 | |
|                 onChanged: (value) => selectedType.value = value!,
 | |
|                 title: Text(type['label'] as String),
 | |
|                 contentPadding: EdgeInsets.zero,
 | |
|                 visualDensity: VisualDensity.compact,
 | |
|               );
 | |
|             }),
 | |
|             const Gap(24),
 | |
| 
 | |
|             // Reason text field
 | |
|             Text(
 | |
|               'abuseReportReason'.tr(),
 | |
|               style: Theme.of(
 | |
|                 context,
 | |
|               ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
 | |
|             ),
 | |
|             const Gap(8),
 | |
|             TextField(
 | |
|               controller: reasonController,
 | |
|               maxLines: 4,
 | |
|               decoration: InputDecoration(
 | |
|                 hintText: 'abuseReportReasonHint'.tr(),
 | |
|                 border: OutlineInputBorder(
 | |
|                   borderRadius: BorderRadius.circular(8),
 | |
|                 ),
 | |
|                 filled: true,
 | |
|                 fillColor: Theme.of(context).colorScheme.surface,
 | |
|               ),
 | |
|             ),
 | |
|             const Gap(24),
 | |
| 
 | |
|             // Submit button
 | |
|             SizedBox(
 | |
|               width: double.infinity,
 | |
|               child: FilledButton(
 | |
|                 onPressed: isSubmitting.value ? null : submitReport,
 | |
|                 child:
 | |
|                     isSubmitting.value
 | |
|                         ? const SizedBox(
 | |
|                           height: 20,
 | |
|                           width: 20,
 | |
|                           child: CircularProgressIndicator(strokeWidth: 2),
 | |
|                         )
 | |
|                         : Text('abuseReportSubmit'.tr()),
 | |
|               ),
 | |
|             ),
 | |
|             const Gap(16),
 | |
|           ],
 | |
|         ),
 | |
|       ),
 | |
|     );
 | |
|   }
 | |
| }
 |