diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index d7836c494..8bd3840a9 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -3411,5 +3411,34 @@ } } }, - "donate": "Donate" + "donate": "Donate", + "startedAPoll": "{username} started a poll.", + "@startedAPoll": { + "type": "String", + "placeholders": { + "username": { + "type": "String" + } + } + }, + "poll": "Poll", + "startPoll": "Start poll", + "endPoll": "End poll", + "answersVisible": "Answers visible", + "answersHidden": "Answers hidden", + "pollQuestion": "Poll question", + "answerOption": "Answer option", + "addAnswerOption": "Add answer option", + "allowMultipleAnswers": "Allow multiple answers", + "pollHasBeenEnded": "Poll has been ended", + "countVotes": "{count} votes", + "@countVotes": { + "type": "int", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "answersWillBeVisibleWhenPollHasEnded": "Answers will be visible when poll has ended" } diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 125552c43..79fb96779 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -20,7 +20,9 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pages/chat/chat_view.dart'; import 'package:fluffychat/pages/chat/event_info_dialog.dart'; +import 'package:fluffychat/pages/chat/start_poll_bottom_sheet.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart'; +import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/error_reporter.dart'; import 'package:fluffychat/utils/file_selector.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/event_extension.dart'; @@ -1138,26 +1140,34 @@ class ChatController extends State FocusScope.of(context).requestFocus(inputFocus); } - void onAddPopupMenuButtonSelected(String choice) { - room.client.getConfig(); // Preload server file configuration. + void onAddPopupMenuButtonSelected(AddPopupMenuActions choice) { + room.client.getConfig(); - if (choice == 'file') { - sendFileAction(); - } - if (choice == 'image') { - sendFileAction(type: FileSelectorType.images); - } - if (choice == 'video') { - sendFileAction(type: FileSelectorType.videos); - } - if (choice == 'camera') { - openCameraAction(); - } - if (choice == 'camera-video') { - openVideoCameraAction(); - } - if (choice == 'location') { - sendLocationAction(); + switch (choice) { + case AddPopupMenuActions.image: + sendFileAction(type: FileSelectorType.images); + return; + case AddPopupMenuActions.video: + sendFileAction(type: FileSelectorType.videos); + return; + case AddPopupMenuActions.file: + sendFileAction(); + return; + case AddPopupMenuActions.poll: + showAdaptiveBottomSheet( + context: context, + builder: (context) => StartPollBottomSheet(room: room), + ); + return; + case AddPopupMenuActions.photoCamera: + openCameraAction(); + return; + case AddPopupMenuActions.videoCamera: + openVideoCameraAction(); + return; + case AddPopupMenuActions.location: + sendLocationAction(); + return; } } @@ -1358,3 +1368,13 @@ class ChatController extends State ); } } + +enum AddPopupMenuActions { + image, + video, + file, + poll, + photoCamera, + videoCamera, + location, +} diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index 7d520bac1..1da3fab11 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -132,16 +132,15 @@ class ChatInputRow extends StatelessWidget { alignment: Alignment.center, decoration: const BoxDecoration(), clipBehavior: Clip.hardEdge, - child: PopupMenuButton( + child: PopupMenuButton( useRootNavigator: true, icon: const Icon(Icons.add_circle_outline), iconColor: theme.colorScheme.onPrimaryContainer, onSelected: controller.onAddPopupMenuButtonSelected, - itemBuilder: (BuildContext context) => - >[ + itemBuilder: (BuildContext context) => [ if (PlatformInfos.isMobile) - PopupMenuItem( - value: 'location', + PopupMenuItem( + value: AddPopupMenuActions.location, child: ListTile( leading: CircleAvatar( backgroundColor: @@ -154,8 +153,22 @@ class ChatInputRow extends StatelessWidget { contentPadding: const EdgeInsets.all(0), ), ), - PopupMenuItem( - value: 'image', + PopupMenuItem( + value: AddPopupMenuActions.poll, + child: ListTile( + leading: CircleAvatar( + backgroundColor: + theme.colorScheme.onPrimaryContainer, + foregroundColor: + theme.colorScheme.primaryContainer, + child: const Icon(Icons.poll_outlined), + ), + title: Text(L10n.of(context).startPoll), + contentPadding: const EdgeInsets.all(0), + ), + ), + PopupMenuItem( + value: AddPopupMenuActions.image, child: ListTile( leading: CircleAvatar( backgroundColor: @@ -168,8 +181,8 @@ class ChatInputRow extends StatelessWidget { contentPadding: const EdgeInsets.all(0), ), ), - PopupMenuItem( - value: 'video', + PopupMenuItem( + value: AddPopupMenuActions.video, child: ListTile( leading: CircleAvatar( backgroundColor: @@ -183,8 +196,8 @@ class ChatInputRow extends StatelessWidget { contentPadding: const EdgeInsets.all(0), ), ), - PopupMenuItem( - value: 'file', + PopupMenuItem( + value: AddPopupMenuActions.file, child: ListTile( leading: CircleAvatar( backgroundColor: @@ -217,8 +230,8 @@ class ChatInputRow extends StatelessWidget { onSelected: controller.onAddPopupMenuButtonSelected, iconColor: theme.colorScheme.onPrimaryContainer, itemBuilder: (context) => [ - PopupMenuItem( - value: 'camera-video', + PopupMenuItem( + value: AddPopupMenuActions.videoCamera, child: ListTile( leading: CircleAvatar( backgroundColor: @@ -231,8 +244,8 @@ class ChatInputRow extends StatelessWidget { contentPadding: const EdgeInsets.all(0), ), ), - PopupMenuItem( - value: 'camera', + PopupMenuItem( + value: AddPopupMenuActions.photoCamera, child: ListTile( leading: CircleAvatar( backgroundColor: @@ -394,7 +407,7 @@ class _ChatAccountPicker extends StatelessWidget { onSelected: (mxid) => _popupMenuButtonSelected(mxid, context), itemBuilder: (BuildContext context) => clients .map( - (client) => PopupMenuItem( + (client) => PopupMenuItem( value: client!.userID, child: FutureBuilder( future: client.fetchOwnProfile(), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index e9a8b3cc2..f2b867927 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -83,6 +83,7 @@ class Message extends StatelessWidget { EventTypes.Sticker, EventTypes.Encrypted, EventTypes.CallInvite, + PollEventContent.startType, }.contains(event.type)) { if (event.type.startsWith('m.call.')) { return const SizedBox.shrink(); diff --git a/lib/pages/chat/events/message_content.dart b/lib/pages/chat/events/message_content.dart index 85aa05941..46beaa441 100644 --- a/lib/pages/chat/events/message_content.dart +++ b/lib/pages/chat/events/message_content.dart @@ -6,6 +6,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/setting_keys.dart'; import 'package:fluffychat/l10n/l10n.dart'; +import 'package:fluffychat/pages/chat/events/poll.dart'; import 'package:fluffychat/pages/chat/events/video_player.dart'; import 'package:fluffychat/utils/adaptive_bottom_sheet.dart'; import 'package:fluffychat/utils/date_time_extension.dart'; @@ -234,27 +235,11 @@ class MessageContent extends StatelessWidget { textmessage: default: if (event.redacted) { - return FutureBuilder( - future: event.redactedBecause?.fetchSenderUser(), - builder: (context, snapshot) { - final reason = - event.redactedBecause?.content.tryGet('reason'); - final redactedBy = snapshot.data?.calcDisplayname() ?? - event.redactedBecause?.senderId.localpart ?? - L10n.of(context).user; - return _ButtonContent( - label: reason == null - ? L10n.of(context).redactedBy(redactedBy) - : L10n.of(context).redactedByBecause( - redactedBy, - reason, - ), - icon: '🗑️', - textColor: buttonTextColor.withAlpha(128), - onPressed: () => onInfoTab!(event), - fontSize: fontSize, - ); - }, + return RedactionWidget( + event: event, + buttonTextColor: buttonTextColor, + onInfoTab: onInfoTab, + fontSize: fontSize, ); } var html = AppSettings.renderHtml.value && event.isRichMessage @@ -296,6 +281,21 @@ class MessageContent extends StatelessWidget { ), ); } + case PollEventContent.startType: + if (event.redacted) { + return RedactionWidget( + event: event, + buttonTextColor: buttonTextColor, + onInfoTab: onInfoTab, + fontSize: fontSize, + ); + } + return PollWidget( + event: event, + timeline: timeline, + textColor: textColor, + linkColor: linkColor, + ); case EventTypes.CallInvite: return FutureBuilder( future: event.fetchSenderUser(), @@ -333,6 +333,46 @@ class MessageContent extends StatelessWidget { } } +class RedactionWidget extends StatelessWidget { + const RedactionWidget({ + super.key, + required this.event, + required this.buttonTextColor, + required this.onInfoTab, + required this.fontSize, + }); + + final Event event; + final Color buttonTextColor; + final void Function(Event p1)? onInfoTab; + final double fontSize; + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: event.redactedBecause?.fetchSenderUser(), + builder: (context, snapshot) { + final reason = event.redactedBecause?.content.tryGet('reason'); + final redactedBy = snapshot.data?.calcDisplayname() ?? + event.redactedBecause?.senderId.localpart ?? + L10n.of(context).user; + return _ButtonContent( + label: reason == null + ? L10n.of(context).redactedBy(redactedBy) + : L10n.of(context).redactedByBecause( + redactedBy, + reason, + ), + icon: '🗑️', + textColor: buttonTextColor.withAlpha(128), + onPressed: () => onInfoTab!(event), + fontSize: fontSize, + ); + }, + ); + } +} + class _ButtonContent extends StatelessWidget { final void Function() onPressed; final String label; diff --git a/lib/pages/chat/events/poll.dart b/lib/pages/chat/events/poll.dart new file mode 100644 index 000000000..af948d70f --- /dev/null +++ b/lib/pages/chat/events/poll.dart @@ -0,0 +1,233 @@ +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, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/chat/start_poll_bottom_sheet.dart b/lib/pages/chat/start_poll_bottom_sheet.dart new file mode 100644 index 000000000..9c16c13ea --- /dev/null +++ b/lib/pages/chat/start_poll_bottom_sheet.dart @@ -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 createState() => _StartPollBottomSheetState(); +} + +class _StartPollBottomSheetState extends State { + final TextEditingController _bodyController = TextEditingController(); + bool _allowMultipleAnswers = false; + final List _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( + 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), + ), + ], + ), + ); + } +} diff --git a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart index a72666f4e..106b28d9a 100644 --- a/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart +++ b/lib/utils/matrix_sdk_extensions/filtered_timeline_extension.dart @@ -16,8 +16,8 @@ extension IsStateExtension on Event { // always filter out edit and reaction relationships !{RelationshipTypes.edit, RelationshipTypes.reaction} .contains(relationshipType) && - // always filter out m.key.* events - !type.startsWith('m.key.verification.') && + // always filter out m.key.* and other known but unimportant events + !isKnownHiddenStates && // event types to hide: redaction and reaction events // if a reaction has been redacted we also want it to be hidden in the timeline !{EventTypes.Reaction, EventTypes.Redaction}.contains(type) && @@ -39,4 +39,10 @@ extension IsStateExtension on Event { EventTypes.RoomCreate, EventTypes.RoomTombstone, }.contains(type); + + bool get isKnownHiddenStates => + { + PollEventContent.responseType, + }.contains(type) || + type.startsWith('m.key.verification.'); } diff --git a/lib/utils/matrix_sdk_extensions/matrix_locals.dart b/lib/utils/matrix_sdk_extensions/matrix_locals.dart index 882faedb2..11f09a9ab 100644 --- a/lib/utils/matrix_sdk_extensions/matrix_locals.dart +++ b/lib/utils/matrix_sdk_extensions/matrix_locals.dart @@ -363,4 +363,10 @@ class MatrixLocals extends MatrixLocalizations { @override String get refreshingLastEvent => l10n.loadingPleaseWait; + + @override + String startedAPoll(String senderName) => '$senderName started a poll'; + + @override + String get pollHasBeenEnded => l10n.pollHasBeenEnded; } diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 8507a72ef..b125731ee 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -180,7 +180,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: app_links: 05a6ec2341985eb05e9f97dc63f5837c39895c3f audio_session: eaca2512cf2b39212d724f35d11f46180ad3a33e - desktop_drop: 248706031734554504f939cab1ad4c5fbc9c9c72 + desktop_drop: 10a3e6a7fa9dbe350541f2574092fecfa345a07b device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 dynamic_color: cb7c2a300ee67ed3bd96c3e852df3af0300bf610 emoji_picker_flutter: 51ca408e289d84d1e460016b2a28721ec754fcf7 diff --git a/pubspec.lock b/pubspec.lock index 70b1c28db..549ab8f22 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1114,8 +1114,8 @@ packages: dependency: "direct main" description: path: "." - ref: "krille/refactor-update-user-device-keys" - resolved-ref: d35d78f3b7c38671888e87b9aa80d2a93c811b82 + ref: "krille/implement-polls-msc" + resolved-ref: "83c5b82f2ada7b3c47cbf8b8baaf88b5755f2d86" url: "https://github.com/famedly/matrix-dart-sdk.git" source: git version: "3.0.2" diff --git a/pubspec.yaml b/pubspec.yaml index 6f03a9c88..e1cface8a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -55,7 +55,7 @@ dependencies: matrix: #^3.0.1 git: url: https://github.com/famedly/matrix-dart-sdk.git - ref: krille/refactor-update-user-device-keys + ref: krille/implement-polls-msc mime: ^2.0.0 native_imaging: ^0.2.0 opus_caf_converter_dart: ^1.0.1