import 'dart:io'; import 'package:client_proxy_framework/client_proxy_framework.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:image_picker/image_picker.dart'; import '../../design/pencil_theme.dart'; import '../../widgets/pencil_chrome.dart'; import 'report_feedback_upload.dart'; /// Report / feedback screen. /// /// API: [FeedbackApi.getUploadPresignedUrl] → PUT → [FeedbackApi.submit] (`fileUrls`, `content`, `contentType`), per FunyMee client guide — feedback section. /// Design reference: `desgin/funymee_home.pen` node `Y9WlO` (Pencil); English copy only; **one** optional image. class ReportScreen extends StatefulWidget { const ReportScreen({super.key, required this.taskId}); final String taskId; @override State createState() => _ReportScreenState(); } class _ReportScreenState extends State { final _controller = TextEditingController(); final _picker = ImagePicker(); File? _imageFile; bool _submitting = false; /// Logical `contentType` for [FeedbackApi.submit] (maps via `fieldMapping` when sent). static const _feedbackContentType = 'report'; @override void dispose() { _controller.dispose(); super.dispose(); } Future _pickImage() async { if (_submitting) return; final x = await _picker.pickImage( source: ImageSource.gallery, imageQuality: 85, ); if (x == null || !mounted) return; setState(() => _imageFile = File(x.path)); } void _clearImage() { if (_submitting) return; setState(() => _imageFile = null); } Future _submit() async { final text = _controller.text.trim(); if (text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Please describe the issue.')), ); return; } setState(() => _submitting = true); try { final urls = []; if (_imageFile != null) { final path = await uploadFeedbackAttachment(_imageFile!); urls.add(path); } final content = 'Task ID: ${widget.taskId}\n\n$text'; final res = await FeedbackApi.submit( fileUrls: urls, content: content, contentType: _feedbackContentType, ); if (!mounted) return; if (!res.isSuccess) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( res.msg.isNotEmpty ? res.msg : 'Submit failed (code ${res.code})', ), ), ); return; } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Thank you. Your report was sent.', style: GoogleFonts.inter(fontWeight: FontWeight.w600), ), ), ); Navigator.of(context).pop(); } catch (e) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('$e'))); } finally { if (mounted) setState(() => _submitting = false); } } @override Widget build(BuildContext context) { return Container( decoration: const BoxDecoration( gradient: PencilTheme.yellowWhitePageGradient, ), child: Scaffold( backgroundColor: Colors.transparent, body: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.fromLTRB(2, 0, 14, 10), child: SizedBox( height: 56, child: Row( children: [ PencilRoundBackButton( onPressed: () { if (_submitting) return; Navigator.of(context).pop(); }, ), Expanded( child: Center( child: Text( 'Report', style: GoogleFonts.inter( fontSize: 19, fontWeight: FontWeight.w700, fontStyle: FontStyle.italic, color: PencilTheme.stone900, ), ), ), ), const SizedBox(width: 44), ], ), ), ), Expanded( child: ListView( padding: const EdgeInsets.fromLTRB(20, 0, 20, 24), children: [ Text( 'Tell us what went wrong.', style: GoogleFonts.inter( fontSize: 14, height: 1.4, color: PencilTheme.stone600, ), ), const SizedBox(height: 16), Text( 'Related task', style: GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w600, color: PencilTheme.stone900, ), ), const SizedBox(height: 6), SelectableText( widget.taskId, style: GoogleFonts.inter( fontSize: 14, color: PencilTheme.stone600, ), ), const SizedBox(height: 20), Text( 'Description', style: GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w600, color: PencilTheme.stone900, ), ), const SizedBox(height: 8), TextField( controller: _controller, maxLines: 6, readOnly: _submitting, style: GoogleFonts.inter( fontSize: 15, height: 1.45, color: PencilTheme.stone900, ), cursorColor: PencilTheme.underlineGold, decoration: InputDecoration( hintText: 'Describe the issue in detail…', hintStyle: GoogleFonts.inter( fontSize: 15, height: 1.45, color: PencilTheme.stone700, ), filled: true, fillColor: Colors.white, contentPadding: const EdgeInsets.symmetric( horizontal: 14, vertical: 12, ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: PencilTheme.stone600.withValues(alpha: 0.55), width: 1, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: const BorderSide( color: PencilTheme.underlineGold, width: 1.5, ), ), disabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12), borderSide: BorderSide( color: PencilTheme.stone600.withValues(alpha: 0.4), ), ), ), ), const SizedBox(height: 20), Text( 'Screenshot (optional, one image only)', style: GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w600, color: PencilTheme.stone900, ), ), const SizedBox(height: 10), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Material( color: Colors.transparent, child: InkWell( onTap: _submitting ? null : _pickImage, borderRadius: BorderRadius.circular(14), child: Ink( width: 112, height: 112, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(14), border: Border.all( color: PencilTheme.genNavBackStroke, ), ), child: _imageFile == null ? Icon( Icons.add_photo_alternate_outlined, size: 36, color: PencilTheme.stone600.withValues( alpha: 0.7, ), ) : ClipRRect( borderRadius: BorderRadius.circular(12), child: Image.file( _imageFile!, fit: BoxFit.cover, width: double.infinity, height: double.infinity, ), ), ), ), ), if (_imageFile != null) ...[ const SizedBox(width: 12), Padding( padding: const EdgeInsets.only(top: 4), child: TextButton( onPressed: _submitting ? null : _clearImage, child: Text( 'Remove', style: GoogleFonts.inter( fontWeight: FontWeight.w600, color: PencilTheme.stone700, ), ), ), ), ], ], ), const SizedBox(height: 28), FilledButton( style: FilledButton.styleFrom( backgroundColor: PencilTheme.underlineGold, foregroundColor: Colors.white, minimumSize: const Size.fromHeight(52), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(14), ), ), onPressed: () { if (_submitting) return; _submit(); }, child: _submitting ? const SizedBox( width: 22, height: 22, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : Text( 'Submit', style: GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w700, ), ), ), ], ), ), ], ), ), ), ); } }