From a4ce168aecc7b387f221bbfa3bf3564fa09e3c5d Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Wed, 26 Jan 2022 20:03:48 -0500 Subject: [PATCH 1/4] calculate server progress based on last message, nowtime, and message flow; display progress on group contact and remote server --- lib/cwtch/cwtchNotifier.dart | 6 +++- lib/models/contact.dart | 8 +++++ lib/models/profile.dart | 3 +- lib/models/profileservers.dart | 35 ++++---------------- lib/models/remoteserver.dart | 54 +++++++++++++++++++++++++++++++ lib/views/addcontactview.dart | 1 + lib/views/profileserversview.dart | 1 + lib/views/remoteserverview.dart | 1 + lib/widgets/contactrow.dart | 2 ++ lib/widgets/remoteserverrow.dart | 9 +++++- 10 files changed, 89 insertions(+), 31 deletions(-) create mode 100644 lib/models/remoteserver.dart diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index f8ac337c..0367c7ee 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -5,6 +5,7 @@ import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/profilelist.dart'; import 'package:cwtch/models/profileservers.dart'; +import 'package:cwtch/models/remoteserver.dart'; import 'package:cwtch/models/servers.dart'; import 'package:cwtch/notification_manager.dart'; import 'package:provider/provider.dart'; @@ -193,7 +194,8 @@ class CwtchNotifier { var senderHandle = data['RemotePeer']; var senderImage = data['Picture']; var timestampSent = DateTime.tryParse(data['TimestampSent'])!; - var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages; + var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier); + var currentTotal = contact!.totalMessages; var isAuto = data['Auto'] == "true"; String? contenthash = data['ContentHash']; var selectedConversation = appState.selectedProfile == data["ProfileOnion"] && appState.selectedConversation == identifier; @@ -214,6 +216,8 @@ class CwtchNotifier { notificationManager.notify("New Message From Group!"); } + RemoteServerInfoState? server = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(contact.server); + server?.updateSyncProgressFor(timestampSent); } else { // This is dealt with by IndexedAcknowledgment EnvironmentConfig.debugLog("new message from group from yourself - this should not happen"); diff --git a/lib/models/contact.dart b/lib/models/contact.dart index d39aa193..064950c6 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -26,6 +26,7 @@ class ContactInfoState extends ChangeNotifier { // todo: a nicer way to model contacts, groups and other "entities" late bool _isGroup; String? _server; + double _serverSyncProgress = 0.0; late bool _archived; String? _acnCircuit; @@ -176,6 +177,12 @@ class ContactInfoState extends ChangeNotifier { // we only allow callers to fetch the server get server => this._server; + double get serverSyncProgress => this._serverSyncProgress; + set serverSyncProgress(double newProgress) { + _serverSyncProgress = newProgress; + notifyListeners(); + } + bool isOnline() { if (this.isGroup == true) { // We now have an out of sync warning so we will mark these as online... @@ -211,6 +218,7 @@ class ContactInfoState extends ChangeNotifier { newMarker++; } + this._lastMessageTime = timestamp; this.messageCache.addNew(profileOnion, identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash); this.totalMessages += 1; diff --git a/lib/models/profile.dart b/lib/models/profile.dart index d79617ef..268eeb78 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:cwtch/models/remoteserver.dart'; import 'package:flutter/widgets.dart'; import 'contact.dart'; @@ -72,7 +73,7 @@ class ProfileInfoState extends ChangeNotifier { List servers = jsonDecode(serversJson); this._servers.replace(servers.map((server) { // TODO Keys... - return RemoteServerInfoState(onion: server["onion"], identifier: server["identifier"], description: server["description"], status: server["status"]); + return RemoteServerInfoState(server["onion"], server["identifier"], server["description"], server["status"]); })); this._contacts.contacts.forEach((contact) { diff --git a/lib/models/profileservers.dart b/lib/models/profileservers.dart index 9dd7af49..fbc02203 100644 --- a/lib/models/profileservers.dart +++ b/lib/models/profileservers.dart @@ -1,3 +1,4 @@ +import 'package:cwtch/models/remoteserver.dart'; import 'package:flutter/material.dart'; import 'contact.dart'; @@ -33,12 +34,17 @@ class ProfileServerListState extends ChangeNotifier { // return -1 = a first in list // return 1 = b first in list - // online v offline + // online v syncing v offline if (a.status == "Synced" && b.status != "Synced") { return -1; } else if (a.status != "Synced" && b.status == "Synced") { return 1; } + if (a.status == "Authenticated" && b.status != "Authenticated") { + return -1; + } else if (a.status != "Authenticated" && b.status == "Authenticated") { + return 1; + } // num of groups if (a.groups.length > b.groups.length) { @@ -65,30 +71,3 @@ class ProfileServerListState extends ChangeNotifier { List get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier } - -class RemoteServerInfoState extends ChangeNotifier { - final String onion; - final int identifier; - String status; - String description; - List _groups = []; - - RemoteServerInfoState({required this.onion, required this.identifier, required this.description, required this.status}); - - void updateDescription(String newDescription) { - this.description = newDescription; - notifyListeners(); - } - - void clearGroups() { - _groups = []; - } - - void addGroup(ContactInfoState group) { - _groups.add(group); - notifyListeners(); - } - - List get groups => _groups.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier - -} diff --git a/lib/models/remoteserver.dart b/lib/models/remoteserver.dart new file mode 100644 index 00000000..b83a8844 --- /dev/null +++ b/lib/models/remoteserver.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.dart'; + +import 'contact.dart'; + +class RemoteServerInfoState extends ChangeNotifier { + final String onion; + final int identifier; + String _status; + String description; + List _groups = []; + + double syncProgress = 0; + DateTime lastPreSyncMessagTime = new DateTime(2020); + + RemoteServerInfoState(this.onion, this.identifier, this.description, this._status); + + void updateDescription(String newDescription) { + this.description = newDescription; + notifyListeners(); + } + + void clearGroups() { + _groups = []; + } + + void addGroup(ContactInfoState group) { + _groups.add(group); + notifyListeners(); + } + + String get status => _status; + set status(String newStatus) { + _status = newStatus; + if (status == "Authenticated") { + // syncing, set lastPreSyncMessageTime + _groups.forEach((g) { + if(g.lastMessageTime.isAfter(lastPreSyncMessagTime)) { + lastPreSyncMessagTime = g.lastMessageTime; + } + }); + } + notifyListeners(); + } + + void updateSyncProgressFor(DateTime point) { + var range = lastPreSyncMessagTime.difference(DateTime.now()); + var pointFromStart = lastPreSyncMessagTime.difference(point); + syncProgress = pointFromStart.inSeconds / range.inSeconds * 100; + groups.forEach((g) { g.serverSyncProgress = syncProgress;}); + notifyListeners(); + } + + List get groups => _groups.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier +} \ No newline at end of file diff --git a/lib/views/addcontactview.dart b/lib/views/addcontactview.dart index 2d58c43f..3cee78c1 100644 --- a/lib/views/addcontactview.dart +++ b/lib/views/addcontactview.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/models/profile.dart'; +import 'package:cwtch/models/remoteserver.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:cwtch/errorHandler.dart'; diff --git a/lib/views/profileserversview.dart b/lib/views/profileserversview.dart index 6a4d3ebd..2c4e8aab 100644 --- a/lib/views/profileserversview.dart +++ b/lib/views/profileserversview.dart @@ -1,5 +1,6 @@ import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profileservers.dart'; +import 'package:cwtch/models/remoteserver.dart'; import 'package:cwtch/models/servers.dart'; import 'package:cwtch/widgets/remoteserverrow.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/remoteserverview.dart b/lib/views/remoteserverview.dart index 46ac8567..22e0a6f2 100644 --- a/lib/views/remoteserverview.dart +++ b/lib/views/remoteserverview.dart @@ -4,6 +4,7 @@ import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profileservers.dart'; +import 'package:cwtch/models/remoteserver.dart'; import 'package:cwtch/models/servers.dart'; import 'package:cwtch/widgets/buttontextfield.dart'; import 'package:cwtch/widgets/contactrow.dart'; diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index 53dfe32c..ef53d95a 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/profile.dart'; +import 'package:cwtch/models/profileservers.dart'; import 'package:cwtch/views/contactsview.dart'; import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; @@ -66,6 +67,7 @@ class _ContactRowState extends State { visible: contact.isGroup && contact.status == "Authenticated", child: LinearProgressIndicator( color: Provider.of(context).theme.defaultButtonActiveColor, + value: contact.serverSyncProgress, )), Visibility( visible: !Provider.of(context).streamerMode, diff --git a/lib/widgets/remoteserverrow.dart b/lib/widgets/remoteserverrow.dart index c35b0e9a..fab0076f 100644 --- a/lib/widgets/remoteserverrow.dart +++ b/lib/widgets/remoteserverrow.dart @@ -1,6 +1,7 @@ import 'package:cwtch/main.dart'; import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profileservers.dart'; +import 'package:cwtch/models/remoteserver.dart'; import 'package:cwtch/models/servers.dart'; import 'package:cwtch/views/addeditservers.dart'; import 'package:cwtch/views/remoteserverview.dart'; @@ -55,7 +56,13 @@ class _RemoteServerRowState extends State { softWrap: true, overflow: TextOverflow.ellipsis, style: TextStyle(color: running ? Provider.of(context).theme.portraitOnlineBorderColor : Provider.of(context).theme.portraitOfflineBorderColor), - ))) + ))), + Visibility( + visible: server.status == "Authenticated", + child: LinearProgressIndicator( + color: Provider.of(context).theme.defaultButtonActiveColor, + value: server.syncProgress, + )), ], )), ]), From d84850af490582d2ff77b9e5b4a7685d39f3c5df Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 27 Jan 2022 12:58:16 -0800 Subject: [PATCH 2/4] Fixup Length Display so it counts Bytes not Chars --- lib/notification_manager.dart | 2 +- lib/views/messageview.dart | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/notification_manager.dart b/lib/notification_manager.dart index 1a0200fe..91f1cb52 100644 --- a/lib/notification_manager.dart +++ b/lib/notification_manager.dart @@ -54,7 +54,7 @@ class WindowsNotificationManager implements NotificationsManager { } // clicked if (event is ToastActivated) { - active = false; + active = false; } // if a supplied action was clicked if (event is ToastInteracted) { diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 9437dda8..89fa5b29 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -179,13 +179,14 @@ class _MessageViewState extends State { // size because of the additional wrapping end encoding // hybrid groups should allow these numbers to be the same. static const P2PMessageLengthMax = 7000; - static const GroupMessageLengthMax = 1800; + static const GroupMessageLengthMax = 1600; void _sendMessage([String? ignoredParam]) { var isGroup = Provider.of(context).contactList.getContact(Provider.of(context, listen: false).selectedConversation!)!.isGroup; // peers and groups currently have different length constraints (servers can store less)... - var lengthOk = (isGroup && ctrlrCompose.value.text.length < GroupMessageLengthMax) || ctrlrCompose.value.text.length <= P2PMessageLengthMax; + var actualMessageLength = ctrlrCompose.value.text.length; + var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax; if (ctrlrCompose.value.text.isNotEmpty && lengthOk) { if (Provider.of(context, listen: false).selectedConversation != null && Provider.of(context, listen: false).selectedIndex != null) { @@ -250,6 +251,10 @@ class _MessageViewState extends State { bool isOffline = Provider.of(context).isOnline() == false; bool isGroup = Provider.of(context).isGroup; + var charLength = ctrlrCompose.value.text.characters.length; + var expectedLength = ctrlrCompose.value.text.length; + var numberOfBytesMoreThanChar = (expectedLength - charLength); + var composeBox = Container( color: Provider.of(context).theme.backgroundMainColor, padding: EdgeInsets.all(2), @@ -274,11 +279,16 @@ class _MessageViewState extends State { keyboardType: TextInputType.multiline, enableIMEPersonalizedLearning: false, minLines: 1, - maxLength: isGroup ? GroupMessageLengthMax : P2PMessageLengthMax, + maxLength: (isGroup ? GroupMessageLengthMax : P2PMessageLengthMax) - numberOfBytesMoreThanChar, maxLengthEnforcement: MaxLengthEnforcement.enforced, maxLines: null, onFieldSubmitted: _sendMessage, enabled: !isOffline, + onChanged: (String x) { + setState(() { + // we need to force a rerender here to update the max length count + }); + }, decoration: InputDecoration( hintText: isOffline ? "" : AppLocalizations.of(context)!.placeholderEnterMessage, hintStyle: TextStyle(color: Provider.of(context).theme.sendHintTextColor), From f818d4f2f8bd401f767975fd52e3c7630e242c6e Mon Sep 17 00:00:00 2001 From: Dan Ballard Date: Thu, 27 Jan 2022 18:40:45 -0500 Subject: [PATCH 3/4] remove syncProgress from contact and wire contact row to search server's list --- lib/models/contact.dart | 7 ------- lib/models/remoteserver.dart | 5 +++-- lib/views/contactsview.dart | 10 +++++++++- lib/widgets/contactrow.dart | 2 +- lib/widgets/profilerow.dart | 2 +- 5 files changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 064950c6..5ac0edd4 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -26,7 +26,6 @@ class ContactInfoState extends ChangeNotifier { // todo: a nicer way to model contacts, groups and other "entities" late bool _isGroup; String? _server; - double _serverSyncProgress = 0.0; late bool _archived; String? _acnCircuit; @@ -177,12 +176,6 @@ class ContactInfoState extends ChangeNotifier { // we only allow callers to fetch the server get server => this._server; - double get serverSyncProgress => this._serverSyncProgress; - set serverSyncProgress(double newProgress) { - _serverSyncProgress = newProgress; - notifyListeners(); - } - bool isOnline() { if (this.isGroup == true) { // We now have an out of sync warning so we will mark these as online... diff --git a/lib/models/remoteserver.dart b/lib/models/remoteserver.dart index b83a8844..f9e658dd 100644 --- a/lib/models/remoteserver.dart +++ b/lib/models/remoteserver.dart @@ -42,11 +42,12 @@ class RemoteServerInfoState extends ChangeNotifier { notifyListeners(); } + // updateSyncProgressFor point takes a message's time, and updates the server sync progress, + // based on that point in time between the precalculated lastPreSyncMessagTime and Now void updateSyncProgressFor(DateTime point) { var range = lastPreSyncMessagTime.difference(DateTime.now()); var pointFromStart = lastPreSyncMessagTime.difference(point); - syncProgress = pointFromStart.inSeconds / range.inSeconds * 100; - groups.forEach((g) { g.serverSyncProgress = syncProgress;}); + syncProgress = pointFromStart.inSeconds / range.inSeconds; notifyListeners(); } diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index 4e7b6ade..7f6b2236 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -3,6 +3,7 @@ import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contactlist.dart'; import 'package:cwtch/models/profile.dart'; +import 'package:cwtch/models/profilelist.dart'; import 'package:cwtch/views/profileserversview.dart'; import 'package:flutter/material.dart'; import 'package:cwtch/widgets/contactrow.dart'; @@ -154,8 +155,15 @@ class _ContactsViewState extends State { Widget _buildContactList() { final tiles = Provider.of(context).filteredList().map((ContactInfoState contact) { - return ChangeNotifierProvider.value(key: ValueKey(contact.profileOnion + "" + contact.onion), value: contact, builder: (_, __) => RepaintBoundary(child: ContactRow())); + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: contact), + ChangeNotifierProvider.value(value: Provider.of(context).serverList), + ], + builder: (context, child) => RepaintBoundary(child: ContactRow()), + ); }); + final divided = ListTile.divideTiles( context: context, tiles: tiles, diff --git a/lib/widgets/contactrow.dart b/lib/widgets/contactrow.dart index ef53d95a..b61b8ffa 100644 --- a/lib/widgets/contactrow.dart +++ b/lib/widgets/contactrow.dart @@ -67,7 +67,7 @@ class _ContactRowState extends State { visible: contact.isGroup && contact.status == "Authenticated", child: LinearProgressIndicator( color: Provider.of(context).theme.defaultButtonActiveColor, - value: contact.serverSyncProgress, + value: Provider.of(context).serverList.getServer(contact.server)?.syncProgress, )), Visibility( visible: !Provider.of(context).streamerMode, diff --git a/lib/widgets/profilerow.dart b/lib/widgets/profilerow.dart index 6d2c8e8e..acbdce0a 100644 --- a/lib/widgets/profilerow.dart +++ b/lib/widgets/profilerow.dart @@ -91,7 +91,7 @@ class _ProfileRowState extends State { return MultiProvider( providers: [ ChangeNotifierProvider.value(value: profile), - ChangeNotifierProvider.value(value: profile.contactList), + ChangeNotifierProvider.value(value: profile.contactList) ], builder: (innercontext, widget) { var appState = Provider.of(context); From 247f4073b8cb1dc606155e988017ca8c14e552b6 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 2 Feb 2022 10:45:58 -0800 Subject: [PATCH 4/4] Update Url Launcher --- lib/main.dart | 5 ++--- linux/my_application.cc | 7 ++++--- pubspec.yaml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 63aed68d..21b1a01f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -51,13 +51,14 @@ class Flwtch extends StatefulWidget { } } -class FlwtchState extends State with WindowListener, WidgetsBindingObserver { +class FlwtchState extends State with WindowListener { final TextStyle biggerFont = const TextStyle(fontSize: 18); late Cwtch cwtch; late ProfileListState profs; final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler'); final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdownClickHandler'); final MethodChannel shutdownLinuxMethodChannel = MethodChannel('im.cwtch.linux.shutdown'); + final GlobalKey navKey = GlobalKey(); Future shutdownDirect(MethodCall call) { @@ -186,7 +187,6 @@ class FlwtchState extends State with WindowListener, WidgetsBindingObser // coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID Future _externalNotificationClicked(MethodCall call) async { var args = jsonDecode(call.arguments); - var profile = profs.getProfile(args["ProfileOnion"])!; var convo = profile.contactList.getContact(args["Handle"])!; Provider.of(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages; @@ -231,7 +231,6 @@ class FlwtchState extends State with WindowListener, WidgetsBindingObser globalAppState.focus = false; } - @override void dispose() { cwtch.Shutdown(); diff --git a/linux/my_application.cc b/linux/my_application.cc index 07ad9a37..86d95331 100644 --- a/linux/my_application.cc +++ b/linux/my_application.cc @@ -132,14 +132,15 @@ static void my_application_activate(GApplication* application) { gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + // Create a specific channel for shutting down cwtch when the close button is triggered // We have registered the "destroy" handle above for this reason FlEngine *engine = fl_view_get_engine(view); g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); g_autoptr(FlBinaryMessenger) messenger = fl_engine_get_binary_messenger(engine); - channel = - fl_method_channel_new(messenger, - "im.cwtch.linux.shutdown", FL_METHOD_CODEC(codec)); + channel = fl_method_channel_new(messenger, "im.cwtch.linux.shutdown", FL_METHOD_CODEC(codec)); + gtk_widget_grab_focus(GTK_WIDGET(view)); diff --git a/pubspec.yaml b/pubspec.yaml index 9c74a0b6..ee0dc128 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,7 +41,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 file_picker: ^4.3.2 file_picker_desktop: ^1.1.0 - url_launcher: ^6.0.12 + url_launcher: ^6.0.18 desktoasts: ^0.0.2 window_manager: ^0.1.4