💄 Optimize poll

This commit is contained in:
2025-11-16 22:29:24 +08:00
parent c247cdf81c
commit e43bc6b8a8

View File

@@ -62,9 +62,19 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
bool? _yesNoSelected; bool? _yesNoSelected;
int? _ratingSelected; // 1..5 int? _ratingSelected; // 1..5
/// Flag to track if user has edited the current question to prevent provider rebuilds from resetting state
bool _userHasEdited = false;
/// Listener for text controller to mark as edited when user types
late final VoidCallback _controllerListener;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_controllerListener = () {
_userHasEdited = true;
};
_textController.addListener(_controllerListener);
_answers = Map<String, dynamic>.from(widget.initialAnswers ?? {}); _answers = Map<String, dynamic>.from(widget.initialAnswers ?? {});
// Set initial collapse state based on the parameter // Set initial collapse state based on the parameter
_isCollapsed = !widget.isInitiallyExpanded; _isCollapsed = !widget.isInitiallyExpanded;
@@ -75,6 +85,11 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
_isModifying = false; _isModifying = false;
} }
} }
// Load initial answers into local state
if (_questions != null) {
_loadCurrentIntoLocalState();
_userHasEdited = false;
}
} }
void _initializeFromPollData(SnPollWithStats poll) { void _initializeFromPollData(SnPollWithStats poll) {
@@ -101,6 +116,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
@override @override
void dispose() { void dispose() {
_textController.removeListener(_controllerListener);
_textController.dispose(); _textController.dispose();
super.dispose(); super.dispose();
} }
@@ -111,30 +127,35 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
final q = _current; final q = _current;
final saved = _answers[q.id]; final saved = _answers[q.id];
_singleChoiceSelected = null; if (!_userHasEdited) {
_multiChoiceSelected.clear(); _singleChoiceSelected = null;
_yesNoSelected = null; _multiChoiceSelected.clear();
_ratingSelected = null; _yesNoSelected = null;
_textController.text = ''; _ratingSelected = null;
switch (q.type) { switch (q.type) {
case SnPollQuestionType.singleChoice: case SnPollQuestionType.singleChoice:
if (saved is String) _singleChoiceSelected = saved; if (saved is String) _singleChoiceSelected = saved;
break; break;
case SnPollQuestionType.multipleChoice: case SnPollQuestionType.multipleChoice:
if (saved is List) { if (saved is List) {
_multiChoiceSelected.addAll(saved.whereType<String>()); _multiChoiceSelected.addAll(saved.whereType<String>());
} }
break; break;
case SnPollQuestionType.yesNo: case SnPollQuestionType.yesNo:
if (saved is bool) _yesNoSelected = saved; if (saved is bool) _yesNoSelected = saved;
break; break;
case SnPollQuestionType.rating: case SnPollQuestionType.rating:
if (saved is int) _ratingSelected = saved; if (saved is int) _ratingSelected = saved;
break; break;
case SnPollQuestionType.freeText: case SnPollQuestionType.freeText:
if (saved is String) _textController.text = saved; if (saved is String) {
break; _textController.removeListener(_controllerListener);
_textController.text = saved;
_textController.addListener(_controllerListener);
}
break;
}
} }
} }
@@ -214,6 +235,9 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
data: {'answer': _answers}, data: {'answer': _answers},
); );
// Refresh poll data to show submitted answer
ref.invalidate(pollWithStatsProvider(widget.pollId));
// Only call onSubmit after server accepts // Only call onSubmit after server accepts
widget.onSubmit(Map<String, dynamic>.unmodifiable(_answers)); widget.onSubmit(Map<String, dynamic>.unmodifiable(_answers));
@@ -236,6 +260,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
if (_index < _questions!.length - 1) { if (_index < _questions!.length - 1) {
setState(() { setState(() {
_index++; _index++;
_userHasEdited = false;
_loadCurrentIntoLocalState(); _loadCurrentIntoLocalState();
}); });
} else { } else {
@@ -250,6 +275,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
if (_index > 0) { if (_index > 0) {
setState(() { setState(() {
_index--; _index--;
_userHasEdited = false;
_loadCurrentIntoLocalState(); _loadCurrentIntoLocalState();
}); });
} else { } else {
@@ -342,7 +368,11 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
RadioListTile<String>( RadioListTile<String>(
value: opt.id, value: opt.id,
groupValue: _singleChoiceSelected, groupValue: _singleChoiceSelected,
onChanged: (val) => setState(() => _singleChoiceSelected = val), onChanged:
(val) => setState(() {
_singleChoiceSelected = val;
_userHasEdited = true;
}),
title: Text(opt.label), title: Text(opt.label),
subtitle: opt.description != null ? Text(opt.description!) : null, subtitle: opt.description != null ? Text(opt.description!) : null,
), ),
@@ -364,6 +394,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
} else { } else {
_multiChoiceSelected.remove(opt.id); _multiChoiceSelected.remove(opt.id);
} }
_userHasEdited = true;
}); });
}, },
title: Text(opt.label), title: Text(opt.label),
@@ -386,6 +417,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
onSelectionChanged: (sel) { onSelectionChanged: (sel) {
setState(() { setState(() {
_yesNoSelected = sel.isEmpty ? null : sel.first; _yesNoSelected = sel.isEmpty ? null : sel.first;
_userHasEdited = true;
}); });
}, },
multiSelectionEnabled: false, multiSelectionEnabled: false,
@@ -411,6 +443,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
onPressed: () { onPressed: () {
setState(() { setState(() {
_ratingSelected = value; _ratingSelected = value;
_userHasEdited = true;
}); });
}, },
); );
@@ -441,6 +474,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
setState(() { setState(() {
_isModifying = true; _isModifying = true;
_index = 0; // Reset to first question for modification _index = 0; // Reset to first question for modification
_userHasEdited = false;
_loadCurrentIntoLocalState(); _loadCurrentIntoLocalState();
}); });
}, },
@@ -487,32 +521,6 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (poll.title != null || poll.description != null)
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (poll.title?.isNotEmpty ?? false)
Text(
poll.title!,
style: Theme.of(context).textTheme.titleLarge,
),
if (poll.description?.isNotEmpty ?? false)
Padding(
padding: const EdgeInsets.only(top: 4),
child: Text(
poll.description!,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(
context,
).textTheme.bodyMedium?.color?.withOpacity(0.7),
),
),
),
],
),
),
for (final q in _questions!) for (final q in _questions!)
Padding( Padding(
padding: const EdgeInsets.only(bottom: 16.0), padding: const EdgeInsets.only(bottom: 16.0),