diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index 8a2c740e..47e14a2a 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -15,7 +15,9 @@ abstract class Cwtch { // ignore: non_constant_identifier_names void LoadProfiles(String pass); // ignore: non_constant_identifier_names - void DeleteProfile(String onion, String pass); + void DeleteProfile(String profile, String pass); + // ignore: non_constant_identifier_names + void ChangePassword(String profile, String pass, String newpass, String newpassAgain); // ignore: non_constant_identifier_names void ResetTor(); diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 67add61d..0734ed0a 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -224,7 +224,10 @@ class CwtchNotifier { } break; case "SendMessageToPeerError": - // Ignore + // Ignore dealt with by IndexedFailure + break; + case "SendMessageToGroupError": + // Ignore dealt with by IndexedFailure break; case "IndexedFailure": var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]); diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 3c829794..75f9b3c3 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -115,13 +115,13 @@ class CwtchFfi implements Cwtch { } CwtchFfi(CwtchNotifier _cwtchNotifier) { - String library_path = getLibraryPath(); - if (library_path == UNSUPPORTED_OS) { + String libraryPath = getLibraryPath(); + if (libraryPath == UNSUPPORTED_OS) { print("OS ${Platform.operatingSystem} not supported by cwtch/ffi"); // emergency, ideally the app stays on splash and just posts the error till user closes exit(0); } - library = DynamicLibrary.open(library_path); + library = DynamicLibrary.open(libraryPath); cwtchNotifier = _cwtchNotifier; } @@ -716,4 +716,21 @@ class CwtchFfi implements Cwtch { malloc.free(utf8profile); return jsonMessage; } + + @override + // ignore: non_constant_identifier_names + void ChangePassword(String profile, String pass, String newpass, String newpassAgain) { + var changePasswordC = library.lookup>("c_ChangePassword"); + // ignore: non_constant_identifier_names + final ChangePasswordFn = changePasswordC.asFunction(); + final utf8profile = profile.toNativeUtf8(); + final utf8pass = pass.toNativeUtf8(); + final utf8newpass = newpass.toNativeUtf8(); + final utf8newpasssagain = newpassAgain.toNativeUtf8(); + ChangePasswordFn(utf8profile, utf8profile.length, utf8pass, utf8pass.length, utf8newpass, utf8newpass.length, utf8newpasssagain, utf8newpasssagain.length); + malloc.free(utf8profile); + malloc.free(utf8pass); + malloc.free(utf8newpass); + malloc.free(utf8newpasssagain); + } } diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index 36f4c24a..29bf082f 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -284,4 +284,9 @@ class CwtchGomobile implements Cwtch { String defaultDownloadPath() { return this.androidHomeDirectoryStr; } + + @override + void ChangePassword(String profile, String pass, String newpass, String newpassAgain) { + cwtchPlatform.invokeMethod("ChangePassword", {"ProfileOnion": profile, "OldPass": pass, "NewPass": newpass, "NewPassAgain": newpassAgain}); + } } diff --git a/lib/errorHandler.dart b/lib/errorHandler.dart index a54094c7..156b708a 100644 --- a/lib/errorHandler.dart +++ b/lib/errorHandler.dart @@ -6,12 +6,17 @@ class ErrorHandler extends ChangeNotifier { // Add Contact Specific Errors... static const String addContactErrorPrefix = "addcontact"; + static const String changePasswordErrorPrefix = "changepassword"; static const String invalidImportStringErrorType = "invalid_import_string"; static const String contactAlreadyExistsErrorType = "contact_already_exists"; bool invalidImportStringError = false; bool contactAlreadyExistsError = false; bool explicitAddContactSuccess = false; + // ChangePassword + bool changePasswordError = false; + bool explicitChangePasswordSuccess = false; + // Import Bundle Specific Errors static const String importBundleErrorPrefix = "importBundle"; bool importBundleError = false; @@ -39,6 +44,9 @@ class ErrorHandler extends ChangeNotifier { deletedServerError = false; deletedServerSuccess = false; + changePasswordError = false; + explicitChangePasswordSuccess = false; + notifyListeners(); } @@ -58,6 +66,9 @@ class ErrorHandler extends ChangeNotifier { case deleteProfileErrorPrefix: handleDeleteProfileError(errorType); break; + case changePasswordErrorPrefix: + handleChangePasswordError(errorType); + break; case deletedServerErrorPrefix: handleDeletedServerError(errorType); } @@ -115,6 +126,21 @@ class ErrorHandler extends ChangeNotifier { } } + handleChangePasswordError(String errorType) { + // Reset add contact errors + changePasswordError = false; + explicitChangePasswordSuccess = false; + + switch (errorType) { + case successErrorType: + explicitChangePasswordSuccess = true; + break; + default: + changePasswordError = true; + break; + } + } + handleDeletedServerError(String errorType) { // reset deletedServerError = false; diff --git a/lib/models/message.dart b/lib/models/message.dart index 5c3b322c..dc4c5b97 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -150,5 +150,6 @@ class MessageMetadata extends ChangeNotifier { notifyListeners(); } - MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error, this.isAuto); + MessageMetadata( + this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error, this.isAuto); } diff --git a/lib/models/messages/filemessage.dart b/lib/models/messages/filemessage.dart index 52e455fc..83387ac9 100644 --- a/lib/models/messages/filemessage.dart +++ b/lib/models/messages/filemessage.dart @@ -54,14 +54,16 @@ class FileMessage extends Message { if (!validHash(rootHash, nonce)) { return MessageRow(MalformedBubble()); } - return FileBubble( - nameSuggestion, - rootHash, - nonce, - fileSize, - isAuto: metadata.isAuto, - interactive: false, - ); + return Container( + alignment: Alignment.center, + child: FileBubble( + nameSuggestion, + rootHash, + nonce, + fileSize, + isAuto: metadata.isAuto, + interactive: false, + )); }); } diff --git a/lib/views/addeditprofileview.dart b/lib/views/addeditprofileview.dart index 8065c9b4..0f8a3dd5 100644 --- a/lib/views/addeditprofileview.dart +++ b/lib/views/addeditprofileview.dart @@ -1,6 +1,8 @@ import 'dart:convert'; +import 'dart:io'; import 'dart:math'; +import 'package:cwtch/config.dart'; import 'package:cwtch/cwtch/cwtch.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -278,7 +280,7 @@ class _AddEditProfileViewState extends State { // TODO Toast } - void _createPressed() { + void _createPressed() async { // This will run all the validations in the form including // checking that display name is not empty, and an actual check that the passwords // match (and are provided if the user has requested an encrypted profile). @@ -301,17 +303,32 @@ class _AddEditProfileViewState extends State { } else { // At this points passwords have been validated to be the same and not empty // Update both password and name, even if name hasn't been changed... + var profile = Provider.of(context, listen: false).onion; Provider.of(context, listen: false).nickname = ctrlrNick.value.text; - Provider.of(context, listen: false).cwtch.SetProfileAttribute(Provider.of(context, listen: false).onion, "profile.name", ctrlrNick.value.text); - final updatePasswordEvent = { - "EventType": "ChangePassword", - "Data": {"Password": ctrlrOldPass.text, "NewPassword": ctrlrPass.text} - }; - final updatePasswordEventJson = jsonEncode(updatePasswordEvent); + Provider.of(context, listen: false).cwtch.SetProfileAttribute(profile, "profile.name", ctrlrNick.value.text); + Provider.of(context, listen: false).cwtch.ChangePassword(profile, ctrlrOldPass.text, ctrlrPass.text, ctrlrPass2.text); - Provider.of(context, listen: false).cwtch.SendProfileEvent(Provider.of(context, listen: false).onion, updatePasswordEventJson); - - Navigator.of(context).pop(); + EnvironmentConfig.debugLog("waiting for change password response"); + Future.delayed(const Duration(milliseconds: 500), () { + if (globalErrorHandler.changePasswordError) { + // TODO: This isn't ideal, but because onChange can be fired during this future check + // and because the context can change after being popped we have this kind of double assertion... + // There is probably a better pattern to handle this... + if (AppLocalizations.of(context) != null) { + final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.passwordChangeError)); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + Navigator.pop(context); + return; + } + } + }).whenComplete(() { + if (globalErrorHandler.explicitChangePasswordSuccess) { + final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.newPassword)); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + Navigator.pop(context); + return; // otherwise round and round we go... + } + }); } } } diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index a3840f72..e824baf9 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -83,8 +83,8 @@ class FileBubbleState extends State { } } return LayoutBuilder(builder: (bcontext, constraints) { - var wdgSender = Center( - widthFactor: 1, + var wdgSender = Visibility( + visible: widget.interactive, child: SelectableText(senderDisplayStr + '\u202F', style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of(context).theme.messageFromMeTextColor() : Provider.of(context).theme.messageFromOtherTextColor()))); @@ -99,40 +99,47 @@ class FileBubbleState extends State { if (!showFileSharing) { wdgDecorations = Text('\u202F'); } else if (fromMe) { - wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, prettyDate: prettyDate); + wdgDecorations = Visibility( + visible: widget.interactive, + child: MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, prettyDate: prettyDate)); } else if (downloadComplete) { // in this case, whatever marked download.complete would have also set the path var lpath = path!.toLowerCase(); if (lpath.endsWith("jpg") || lpath.endsWith("jpeg") || lpath.endsWith("png") || lpath.endsWith("gif") || lpath.endsWith("webp") || lpath.endsWith("bmp")) { isPreview = true; - wdgDecorations = GestureDetector( - child: Image.file( - myFile!, - cacheWidth: 2048, // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews... - filterQuality: FilterQuality.medium, - fit: BoxFit.fill, - alignment: Alignment.center, - width: constraints.maxWidth, - isAntiAlias: false, - errorBuilder: (context, error, stackTrace) { - return MalformedBubble(); - }, - ), + wdgDecorations = Center( + child: GestureDetector( + child: Padding( + padding: EdgeInsets.all(1.0), + child: Image.file( + myFile!, + cacheWidth: 2048, // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews... + filterQuality: FilterQuality.medium, + fit: BoxFit.cover, + alignment: Alignment.center, + height: MediaQuery.of(bcontext).size.height * 0.30, + isAntiAlias: false, + errorBuilder: (context, error, stackTrace) { + return MalformedBubble(); + }, + )), onTap: () { pop(bcontext, myFile!, wdgMessage); }, - ); + )); } else { - wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F'); + wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F')); } } else if (downloadActive) { if (!downloadGotManifest) { - wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F'); + wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F')); } else { - wdgDecorations = LinearProgressIndicator( - value: Provider.of(context).downloadProgress(widget.fileKey()), - color: Provider.of(context).theme.defaultButtonActiveColor(), - ); + wdgDecorations = Visibility( + visible: widget.interactive, + child: LinearProgressIndicator( + value: Provider.of(context).downloadProgress(widget.fileKey()), + color: Provider.of(context).theme.defaultButtonActiveColor(), + )); } } else if (flagStarted) { // in this case, the download was done in a previous application launch, @@ -141,17 +148,21 @@ class FileBubbleState extends State { wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F'); } else { var path = Provider.of(context).downloadFinalPath(widget.fileKey()) ?? ""; - wdgDecorations = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'), - ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) - ]); + wdgDecorations = Visibility( + visible: widget.interactive, + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'), + ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) + ])); } } else if (!widget.isAuto) { - wdgDecorations = Center( - widthFactor: 1, - child: Wrap(children: [ - Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)), - ])); + wdgDecorations = Visibility( + visible: widget.interactive, + child: Center( + widthFactor: 1, + child: Wrap(children: [ + Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)), + ]))); } else { wdgDecorations = Container(); } @@ -174,9 +185,7 @@ class FileBubbleState extends State { crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, mainAxisSize: MainAxisSize.min, - children: fromMe - ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] - : [wdgSender, isPreview ? Container() : wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]), + children: fromMe ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] : [wdgSender, isPreview ? Container() : wdgMessage, wdgDecorations]), )); }); }