234 lines
9.0 KiB
Dart
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,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|