mowetentertainment1 8ee77e3548
Some checks failed
Main Deploy Workflow / deploy_web (push) Has been cancelled
Main Deploy Workflow / deploy_playstore_internal (push) Has been cancelled
12/6/2025
2025-12-06 12:46:34 -05:00

234 lines
9.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:async/async.dart';
import 'package:flutter_linkify/flutter_linkify.dart';
import 'package:matrix/matrix.dart' hide Result;
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/config/setting_keys.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/utils/url_launcher.dart';
import 'package:fluffychat/widgets/avatar.dart';
import 'package:fluffychat/widgets/future_loading_dialog.dart';
class PollWidget extends StatelessWidget {
final Event event;
final Timeline timeline;
final Color textColor;
final Color linkColor;
const PollWidget({
required this.event,
required this.timeline,
required this.textColor,
required this.linkColor,
super.key,
});
void _endPoll(BuildContext context) => showFutureLoadingDialog(
context: context,
future: () => event.endPoll(),
);
void _toggleVote(
BuildContext context,
String answerId,
int maxSelection,
) async {
final userId = event.room.client.userID!;
final answerIds = event.getPollResponses(timeline)[userId] ?? {};
if (!answerIds.remove(answerId)) {
answerIds.add(answerId);
if (answerIds.length > maxSelection) {
answerIds.clear();
answerIds.add(answerId);
}
}
showFutureLoadingDialog(
context: context,
future: () => event.answerPoll(answerIds.toList()),
);
}
@override
Widget build(BuildContext context) {
final eventContentResult = Result(() => event.parsedPollEventContent);
final eventContent = eventContentResult.asValue?.value;
if (eventContent == null) {
Logs().w('Invalid poll event', eventContentResult.error);
return const Text('Unable to parse poll event...');
}
final responses = event.getPollResponses(timeline);
final pollHasBeenEnded = event.getPollHasBeenEnded(timeline);
final canVote = event.room.canSendEvent(PollEventContent.responseType) &&
!pollHasBeenEnded;
final maxPolls = responses.length;
final answersVisible =
eventContent.pollStartContent.kind == PollKind.disclosed ||
pollHasBeenEnded;
return Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Linkify(
text: eventContent.pollStartContent.question.mText,
textScaleFactor: MediaQuery.textScalerOf(context).scale(1),
style: TextStyle(
color: textColor,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
),
options: const LinkifyOptions(humanize: false),
linkStyle: TextStyle(
color: linkColor,
fontSize: AppSettings.fontSizeFactor.value *
AppConfig.messageFontSize,
decoration: TextDecoration.underline,
decorationColor: linkColor,
),
onOpen: (url) => UrlLauncher(context, url.url).launchUrl(),
),
),
Divider(color: linkColor.withAlpha(64)),
...eventContent.pollStartContent.answers.map(
(answer) {
final votedUserIds = responses.entries
.where((entry) => entry.value.contains(answer.id))
.map((entry) => entry.key)
.toSet();
return Material(
color: Colors.transparent,
clipBehavior: Clip.hardEdge,
child: CheckboxListTile.adaptive(
value: responses[event.room.client.userID!]
?.contains(answer.id) ??
false,
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
checkboxScaleFactor: 1.5,
checkboxShape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(32),
),
onChanged: !canVote
? null
: (_) => _toggleVote(
context,
answer.id,
eventContent.pollStartContent.maxSelections,
),
title: Text(
answer.mText,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: textColor,
fontSize: AppConfig.messageFontSize *
AppSettings.fontSizeFactor.value,
),
),
subtitle: answersVisible
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
Text(
L10n.of(context)
.countVotes(votedUserIds.length),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
color: linkColor,
fontSize:
12 * AppSettings.fontSizeFactor.value,
),
),
const SizedBox(width: 2),
...votedUserIds.map((userId) {
final user = event.room
.getState(EventTypes.RoomMember, userId)
?.asUser(event.room);
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 2.0,
),
child: Avatar(
mxContent: user?.avatarUrl,
name: user?.calcDisplayname() ??
userId.localpart,
size: 12 *
AppSettings.fontSizeFactor.value,
),
);
}),
const SizedBox(width: 2),
],
),
),
LinearProgressIndicator(
color: linkColor,
backgroundColor: linkColor.withAlpha(128),
borderRadius:
BorderRadius.circular(AppConfig.borderRadius),
value: maxPolls == 0
? 0
: votedUserIds.length / maxPolls,
),
],
)
: null,
),
);
},
),
if (!pollHasBeenEnded && event.senderId == event.room.client.userID)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: OutlinedButton(
onPressed: () => _endPoll(context),
style: OutlinedButton.styleFrom(
foregroundColor: linkColor,
side: BorderSide(color: linkColor.withAlpha(64)),
),
child: Text(L10n.of(context).endPoll),
),
)
else if (!answersVisible)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
L10n.of(context).answersWillBeVisibleWhenPollHasEnded,
style: TextStyle(
color: linkColor,
fontSize: 12 * AppSettings.fontSizeFactor.value,
fontStyle: FontStyle.italic,
),
),
)
else if (pollHasBeenEnded)
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
L10n.of(context).pollHasBeenEnded,
style: TextStyle(
color: linkColor,
fontSize: 12 * AppSettings.fontSizeFactor.value,
fontStyle: FontStyle.italic,
),
),
),
],
),
);
}
}