diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 34ba1f16..d1cfc45d 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -215,8 +215,9 @@ class CwtchFfi implements Cwtch { // ignore: non_constant_identifier_names final StartCwtch = startCwtchC.asFunction(); - final ut8CwtchDir = cwtchDir.toNativeUtf8(); - StartCwtch(ut8CwtchDir, ut8CwtchDir.length, bundledTor.toNativeUtf8(), bundledTor.length); + final utf8CwtchDir = cwtchDir.toNativeUtf8(); + StartCwtch(utf8CwtchDir, utf8CwtchDir.length, bundledTor.toNativeUtf8(), bundledTor.length); + malloc.free(utf8CwtchDir); // Spawn an isolate to listen to events from libcwtch-go and then dispatch them when received on main thread to cwtchNotifier cwtchIsolate = await Isolate.spawn(_checkAppbusEvents, _receivePort.sendPort); @@ -326,6 +327,7 @@ class CwtchFfi implements Cwtch { String jsonMessage = jsonMessageBytes.toDartString(); _UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(jsonMessageBytes); malloc.free(utf8profile); + return jsonMessage; } diff --git a/lib/models/messagecache.dart b/lib/models/messagecache.dart index eefc6206..33325c22 100644 --- a/lib/models/messagecache.dart +++ b/lib/models/messagecache.dart @@ -103,7 +103,7 @@ class MessageCache extends ChangeNotifier { void addFrontIndexGap(int count, int lastSeenId) { // scan across indexed message the unread count amount (that's the last time UI/BE acked a message) // if we find the last seen ID, the diff of unread count is what's unsynced - for(var i = 0; i < (count+1) && i < cacheByIndex.length; i++) { + for (var i = 0; i < (count + 1) && i < cacheByIndex.length; i++) { if (this.cacheByIndex[i].messageId == lastSeenId) { // we have found the matching lastSeenId so we can calculate the unsynced as the unread messages before it this._indexUnsynced = count - i; @@ -114,7 +114,7 @@ class MessageCache extends ChangeNotifier { // we did not find a matching index, diff to the back end is too great, reset index cache resetIndexCache(); } - + int get indexUnsynced => _indexUnsynced; void resetIndexCache() { diff --git a/lib/models/profile.dart b/lib/models/profile.dart index a7b746da..61ce24f9 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -350,4 +350,9 @@ class ProfileInfoState extends ChangeNotifier { int cacheMemUsage() { return _contacts.cacheMemUsage(); } + + void downloadReset(String fileKey) { + this._downloads.remove(fileKey); + notifyListeners(); + } } diff --git a/lib/notification_manager.dart b/lib/notification_manager.dart index a35e344d..4e2f58d4 100644 --- a/lib/notification_manager.dart +++ b/lib/notification_manager.dart @@ -73,10 +73,10 @@ class LinuxNotificationsManager implements NotificationsManager { // Cwtch can install in non flutter supported ways on linux, this code detects where the assets are on Linux Future detectLinuxAssetsPath() async { - var devStat = FileStat.stat("assets"); - var localStat = FileStat.stat("data/flutter_assets"); - var homeStat = FileStat.stat((Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets"); - var rootStat = FileStat.stat("/usr/share/cwtch/data/flutter_assets"); + var devStat = FileStat.stat("assets"); + var localStat = FileStat.stat("data/flutter_assets"); + var homeStat = FileStat.stat((Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets"); + var rootStat = FileStat.stat("/usr/share/cwtch/data/flutter_assets"); if ((await devStat).type == FileSystemEntityType.directory) { return Directory.current.path; //appPath; @@ -94,7 +94,7 @@ class LinuxNotificationsManager implements NotificationsManager { var iconPath = Uri.file(path.join(assetsPath, "assets/knott.png")); client.notify(message, appName: "cwtch", appIcon: iconPath.toString(), replacesId: this.previous_id).then((linux_notifications.Notification value) async { previous_id = value.id; - if ((await value.closeReason) == linux_notifications.NotificationClosedReason.dismissed) { + if ((await value.closeReason) == linux_notifications.NotificationClosedReason.dismissed) { this.notificationSelectConvo(profile, conversationId); } }); @@ -115,9 +115,9 @@ class NotificationPayload { convoId = json['convoId']; Map toJson() => { - 'profileOnion': profileOnion, - 'convoId': convoId, - }; + 'profileOnion': profileOnion, + 'convoId': convoId, + }; } // FlutterLocalNotificationsPlugin based NotificationManager that handles MacOS @@ -138,10 +138,10 @@ class NixNotificationManager implements NotificationsManager { final InitializationSettings initializationSettings = InitializationSettings(android: null, iOS: null, macOS: initializationSettingsMacOS, linux: initializationSettingsLinux); scheduleMicrotask(() async { flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation()?.requestPermissions( - alert: true, - badge: false, - sound: false, - ); + alert: true, + badge: false, + sound: false, + ); await flutterLocalNotificationsPlugin.initialize(initializationSettings, onSelectNotification: selectNotification); }); diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index 4b36eca8..106a9722 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -130,7 +130,10 @@ class _ContactsViewState extends State { floatingActionButton: FloatingActionButton( onPressed: _modalAddImportChoice, tooltip: AppLocalizations.of(context)!.tooltipAddContact, - child: Icon(CwtchIcons.person_add_alt_1_24px, color: Provider.of(context).theme.defaultButtonTextColor,), + child: Icon( + CwtchIcons.person_add_alt_1_24px, + color: Provider.of(context).theme.defaultButtonTextColor, + ), ), body: showSearchBar || Provider.of(context).isFiltered ? _buildFilterable() : _buildContactList()); } diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index 29adecb1..b96a70d3 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -464,7 +464,7 @@ class _GlobalSettingsViewState extends State { Visibility( visible: EnvironmentConfig.BUILD_VER == dev_version && !Platform.isAndroid, child: FutureBuilder( - future: EnvironmentConfig.BUILD_VER != dev_version ||Platform.isAndroid ? null : Provider.of(context).cwtch.GetDebugInfo(), + future: EnvironmentConfig.BUILD_VER != dev_version || Platform.isAndroid ? null : Provider.of(context).cwtch.GetDebugInfo(), builder: (context, snapshot) { if (snapshot.hasData) { return Column( diff --git a/lib/views/groupsettingsview.dart b/lib/views/groupsettingsview.dart index 21f92fb6..8acfda68 100644 --- a/lib/views/groupsettingsview.dart +++ b/lib/views/groupsettingsview.dart @@ -183,7 +183,9 @@ class _GroupSettingsViewState extends State { onPressed: () { showAlertDialog(context); }, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.backgroundPaneColor), foregroundColor: MaterialStateProperty.all(Provider.of(context).theme.mainTextColor)), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.backgroundPaneColor), + foregroundColor: MaterialStateProperty.all(Provider.of(context).theme.mainTextColor)), icon: Icon(CwtchIcons.leave_group), label: Text( AppLocalizations.of(context)!.leaveConversation, diff --git a/lib/views/peersettingsview.dart b/lib/views/peersettingsview.dart index 20354246..1fed9e22 100644 --- a/lib/views/peersettingsview.dart +++ b/lib/views/peersettingsview.dart @@ -269,7 +269,9 @@ class _PeerSettingsViewState extends State { onPressed: () { showAlertDialog(context); }, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.backgroundPaneColor), foregroundColor: MaterialStateProperty.all(Provider.of(context).theme.mainTextColor)), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(Provider.of(context).theme.backgroundPaneColor), + foregroundColor: MaterialStateProperty.all(Provider.of(context).theme.mainTextColor)), icon: Icon(CwtchIcons.leave_group), label: Text( AppLocalizations.of(context)!.leaveConversation, diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index 44d09d75..d3e1e7b2 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -48,7 +48,7 @@ class FileBubbleState extends State { Widget build(BuildContext context) { var fromMe = Provider.of(context).senderHandle == Provider.of(context).onion; var flagStarted = Provider.of(context).attributes["file-downloaded"] == "true"; - var borderRadiousEh = 15.0; + var borderRadius = 15.0; var showFileSharing = Provider.of(context).isExperimentEnabled(FileSharingExperiment); DateTime messageDate = Provider.of(context).timestamp; @@ -56,7 +56,7 @@ class FileBubbleState extends State { var path = Provider.of(context).downloadFinalPath(widget.fileKey()); // If we haven't stored the filepath in message attributes then save it - if (metadata.attributes["filepath"] != null) { + if (metadata.attributes["filepath"] != null && metadata.attributes["filepath"].toString().isNotEmpty) { path = metadata.attributes["filepath"]; } else if (path != null && metadata.attributes["filepath"] == null) { Provider.of(context).cwtch.SetMessageAttribute(metadata.profileOnion, metadata.conversationIdentifier, 0, metadata.messageID, "filepath", path); @@ -72,6 +72,21 @@ class FileBubbleState extends State { if (myFile == null) { setState(() { myFile = new File(path!); + + // reset + if (myFile?.existsSync() == false) { + myFile = null; + Provider.of(context).downloadReset(widget.fileKey()); + Provider.of(context).attributes["filepath"] = null; + Provider.of(context).attributes["file-downloaded"] = "false"; + Provider.of(context).attributes["file-missing"] = "true"; + Provider.of(context).cwtch.SetMessageAttribute(metadata.profileOnion, metadata.conversationIdentifier, 0, metadata.messageID, "file-downloaded", "false"); + Provider.of(context).cwtch.SetMessageAttribute(metadata.profileOnion, metadata.conversationIdentifier, 0, metadata.messageID, "filepath", ""); + Provider.of(context).cwtch.SetMessageAttribute(metadata.profileOnion, metadata.conversationIdentifier, 0, metadata.messageID, "file-missing", "true"); + } else { + Provider.of(context).attributes["file-missing"] = "false"; + Provider.of(context).cwtch.SetMessageAttribute(metadata.profileOnion, metadata.conversationIdentifier, 0, metadata.messageID, "file-missing", "false"); + } }); } } @@ -122,7 +137,7 @@ class FileBubbleState extends State { padding: EdgeInsets.all(1.0), child: Image.file( myFile!, - cacheWidth: 2048, + cacheWidth: (MediaQuery.of(bcontext).size.width * 0.6).floor(), // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews... filterQuality: FilterQuality.medium, fit: BoxFit.scaleDown, @@ -167,7 +182,9 @@ class FileBubbleState extends State { } } else if (!senderIsContact) { wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept); - } else if (!widget.isAuto) { + } else if (!widget.isAuto || Provider.of(context).attributes["file-missing"] == "false") { + //Note: we need this second case to account for scenarios where a user deletes the downloaded file, we won't automatically + // fetch it again, so we need to offer the user the ability to restart.. wdgDecorations = Visibility( visible: widget.interactive, child: Center( @@ -185,10 +202,10 @@ class FileBubbleState extends State { color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor, border: Border.all(color: fromMe ? Provider.of(context).theme.messageFromMeBackgroundColor : Provider.of(context).theme.messageFromOtherBackgroundColor, width: 1), borderRadius: BorderRadius.only( - topLeft: Radius.circular(borderRadiousEh), - topRight: Radius.circular(borderRadiousEh), - bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero, - bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh), + topLeft: Radius.circular(borderRadius), + topRight: Radius.circular(borderRadius), + bottomLeft: fromMe ? Radius.circular(borderRadius) : Radius.zero, + bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadius), ), ), child: Padding( diff --git a/lib/widgets/messagebubble.dart b/lib/widgets/messagebubble.dart index 668f1663..cd563fab 100644 --- a/lib/widgets/messagebubble.dart +++ b/lib/widgets/messagebubble.dart @@ -118,37 +118,37 @@ class MessageBubbleState extends State { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Text(AppLocalizations.of(context)!.clickableLinksWarning), + Text(AppLocalizations.of(bcontext)!.clickableLinksWarning), Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), child: ElevatedButton( - child: Text(AppLocalizations.of(context)!.clickableLinksCopy, semanticsLabel: AppLocalizations.of(context)!.clickableLinksCopy), + child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy), onPressed: () { Clipboard.setData(new ClipboardData(text: link.url)); final snackBar = SnackBar( - content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification), + content: Text(AppLocalizations.of(bcontext)!.copiedToClipboardNotification), ); Navigator.pop(bcontext); - ScaffoldMessenger.of(context).showSnackBar(snackBar); + ScaffoldMessenger.of(bcontext).showSnackBar(snackBar); }, ), ), Container( margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), child: ElevatedButton( - child: Text(AppLocalizations.of(context)!.clickableLinkOpen, semanticsLabel: AppLocalizations.of(context)!.clickableLinkOpen), + child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen), onPressed: () async { if (await canLaunch(link.url)) { await launch(link.url); Navigator.pop(bcontext); } else { final snackBar = SnackBar( - content: Text(AppLocalizations.of(context)!.clickableLinkError), + content: Text(AppLocalizations.of(bcontext)!.clickableLinkError), ); - ScaffoldMessenger.of(context).showSnackBar(snackBar); + ScaffoldMessenger.of(bcontext).showSnackBar(snackBar); } }, ),