From 880c1c107b75f29009f4c7d75c23aa01b472d0ba Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 25 Nov 2021 15:59:54 -0800 Subject: [PATCH] UI Updates to new Cwtch API --- lib/cwtch/cwtchNotifier.dart | 23 +- lib/cwtch/ffi.dart | 21 +- lib/cwtch/gomobile.dart | 3 +- lib/model.dart | 4 +- lib/models/message.dart | 3 +- lib/models/servers.dart | 9 +- lib/views/addeditprofileview.dart | 2 + lib/views/addeditservers.dart | 447 ++++++++++++++---------------- lib/views/contactsview.dart | 1 - lib/views/globalsettingsview.dart | 5 +- lib/views/messageview.dart | 8 +- lib/views/profilemgrview.dart | 8 +- lib/views/serversview.dart | 149 +++++----- lib/widgets/filebubble.dart | 22 +- lib/widgets/messagebubble.dart | 7 +- lib/widgets/messagerow.dart | 10 +- lib/widgets/serverrow.dart | 62 ++--- 17 files changed, 369 insertions(+), 415 deletions(-) diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index 5a3f9b6a..274026bf 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -23,7 +23,8 @@ class CwtchNotifier { late AppState appState; late ServerListState serverListState; - CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) { + CwtchNotifier( + ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) { profileCN = pcn; settings = settingsCN; error = errorCN; @@ -51,7 +52,7 @@ class CwtchNotifier { EnvironmentConfig.debugLog("NewServer $data"); profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState( data["ProfileOnion"], - data["ConversationID"], + int.parse(data["ConversationID"]), data["RemotePeer"], nickname: data["nick"], status: data["status"], @@ -68,13 +69,7 @@ class CwtchNotifier { break; case "NewServer": EnvironmentConfig.debugLog("NewServer $data"); - serverListState.add( - data["Onion"], - data["ServerBundle"], - data["Running"] == "true", - data["Description"], - data["Autostart"] == "true", - data["StorageType"] == "storage-password"); + serverListState.add(data["Onion"], data["ServerBundle"], data["Running"] == "true", data["Description"], data["Autostart"] == "true", data["StorageType"] == "storage-password"); break; case "ServerIntentUpdate": EnvironmentConfig.debugLog("ServerIntentUpdate $data"); @@ -158,7 +153,7 @@ class CwtchNotifier { case "IndexedAcknowledgement": var messageID = data["Index"]; var identifier = int.parse(data["ConversationID"]); - var idx = identifier.toString() + messageID; + var idx = identifier.toString() + messageID; // We return -1 for protocol message acks if there is no message if (idx == "-1") break; @@ -207,7 +202,7 @@ class CwtchNotifier { // For now we perform some minimal checks on the sent timestamp to use to provide a useful ordering for honest contacts // and ensure that malicious contacts in groups can only set this timestamp to a value within the range of `last seen message time` // and `local now`. - profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], timestampSent.toLocal()); + profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, timestampSent.toLocal()); notificationManager.notify("New Message From Group!"); } } else { @@ -271,8 +266,8 @@ class CwtchNotifier { case "UpdateGlobalSettings": settings.handleUpdate(jsonDecode(data["Data"])); break; - case "SetAttribute": - if (data["Key"] == "public.name") { + case "UpdatedProfileAttribute": + if (data["Key"] == "public.profile.name") { profileCN.getProfile(data["ProfileOnion"])?.nickname = data["Data"]; } else { EnvironmentConfig.debugLog("unhandled set attribute event: ${data['Key']}"); @@ -308,7 +303,7 @@ class CwtchNotifier { } if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(groupInvite["GroupID"]) == null) { - profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["ConversationID"], groupInvite["GroupID"], + profileCN.getProfile(data["ProfileOnion"])?.contactList.add(ContactInfoState(data["ProfileOnion"], data["ConversationID"], groupInvite["GroupID"], authorization: ContactAuthorization.approved, imagePath: data["PicturePath"], nickname: groupInvite["GroupName"], diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 14cdffe9..78be3e70 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -63,10 +63,16 @@ typedef get_json_blob_from_str_str_int_function = Pointer Function(Pointer typedef GetJsonBlobFromStrStrIntFn = Pointer Function(Pointer, int, Pointer, int, int); typedef get_json_blob_from_str_int_int_function = Pointer Function(Pointer, Int32, Int32, Int32); -typedef GetJsonBlobFromStrIntIntFn = Pointer Function(Pointer, int, int, int); +typedef GetJsonBlobFromStrIntIntFn = Pointer Function(Pointer, int, int, int); typedef get_json_blob_from_str_int_string_function = Pointer Function(Pointer, Int32, Int32, Pointer, Int32); -typedef GetJsonBlobFromStrIntStringFn = Pointer Function(Pointer, int, int, Pointer, int,); +typedef GetJsonBlobFromStrIntStringFn = Pointer Function( + Pointer, + int, + int, + Pointer, + int, +); // func c_GetMessagesByContentHash(profile_ptr *C.char, profile_len C.int, handle_ptr *C.char, handle_len C.int, contenthash_ptr *C.char, contenthash_len C.int) *C.char typedef get_json_blob_from_str_str_str_function = Pointer Function(Pointer, Int32, Pointer, Int32, Pointer, Int32); @@ -78,10 +84,9 @@ typedef VoidFromStringIntStringFn = void Function(Pointer, int, int, Point typedef void_from_string_int_string_string_function = Void Function(Pointer, Int32, Int32, Pointer, Int32, Pointer, Int32); typedef VoidFromStringIntStringStringFn = void Function(Pointer, int, int, Pointer, int, Pointer, int); -typedef void_from_string_int_int_function =Void Function(Pointer, Int32, Int32, Int32); +typedef void_from_string_int_int_function = Void Function(Pointer, Int32, Int32, Int32); typedef VoidFromStringIntIntFn = void Function(Pointer, int, int, int); - typedef appbus_events_function = Pointer Function(); typedef AppbusEventsFn = Pointer Function(); @@ -325,7 +330,7 @@ class CwtchFfi implements Cwtch { @override // ignore: non_constant_identifier_names void AcceptContact(String profileOnion, int contactHandle) { - var acceptContact = library.lookup>("c_AcceptContact"); + var acceptContact = library.lookup>("c_AcceptConversation"); // ignore: non_constant_identifier_names final AcceptContact = acceptContact.asFunction(); final u1 = profileOnion.toNativeUtf8(); @@ -430,7 +435,6 @@ class CwtchFfi implements Cwtch { malloc.free(u3); } - @override // ignore: non_constant_identifier_names void ResetTor() { @@ -453,7 +457,6 @@ class CwtchFfi implements Cwtch { malloc.free(u2); } - @override // ignore: non_constant_identifier_names void RejectInvite(String profileOnion, int groupHandle) { @@ -490,7 +493,6 @@ class CwtchFfi implements Cwtch { final u1 = profileOnion.toNativeUtf8(); ArchiveConversation(u1, u1.length, handle); malloc.free(u1); - } @override @@ -504,7 +506,6 @@ class CwtchFfi implements Cwtch { malloc.free(u1); } - @override // ignore: non_constant_identifier_names void DeleteProfile(String onion, String currentPassword) { @@ -526,7 +527,7 @@ class CwtchFfi implements Cwtch { final SetProfileAttribute = setProfileAttribute.asFunction(); final u1 = profile.toNativeUtf8(); final u2 = key.toNativeUtf8(); - final u3 = key.toNativeUtf8(); + final u3 = val.toNativeUtf8(); SetProfileAttribute(u1, u1.length, u2, u2.length, u3, u3.length); malloc.free(u1); malloc.free(u2); diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index 06c91f74..f8d62f3a 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -201,7 +201,6 @@ class CwtchGomobile implements Cwtch { cwtchPlatform.invokeMethod("ArchiveConversation", {"ProfileOnion": profileOnion, "handle": contactHandle}); } - @override // ignore: non_constant_identifier_names void SetProfileAttribute(String profile, String key, String val) { @@ -268,7 +267,7 @@ class CwtchGomobile implements Cwtch { cwtchPlatform.invokeMethod("SetServerAttribute", {"ServerOnion": serverOnion, "Key": key, "Val": val}); } - @override + @override Future Shutdown() async { print("gomobile.dart Shutdown"); cwtchPlatform.invokeMethod("Shutdown", {}); diff --git a/lib/model.dart b/lib/model.dart index a0882561..1526d190 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -208,7 +208,6 @@ class ContactListState extends ChangeNotifier { int idx = _contacts.indexWhere((element) => element.onion == byHandle); return idx >= 0 ? _contacts[idx] : null; } - } class ProfileInfoState extends ChangeNotifier { @@ -601,7 +600,7 @@ class ContactInfoState extends ChangeNotifier { if (newVal > 0) { this._newMarker = newVal; } else { - this._newMarkerClearAt = DateTime.now().add(const Duration(minutes:2)); + this._newMarkerClearAt = DateTime.now().add(const Duration(minutes: 2)); } this._unreadMessages = newVal; notifyListeners(); @@ -616,6 +615,7 @@ class ContactInfoState extends ChangeNotifier { } return this._newMarker; } + // what's a getter that sometimes sets without a setter // that sometimes doesn't set set newMarker(int newVal) { diff --git a/lib/models/message.dart b/lib/models/message.dart index 062c60a8..708cd8af 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -127,5 +127,6 @@ class MessageMetadata extends ChangeNotifier { notifyListeners(); } - MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageIndex, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._flags, this._ackd, this._error); + MessageMetadata( + this.profileOnion, this.conversationIdentifier, this.messageIndex, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._flags, this._ackd, this._error); } diff --git a/lib/models/servers.dart b/lib/models/servers.dart index bf9710dd..2850668b 100644 --- a/lib/models/servers.dart +++ b/lib/models/servers.dart @@ -24,12 +24,7 @@ class ServerListState extends ChangeNotifier { if (idx >= 0) { _servers[idx] = sis; } else { - _servers.add(ServerInfoState(onion: onion, - serverBundle: serverBundle, - running: running, - description: description, - autoStart: autoStart, - isEncrypted: isEncrypted)); + _servers.add(ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted)); } notifyListeners(); } @@ -37,7 +32,7 @@ class ServerListState extends ChangeNotifier { void updateServer(String onion, String serverBundle, bool running, String description, bool autoStart, bool isEncrypted) { int idx = _servers.indexWhere((element) => element.onion == onion); if (idx >= 0) { - _servers[idx] = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted); + _servers[idx] = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted); } else { print("Tried to update server list without a starting state...this is probably an error"); } diff --git a/lib/views/addeditprofileview.dart b/lib/views/addeditprofileview.dart index 6b8f1e7c..2355fd57 100644 --- a/lib/views/addeditprofileview.dart +++ b/lib/views/addeditprofileview.dart @@ -296,11 +296,13 @@ class _AddEditProfileViewState extends State { // Profile Editing if (ctrlrPass.value.text.isEmpty) { // Don't update password, only update name + 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); Navigator.of(context).pop(); } 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... + 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", diff --git a/lib/views/addeditservers.dart b/lib/views/addeditservers.dart index c551ea4d..2a1c8f46 100644 --- a/lib/views/addeditservers.dart +++ b/lib/views/addeditservers.dart @@ -53,7 +53,6 @@ class _AddEditServerViewState extends State { @override Widget build(BuildContext context) { - return Scaffold( appBar: AppBar( title: ctrlrOnion.text.isEmpty ? Text(AppLocalizations.of(context)!.addServerTitle) : Text(AppLocalizations.of(context)!.editServerTitle), @@ -82,232 +81,222 @@ class _AddEditServerViewState extends State { child: Form( key: _formKey, child: Container( - margin: EdgeInsets.fromLTRB(30, 0, 30, 10), - padding: EdgeInsets.fromLTRB(20, 0 , 20, 10), - child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ + margin: EdgeInsets.fromLTRB(30, 0, 30, 10), + padding: EdgeInsets.fromLTRB(20, 0, 20, 10), + child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ + // Onion + Visibility( + visible: serverInfoState.onion.isNotEmpty, + child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox( + height: 20, + ), + CwtchLabel(label: AppLocalizations.of(context)!.serverAddress), + SizedBox( + height: 20, + ), + SelectableText(serverInfoState.onion) + ])), - // Onion - Visibility( - visible: serverInfoState.onion.isNotEmpty, - child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - height: 20, - ), - CwtchLabel(label: AppLocalizations.of(context)!.serverAddress), - SizedBox( - height: 20, - ), - SelectableText( - serverInfoState.onion - ) - ])), + // Description + Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox( + height: 20, + ), + CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel), + Text(AppLocalizations.of(context)!.serverDescriptionDescription), + SizedBox( + height: 20, + ), + CwtchTextField( + controller: ctrlrDesc, + labelText: "Description", + autofocus: false, + ) + ]), - // Description - Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - height: 20, - ), - CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel), - Text(AppLocalizations.of(context)!.serverDescriptionDescription), - SizedBox( - height: 20, - ), - CwtchTextField( - controller: ctrlrDesc, - labelText: "Description", - autofocus: false, - ) - ]), + SizedBox( + height: 20, + ), - SizedBox( - height: 20, - ), + // Enabled + Visibility( + visible: serverInfoState.onion.isNotEmpty, + child: SwitchListTile( + title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor())), + subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription), + value: serverInfoState.running, + onChanged: (bool value) { + serverInfoState.setRunning(value); + if (value) { + Provider.of(context, listen: false).cwtch.LaunchServer(serverInfoState.onion); + } else { + Provider.of(context, listen: false).cwtch.StopServer(serverInfoState.onion); + } + }, + activeTrackColor: settings.theme.defaultButtonActiveColor(), + inactiveTrackColor: settings.theme.defaultButtonDisabledColor(), + secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor()), + )), - // Enabled - Visibility( - visible: serverInfoState.onion.isNotEmpty, - child: SwitchListTile( - title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor())), - subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription), - value: serverInfoState.running, - onChanged: (bool value) { - serverInfoState.setRunning(value); - if (value) { - Provider.of(context, listen: false).cwtch.LaunchServer(serverInfoState.onion); - } else { - Provider.of(context, listen: false).cwtch.StopServer(serverInfoState.onion); - } - }, - activeTrackColor: settings.theme.defaultButtonActiveColor(), - inactiveTrackColor: settings.theme.defaultButtonDisabledColor(), - secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor()), - )), + // Auto start + SwitchListTile( + title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor())), + subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription), + value: serverInfoState.autoStart, + onChanged: (bool value) { + serverInfoState.setAutostart(value); - // Auto start - SwitchListTile( - title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor())), - subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription), - value: serverInfoState.autoStart, - onChanged: (bool value) { - serverInfoState.setAutostart(value); + if (!serverInfoState.onion.isEmpty) { + Provider.of(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false"); + } + }, + activeTrackColor: settings.theme.defaultButtonActiveColor(), + inactiveTrackColor: settings.theme.defaultButtonDisabledColor(), + secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor()), + ), - if (! serverInfoState.onion.isEmpty) { - Provider.of(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false"); - } - }, - activeTrackColor: settings.theme.defaultButtonActiveColor(), - inactiveTrackColor: settings.theme.defaultButtonDisabledColor(), - secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor()), - ), + // ***** Password ***** + // use password toggle + Visibility( + visible: serverInfoState.onion.isEmpty, + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + SizedBox( + height: 20, + ), + Checkbox( + value: usePassword, + fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor()), + activeColor: settings.current().defaultButtonActiveColor(), + onChanged: _handleSwitchPassword, + ), + Text( + AppLocalizations.of(context)!.radioUsePassword, + style: TextStyle(color: settings.current().mainTextColor()), + ), + SizedBox( + height: 20, + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 24), + child: Text( + usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription, + textAlign: TextAlign.center, + )), + SizedBox( + height: 20, + ), + ])), - // ***** Password ***** + // current password + Visibility( + visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted, + child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel), + SizedBox( + height: 20, + ), + CwtchPasswordField( + controller: ctrlrOldPass, + autoFillHints: [AutofillHints.newPassword], + validator: (value) { + // Password field can be empty when just updating the profile, not on creation + if (serverInfoState.isEncrypted && serverInfoState.onion.isEmpty && value.isEmpty && usePassword) { + return AppLocalizations.of(context)!.passwordErrorEmpty; + } + if (Provider.of(context).deletedServerError == true) { + return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer; + } + return null; + }, + ), + SizedBox( + height: 20, + ), + ])), - // use password toggle - Visibility( - visible: serverInfoState.onion.isEmpty, - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox( - height: 20, - ), - Checkbox( - value: usePassword, - fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor()), - activeColor: settings.current().defaultButtonActiveColor(), - onChanged: _handleSwitchPassword, - ), - Text( - AppLocalizations.of(context)!.radioUsePassword, - style: TextStyle(color: settings.current().mainTextColor()), - ), - SizedBox( - height: 20, - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 24), - child: Text( - usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription, - textAlign: TextAlign.center, - )), - SizedBox( - height: 20, - ), - ])), + // new passwords 1 & 2 + Visibility( + // Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check + visible: serverInfoState.onion.isEmpty && usePassword, + child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ + CwtchLabel(label: AppLocalizations.of(context)!.newPassword), + SizedBox( + height: 20, + ), + CwtchPasswordField( + controller: ctrlrPass, + validator: (value) { + // Password field can be empty when just updating the profile, not on creation + if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) { + return AppLocalizations.of(context)!.passwordErrorEmpty; + } + if (value != ctrlrPass2.value.text) { + return AppLocalizations.of(context)!.passwordErrorMatch; + } + return null; + }, + ), + SizedBox( + height: 20, + ), + CwtchLabel(label: AppLocalizations.of(context)!.password2Label), + SizedBox( + height: 20, + ), + CwtchPasswordField( + controller: ctrlrPass2, + validator: (value) { + // Password field can be empty when just updating the profile, not on creation + if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) { + return AppLocalizations.of(context)!.passwordErrorEmpty; + } + if (value != ctrlrPass.value.text) { + return AppLocalizations.of(context)!.passwordErrorMatch; + } + return null; + }), + ]), + ), + SizedBox( + height: 20, + ), - // current password - Visibility( - visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted, - child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel), - SizedBox( - height: 20, - ), - CwtchPasswordField( - controller: ctrlrOldPass, - autoFillHints: [AutofillHints.newPassword], - validator: (value) { - // Password field can be empty when just updating the profile, not on creation - if (serverInfoState.isEncrypted && - serverInfoState.onion.isEmpty && - value.isEmpty && - usePassword) { - return AppLocalizations.of(context)!.passwordErrorEmpty; - } - if (Provider.of(context).deletedServerError == true) { - return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer; - } - return null; - }, - ), - SizedBox( - height: 20, - ), - ])), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: ElevatedButton( + onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed, + child: Text( + serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton, + textAlign: TextAlign.center, + ), + ), + ), + ], + ), + Visibility( + visible: serverInfoState.onion.isNotEmpty, + child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ + SizedBox( + height: 20, + ), + Tooltip( + message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer, + child: ElevatedButton.icon( + onPressed: () { + showAlertDialog(context); + }, + icon: Icon(Icons.delete_forever), + label: Text(AppLocalizations.of(context)!.deleteBtn), + )) + ])) - // new passwords 1 & 2 - Visibility( - // Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check - visible: serverInfoState.onion.isEmpty && usePassword, - child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - CwtchLabel(label: AppLocalizations.of(context)!.newPassword), - SizedBox( - height: 20, - ), - CwtchPasswordField( - controller: ctrlrPass, - validator: (value) { - // Password field can be empty when just updating the profile, not on creation - if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) { - return AppLocalizations.of(context)!.passwordErrorEmpty; - } - if (value != ctrlrPass2.value.text) { - return AppLocalizations.of(context)!.passwordErrorMatch; - } - return null; - }, - ), - SizedBox( - height: 20, - ), - CwtchLabel(label: AppLocalizations.of(context)!.password2Label), - SizedBox( - height: 20, - ), - CwtchPasswordField( - controller: ctrlrPass2, - validator: (value) { - // Password field can be empty when just updating the profile, not on creation - if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) { - return AppLocalizations.of(context)!.passwordErrorEmpty; - } - if (value != ctrlrPass.value.text) { - return AppLocalizations.of(context)!.passwordErrorMatch; - } - return null; - }), - ]), - ), - - SizedBox( - height: 20, - ), - - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: ElevatedButton( - onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed, - child: Text( - serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton, - textAlign: TextAlign.center, - ), - ), - ), - ], - ), - Visibility( - visible: serverInfoState.onion.isNotEmpty, - child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ - SizedBox( - height: 20, - ), - Tooltip( - message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer, - child: ElevatedButton.icon( - onPressed: () { - showAlertDialog(context); - }, - icon: Icon(Icons.delete_forever), - label: Text(AppLocalizations.of(context)!.deleteBtn), - )) - ])) - - // ***** END Password ***** - - ])))))); + // ***** END Password ***** + ])))))); }); }); } @@ -318,29 +307,20 @@ class _AddEditServerViewState extends State { // match (and are provided if the user has requested an encrypted profile). if (_formKey.currentState!.validate()) { if (usePassword) { - Provider - .of(context, listen: false) - .cwtch - .CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of(context, listen: false).autoStart); + Provider.of(context, listen: false).cwtch.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of(context, listen: false).autoStart); } else { - Provider - .of(context, listen: false) - .cwtch - .CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of(context, listen: false).autoStart); + Provider.of(context, listen: false).cwtch.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of(context, listen: false).autoStart); } Navigator.of(context).pop(); } } void _savePressed() { - var server = Provider.of(context, listen: false); - Provider.of(context, listen: false) - .cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text); + Provider.of(context, listen: false).cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text); server.setDescription(ctrlrDesc.text); - if (_formKey.currentState!.validate()) { // TODO support change password } @@ -358,16 +338,11 @@ class _AddEditServerViewState extends State { Widget continueButton = ElevatedButton( child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn), onPressed: () { - var onion = Provider - .of(context, listen: false) - .onion; - Provider - .of(context, listen: false) - .cwtch - .DeleteServer(onion, Provider.of(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword); + var onion = Provider.of(context, listen: false).onion; + Provider.of(context, listen: false).cwtch.DeleteServer(onion, Provider.of(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword); Future.delayed( const Duration(milliseconds: 500), - () { + () { if (globalErrorHandler.deletedServerSuccess) { final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteServerSuccess + ":" + onion)); ScaffoldMessenger.of(context).showSnackBar(snackBar); @@ -395,4 +370,4 @@ class _AddEditServerViewState extends State { }, ); } -} \ No newline at end of file +} diff --git a/lib/views/contactsview.dart b/lib/views/contactsview.dart index bb9c585c..990899f3 100644 --- a/lib/views/contactsview.dart +++ b/lib/views/contactsview.dart @@ -112,7 +112,6 @@ class _ContactsViewState extends State { Clipboard.setData(new ClipboardData(text: Provider.of(context, listen: false).onion)); })); - // TODO servers // Search contacts diff --git a/lib/views/globalsettingsview.dart b/lib/views/globalsettingsview.dart index d8cb7b04..6668ed52 100644 --- a/lib/views/globalsettingsview.dart +++ b/lib/views/globalsettingsview.dart @@ -191,9 +191,8 @@ class _GlobalSettingsViewState extends State { secondary: Icon(CwtchIcons.enable_groups, color: settings.current().mainTextColor()), ), Visibility( - visible: !Platform.isAndroid && !Platform.isIOS, - child: - SwitchListTile( + visible: !Platform.isAndroid && !Platform.isIOS, + child: SwitchListTile( title: Text(AppLocalizations.of(context)!.settingServers, style: TextStyle(color: settings.current().mainTextColor())), subtitle: Text(AppLocalizations.of(context)!.settingServersDescription), value: settings.isExperimentEnabled(ServerManagementExperiment), diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 79ae41e3..c2170a20 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -41,10 +41,10 @@ class _MessageViewState extends State { void initState() { scrollListener.itemPositions.addListener(() { if (scrollListener.itemPositions.value.length != 0 && - Provider.of(context, listen: false).unreadMessagesBelow == true && - scrollListener.itemPositions.value.any((element) => element.index == 0)) { - Provider.of(context, listen: false).initialScrollIndex = 0; - Provider.of(context, listen: false).unreadMessagesBelow = false; + Provider.of(context, listen: false).unreadMessagesBelow == true && + scrollListener.itemPositions.value.any((element) => element.index == 0)) { + Provider.of(context, listen: false).initialScrollIndex = 0; + Provider.of(context, listen: false).unreadMessagesBelow = false; } }); super.initState(); diff --git a/lib/views/profilemgrview.dart b/lib/views/profilemgrview.dart index de7f65b5..e4a6eba0 100644 --- a/lib/views/profilemgrview.dart +++ b/lib/views/profilemgrview.dart @@ -57,9 +57,9 @@ class _ProfileMgrViewState extends State { SizedBox( width: 10, ), - Expanded(child: Text(MediaQuery.of(context).size.width > 600 ? - AppLocalizations.of(context)!.titleManageProfiles : AppLocalizations.of(context)!.titleManageProfilesShort, - style: TextStyle(color: settings.current().mainTextColor()))) + Expanded( + child: Text(MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.titleManageProfiles : AppLocalizations.of(context)!.titleManageProfilesShort, + style: TextStyle(color: settings.current().mainTextColor()))) ]), actions: getActions(), ), @@ -238,7 +238,7 @@ class _ProfileMgrViewState extends State { if (tiles.isEmpty) { return Center( child: Text( - AppLocalizations.of(context)!.unlockProfileTip, + AppLocalizations.of(context)!.unlockProfileTip, textAlign: TextAlign.center, )); } diff --git a/lib/views/serversview.dart b/lib/views/serversview.dart index 84c7e532..148d1daf 100644 --- a/lib/views/serversview.dart +++ b/lib/views/serversview.dart @@ -30,44 +30,45 @@ class _ServersView extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text( MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.serversManagerTitleLong : AppLocalizations.of(context)!.serversManagerTitleShort), - actions: getActions(), - ), - floatingActionButton: FloatingActionButton( - onPressed: _pushAddServer, - tooltip: AppLocalizations.of(context)!.addServerTooltip, - child: Icon( - Icons.add, - semanticLabel: AppLocalizations.of(context)!.addServerTooltip, + appBar: AppBar( + title: Text(MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.serversManagerTitleLong : AppLocalizations.of(context)!.serversManagerTitleShort), + actions: getActions(), ), - ), - body: Consumer( - builder: (context, svrs, child) { - final tiles = svrs.servers.map((ServerInfoState server) { - return ChangeNotifierProvider.value( - value: server, - builder: (context, child) => RepaintBoundary(child: ServerRow()), + floatingActionButton: FloatingActionButton( + onPressed: _pushAddServer, + tooltip: AppLocalizations.of(context)!.addServerTooltip, + child: Icon( + Icons.add, + semanticLabel: AppLocalizations.of(context)!.addServerTooltip, + ), + ), + body: Consumer( + builder: (context, svrs, child) { + final tiles = svrs.servers.map( + (ServerInfoState server) { + return ChangeNotifierProvider.value( + value: server, + builder: (context, child) => RepaintBoundary(child: ServerRow()), + ); + }, ); + + final divided = ListTile.divideTiles( + context: context, + tiles: tiles, + ).toList(); + + if (tiles.isEmpty) { + return Center( + child: Text( + AppLocalizations.of(context)!.unlockServerTip, + textAlign: TextAlign.center, + )); + } + + return ListView(children: divided); }, - ); - - final divided = ListTile.divideTiles( - context: context, - tiles: tiles, - ).toList(); - - if (tiles.isEmpty) { - return Center( - child: Text( - AppLocalizations.of(context)!.unlockServerTip, - textAlign: TextAlign.center, - )); - } - - return ListView(children: divided); - }, - )); + )); } List getActions() { @@ -93,41 +94,41 @@ class _ServersView extends State { padding: MediaQuery.of(context).viewInsets, child: RepaintBoundary( child: Container( - height: 200, // bespoke value courtesy of the [TextField] docs - child: Center( - child: Padding( - padding: EdgeInsets.all(10.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text(AppLocalizations.of(context)!.enterServerPassword), - SizedBox( - height: 20, - ), - CwtchPasswordField( - autofocus: true, - controller: ctrlrPassword, - action: unlock, - validator: (value) {}, - ), - SizedBox( - height: 20, - ), - Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - Spacer(), - Expanded( - child: ElevatedButton( - child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock), - onPressed: () { - unlock(ctrlrPassword.value.text); - }, - )), - Spacer() - ]), - ], - ))), - ))); + height: 200, // bespoke value courtesy of the [TextField] docs + child: Center( + child: Padding( + padding: EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.of(context)!.enterServerPassword), + SizedBox( + height: 20, + ), + CwtchPasswordField( + autofocus: true, + controller: ctrlrPassword, + action: unlock, + validator: (value) {}, + ), + SizedBox( + height: 20, + ), + Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + Spacer(), + Expanded( + child: ElevatedButton( + child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock), + onPressed: () { + unlock(ctrlrPassword.value.text); + }, + )), + Spacer() + ]), + ], + ))), + ))); }); } @@ -141,9 +142,11 @@ class _ServersView extends State { Navigator.of(context).push(MaterialPageRoute( builder: (BuildContext context) { return MultiProvider( - providers: [ChangeNotifierProvider( - create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true), - )], + providers: [ + ChangeNotifierProvider( + create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true), + ) + ], child: AddEditServerView(), ); }, diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index e3aea951..bc75a6d9 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -89,15 +89,15 @@ class FileBubbleState extends State { } else if (flagStarted) { // in this case, the download was done in a previous application launch, // so we probably have to request an info lookup - if (!Provider.of(context).downloadInterrupted(widget.fileKey()) ) { + if (!Provider.of(context).downloadInterrupted(widget.fileKey())) { wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F'); Provider.of(context, listen: false).cwtch.CheckDownloadStatus(Provider.of(context, listen: false).onion, widget.fileKey()); } 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 = Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'), + ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) + ]); } } else { wdgDecorations = Center( @@ -156,11 +156,7 @@ class FileBubbleState extends State { Provider.of(context, listen: false).flags |= 0x02; ContactInfoState? contact = Provider.of(context).contactList.findContact(Provider.of(context).senderHandle); if (contact != null) { - Provider - .of(context, listen: false) - .cwtch - .CreateDownloadableFile( - profileOnion, contact.identifier, widget.nameSuggestion, widget.fileKey()); + Provider.of(context, listen: false).cwtch.CreateDownloadableFile(profileOnion, contact.identifier, widget.nameSuggestion, widget.fileKey()); } } else { try { @@ -176,11 +172,7 @@ class FileBubbleState extends State { Provider.of(context, listen: false).flags |= 0x02; ContactInfoState? contact = Provider.of(context).contactList.findContact(Provider.of(context).senderHandle); if (contact != null) { - Provider - .of(context, listen: false) - .cwtch - .DownloadFile(profileOnion, contact.identifier, file.path, manifestPath, - widget.fileKey()); + Provider.of(context, listen: false).cwtch.DownloadFile(profileOnion, contact.identifier, file.path, manifestPath, widget.fileKey()); } } } catch (e) { diff --git a/lib/widgets/messagebubble.dart b/lib/widgets/messagebubble.dart index 55e67bc2..ae68962b 100644 --- a/lib/widgets/messagebubble.dart +++ b/lib/widgets/messagebubble.dart @@ -52,7 +52,7 @@ class MessageBubbleState extends State { var wdgMessage; - if (!showClickableLinks) { + if (!showClickableLinks) { wdgMessage = SelectableText( widget.content + '\u202F', //key: Key(myKey), @@ -134,8 +134,7 @@ class MessageBubbleState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - "Opening this link will launch an application outside of Cwtch and may reveal metadata or otherwise compromise the security of Cwtch. Only open links from people you trust. Are you sure you want to continue?" - ), + "Opening this link will launch an application outside of Cwtch and may reveal metadata or otherwise compromise the security of Cwtch. Only open links from people you trust. Are you sure you want to continue?"), Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), @@ -170,6 +169,6 @@ class MessageBubbleState extends State { ], )), )); - }); + }); } } diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index 0e9420e0..6c718a26 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -199,7 +199,7 @@ class MessageRowState extends State with SingleTickerProviderStateMi ))))); var mark = Provider.of(context).newMarker; if (mark > 0 && mark == Provider.of(context).totalMessages - Provider.of(context).messageIndex) { - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Align(alignment:Alignment.center ,child:_bubbleNew()), mr]); + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Align(alignment: Alignment.center, child: _bubbleNew()), mr]); } else { return mr; } @@ -209,9 +209,7 @@ class MessageRowState extends State with SingleTickerProviderStateMi return Container( decoration: BoxDecoration( color: Provider.of(context).theme.messageFromMeBackgroundColor(), - border: Border.all( - color: Provider.of(context).theme.messageFromMeBackgroundColor(), - width: 1), + border: Border.all(color: Provider.of(context).theme.messageFromMeBackgroundColor(), width: 1), borderRadius: BorderRadius.only( topLeft: Radius.circular(8), topRight: Radius.circular(8), @@ -219,9 +217,7 @@ class MessageRowState extends State with SingleTickerProviderStateMi bottomRight: Radius.circular(8), ), ), - child: Padding( - padding: EdgeInsets.all(9.0), - child: Text(AppLocalizations.of(context)!.newMessagesLabel))); + child: Padding(padding: EdgeInsets.all(9.0), child: Text(AppLocalizations.of(context)!.newMessagesLabel))); } void _runAnimation(Offset pixelsPerSecond, Size size) { diff --git a/lib/widgets/serverrow.dart b/lib/widgets/serverrow.dart index 574d7c7e..9c12f477 100644 --- a/lib/widgets/serverrow.dart +++ b/lib/widgets/serverrow.dart @@ -21,40 +21,38 @@ class _ServerRowState extends State { @override Widget build(BuildContext context) { var server = Provider.of(context); - return Card(clipBehavior: Clip.antiAlias, + return Card( + clipBehavior: Clip.antiAlias, margin: EdgeInsets.all(0.0), child: InkWell( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ + child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.all(6.0), //border size child: Icon(CwtchIcons.dns_24px, - color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor(), - size: 64) - - ), + color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor(), size: 64)), Expanded( child: Column( - children: [ - Text( - server.description, - semanticsLabel: server.description, - style: Provider.of(context).biggerFont.apply(color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), + children: [ + Text( + server.description, + semanticsLabel: server.description, + style: Provider.of(context) + .biggerFont + .apply(color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + Visibility( + visible: !Provider.of(context).streamerMode, + child: ExcludeSemantics( + child: Text( + server.onion, softWrap: true, overflow: TextOverflow.ellipsis, - ), - Visibility( - visible: !Provider.of(context).streamerMode, - child: ExcludeSemantics( - child: Text( - server.onion, - softWrap: true, - overflow: TextOverflow.ellipsis, - style: TextStyle(color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), - ))) - ], - )), + style: TextStyle(color: server.running ? Provider.of(context).theme.portraitOnlineBorderColor() : Provider.of(context).theme.portraitOfflineBorderColor()), + ))) + ], + )), // Copy server button IconButton( @@ -75,23 +73,23 @@ class _ServerRowState extends State { _pushEditServer(server); }, ) - - ]))); } - void _pushEditServer(ServerInfoState server ) { + void _pushEditServer(ServerInfoState server) { Provider.of(context).reset(); Navigator.of(context).push(MaterialPageRoute( settings: RouteSettings(name: "serveraddedit"), builder: (BuildContext context) { return MultiProvider( - providers: [ChangeNotifierProvider( - create: (_) => server, - )], + providers: [ + ChangeNotifierProvider( + create: (_) => server, + ) + ], child: AddEditServerView(), ); }, )); } -} \ No newline at end of file +}