Delete Profile / Leave Conversation + Android Fixes

This commit is contained in:
Sarah Jamie Lewis 2021-06-14 17:28:45 -07:00
parent 16f9928275
commit 2a3945639d
12 changed files with 214 additions and 64 deletions

View File

@ -203,11 +203,20 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
val groupName = (a.get("groupname") as? String) ?: ""; val groupName = (a.get("groupname") as? String) ?: "";
Cwtch.createGroup(profile, server, groupName); Cwtch.createGroup(profile, server, groupName);
} }
"DeleteProfile" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val groupHandle = (a.get("pass") as? String) ?: "";
Cwtch.deleteProfile(profile, groupHandle);
}
"LeaveConversation" -> {
val profile = (a.get("ProfileOnion") as? String) ?: "";
val contactHandle = (a.get("contactHandle") as? String) ?: "";
Cwtch.leaveConversation(profile, contactHandle);
}
"LeaveGroup" -> { "LeaveGroup" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""; val profile = (a.get("ProfileOnion") as? String) ?: "";
val groupHandle = (a.get("groupHandle") as? String) ?: ""; val groupHandle = (a.get("groupHandle") as? String) ?: "";
Log.i("FlwtchWorker.kt", "LeaveGroup: need to recompile aar and uncomment this line")//todo Cwtch.leaveGroup(profile, groupHandle);
//Cwtch.leaveGroup(profile, groupHandle);
} }
"RejectInvite" -> { "RejectInvite" -> {
val profile = (a.get("ProfileOnion") as? String) ?: ""; val profile = (a.get("ProfileOnion") as? String) ?: "";

View File

@ -1,3 +1,5 @@
import 'package:flutter/src/services/text_input.dart';
abstract class Cwtch { abstract class Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Future<dynamic> Start(); Future<dynamic> Start();
@ -10,6 +12,8 @@ abstract class Cwtch {
void CreateProfile(String nick, String pass); void CreateProfile(String nick, String pass);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void LoadProfiles(String pass); void LoadProfiles(String pass);
// ignore: non_constant_identifier_names
void DeleteProfile(String onion, String pass);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void ResetTor(); void ResetTor();
@ -48,6 +52,9 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SendInvitation(String profile, String handle, String target); void SendInvitation(String profile, String handle, String target);
// ignore: non_constant_identifier_names
void LeaveConversation(String profile, String handle);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void CreateGroup(String profile, String server, String groupName); void CreateGroup(String profile, String server, String groupName);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names

View File

@ -64,6 +64,14 @@ class CwtchNotifier {
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now()); profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], DateTime.now());
} }
break; break;
case "PeerDeleted":
profileCN.delete(data["Identity"]);
// todo standarize
error.handleUpdate("deleteprofile.success");
break;
case "DeleteContact":
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["RemotePeer"]);
break;
case "DeleteGroup": case "DeleteGroup":
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["GroupID"]); profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["GroupID"]);
break; break;
@ -152,7 +160,10 @@ class CwtchNotifier {
break; break;
case "AppError": case "AppError":
print("New App Error: $data"); print("New App Error: $data");
if (data["Data"] != null) { // special case for delete error (todo: standardize cwtch errors)
if (data["Error"] == "Password did not match") {
error.handleUpdate("deleteprofile.error");
} else if (data["Data"] != null) {
error.handleUpdate(data["Data"]); error.handleUpdate(data["Data"]);
} }
break; break;

View File

@ -4,6 +4,7 @@ import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:io' show Platform; import 'dart:io' show Platform;
import 'package:cwtch/cwtch/cwtchNotifier.dart'; import 'package:cwtch/cwtch/cwtchNotifier.dart';
import 'package:flutter/src/services/text_input.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:ffi/ffi.dart'; import 'package:ffi/ffi.dart';
@ -385,15 +386,26 @@ class CwtchFfi implements Cwtch {
CreateGroup(u1, u1.length, u2, u2.length, u3, u3.length); CreateGroup(u1, u1.length, u2, u2.length, u3, u3.length);
} }
@override
// ignore: non_constant_identifier_names
void LeaveConversation(String profileOnion, String handle) {
var leaveConversation = library.lookup<NativeFunction<string_string_to_void_function>>("c_LeaveConversation");
// ignore: non_constant_identifier_names
final LeaveConversation = leaveConversation.asFunction<VoidFromStringStringFn>();
final u1 = profileOnion.toNativeUtf8();
final u2 = handle.toNativeUtf8();
LeaveConversation(u1, u1.length, u2, u2.length);
}
@override @override
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void LeaveGroup(String profileOnion, String groupHandle) { void LeaveGroup(String profileOnion, String groupHandle) {
var leaveGroup = library.lookup<NativeFunction<string_string_to_void_function>>("c_LeaveGroup"); var leaveGroup = library.lookup<NativeFunction<string_string_to_void_function>>("c_LeaveGroup");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final RejectInvite = leaveGroup.asFunction<VoidFromStringStringFn>(); final LeaveGroup = leaveGroup.asFunction<VoidFromStringStringFn>();
final u1 = profileOnion.toNativeUtf8(); final u1 = profileOnion.toNativeUtf8();
final u2 = groupHandle.toNativeUtf8(); final u2 = groupHandle.toNativeUtf8();
RejectInvite(u1, u1.length, u2, u2.length); LeaveGroup(u1, u1.length, u2, u2.length);
} }
@override @override
@ -405,4 +417,15 @@ class CwtchFfi implements Cwtch {
final utf8handle = handle.toNativeUtf8(); final utf8handle = handle.toNativeUtf8();
updateMessageFlags(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index, flags); updateMessageFlags(utf8profile, utf8profile.length, utf8handle, utf8handle.length, index, flags);
} }
@override
// ignore: non_constant_identifier_names
void DeleteProfile(String onion, String currentPassword) {
var deleteprofile = library.lookup<NativeFunction<string_string_to_void_function>>("c_DeleteProfile");
// ignore: non_constant_identifier_names
final DeleteProfile = deleteprofile.asFunction<VoidFromStringStringFn>();
final u1 = onion.toNativeUtf8();
final u2 = currentPassword.toNativeUtf8();
DeleteProfile(u1, u1.length, u2, u2.length);
}
} }

