From 44925783f559dee3ba3a182ce2e04214589b6bf1 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 2 Jan 2024 09:14:49 -0800 Subject: [PATCH 1/9] Force prettyDateString to use .toLocal() time Fixes an issue where, on some platforms, contact row dates in non-streaming mode were displayed in UTC. --- lib/models/redaction.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/models/redaction.dart b/lib/models/redaction.dart index ab62a08d..2fb3b2fd 100644 --- a/lib/models/redaction.dart +++ b/lib/models/redaction.dart @@ -39,5 +39,5 @@ String prettyDateString(BuildContext context, DateTime date) { // } return AppLocalizations.of(context)!.now; } - return DateFormat.yMd(Platform.localeName).add_jm().format(date); + return DateFormat.yMd(Platform.localeName).add_jm().format(date.toLocal()); } -- 2.25.1 From d4e57f493ef323c5a09af7cb64afb31e0ad2963e Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 2 Jan 2024 10:48:31 -0800 Subject: [PATCH 2/9] Fix Crash Bug in Android (ShareFile and Reconnect) In rare situtaitons (exacerbated by debug mode and multiple file shares in succession) ReconnectCwtchForeground events can result in negative message counts being calculated in the UI. This fix ensures that doesn't happen, but a complete fix will need to wait until #664 is implement in the backend --- .../kotlin/im/cwtch/flwtch/MainActivity.kt | 3 ++- lib/models/contact.dart | 1 + lib/models/messagecache.dart | 21 ++++++++++++------- lib/models/profile.dart | 17 ++++++++++++++- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt index d2eafd73..7d19a003 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt @@ -331,8 +331,9 @@ class MainActivity: FlutterActivity() { val conversation: Int = call.argument("conversation") ?: 0 val indexI: Int = call.argument("index") ?: 0 val count: Int = call.argument("count") ?: 1 + val ucount : Int = maxOf(1, count) // don't allow negative counts - result.success(Cwtch.getMessages(profile, conversation.toLong(), indexI.toLong(), count.toLong())) + result.success(Cwtch.getMessages(profile, conversation.toLong(), indexI.toLong(), ucount.toLong())) return } "SendMessage" -> { diff --git a/lib/models/contact.dart b/lib/models/contact.dart index e0931169..851b54a0 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -230,6 +230,7 @@ class ContactInfoState extends ChangeNotifier { return this._newMarkerMsgIndex; } + int get totalMessages => this._totalMessages; int get totalMessages => this._totalMessages; set totalMessages(int newVal) { diff --git a/lib/models/messagecache.dart b/lib/models/messagecache.dart index b1807a0a..94bb834e 100644 --- a/lib/models/messagecache.dart +++ b/lib/models/messagecache.dart @@ -30,22 +30,27 @@ class LocalIndexMessage { this.messageId = messageId; this.cacheOnly = cacheOnly; this.isLoading = isLoading; - if (isLoading) { - loader = Completer(); - loaded = loader.future; + loader = Completer(); + loaded = loader.future; + if (!isLoading) { + loader.complete(); // complete this } } void finishLoad(int messageId) { this.messageId = messageId; - isLoading = false; - loader.complete(true); + if (!loader.isCompleted) { + isLoading = false; + loader.complete(true); + } } void failLoad() { this.messageId = null; - isLoading = false; - loader.complete(true); + if (!loader.isCompleted) { + isLoading = false; + loader.complete(true); + } } Future waitForLoad() { @@ -95,7 +100,7 @@ class MessageCache extends ChangeNotifier { this._storageMessageCount = newval; } - // On android reconnect, if backend supplied message count > UI message count, add the differnce to the front of the index + // On android reconnect, if backend supplied message count > UI message count, add the difference to the front of the index void addFrontIndexGap(int count) { this._indexUnsynced = count; } diff --git a/lib/models/profile.dart b/lib/models/profile.dart index 3269f25a..a1e62ac3 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:math'; import 'package:cwtch/config.dart'; import 'package:cwtch/models/remoteserver.dart'; @@ -248,8 +249,22 @@ class ProfileInfoState extends ChangeNotifier { if (profileContact != null) { profileContact.status = contact["status"]; - var newCount = contact["numMessages"]; + var newCount = contact["numMessages"] as int; if (newCount != profileContact.totalMessages) { + if (newCount < profileContact.totalMessages) { + // on Android, when sharing a file the UI may be briefly unloaded for the + // OS to display the file management/selection screen. Afterwards a + // call to ReconnectCwtchForeground will be made which will refresh all values (including count of numMessages) + // **at the same time** the foreground will increment .totalMessages and send a new message to the backend. + // This will result in a negative number of messages being calculated here, and an incorrect totalMessage count. + // This bug is exacerbated in debug mode, and when multiple files are sent in succession. Both cases result in multiple ReconnectCwtchForeground + // events that have the potential to conflict with currentMessageCounts. + // Note that *if* a new message came in at the same time, we would be unable to distinguish this case - as such this is specific instance of a more general problem + // TODO: A true-fix to this bug is to implement a syncing step in the foreground where totalMessages and inFlightMessages can be distinguished + // This requires a change to the backend to confirm submission of an inFlightMessage, which will be implemented in #664 + EnvironmentConfig.debugLog("Conflicting message counts: $newCount ${profileContact.totalMessages}"); + newCount = max(newCount, profileContact.totalMessages); + } profileContact.messageCache.addFrontIndexGap(newCount - profileContact.totalMessages); } profileContact.totalMessages = newCount; -- 2.25.1 From fe4726986f82104eaa9af5a0a0b8a35f8f0c60f0 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 2 Jan 2024 10:53:15 -0800 Subject: [PATCH 3/9] Formatting --- lib/models/contactlist.dart | 2 +- lib/themes/opaque.dart | 2 +- lib/themes/yamltheme.dart | 16 +++++++--------- lib/widgets/messagelist.dart | 4 ++-- 4 files changed, 11 insertions(+), 13 deletions(-) diff --git a/lib/models/contactlist.dart b/lib/models/contactlist.dart index ef8a0cf4..e8709508 100644 --- a/lib/models/contactlist.dart +++ b/lib/models/contactlist.dart @@ -98,7 +98,7 @@ class ContactListState extends ChangeNotifier { notifyListeners(); //} } - + void updateLastMessageReceivedTime(int forIdentifier, DateTime newMessageTime) { var contact = getContact(forIdentifier); if (contact == null) return; diff --git a/lib/themes/opaque.dart b/lib/themes/opaque.dart index 74fdca15..e012c730 100644 --- a/lib/themes/opaque.dart +++ b/lib/themes/opaque.dart @@ -24,7 +24,7 @@ LoadAssetThemes() async { themes = await loadYamlThemes(); } -OpaqueThemeType getTheme(String themeId, String mode) { +OpaqueThemeType getTheme(String themeId, String mode) { if (themeId == "") { themeId = cwtch_theme; } diff --git a/lib/themes/yamltheme.dart b/lib/themes/yamltheme.dart index c788d50b..98e2304b 100644 --- a/lib/themes/yamltheme.dart +++ b/lib/themes/yamltheme.dart @@ -8,8 +8,6 @@ import 'package:flutter/services.dart'; import 'package:yaml/yaml.dart'; import 'package:path/path.dart' as path; - - Future>> loadYamlThemes() async { final manifestJson = await rootBundle.loadString('AssetManifest.json'); final themesList = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/themes')); @@ -17,7 +15,7 @@ Future>> loadYamlThemes() async { Map> themes = Map(); for (String themefile in themesList) { - if (themefile.substring(themefile.length-4) != ".yml") { + if (themefile.substring(themefile.length - 4) != ".yml") { continue; } @@ -77,16 +75,16 @@ class YmlTheme extends OpaqueThemeType { Color? getColor(String name) { var val = yml["themes"][mode]["theme"][name]; - if (! (val is int)) { + if (!(val is int)) { val = yml["themes"][mode]["theme"][val] ?? val; } - if (! (val is int)) { + if (!(val is int)) { val = yml["themes"][mode]?["colors"][val] ?? val; } - if (! (val is int)) { + if (!(val is int)) { val = yml["colors"]?[val]; } - if (! (val is int)) { + if (!(val is int)) { return null; } return Color(0xFF000000 + val as int); @@ -95,7 +93,7 @@ class YmlTheme extends OpaqueThemeType { String? getImage(String name) { var val = yml["themes"][mode]["theme"]?[name]; if (val != null) { - return path.join("assets", "themes", yml["themes"]["name"], val); + return path.join("assets", "themes", yml["themes"]["name"], val); } return null; } @@ -137,4 +135,4 @@ class YmlTheme extends OpaqueThemeType { // Images get chatImage => getImage("chatImage") ?? fallbackTheme.chatImage; -} \ No newline at end of file +} diff --git a/lib/widgets/messagelist.dart b/lib/widgets/messagelist.dart index 75623952..b9cf4686 100644 --- a/lib/widgets/messagelist.dart +++ b/lib/widgets/messagelist.dart @@ -116,8 +116,8 @@ class _MessageListState extends State { // Only show broken heart is the contact is offline... decoration: BoxDecoration( image: Provider.of(outerContext).isOnline() - ? (Provider.of(context).theme.chatImage != null) ? - DecorationImage( + ? (Provider.of(context).theme.chatImage != null) + ? DecorationImage( repeat: ImageRepeat.repeat, image: AssetImage(Provider.of(context).theme.chatImage), colorFilter: ColorFilter.mode(Provider.of(context).theme.hilightElementColor.withOpacity(0.15), BlendMode.srcIn)) -- 2.25.1 From 2ab8cecac6507c6e31079e38769e09b308f08b70 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Tue, 2 Jan 2024 10:54:21 -0800 Subject: [PATCH 4/9] Remove Duplicate Function --- lib/models/contact.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 851b54a0..e0931169 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -230,7 +230,6 @@ class ContactInfoState extends ChangeNotifier { return this._newMarkerMsgIndex; } - int get totalMessages => this._totalMessages; int get totalMessages => this._totalMessages; set totalMessages(int newVal) { -- 2.25.1 From 05946c57fa1b3b53c3c20867809a7566222004e8 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 3 Jan 2024 10:18:16 -0800 Subject: [PATCH 5/9] Upgrade Cwtch, Add Per-Profile Event Log --- LIBCWTCH-GO.version | 2 +- lib/models/contact.dart | 11 +++++++++++ lib/views/messageview.dart | 1 + lib/views/peersettingsview.dart | 14 ++++++++++++-- lib/widgets/messagelist.dart | 1 + 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index eeac635b..86181531 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -2023-09-26-13-15-v0.0.10 \ No newline at end of file +2024-01-03-17-55-v0.0.10-3-g7c04233 \ No newline at end of file diff --git a/lib/models/contact.dart b/lib/models/contact.dart index e0931169..2dfae7f2 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -75,6 +75,8 @@ class ContactInfoState extends ChangeNotifier { DateTime _lastRetryTime = DateTime.now(); DateTime loaded = DateTime.now(); + List contactEvents = List.empty(growable: true); + ContactInfoState( this.profileOnion, this.identifier, @@ -198,6 +200,7 @@ class ContactInfoState extends ChangeNotifier { set status(String newVal) { this._status = newVal; + this.contactEvents.add(ContactEvent("Update Peer Status Received: $newVal")); notifyListeners(); } @@ -486,3 +489,11 @@ class ContactInfoState extends ChangeNotifier { } } } + +class ContactEvent { + String summary; + late DateTime timestamp; + ContactEvent(this.summary) { + this.timestamp = DateTime.now(); + } +} diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index c5044d9c..0f5a18a7 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -125,6 +125,7 @@ class _MessageViewState extends State { } // reset the disconnect button to allow for immediate connection... Provider.of(context, listen: false).lastRetryTime = DateTime.now().subtract(Duration(minutes: 2)); + Provider.of(context, listen: false).contactEvents.add(ContactEvent("Disconnect from Peer")); })); } diff --git a/lib/views/peersettingsview.dart b/lib/views/peersettingsview.dart index 8e1fe04c..ed4bb2ed 100644 --- a/lib/views/peersettingsview.dart +++ b/lib/views/peersettingsview.dart @@ -84,6 +84,13 @@ class _PeerSettingsViewState extends State { } } + List evWidgets = Provider.of(context, listen: false).contactEvents.map((ev) { + return ListTile( + title: Text(ev.summary, style: Provider.of(context).scaleFonts(defaultTextStyle)), + subtitle: Text(ev.timestamp.toLocal().toIso8601String(), style: Provider.of(context).scaleFonts(defaultTextStyle)), + ); + }).toList(); + return Scrollbar( trackVisibility: true, controller: peerSettingsScrollController, @@ -319,8 +326,11 @@ class _PeerSettingsViewState extends State { style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)), ), )) - ]) - ]) + ]), + ]), + Column( + children: evWidgets, + ) ]))))); }); }); diff --git a/lib/widgets/messagelist.dart b/lib/widgets/messagelist.dart index b9cf4686..e71d01e0 100644 --- a/lib/widgets/messagelist.dart +++ b/lib/widgets/messagelist.dart @@ -75,6 +75,7 @@ class _MessageListState extends State { .AttemptReconnection(Provider.of(context, listen: false).onion, Provider.of(context, listen: false).onion); } Provider.of(context, listen: false).lastRetryTime = DateTime.now(); + Provider.of(context, listen: false).contactEvents.add(ContactEvent("Actively Retried Connection")); setState(() { // force update of this view...otherwise the button won't be removed fast enough... }); -- 2.25.1 From deefbb904fcd2102b70d2df7cc89abe9ad6d630f Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 3 Jan 2024 10:27:52 -0800 Subject: [PATCH 6/9] Hide Ev-Log When Not Devmode --- lib/views/peersettingsview.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/views/peersettingsview.dart b/lib/views/peersettingsview.dart index ed4bb2ed..ff307427 100644 --- a/lib/views/peersettingsview.dart +++ b/lib/views/peersettingsview.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:ui'; +import 'package:cwtch/config.dart'; import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; @@ -328,9 +329,12 @@ class _PeerSettingsViewState extends State { )) ]), ]), - Column( - children: evWidgets, - ) + Visibility( + visible: EnvironmentConfig.BUILD_VER == dev_version, + maintainSize: false, + child: Column( + children: evWidgets, + )) ]))))); }); }); -- 2.25.1 From a98068749ed7efa08556916f1c17090e40282eb5 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 3 Jan 2024 11:37:34 -0800 Subject: [PATCH 7/9] More robust UI Test Setup --- run-tests.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index eb9c68c5..a3b3b696 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -5,11 +5,11 @@ sed "s|featurePaths: REPLACED_BY_SCRIPT|featurePaths: [$paths]|" integra flutter pub run build_runner clean flutter pub run build_runner build --delete-conflicting-outputs -PATH=$PATH:$PWD/linux/Tor -LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"$PWD/linux/":"$PWD/linux/Tor/" -PATH=$PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH LOG_FILE=test.log CWTCH_HOME=$PWD/integration_test/env/temp/ flutter test -d linux --dart-define TEST_MODE=true integration_test/gherkin_suite_test.dart -#node index2.js -#if [ "$HEADLESS" = "false" ]; then +PATH=$PATH:"$PWD/linux/Tor" +LD_LIBRARY_PATH="$PWD/linux/":"$PWD/linux/Tor/":$LD_LIBRARY_PATH +env PATH=$PATH LD_LIBRARY_PATH=$LD_LIBRARY_PATH LOG_FILE=test.log CWTCH_HOME=$PWD/integration_test/env/temp/ flutter test -d linux --dart-define TEST_MODE=true integration_test/gherkin_suite_test.dart +# node index2.js +# if [ "$HEADLESS" = "false" ]; then # xdg-open integration_test/gherkin/reports/cucumber_report.html -#fi +# fi -- 2.25.1 From f50242d664ae9c1524b3729e98a3140692d535fb Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 3 Jan 2024 11:44:55 -0800 Subject: [PATCH 8/9] Force Checking of libCwtch.so Loading Status in Tests --- lib/cwtch/cwtch.dart | 2 ++ lib/cwtch/ffi.dart | 9 +++++++++ lib/cwtch/gomobile.dart | 5 +++++ lib/main.dart | 6 +++++- lib/views/globalsettingsview.dart | 2 +- 5 files changed, 22 insertions(+), 2 deletions(-) diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index 1cc06e22..b19d0f72 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -156,4 +156,6 @@ abstract class Cwtch { void DeleteServerInfo(String profile, String handle); void PublishServerUpdate(String onion); Future ConfigureConnections(String onion, bool listen, bool peers, bool servers); + + bool IsLoaded(); } diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index aeb81df9..059ff55d 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -268,6 +268,8 @@ class CwtchFfi implements Cwtch { // Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events @override void dispose() { + EnvironmentConfig.debugLog("tearing down cwtch FFI isolate"); + library.close(); cwtchIsolate.kill(priority: Isolate.immediate); } @@ -1121,4 +1123,11 @@ class CwtchFfi implements Cwtch { PublishServerUpdate(utf8profile, utf8profile.length); malloc.free(utf8profile); } + + @override + bool IsLoaded() { + bool check = library.providesSymbol("c_UpdateSettings"); + EnvironmentConfig.debugLog("Checking that the FFI Interface is Correctly Loaded... $check"); + return check; + } } diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index 4e9804f0..3eca7ecd 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -469,4 +469,9 @@ class CwtchGomobile implements Cwtch { void PublishServerUpdate(String profile) { cwtchPlatform.invokeMethod("PublishServerUpdate", {"ProfileOnion": profile}); } + + @override + bool IsLoaded() { + return true; + } } diff --git a/lib/main.dart b/lib/main.dart index d58ef0b7..45dd4b64 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -173,6 +173,10 @@ class FlwtchState extends State with WindowListener { getServerListStateProvider(), ], builder: (context, widget) { + // in test mode...rebuild everything every second...if cwtch isn't loaded... + if (EnvironmentConfig.TEST_MODE && cwtch.IsLoaded() == false) { + Timer t = new Timer.periodic(Duration(seconds: 1), (Timer t) => setState(() {})); + } return Consumer2( builder: (context, settings, appState, child) => MaterialApp( key: Key('app'), @@ -190,7 +194,7 @@ class FlwtchState extends State with WindowListener { title: 'Cwtch', showSemanticsDebugger: settings.useSemanticDebugger, theme: mkThemeData(settings), - home: (!appState.cwtchInit || appState.modalState != ModalState.none) ? SplashView() : ProfileMgrView(), + home: (!appState.cwtchInit || appState.modalState != ModalState.none) || !cwtch.IsLoaded() ? SplashView() : ProfileMgrView(), ), ); }, diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 2a6062cc..69829d6f 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -83,7 +83,7 @@ class _GlobalSettingsViewState extends State { } Widget _buildSettingsList() { - return Consumer(builder: (context, settings, child) { + return Consumer(builder: (ccontext, settings, child) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { var appIcon = Icon(Icons.info, color: settings.current().mainTextColor); return Scrollbar( -- 2.25.1 From 68af49cfec7399c2a63df998c7f3f3faee18a2b1 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Wed, 3 Jan 2024 13:39:08 -0800 Subject: [PATCH 9/9] Updated Cwtch --- LIBCWTCH-GO.version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LIBCWTCH-GO.version b/LIBCWTCH-GO.version index 86181531..e6cb9215 100644 --- a/LIBCWTCH-GO.version +++ b/LIBCWTCH-GO.version @@ -1 +1 @@ -2024-01-03-17-55-v0.0.10-3-g7c04233 \ No newline at end of file +2024-01-03-20-52-v0.0.10-4-g6c0b2e2 \ No newline at end of file -- 2.25.1