From 81a6530ed88e67d34411362fd8cdc84db5220cdb Mon Sep 17 00:00:00 2001 From: Sorunome Date: Wed, 12 Aug 2020 09:30:31 +0000 Subject: [PATCH] swith to event aggregations and render message edits and reactions --- CHANGELOG.md | 6 +- lib/components/list_items/message.dart | 77 ++++++-- lib/components/matrix.dart | 1 - lib/components/message_content.dart | 1 - lib/components/message_reactions.dart | 140 +++++++++++++ lib/components/reply_content.dart | 30 +-- lib/utils/database/mobile.dart | 14 +- lib/utils/firebase_controller.dart | 2 +- lib/views/chat.dart | 121 ++++++++++-- pubspec.lock | 262 ++++++++++++++++--------- pubspec.yaml | 12 +- 11 files changed, 514 insertions(+), 152 deletions(-) create mode 100644 lib/components/message_reactions.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e5f91..16f0053 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Version 0.17.0 - 2020-08-?? -### Features: +### Features - Pin and unpin chats +- Implement event aggregations +- Implement message edits +- Render reactions +- Add / Remove reactions by tapping on existing reactions ### Fixes: - Don't re-render the room list nearly as often, increasing performance diff --git a/lib/components/list_items/message.dart b/lib/components/list_items/message.dart index 35e064d..5305341 100644 --- a/lib/components/list_items/message.dart +++ b/lib/components/list_items/message.dart @@ -12,6 +12,7 @@ import 'package:flutter/material.dart'; import '../adaptive_page_layout.dart'; import '../avatar.dart'; import '../matrix.dart'; +import '../message_reactions.dart'; import 'state_message.dart'; class Message extends StatelessWidget { @@ -64,11 +65,13 @@ class Message extends StatelessWidget { var rowMainAxisAlignment = ownMessage ? MainAxisAlignment.end : MainAxisAlignment.start; + final displayEvent = event.getDisplayEvent(timeline); + if (event.showThumbnail) { color = Theme.of(context).scaffoldBackgroundColor.withOpacity(0.66); textColor = Theme.of(context).textTheme.bodyText2.color; } else if (ownMessage) { - color = event.status == -1 + color = displayEvent.status == -1 ? Colors.redAccent : Theme.of(context).primaryColor; } @@ -91,15 +94,14 @@ class Message extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (event.isReply) + if (event.relationshipType == RelationshipTypes.Reply) FutureBuilder( future: event.getReplyEvent(timeline), builder: (BuildContext context, snapshot) { final replyEvent = snapshot.hasData ? snapshot.data : Event( - eventId: event.content['m.relates_to'] - ['m.in_reply_to']['event_id'], + eventId: event.relationshipEventId, content: {'msgtype': 'm.text', 'body': '...'}, senderId: event.senderId, type: 'm.room.message', @@ -110,18 +112,18 @@ class Message extends StatelessWidget { ); return Container( margin: EdgeInsets.symmetric(vertical: 4.0), - child: - ReplyContent(replyEvent, lightText: ownMessage), + child: ReplyContent(replyEvent, + lightText: ownMessage, timeline: timeline), ); }, ), MessageContent( - event, + displayEvent, textColor: textColor, ), - if (event.type == EventTypes.Encrypted && - event.messageType == MessageTypes.BadEncrypted && - event.content['can_request_session'] == true) + if (displayEvent.type == EventTypes.Encrypted && + displayEvent.messageType == MessageTypes.BadEncrypted && + displayEvent.content['can_request_session'] == true) RaisedButton( color: color.withAlpha(100), child: Text( @@ -129,15 +131,18 @@ class Message extends StatelessWidget { style: TextStyle(color: textColor), ), onPressed: () => SimpleDialogs(context) - .tryRequestWithLoadingDialog(event.requestKey()), + .tryRequestWithLoadingDialog( + displayEvent.requestKey()), ), SizedBox(height: 4), Opacity( opacity: 0, child: _MetaRow( - event, + event, // meta information should be from the unedited event ownMessage, textColor, + timeline, + displayEvent, ), ), ], @@ -150,6 +155,8 @@ class Message extends StatelessWidget { event, ownMessage, textColor, + timeline, + displayEvent, ), ), ], @@ -170,6 +177,32 @@ class Message extends StatelessWidget { } else { rowChildren.insert(0, avatarOrSizedBox); } + final row = Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: rowMainAxisAlignment, + children: rowChildren, + ); + Widget container; + if (event.hasAggregatedEvents(timeline, RelationshipTypes.Reaction)) { + container = Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: + ownMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, + children: [ + row, + Padding( + padding: EdgeInsets.only( + top: 4.0, + left: (ownMessage ? 0 : Avatar.defaultSize) + 12.0, + right: (ownMessage ? Avatar.defaultSize : 0) + 12.0, + ), + child: MessageReactions(event, timeline), + ), + ], + ); + } else { + container = row; + } return InkWell( onHover: (b) => useMouse = true, @@ -185,11 +218,7 @@ class Message extends StatelessWidget { child: Padding( padding: EdgeInsets.only( left: 8.0, right: 8.0, bottom: sameSender ? 4.0 : 8.0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: rowMainAxisAlignment, - children: rowChildren, - ), + child: container, ), ), ); @@ -200,8 +229,12 @@ class _MetaRow extends StatelessWidget { final Event event; final bool ownMessage; final Color color; + final Timeline timeline; + final Event displayEvent; - const _MetaRow(this.event, this.ownMessage, this.color, {Key key}) + const _MetaRow( + this.event, this.ownMessage, this.color, this.timeline, this.displayEvent, + {Key key}) : super(key: key); @override @@ -229,10 +262,16 @@ class _MetaRow extends StatelessWidget { fontSize: 11, ), ), + if (event.hasAggregatedEvents(timeline, RelationshipTypes.Edit)) + Icon( + Icons.edit, + size: 12, + color: color, + ), if (ownMessage) SizedBox(width: 2), if (ownMessage) Icon( - event.statusIcon, + displayEvent.statusIcon, size: 12, color: color, ), diff --git a/lib/components/matrix.dart b/lib/components/matrix.dart index bb0886a..995c780 100644 --- a/lib/components/matrix.dart +++ b/lib/components/matrix.dart @@ -194,7 +194,6 @@ class MatrixState extends State { verificationMethods.add(KeyVerificationMethod.emoji); } client = Client(widget.clientName, - debug: false, enableE2eeRecovery: true, verificationMethods: verificationMethods, importantStateEvents: { diff --git a/lib/components/message_content.dart b/lib/components/message_content.dart index cff9d08..5064010 100644 --- a/lib/components/message_content.dart +++ b/lib/components/message_content.dart @@ -40,7 +40,6 @@ class MessageContent extends StatelessWidget { case MessageTypes.Text: case MessageTypes.Notice: case MessageTypes.Emote: - case MessageTypes.Reply: if (Matrix.of(context).renderHtml && !event.redacted && event.content['format'] == 'org.matrix.custom.html' && diff --git a/lib/components/message_reactions.dart b/lib/components/message_reactions.dart new file mode 100644 index 0000000..4727c4f --- /dev/null +++ b/lib/components/message_reactions.dart @@ -0,0 +1,140 @@ +import 'package:famedlysdk/famedlysdk.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_advanced_networkimage/provider.dart'; + +import 'matrix.dart'; + +class MessageReactions extends StatelessWidget { + final Event event; + final Timeline timeline; + + const MessageReactions(this.event, this.timeline); + + @override + Widget build(BuildContext context) { + final allReactionEvents = + event.aggregatedEvents(timeline, RelationshipTypes.Reaction); + final reactionMap = {}; + for (final e in allReactionEvents) { + if (e.content['m.relates_to'].containsKey('key')) { + final key = e.content['m.relates_to']['key']; + if (!reactionMap.containsKey(key)) { + reactionMap[key] = _ReactionEntry( + key: key, + count: 0, + reacted: false, + ); + } + reactionMap[key].count++; + reactionMap[key].reacted |= e.senderId == e.room.client.userID; + } + } + final reactionList = reactionMap.values.toList(); + reactionList.sort((a, b) => b.count - a.count > 0 ? 1 : -1); + return Wrap( + spacing: 4.0, + runSpacing: 4.0, + children: reactionList + .map((r) => _Reaction( + reactionKey: r.key, + count: r.count, + reacted: r.reacted, + onTap: () { + if (r.reacted) { + final evt = allReactionEvents.firstWhere( + (e) => + e.senderId == e.room.client.userID && + e.content['m.relates_to']['key'] == r.key, + orElse: () => null); + if (evt != null) { + evt.redact(); + } + } else { + event.room.sendReaction(event.eventId, r.key); + } + }, + )) + .toList(), + ); + } +} + +class _Reaction extends StatelessWidget { + final String reactionKey; + final int count; + final bool reacted; + final void Function() onTap; + + const _Reaction({this.reactionKey, this.count, this.reacted, this.onTap}); + + @override + Widget build(BuildContext context) { + final borderColor = reacted ? Colors.red : Theme.of(context).primaryColor; + final textColor = Theme.of(context).brightness == Brightness.dark + ? Colors.white + : Colors.black; + final color = Theme.of(context).secondaryHeaderColor; + final fontSize = DefaultTextStyle.of(context).style.fontSize; + final padding = fontSize / 5; + Widget content; + if (reactionKey.startsWith('mxc://')) { + final src = Uri.parse(reactionKey)?.getThumbnail( + Matrix.of(context).client, + width: 9999, + height: fontSize * MediaQuery.of(context).devicePixelRatio, + method: ThumbnailMethod.scale, + ); + content = Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image( + image: AdvancedNetworkImage( + src, + useDiskCache: !kIsWeb, + ), + height: fontSize, + ), + Text(count.toString(), + style: TextStyle( + color: textColor, + fontSize: DefaultTextStyle.of(context).style.fontSize, + )), + ], + ); + } else { + var renderKey = reactionKey; + if (renderKey.length > 10) { + renderKey = renderKey.substring(0, 7) + '...'; + } + content = Text('$renderKey $count', + style: TextStyle( + color: textColor, + fontSize: DefaultTextStyle.of(context).style.fontSize, + )); + } + return InkWell( + child: Container( + decoration: BoxDecoration( + color: color, + border: Border.all( + width: fontSize / 20, + color: borderColor, + ), + borderRadius: BorderRadius.all(Radius.circular(padding * 2)), + ), + padding: EdgeInsets.all(padding), + child: content, + ), + onTap: () => onTap != null ? onTap() : null, + ); + } +} + +class _ReactionEntry { + String key; + int count; + bool reacted; + + _ReactionEntry({this.key, this.count, this.reacted}); +} diff --git a/lib/components/reply_content.dart b/lib/components/reply_content.dart index e831d49..2ed7029 100644 --- a/lib/components/reply_content.dart +++ b/lib/components/reply_content.dart @@ -8,23 +8,29 @@ import 'matrix.dart'; class ReplyContent extends StatelessWidget { final Event replyEvent; final bool lightText; + final Timeline timeline; - const ReplyContent(this.replyEvent, {this.lightText = false, Key key}) + const ReplyContent(this.replyEvent, + {this.lightText = false, Key key, this.timeline}) : super(key: key); @override Widget build(BuildContext context) { Widget replyBody; - if (replyEvent != null && + final displayEvent = replyEvent != null && timeline != null + ? replyEvent.getDisplayEvent(timeline) + : replyEvent; + if (displayEvent != null && Matrix.of(context).renderHtml && - [EventTypes.Message, EventTypes.Encrypted].contains(replyEvent.type) && + [EventTypes.Message, EventTypes.Encrypted] + .contains(displayEvent.type) && [MessageTypes.Text, MessageTypes.Notice, MessageTypes.Emote] - .contains(replyEvent.messageType) && - !replyEvent.redacted && - replyEvent.content['format'] == 'org.matrix.custom.html' && - replyEvent.content['formatted_body'] is String) { - String html = replyEvent.content['formatted_body']; - if (replyEvent.messageType == MessageTypes.Emote) { + .contains(displayEvent.messageType) && + !displayEvent.redacted && + displayEvent.content['format'] == 'org.matrix.custom.html' && + displayEvent.content['formatted_body'] is String) { + String html = displayEvent.content['formatted_body']; + if (displayEvent.messageType == MessageTypes.Emote) { html = '* $html'; } replyBody = HtmlMessage( @@ -36,11 +42,11 @@ class ReplyContent extends StatelessWidget { fontSize: DefaultTextStyle.of(context).style.fontSize, ), maxLines: 1, - room: replyEvent.room, + room: displayEvent.room, ); } else { replyBody = Text( - replyEvent?.getLocalizedBody( + displayEvent?.getLocalizedBody( L10n.of(context), withSenderNamePrefix: false, hideReply: true, @@ -71,7 +77,7 @@ class ReplyContent extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - (replyEvent?.sender?.calcDisplayname() ?? '') + ':', + (displayEvent?.sender?.calcDisplayname() ?? '') + ':', maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle( diff --git a/lib/utils/database/mobile.dart b/lib/utils/database/mobile.dart index 8108659..7995de7 100644 --- a/lib/utils/database/mobile.dart +++ b/lib/utils/database/mobile.dart @@ -8,6 +8,18 @@ import 'package:moor/moor.dart'; import 'package:moor/isolate.dart'; import 'cipher_db.dart' as cipher; +class DatabaseNoTransactions extends Database { + DatabaseNoTransactions.connect(DatabaseConnection connection) + : super.connect(connection); + + // moor transactions are sometimes rather weird and freeze. Until there is a + // proper fix in moor we override that there aren't actually using transactions + @override + Future transaction(Future Function() action) async { + return action(); + } +} + bool _inited = false; // see https://moor.simonbinder.eu/docs/advanced-features/isolates/ @@ -57,7 +69,7 @@ Future constructDb( receivePort.sendPort, targetPath, password, logStatements), ); final isolate = (await receivePort.first as MoorIsolate); - return Database.connect(await isolate.connect()); + return DatabaseNoTransactions.connect(await isolate.connect()); } Future getLocalstorage(String key) async { diff --git a/lib/utils/firebase_controller.dart b/lib/utils/firebase_controller.dart index efd8b99..0072c26 100644 --- a/lib/utils/firebase_controller.dart +++ b/lib/utils/firebase_controller.dart @@ -161,7 +161,7 @@ abstract class FirebaseController { } else { final platform = kIsWeb ? 'Web' : Platform.operatingSystem; final clientName = 'FluffyChat $platform'; - client = Client(clientName, debug: false); + client = Client(clientName); client.database = await getDatabase(client); client.connect(); await client.onLoginStateChanged.stream diff --git a/lib/views/chat.dart b/lib/views/chat.dart index e364bc8..fb939ae 100644 --- a/lib/views/chat.dart +++ b/lib/views/chat.dart @@ -76,6 +76,8 @@ class _ChatState extends State<_Chat> { Event replyEvent; + Event editEvent; + bool showScrollDownButton = false; bool get selectMode => selectedEvents.isNotEmpty; @@ -174,13 +176,15 @@ class _ChatState extends State<_Chat> { void send() { if (sendController.text.isEmpty) return; - room.sendTextEvent(sendController.text, inReplyTo: replyEvent); + room.sendTextEvent(sendController.text, + inReplyTo: replyEvent, editEventId: editEvent?.eventId); sendController.text = ''; - if (replyEvent != null) { - setState(() => replyEvent = null); - } - setState(() => inputText = ''); + setState(() { + inputText = ''; + replyEvent = null; + editEvent = null; + }); } void sendFileAction(BuildContext context) async { @@ -289,8 +293,17 @@ class _ChatState extends State<_Chat> { Navigator.of(context).popUntil((r) => r.isFirst); } - void sendAgainAction() { - selectedEvents.first.sendAgain(); + void sendAgainAction(Timeline timeline) { + final event = selectedEvents.first; + if (event.status == -1) { + event.sendAgain(); + } + final allEditEvents = event + .aggregatedEvents(timeline, RelationshipTypes.Edit) + .where((e) => e.status == -1); + for (final e in allEditEvents) { + e.sendAgain(); + } setState(() => selectedEvents.clear()); } @@ -411,6 +424,23 @@ class _ChatState extends State<_Chat> { .numberSelected(selectedEvents.length.toString())), actions: selectMode ? [ + if (selectedEvents.length == 1 && + selectedEvents.first.status > 0 && + selectedEvents.first.senderId == client.userID) + IconButton( + icon: Icon(Icons.edit), + onPressed: () { + setState(() { + editEvent = selectedEvents.first; + sendController.text = editEvent + .getDisplayEvent(timeline) + .getLocalizedBody(L10n.of(context), + withSenderNamePrefix: false, hideReply: true); + selectedEvents.clear(); + }); + inputFocus.requestFocus(); + }, + ), IconButton( icon: Icon(Icons.content_copy), onPressed: () => copyEventsAction(context), @@ -467,7 +497,16 @@ class _ChatState extends State<_Chat> { room.sendReadReceipt(timeline.events.first.eventId); } - if (timeline.events.isEmpty) return Container(); + final filteredEvents = timeline.events + .where((e) => + ![ + RelationshipTypes.Edit, + RelationshipTypes.Reaction + ].contains(e.relationshipType) && + e.type != 'm.reaction') + .toList(); + + if (filteredEvents.isEmpty) return Container(); return ListView.builder( padding: EdgeInsets.symmetric( @@ -479,10 +518,10 @@ class _ChatState extends State<_Chat> { 2), ), reverse: true, - itemCount: timeline.events.length + 2, + itemCount: filteredEvents.length + 2, controller: _scrollController, itemBuilder: (BuildContext context, int i) { - return i == timeline.events.length + 1 + return i == filteredEvents.length + 1 ? _loadingHistory ? Container( height: 50, @@ -512,7 +551,7 @@ class _ChatState extends State<_Chat> { ? Duration(milliseconds: 0) : Duration(milliseconds: 500), alignment: - timeline.events.first.senderId == + filteredEvents.first.senderId == client.userID ? Alignment.topRight : Alignment.topLeft, @@ -530,7 +569,7 @@ class _ChatState extends State<_Chat> { bottom: 8, ), ) - : Message(timeline.events[i - 1], + : Message(filteredEvents[i - 1], onAvatarTab: (Event event) { sendController.text += ' ${event.senderId}'; @@ -553,10 +592,10 @@ class _ChatState extends State<_Chat> { }, longPressSelect: selectedEvents.isEmpty, selected: selectedEvents - .contains(timeline.events[i - 1]), + .contains(filteredEvents[i - 1]), timeline: timeline, nextEvent: i >= 2 - ? timeline.events[i - 2] + ? filteredEvents[i - 2] : null); }); }, @@ -565,17 +604,23 @@ class _ChatState extends State<_Chat> { ConnectionStatusHeader(), AnimatedContainer( duration: Duration(milliseconds: 300), - height: replyEvent != null ? 56 : 0, + height: editEvent != null || replyEvent != null ? 56 : 0, child: Material( color: Theme.of(context).secondaryHeaderColor, child: Row( children: [ IconButton( icon: Icon(Icons.close), - onPressed: () => setState(() => replyEvent = null), + onPressed: () => setState(() { + replyEvent = null; + editEvent = null; + }), ), Expanded( - child: ReplyContent(replyEvent), + child: replyEvent != null + ? ReplyContent(replyEvent, timeline: timeline) + : _EditContent( + editEvent?.getDisplayEvent(timeline)), ), ], ), @@ -611,7 +656,10 @@ class _ChatState extends State<_Chat> { ), ), selectedEvents.length == 1 - ? selectedEvents.first.status > 0 + ? selectedEvents.first + .getDisplayEvent(timeline) + .status > + 0 ? Container( height: 56, child: FlatButton( @@ -629,7 +677,7 @@ class _ChatState extends State<_Chat> { height: 56, child: FlatButton( onPressed: () => - sendAgainAction(), + sendAgainAction(timeline), child: Row( children: [ Text(L10n.of(context) @@ -804,3 +852,38 @@ class _ChatState extends State<_Chat> { ); } } + +class _EditContent extends StatelessWidget { + final Event event; + + _EditContent(this.event); + + @override + Widget build(BuildContext context) { + if (event == null) { + return Container(); + } + return Row( + children: [ + Icon( + Icons.edit, + color: Theme.of(context).primaryColor, + ), + Container(width: 15.0), + Text( + event?.getLocalizedBody( + L10n.of(context), + withSenderNamePrefix: false, + hideReply: true, + ) ?? + '', + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: TextStyle( + color: Theme.of(context).textTheme.bodyText2.color, + ), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 3a82b5e..89177ff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -14,7 +14,14 @@ packages: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.39.15" + version: "0.39.16" + ansicolor: + dependency: transitive + description: + name: ansicolor + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" archive: dependency: transitive description: @@ -35,14 +42,14 @@ packages: name: asn1lib url: "https://pub.dartlang.org" source: hosted - version: "0.6.4" + version: "0.6.5" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.1" + version: "2.4.2" base58check: dependency: transitive description: @@ -63,7 +70,7 @@ packages: name: bot_toast url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.0.1" bubble: dependency: "direct main" description: @@ -78,6 +85,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" charcode: dependency: transitive description: @@ -105,7 +119,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.12" + version: "1.14.13" convert: dependency: transitive description: @@ -119,21 +133,21 @@ packages: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.13.6" + version: "0.14.0" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.5" csslib: dependency: transitive description: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.1" + version: "0.16.2" cupertino_icons: dependency: "direct main" description: @@ -147,7 +161,7 @@ packages: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.3" + version: "1.3.6" encrypt: dependency: transitive description: @@ -155,12 +169,19 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.0.2" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" famedlysdk: dependency: "direct main" description: path: "." - ref: "3fae58439bdc2100d26fbfb92a62c7fbb7b48903" - resolved-ref: "3fae58439bdc2100d26fbfb92a62c7fbb7b48903" + ref: "574fe27101bb03c8c18c776e98f7f44668e6d159" + resolved-ref: "574fe27101bb03c8c18c776e98f7f44668e6d159" url: "https://gitlab.com/famedly/famedlysdk.git" source: git version: "0.0.1" @@ -171,27 +192,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.3" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.1" file_picker: dependency: transitive description: name: file_picker url: "https://pub.dartlang.org" source: hosted - version: "1.12.0" + version: "1.13.2" file_picker_platform_interface: dependency: transitive description: name: file_picker_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" firebase_messaging: dependency: "direct main" description: name: firebase_messaging url: "https://pub.dartlang.org" source: hosted - version: "6.0.13" + version: "6.0.16" flutter: dependency: "direct main" description: flutter @@ -200,31 +228,33 @@ packages: flutter_advanced_networkimage: dependency: "direct main" description: - name: flutter_advanced_networkimage - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.4" + path: "." + ref: master + resolved-ref: f0f599fb89c494d9158fb6f13d4870582f8ecfcb + url: "https://github.com/mchome/flutter_advanced_networkimage" + source: git + version: "0.8.0" flutter_keyboard_visibility: dependency: transitive description: name: flutter_keyboard_visibility url: "https://pub.dartlang.org" source: hosted - version: "0.7.0" + version: "3.2.1" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons url: "https://pub.dartlang.org" source: hosted - version: "0.7.4" + version: "0.7.5" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications url: "https://pub.dartlang.org" source: hosted - version: "1.4.3" + version: "1.4.4+2" flutter_local_notifications_platform_interface: dependency: transitive description: @@ -240,9 +270,11 @@ packages: flutter_matrix_html: dependency: "direct main" description: - name: flutter_matrix_html - url: "https://pub.dartlang.org" - source: hosted + path: "." + ref: "530df434b50002e04cbad63f53d6f0f5d5adbab5" + resolved-ref: "530df434b50002e04cbad63f53d6f0f5d5adbab5" + url: "https://github.com/Sorunome/flutter_matrix_html" + source: git version: "0.1.2" flutter_olm: dependency: "direct main" @@ -264,14 +296,14 @@ packages: name: flutter_secure_storage url: "https://pub.dartlang.org" source: hosted - version: "3.3.1+1" + version: "3.3.3" flutter_slidable: dependency: "direct main" description: name: flutter_slidable url: "https://pub.dartlang.org" source: hosted - version: "0.5.4" + version: "0.5.5" flutter_sound: dependency: "direct main" description: @@ -285,7 +317,7 @@ packages: name: flutter_svg url: "https://pub.dartlang.org" source: hosted - version: "0.17.4" + version: "0.18.0" flutter_test: dependency: "direct dev" description: flutter @@ -297,7 +329,7 @@ packages: name: flutter_typeahead url: "https://pub.dartlang.org" source: hosted - version: "1.8.1" + version: "1.8.7" flutter_web_plugins: dependency: transitive description: flutter @@ -330,7 +362,7 @@ packages: name: http url: "https://pub.dartlang.org" source: hosted - version: "0.12.1" + version: "0.12.2" http_multi_server: dependency: transitive description: @@ -344,21 +376,21 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.3" + version: "3.1.4" image: dependency: transitive description: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.12" + version: "2.1.14" image_picker: dependency: transitive description: name: image_picker url: "https://pub.dartlang.org" source: hosted - version: "0.6.7+2" + version: "0.6.7+4" image_picker_platform_interface: dependency: transitive description: @@ -379,35 +411,35 @@ packages: name: intl_translation url: "https://pub.dartlang.org" source: hosted - version: "0.17.9" + version: "0.17.10" io: dependency: transitive description: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.3" + version: "0.3.4" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.1+1" + version: "0.6.2" link_text: dependency: "direct main" description: name: link_text url: "https://pub.dartlang.org" source: hosted - version: "0.1.1" + version: "0.1.2" localstorage: dependency: "direct main" description: name: localstorage url: "https://pub.dartlang.org" source: hosted - version: "3.0.1+4" + version: "3.0.2+5" logging: dependency: transitive description: @@ -421,14 +453,14 @@ packages: name: markdown url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.7" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.6" + version: "0.12.8" matrix_file_e2ee: dependency: transitive description: @@ -463,14 +495,14 @@ packages: name: mime_type url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.3.2" moor: dependency: "direct main" description: name: moor url: "https://pub.dartlang.org" source: hosted - version: "3.0.2" + version: "3.3.0" moor_ffi: dependency: "direct main" description: @@ -478,34 +510,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.5.0" - multi_server_socket: - dependency: transitive - description: - name: multi_server_socket - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.2" node_interop: dependency: transitive description: name: node_interop url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.1.1" node_io: dependency: transitive description: name: node_io url: "https://pub.dartlang.org" source: hosted - version: "1.0.1+2" + version: "1.1.1" node_preamble: dependency: transitive description: name: node_preamble url: "https://pub.dartlang.org" source: hosted - version: "1.4.8" + version: "1.4.12" olm: dependency: transitive description: @@ -527,13 +552,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.9.3" - package_resolver: - dependency: transitive - description: - name: package_resolver - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.10" password_hash: dependency: transitive description: @@ -547,7 +565,7 @@ packages: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.6.4" + version: "1.7.0" path_drawing: dependency: transitive description: @@ -568,7 +586,28 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "1.5.1" + version: "1.6.11" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+2" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4+3" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" pedantic: dependency: "direct dev" description: @@ -582,7 +621,7 @@ packages: name: petitparser url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "3.0.4" photo_view: dependency: "direct main" description: @@ -597,6 +636,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.1" + platform_detect: + dependency: transitive + description: + name: platform_detect + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" plugin_platform_interface: dependency: transitive description: @@ -618,55 +664,55 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.4.0" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.13" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.2" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" + version: "1.4.4" random_string: dependency: "direct main" description: name: random_string url: "https://pub.dartlang.org" source: hosted - version: "2.0.1" + version: "2.1.0" receive_sharing_intent: dependency: "direct main" description: name: receive_sharing_intent url: "https://pub.dartlang.org" source: hosted - version: "1.3.3" + version: "1.4.0+2" share: dependency: "direct main" description: name: share url: "https://pub.dartlang.org" source: hosted - version: "0.6.3+5" + version: "0.6.4+3" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.5" + version: "0.7.7" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "1.0.4" + version: "2.0.0" shelf_static: dependency: transitive description: @@ -713,14 +759,28 @@ packages: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.1" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2+1" + sqlite3: + dependency: transitive + description: + name: sqlite3 + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "1.9.5" stream_channel: dependency: transitive description: @@ -741,7 +801,7 @@ packages: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "2.2.0+2" term_glyph: dependency: transitive description: @@ -755,28 +815,28 @@ packages: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.14.4" + version: "1.15.2" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.15" + version: "0.2.17" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "0.3.10" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.1.6" + version: "1.2.0" universal_html: dependency: "direct main" description: @@ -790,7 +850,7 @@ packages: name: universal_io url: "https://pub.dartlang.org" source: hosted - version: "0.8.6" + version: "1.0.1" unorm_dart: dependency: transitive description: @@ -804,28 +864,35 @@ packages: name: url_launcher url: "https://pub.dartlang.org" source: hosted - version: "5.4.1" + version: "5.5.0" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.1+1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos url: "https://pub.dartlang.org" source: hosted - version: "0.0.1+2" + version: "0.0.1+7" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.0.7" url_launcher_web: dependency: "direct main" description: name: url_launcher_web url: "https://pub.dartlang.org" source: hosted - version: "0.1.0+2" + version: "0.1.2" vector_math: dependency: transitive description: @@ -839,14 +906,14 @@ packages: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "2.3.1" + version: "4.2.0" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+13" + version: "0.9.7+15" web_socket_channel: dependency: transitive description: @@ -860,28 +927,35 @@ packages: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "0.5.3" + version: "0.7.3" webview_flutter: dependency: "direct main" description: name: webview_flutter url: "https://pub.dartlang.org" source: hosted - version: "0.3.19+9" + version: "0.3.22+1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.0" xml: dependency: transitive description: name: xml url: "https://pub.dartlang.org" source: hosted - version: "3.6.1" + version: "4.2.0" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "2.2.1" zone_local: dependency: transitive description: @@ -890,5 +964,5 @@ packages: source: hosted version: "0.1.2" sdks: - dart: ">=2.7.0 <3.0.0" - flutter: ">=1.12.13+hotfix.5 <2.0.0" + dart: ">=2.9.0-14.0.dev <3.0.0" + flutter: ">=1.18.0-6.0.pre <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3d83f86..735d086 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,14 +27,17 @@ dependencies: famedlysdk: git: url: https://gitlab.com/famedly/famedlysdk.git - ref: 3fae58439bdc2100d26fbfb92a62c7fbb7b48903 + ref: 574fe27101bb03c8c18c776e98f7f44668e6d159 localstorage: ^3.0.1+4 bubble: ^1.1.9+1 memoryfilepicker: ^0.1.1 url_launcher: ^5.4.1 url_launcher_web: ^0.1.0 - flutter_advanced_networkimage: any + flutter_advanced_networkimage: + git: + url: https://github.com/mchome/flutter_advanced_networkimage + ref: master firebase_messaging: ^6.0.13 flutter_local_notifications: ^1.4.3 link_text: ^0.1.1 @@ -51,7 +54,10 @@ dependencies: open_file: ^3.0.1 mime_type: ^0.3.0 bot_toast: ^3.0.0 - flutter_matrix_html: ^0.1.2 + flutter_matrix_html: + git: + url: https://github.com/Sorunome/flutter_matrix_html + ref: 530df434b50002e04cbad63f53d6f0f5d5adbab5 moor: ^3.0.2 random_string: ^2.0.1 flutter_typeahead: ^1.8.1