View File

@ -81,6 +81,11 @@ class CwtchGomobile implements Cwtch {
cwtchPlatform.invokeMethod("LoadProfiles", {"pass": pass}); cwtchPlatform.invokeMethod("LoadProfiles", {"pass": pass});
} }
// ignore: non_constant_identifier_names
void DeleteProfile(String onion, String pass) {
cwtchPlatform.invokeMethod("DeleteProfile", {"onion": onion, "pass": pass});
}
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Future<dynamic> ACNEvents() { Future<dynamic> ACNEvents() {
return cwtchPlatform.invokeMethod("ACNEvents"); return cwtchPlatform.invokeMethod("ACNEvents");
@ -179,7 +184,7 @@ class CwtchGomobile implements Cwtch {
@override @override
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void RejectInvite(String profileOnion, String groupHandle) { void RejectInvite(String profileOnion, String groupHandle) {
cwtchPlatform.invokeMethod("RejectInvite", {"ProfileOnion": profileOnion, "handle": groupHandle}); cwtchPlatform.invokeMethod("RejectInvite", {"ProfileOnion": profileOnion, "groupHandle": groupHandle});
} }
@override @override
@ -190,7 +195,13 @@ class CwtchGomobile implements Cwtch {
@override @override
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void LeaveGroup(String profileOnion, String groupHandle) { void LeaveGroup(String profileOnion, String groupHandle) {
cwtchPlatform.invokeMethod("LeaveGroup", {"ProfileOnion": profileOnion, "handle": groupHandle}); cwtchPlatform.invokeMethod("LeaveGroup", {"ProfileOnion": profileOnion, "groupHandle": groupHandle});
}
@override
// ignore: non_constant_identifier_names
void LeaveConversation(String profileOnion, String contactHandle) {
cwtchPlatform.invokeMethod("LeaveConversation", {"ProfileOnion": profileOnion, "contactHandle": contactHandle});
} }
@override @override

View File

@ -17,6 +17,10 @@ class ErrorHandler extends ChangeNotifier {
bool importBundleError = false; bool importBundleError = false;
bool importBundleSuccess = false; bool importBundleSuccess = false;
static const String deleteProfileErrorPrefix = "deleteprofile";
bool deleteProfileError = false;
bool deleteProfileSuccess = false;
/// Called by the event bus. /// Called by the event bus.
handleUpdate(String error) { handleUpdate(String error) {
var parts = error.split("."); var parts = error.split(".");
@ -30,6 +34,9 @@ class ErrorHandler extends ChangeNotifier {
case importBundleErrorPrefix: case importBundleErrorPrefix:
handleImportBundleError(errorType); handleImportBundleError(errorType);
break; break;
case deleteProfileErrorPrefix:
handleDeleteProfileError(errorType);
break;
} }
notifyListeners(); notifyListeners();
@ -69,4 +76,19 @@ class ErrorHandler extends ChangeNotifier {
break; break;
} }
} }
handleDeleteProfileError(String errorType) {
// Reset add contact errors
deleteProfileError = false;
deleteProfileSuccess = false;
switch (errorType) {
case successErrorType:
deleteProfileSuccess = true;
break;
default:
deleteProfileError = true;
break;
}
}
} }

View File

@ -54,6 +54,11 @@ class ProfileListState extends ChangeNotifier {
int idx = _profiles.indexWhere((element) => element.onion == onion); int idx = _profiles.indexWhere((element) => element.onion == onion);
return idx >= 0 ? _profiles[idx] : null; return idx >= 0 ? _profiles[idx] : null;
} }
void delete(String onion) {
_profiles.removeWhere((element) => element.onion == onion);
notifyListeners();
}
} }
class ContactListState extends ChangeNotifier { class ContactListState extends ChangeNotifier {

View File

@ -1,4 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -12,6 +13,7 @@ import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../cwtch_icons_icons.dart'; import '../cwtch_icons_icons.dart';
import '../errorHandler.dart';
import '../main.dart'; import '../main.dart';
import '../opaque.dart'; import '../opaque.dart';
import '../settings.dart'; import '../settings.dart';
@ -166,6 +168,9 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) { if (Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty && value.isEmpty && usePassword) {
return AppLocalizations.of(context)!.passwordErrorEmpty; return AppLocalizations.of(context)!.passwordErrorEmpty;
} }
if (Provider.of<ErrorHandler>(context).deleteProfileError == true) {
return AppLocalizations.of(context)!.enterCurrentPasswordForDelete;
}
return null; return null;
}, },
), ),
@ -237,11 +242,9 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
Tooltip( Tooltip(
message: AppLocalizations.of(context)!.enterCurrentPasswordForDelete, message: AppLocalizations.of(context)!.enterCurrentPasswordForDelete,
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: checkCurrentPassword() onPressed: () {
? null showAlertDialog(context);
: () { },
showAlertDialog(context);
},
style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()), style: ElevatedButton.styleFrom(primary: theme.current().defaultButtonColor()),
icon: Icon(Icons.delete_forever), icon: Icon(Icons.delete_forever),
label: Text(AppLocalizations.of(context)!.deleteBtn), label: Text(AppLocalizations.of(context)!.deleteBtn),
@ -307,52 +310,49 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
} }
} }
// TODO Stub - wire this into a libCwtch call. showAlertDialog(BuildContext context) {
bool checkCurrentPassword() { // set up the buttons
return ctrlrOldPass.value.text.isEmpty; Widget cancelButton = TextButton(
child: Text("Cancel"),
onPressed: () {
Navigator.of(context).pop(); // dismiss dialog
},
);
Widget continueButton = TextButton(
child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
onPressed: () {
var onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteProfile(onion, ctrlrOldPass.value.text);
Future.delayed(
const Duration(milliseconds: 500),
() {
if (globalErrorHandler.deleteProfileSuccess) {
final snackBar = SnackBar(content: Text("Successfully deleted profile:" + onion));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.of(context).popUntil((route) => route.isFirst); // dismiss dialog
} else {
Navigator.of(context).pop();
}
},
);
});
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
actions: [
cancelButton,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
} }
} }
showAlertDialog(BuildContext context) {
// set up the buttons
Widget cancelButton = TextButton(
child: Text("Cancel"),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
foregroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(Opaque.current().defaultButtonActiveColor()),
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
onPressed: () {
Navigator.of(context).pop(); // dismiss dialog
},
);
Widget continueButton = TextButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
foregroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonTextColor()),
overlayColor: MaterialStateProperty.all(Opaque.current().defaultButtonActiveColor()),
padding: MaterialStateProperty.all(EdgeInsets.all(20))),
child: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
onPressed: () {
// TODO Actually Delete the Peer
Navigator.of(context).pop(); // dismiss dialog
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text(AppLocalizations.of(context)!.deleteProfileConfirmBtn),
actions: [
cancelButton,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}

View File

@ -189,10 +189,12 @@ class _MessageViewState extends State<MessageView> {
), ),
ElevatedButton( ElevatedButton(
child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn), child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn),
onPressed: () { onPressed: this.selectedContact == ""
this._sendInvitation(); ? null
Navigator.pop(bcontext); : () {
}, this._sendInvitation();
Navigator.pop(bcontext);
},
), ),
], ],
)), )),

