Poll collapse

This commit is contained in:
2025-09-07 23:00:29 +08:00
parent 78d65c39f3
commit 93df294142
2 changed files with 178 additions and 10 deletions

View File

@@ -133,6 +133,25 @@
"other": "{} replies"
},
"forward": "Forward",
"award": "Award",
"awardPost": "Award Post",
"awardMessage": "Message",
"awardMessageHint": "Enter your award message...",
"awardAttitude": "Attitude",
"awardAttitudePositive": "Positive",
"awardAttitudeNegative": "Negative",
"awardAmount": "Amount",
"awardAmountHint": "Enter amount...",
"awardAmountRequired": "Amount is required",
"awardAmountInvalid": "Please enter a valid amount",
"awardMessageTooLong": "Message is too long (max 4096 characters)",
"awardSuccess": "Award sent successfully!",
"awardSubmit": "Award",
"awardPostPreview": "Post Preview",
"awardNoContent": "No content available",
"awardByPublisher": "By {}",
"awardBenefits": "Award Benefits",
"awardBenefitsDescription": "Awarding this post increases its value and visibility. Higher valued posts have a better chance of being featured and highlighted in the community.",
"repliedTo": "Replied to",
"forwarded": "Forwarded",
"hasAttachments": {
@@ -977,5 +996,7 @@
"pinned": "Pinned",
"noResultsFound": "No results found",
"toggleFilters": "Toggle filters",
"notableDayNext": "{} is in"
"notableDayNext": "{} is in",
"expandPoll": "Expand Poll",
"collapsePoll": "Collapse Poll"
}

View File

@@ -17,6 +17,7 @@ class PollSubmit extends ConsumerStatefulWidget {
this.onCancel,
this.showProgress = true,
this.isReadonly = false,
this.isInitiallyExpanded = false,
});
final SnPollWithStats poll;
@@ -36,6 +37,9 @@ class PollSubmit extends ConsumerStatefulWidget {
final bool isReadonly;
/// Whether the poll should start expanded instead of collapsed.
final bool isInitiallyExpanded;
@override
ConsumerState<PollSubmit> createState() => _PollSubmitState();
}
@@ -45,6 +49,7 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
int _index = 0;
bool _submitting = false;
bool _isModifying = false; // New state to track if user is modifying answers
bool _isCollapsed = true; // New state to track collapse/expand
/// Collected answers, keyed by questionId
late Map<String, dynamic> _answers;
@@ -65,6 +70,8 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
_questions = [...widget.poll.questions]
..sort((a, b) => a.order.compareTo(b.order));
_answers = Map<String, dynamic>.from(widget.initialAnswers ?? {});
// Set initial collapse state based on the parameter
_isCollapsed = !widget.isInitiallyExpanded;
if (!widget.isReadonly) {
_loadCurrentIntoLocalState();
// If initial answers are provided, set _isModifying to false initially
@@ -653,39 +660,179 @@ class _PollSubmitState extends ConsumerState<PollSubmit> {
);
}
Widget _buildCollapsedView(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (widget.poll.title != null)
Text(
widget.poll.title!,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (widget.poll.description != null)
Padding(
padding: const EdgeInsets.only(top: 2),
child: Text(
widget.poll.description!,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).textTheme.bodySmall?.color?.withOpacity(0.7),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
)
else
Padding(
padding: const EdgeInsets.only(top: 2),
child: Text(
'${_questions.length} question${_questions.length == 1 ? '' : 's'}',
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Theme.of(
context,
).textTheme.bodySmall?.color?.withOpacity(0.7),
),
),
),
],
),
),
IconButton(
icon: Icon(
_isCollapsed ? Icons.expand_more : Icons.expand_less,
size: 20,
),
onPressed: () {
setState(() {
_isCollapsed = !_isCollapsed;
});
},
visualDensity: VisualDensity.compact,
tooltip: _isCollapsed ? 'expandPoll'.tr() : 'collapsePoll'.tr(),
),
],
),
],
);
}
@override
Widget build(BuildContext context) {
if (_questions.isEmpty) {
return const SizedBox.shrink();
}
// If collapsed, show collapsed view for all states
if (_isCollapsed) {
return _buildCollapsedView(context);
}
// If poll is already submitted and not in readonly mode, and not in modification mode, show submitted view
if (widget.initialAnswers != null && !widget.isReadonly && !_isModifying) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [_buildSubmittedView(context), _buildNavBar(context)],
children: [
_buildCollapsedView(context),
const SizedBox(height: 8),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, anim) {
final offset = Tween<Offset>(
begin: const Offset(0, -0.1),
end: Offset.zero,
).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut));
final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut);
return FadeTransition(
opacity: fade,
child: SlideTransition(position: offset, child: child),
);
},
child: Column(
key: const ValueKey('submitted_expanded'),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [_buildSubmittedView(context), _buildNavBar(context)],
),
),
],
);
}
// If poll is in readonly mode, show readonly view
if (widget.isReadonly) {
return _buildReadonlyView(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildCollapsedView(context),
const SizedBox(height: 8),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, anim) {
final offset = Tween<Offset>(
begin: const Offset(0, -0.1),
end: Offset.zero,
).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut));
final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut);
return FadeTransition(
opacity: fade,
child: SlideTransition(position: offset, child: child),
);
},
child: _buildReadonlyView(context),
),
],
);
}
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildHeader(context),
const SizedBox(height: 12),
_AnimatedStep(
key: ValueKey(_current.id),
_buildCollapsedView(context),
const SizedBox(height: 8),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (child, anim) {
final offset = Tween<Offset>(
begin: const Offset(0, -0.1),
end: Offset.zero,
).animate(CurvedAnimation(parent: anim, curve: Curves.easeOut));
final fade = CurvedAnimation(parent: anim, curve: Curves.easeOut);
return FadeTransition(
opacity: fade,
child: SlideTransition(position: offset, child: child),
);
},
child: Column(
key: const ValueKey('normal_expanded'),
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [_buildBody(context), _buildStats(context, _current)],
children: [
_buildHeader(context),
const SizedBox(height: 12),
_AnimatedStep(
key: ValueKey(_current.id),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildBody(context),
_buildStats(context, _current),
],
),
),
const SizedBox(height: 16),
_buildNavBar(context),
],
),
),
const SizedBox(height: 16),
_buildNavBar(context),
],
);
}