feat: Implement polls
This commit is contained in:
parent
f3ea64086b
commit
71fa853f05
12 changed files with 582 additions and 63 deletions
171
lib/pages/chat/start_poll_bottom_sheet.dart
Normal file
171
lib/pages/chat/start_poll_bottom_sheet.dart
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:matrix/matrix.dart';
|
||||
|
||||
import 'package:fluffychat/l10n/l10n.dart';
|
||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||
|
||||
class StartPollBottomSheet extends StatefulWidget {
|
||||
final Room room;
|
||||
const StartPollBottomSheet({required this.room, super.key});
|
||||
|
||||
@override
|
||||
State<StartPollBottomSheet> createState() => _StartPollBottomSheetState();
|
||||
}
|
||||
|
||||
class _StartPollBottomSheetState extends State<StartPollBottomSheet> {
|
||||
final TextEditingController _bodyController = TextEditingController();
|
||||
bool _allowMultipleAnswers = false;
|
||||
final List<TextEditingController> _answers = [
|
||||
TextEditingController(),
|
||||
TextEditingController(),
|
||||
];
|
||||
PollKind _pollKind = PollKind.disclosed;
|
||||
|
||||
bool _canCreate = false;
|
||||
|
||||
bool isLoading = false;
|
||||
|
||||
String? _txid;
|
||||
|
||||
void _createPoll() async {
|
||||
try {
|
||||
var id = 0;
|
||||
_txid ??= widget.room.client.generateUniqueTransactionId();
|
||||
await widget.room.startPoll(
|
||||
question: _bodyController.text.trim(),
|
||||
answers: _answers
|
||||
.map(
|
||||
(answerController) => PollAnswer(
|
||||
id: (++id).toString(),
|
||||
mText: answerController.text.trim(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
kind: _pollKind,
|
||||
maxSelections: _allowMultipleAnswers ? _answers.length : 1,
|
||||
txid: _txid,
|
||||
);
|
||||
Navigator.of(context).pop();
|
||||
} catch (e, s) {
|
||||
Logs().w('Unable to create poll', e, s);
|
||||
if (!mounted) return;
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(e.toLocalizedString(context))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _updateCanCreate([_]) {
|
||||
final newCanCreate = _bodyController.text.trim().isNotEmpty &&
|
||||
!_answers.any((controller) => controller.text.trim().isEmpty);
|
||||
if (_canCreate != newCanCreate) {
|
||||
setState(() {
|
||||
_canCreate = newCanCreate;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const maxAnswers = 10;
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: CloseButton(
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
title: Text(L10n.of(context).startPoll),
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16),
|
||||
children: [
|
||||
SegmentedButton<PollKind>(
|
||||
selected: {_pollKind},
|
||||
multiSelectionEnabled: false,
|
||||
onSelectionChanged: (pollKind) => setState(() {
|
||||
_pollKind = pollKind.first;
|
||||
}),
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
value: PollKind.disclosed,
|
||||
label: Text(
|
||||
L10n.of(context).answersVisible,
|
||||
),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: PollKind.undisclosed,
|
||||
label: Text(
|
||||
L10n.of(context).answersHidden,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
TextField(
|
||||
controller: _bodyController,
|
||||
minLines: 1,
|
||||
maxLines: 4,
|
||||
maxLength: 512,
|
||||
onChanged: _updateCanCreate,
|
||||
decoration: InputDecoration(
|
||||
hintText: L10n.of(context).pollQuestion,
|
||||
counter: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
const Divider(height: 32),
|
||||
..._answers.map(
|
||||
(answerController) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: TextField(
|
||||
controller: answerController,
|
||||
onChanged: _updateCanCreate,
|
||||
maxLength: 64,
|
||||
decoration: InputDecoration(
|
||||
counter: const SizedBox.shrink(),
|
||||
hintText: L10n.of(context).answerOption,
|
||||
suffixIcon: _answers.length == 2
|
||||
? null
|
||||
: IconButton(
|
||||
icon: const Icon(Icons.cancel_outlined),
|
||||
onPressed: () => setState(() {
|
||||
_answers.remove(answerController..dispose());
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: TextButton.icon(
|
||||
icon: const Icon(Icons.add_outlined),
|
||||
onPressed: _answers.length < maxAnswers
|
||||
? () => setState(() {
|
||||
_answers.add(TextEditingController());
|
||||
})
|
||||
: null,
|
||||
label: Text(L10n.of(context).addAnswerOption),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Switch.adaptive(
|
||||
value: _allowMultipleAnswers,
|
||||
onChanged: (allow) => setState(() {
|
||||
_allowMultipleAnswers = allow;
|
||||
}),
|
||||
),
|
||||
title: Text(L10n.of(context).allowMultipleAnswers),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: !isLoading && _canCreate ? _createPoll : null,
|
||||
child: isLoading
|
||||
? const LinearProgressIndicator()
|
||||
: Text(L10n.of(context).startPoll),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue