import 'package:uuid/uuid.dart'; import 'package:flutter/material.dart'; import 'package:extera_next/generated/l10n/l10n.dart'; import 'package:matrix/matrix.dart'; class SendPollDialog extends StatefulWidget { final Room room; final BuildContext outerContext; final Event? replyEvent; final Thread? thread; const SendPollDialog({ required this.room, required this.thread, required this.outerContext, this.replyEvent, super.key, }); @override SendPollDialogState createState() => SendPollDialogState(); } class SendPollDialogState extends State { final TextEditingController _questionController = TextEditingController(); final List _answerControllers = [ TextEditingController(), TextEditingController(), ]; int _maxSelections = 1; String _kind = 'org.matrix.msc3381.disclosed'; void _addAnswer() { setState(() { _answerControllers.add(TextEditingController()); }); } void _removeAnswer(int index) { if (_answerControllers.length > 2) { setState(() { _answerControllers.removeAt(index); if (_maxSelections > _answerControllers.length) { _maxSelections = _answerControllers.length; } }); } } void _sendPoll() async { final question = _questionController.text.trim(); final answers = _answerControllers .map((controller) => controller.text.trim()) .where((answer) => answer.isNotEmpty) .toList(); if (question.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(L10n.of(context).pleaseEnterQuestion)), ); return; } if (answers.length < 2) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(L10n.of(context).atLeastTwoAnswersRequired)), ); return; } final pollContent = { 'org.matrix.msc3381.poll.start': { 'question': { 'org.matrix.msc1767.text': question, 'm.text': question, 'body': question, }, 'answers': answers .map( (answer) => { 'id': const Uuid().v4(), 'org.matrix.msc1767.text': answer, 'm.text': answer, }, ) .toList(), 'max_selections': _maxSelections, 'kind': _kind, }, }; try { await widget.room.sendEvent( pollContent, type: 'org.matrix.msc3381.poll.start', threadLastEventId: widget.thread?.lastEvent?.eventId ?? widget.thread?.rootEvent.eventId, threadRootEventId: widget.thread?.rootEvent.eventId, ); // ignore: use_build_context_synchronously Navigator.of(context).pop(); } catch (e) { // ignore: use_build_context_synchronously ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Failed to send poll: $e')), ); } } @override void dispose() { _questionController.dispose(); for (final controller in _answerControllers) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { final l10n = L10n.of(context); // Ensure max slider value is at least 1 to prevent division by zero errors final double maxAnswers = _answerControllers.isNotEmpty ? _answerControllers.length.toDouble() : 1.0; return AlertDialog( title: Text(l10n.createPoll), // In M3, the surface tint color is often used, but we keep default styling here content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // --- Question Input --- TextField( controller: _questionController, decoration: InputDecoration( labelText: l10n.question, border: const OutlineInputBorder(), alignLabelWithHint: true, ), maxLines: 2, ), const SizedBox(height: 16), // --- Answer Inputs --- ..._answerControllers.asMap().entries.map((entry) { final index = entry.key; final controller = entry.value; return Padding( padding: const EdgeInsets.only(bottom: 8), child: Row( children: [ Expanded( child: TextField( controller: controller, decoration: InputDecoration( labelText: '${l10n.answer} ${index + 1}', border: const OutlineInputBorder(), ), ), ), const SizedBox(width: 8), IconButton( // M3 uses standard variant colors for destructive actions icon: const Icon(Icons.remove_circle_outline), onPressed: () => _removeAnswer(index), ), ], ), ); }), const SizedBox(height: 8), // Add Answer Button Center( child: OutlinedButton.icon( onPressed: _addAnswer, icon: const Icon(Icons.add), label: Text(l10n.addAnswer), ), ), const Divider(height: 32), // --- Max Selections (Slider) --- Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( l10n.maxSelections, style: Theme.of(context).textTheme.bodyLarge, ), Text( '$_maxSelections', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: Theme.of(context).colorScheme.primary, ), ), ], ), ), Slider( value: _maxSelections.toDouble().clamp(1.0, maxAnswers), min: 1, max: maxAnswers, divisions: (maxAnswers > 1) ? (maxAnswers - 1).toInt() : 1, label: '$_maxSelections', onChanged: (value) { setState(() { _maxSelections = value.toInt(); }); }, ), const SizedBox(height: 16), // --- Poll Type (Segmented Button) --- Padding( padding: const EdgeInsets.only(bottom: 8.0, left: 8.0), child: Text( l10n.pollType, style: Theme.of(context).textTheme.bodyLarge, ), ), SizedBox( width: double.infinity, child: SegmentedButton( showSelectedIcon: false, // Cleaner look for text-only segments segments: [ ButtonSegment( value: 'org.matrix.msc3381.disclosed', label: Text(l10n.publicPoll), icon: const Icon(Icons.visibility_outlined), ), ButtonSegment( value: 'org.matrix.msc3381.undisclosed', label: Text(l10n.anonymousPoll), icon: const Icon(Icons.visibility_off_outlined), ), ], selected: {_kind}, onSelectionChanged: (Set newSelection) { setState(() { // SegmentedButton returns a Set, we just need the first (only) value _kind = newSelection.first; }); }, ), ), ], ), ), actions: [ TextButton( onPressed: Navigator.of(context).pop, child: Text(l10n.cancel), ), FilledButton( onPressed: _sendPoll, child: Text(l10n.send), ), ], ); } }