View File

@ -187,6 +187,22 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
); );
}).toList())), }).toList())),
]), ]),
Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [
SizedBox(
height: 20,
),
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
Tooltip(
message: AppLocalizations.of(context)!.leaveGroup,
child: ElevatedButton.icon(
onPressed: () {
showAlertDialog(context);
},
icon: Icon(CwtchIcons.leave_chat),
label: Text(AppLocalizations.of(context)!.leaveGroup),
))
])
]),
]))))); ])))));
}); });
}); });
@ -197,4 +213,44 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedClipboardNotification)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedClipboardNotification));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }
showAlertDialog(BuildContext context) {
// set up the buttons
Widget cancelButton = TextButton(
child: Text("Cancel"),
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
onPressed: () {
Navigator.of(context).pop(); // dismiss dialog
},
);
Widget continueButton = TextButton(
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
child: Text(AppLocalizations.of(context)!.yesLeave),
onPressed: () {
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
var handle = Provider.of<ContactInfoState>(context, listen: false).onion;
Provider.of<FlwtchState>(context, listen: false).cwtch.LeaveConversation(profileOnion, handle);
Future.delayed(Duration(milliseconds: 500), () {
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
});
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text(AppLocalizations.of(context)!.reallyLeaveThisGroupPrompt),
actions: [
cancelButton,
continueButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
} }

View File

@ -23,10 +23,13 @@ class InvitationBubble extends StatefulWidget {
class InvitationBubbleState extends State<InvitationBubble> { class InvitationBubbleState extends State<InvitationBubble> {
bool rejected = false; bool rejected = false;
bool isAccepted = false; bool isAccepted = false;
FocusNode _focus = FocusNode();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (Provider.of<MessageState>(context).malformed) {
return MalformedBubble();
}
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion; var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
var isGroup = Provider.of<MessageState>(context).overlay == 101; var isGroup = Provider.of<MessageState>(context).overlay == 101;
isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null; isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null;

View File

@ -33,6 +33,7 @@ class _CwtchTextFieldState extends State<CwtchPasswordField> {
controller: widget.controller, controller: widget.controller,
validator: widget.validator, validator: widget.validator,
obscureText: obscureText, obscureText: obscureText,
autovalidateMode: AutovalidateMode.always,
onFieldSubmitted: widget.action, onFieldSubmitted: widget.action,
textInputAction: TextInputAction.unspecified, textInputAction: TextInputAction.unspecified,
enableSuggestions: false, enableSuggestions: false,