Bare bones groups support
continuous-integration/drone/pr Build is failing Details

This commit is contained in:
Sarah Jamie Lewis 2021-04-22 14:15:27 -07:00
parent 484f8a4b77
commit a5a8f59841
11 changed files with 132 additions and 6 deletions

View File

@ -160,6 +160,11 @@ class MainActivity: FlutterActivity() {
"ResetTor" -> { "ResetTor" -> {
Cwtch.resetTor(); Cwtch.resetTor();
} }
"ImportBundle" -> {
val profile = (call.argument("ProfileOnion") as? String) ?: "";
val bundle = (call.argument("bundle") as? String) ?: "";
Cwtch.blockContact(profile, bundle);
}
else -> result.notImplemented() else -> result.notImplemented()
} }
} }

View File

@ -42,5 +42,8 @@ abstract class Cwtch {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SendMessage(String profile, String handle, String message); void SendMessage(String profile, String handle, String message);
// ignore: non_constant_identifier_names
void ImportBundle(String profile, String bundle);
void dispose(); void dispose();
} }

View File

@ -41,6 +41,7 @@ class CwtchNotifier {
numMessages: int.parse(data["numMessages"]), numMessages: int.parse(data["numMessages"]),
numUnread: int.parse(data["unread"]), numUnread: int.parse(data["unread"]),
isGroup: data["isGroup"], isGroup: data["isGroup"],
server: data["groupServer"],
lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet
)); ));
break; break;
@ -61,6 +62,12 @@ class CwtchNotifier {
profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]).totalMessages++; profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]).totalMessages++;
profileCN.getProfile(data["ProfileOnion"]).contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now()); profileCN.getProfile(data["ProfileOnion"]).contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
break; break;
case "NewMessageFromGroup":
profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["GroupID"]).unreadMessages++;
profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["GroupID"]).totalMessages++;
// TODO exception is called in the case of groups.
profileCN.getProfile(data["ProfileOnion"]).contactList.updateLastMessageTime(data["RemotePeer"], DateTime.now());
break;
case "AppError": case "AppError":
print("New App Error: $data"); print("New App Error: $data");
error.handleUpdate(data["Data"]); error.handleUpdate(data["Data"]);
@ -86,6 +93,20 @@ class CwtchNotifier {
case "UpdateServerInfo": case "UpdateServerInfo":
profileCN.getProfile(data["ProfileOnion"]).replaceServers(data["ServerList"]); profileCN.getProfile(data["ProfileOnion"]).replaceServers(data["ServerList"]);
break; break;
case "NewGroupInvite":
print("new group invite: $data");
// TODO Add Group Dynamically
//profileCN.getProfile(data["ProfileOnion"]).contactList.add(ContactInfoState(isGroup: true))
break;
case "ServerStateChange":
print("server state change: $data");
profileCN.getProfile(data["ProfileOnion"]).contactList.contacts.forEach((contact) {
if (contact.isGroup && contact.server == data["GroupServer"]) {
print("server state change: $data " + contact.server);
contact.status = data["ConnectionState"];
}
});
break;
default: default:
print("unhandled event: $type"); print("unhandled event: $type");
} }

View File

@ -299,9 +299,21 @@ class CwtchFfi implements Cwtch {
} }
@override @override
// ignore: non_constant_identifier_names
void ResetTor() { void ResetTor() {
var resetTor = library.lookup<NativeFunction<Void Function()>>("c_ResetTor"); var resetTor = library.lookup<NativeFunction<Void Function()>>("c_ResetTor");
final ResetTor = resetTor.asFunction<void Function()>(); final ResetTor = resetTor.asFunction<void Function()>();
ResetTor(); ResetTor();
} }
@override
// ignore: non_constant_identifier_names
void ImportBundle(String profileOnion, String bundle) {
var importBundle = library.lookup<NativeFunction<string_string_to_void_function>>("c_ImportBundle");
// ignore: non_constant_identifier_names
final ImportBundle = importBundle.asFunction<VoidFromStringStringFn>();
final u1 = profileOnion.toNativeUtf8();
final u2 = bundle.toNativeUtf8();
ImportBundle(u1, u1.length, u2, u2.length);
}
} }

View File

@ -148,4 +148,10 @@ class CwtchGomobile implements Cwtch {
void ResetTor() { void ResetTor() {
cwtchPlatform.invokeMethod("ResetTor", {}); cwtchPlatform.invokeMethod("ResetTor", {});
} }
@override
// ignore: non_constant_identifier_names
void ImportBundle(String profileOnion, String bundle) {
cwtchPlatform.invokeMethod("ImportBundle", {"ProfileOnion": profileOnion, "bundle": bundle});
}
} }

View File

@ -12,6 +12,11 @@ class ErrorHandler extends ChangeNotifier {
bool contactAlreadyExistsError = false; bool contactAlreadyExistsError = false;
bool explicitAddContactSuccess = false; bool explicitAddContactSuccess = false;
// Import Bundle Specific Errors
static const String importBundleErrorPrefix = "importBundle";
bool importBundleError = false;
bool importBundleSuccess = 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(".");
@ -22,6 +27,8 @@ class ErrorHandler extends ChangeNotifier {
case addContactErrorPrefix: case addContactErrorPrefix:
handleAddContactError(errorType); handleAddContactError(errorType);
break; break;
case importBundleErrorPrefix:
handleImportBundleError(errorType);
} }
notifyListeners(); notifyListeners();
@ -45,4 +52,19 @@ class ErrorHandler extends ChangeNotifier {
break; break;
} }
} }
handleImportBundleError(String errorType) {
// Reset add contact errors
importBundleError = false;
importBundleSuccess = false;
switch (errorType) {
case successErrorType:
importBundleSuccess = true;
break;
default:
importBundleError = true;
break;
}
}
} }

View File

@ -149,6 +149,8 @@ class ProfileInfoState extends ChangeNotifier {
savePeerHistory: contact["saveConversationHistory"], savePeerHistory: contact["saveConversationHistory"],
numMessages: contact["numMessages"], numMessages: contact["numMessages"],
numUnread: contact["numUnread"], numUnread: contact["numUnread"],
isGroup: contact["isGroup"],
server: contact["groupServer"],
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"]))); lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])));
})); }));
@ -212,7 +214,7 @@ class ContactInfoState extends ChangeNotifier {
final String profileOnion; final String profileOnion;
final String onion; final String onion;
String _nickname; String _nickname;
bool _isGroup;
bool _isInvitation; bool _isInvitation;
bool _isBlocked; bool _isBlocked;
String _status; String _status;
@ -222,6 +224,10 @@ class ContactInfoState extends ChangeNotifier {
int _totalMessages = 0; int _totalMessages = 0;
DateTime _lastMessageTime; DateTime _lastMessageTime;
// todo: a nicer way to mdoel contats, groups and other "entities"
bool _isGroup;
String _server;
ContactInfoState({ ContactInfoState({
this.profileOnion, this.profileOnion,
this.onion, this.onion,
@ -235,6 +241,7 @@ class ContactInfoState extends ChangeNotifier {
numMessages = 0, numMessages = 0,
numUnread = 0, numUnread = 0,
lastMessageTime = null, lastMessageTime = null,
server = "",
}) { }) {
this._nickname = nickname; this._nickname = nickname;
this._isGroup = isGroup; this._isGroup = isGroup;
@ -246,6 +253,7 @@ class ContactInfoState extends ChangeNotifier {
this._unreadMessages = numUnread; this._unreadMessages = numUnread;
this._savePeerHistory = savePeerHistory; this._savePeerHistory = savePeerHistory;
this._lastMessageTime = lastMessageTime; this._lastMessageTime = lastMessageTime;
this._server = server;
} }
get nickname => this._nickname; get nickname => this._nickname;
@ -308,6 +316,17 @@ class ContactInfoState extends ChangeNotifier {
this._lastMessageTime = newVal; this._lastMessageTime = newVal;
notifyListeners(); notifyListeners();
} }
// we only allow callers to fetch the server
get server => this._server;
bool isOnline() {
if (this.isGroup) {
return this.status == "Synced";
} else {
return this.status == "Authenticated";
}
}
} }
class MessageState extends ChangeNotifier { class MessageState extends ChangeNotifier {

View File

@ -26,6 +26,7 @@ class AddContactView extends StatefulWidget {
class _AddContactViewState extends State<AddContactView> { class _AddContactViewState extends State<AddContactView> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final _createGroupFormKey = GlobalKey<FormState>(); final _createGroupFormKey = GlobalKey<FormState>();
final _joinGroupFormKey = GlobalKey<FormState>();
final ctrlrOnion = TextEditingController(text: ""); final ctrlrOnion = TextEditingController(text: "");
final ctrlrContact = TextEditingController(text: ""); final ctrlrContact = TextEditingController(text: "");
final ctrlrGroupName = TextEditingController(text: ""); final ctrlrGroupName = TextEditingController(text: "");
@ -211,7 +212,42 @@ class _AddContactViewState extends State<AddContactView> {
/// TODO Join Group Pane /// TODO Join Group Pane
Widget joinGroupTab() { Widget joinGroupTab() {
return Icon(Icons.group); return Container(
margin: EdgeInsets.all(30),
padding: EdgeInsets.all(20),
child: Form(
autovalidateMode: AutovalidateMode.always,
key: _joinGroupFormKey,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context).joinGroupTab),
SizedBox(
height: 20,
),
CwtchTextField(
controller: ctrlrContact,
validator: (value) {
if (value == "") {
return null;
}
if (globalErrorHandler.importBundleError) {
return AppLocalizations.of(context).invalidImportString;
} else if (globalErrorHandler.explicitAddContactSuccess) {}
return null;
},
onChanged: (String importBundle) async {
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, importBundle);
Future.delayed(const Duration(milliseconds: 500), () {
if (globalErrorHandler.importBundleSuccess) {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).successfullAddedContact + importBundle));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.pop(context);
}
});
},
)
])));
} }
/// TODO Manage Servers Tab /// TODO Manage Servers Tab

View File

@ -83,7 +83,9 @@ class _MessageViewState extends State<MessageView> {
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm)); .SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
ctrlrCompose.clear(); ctrlrCompose.clear();
focusNode.requestFocus(); focusNode.requestFocus();
Provider.of<ContactInfoState>(context, listen: false).totalMessages++; if (Provider.of<ContactInfoState>(context, listen: false).isGroup == false) {
Provider.of<ContactInfoState>(context, listen: false).totalMessages++;
}
} }
Widget _buildComposeBox() { Widget _buildComposeBox() {

View File

@ -30,8 +30,8 @@ class _ContactRowState extends State<ContactRow> {
badgeTextColor: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor(), badgeTextColor: Provider.of<Settings>(context).theme.portraitContactBadgeTextColor(),
diameter: 64.0, diameter: 64.0,
imagePath: contact.imagePath, imagePath: contact.imagePath,
maskOut: contact.isGroup ? false : contact.status != "Authenticated", maskOut: !contact.isOnline(),
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()), border: contact.isOnline() ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
), ),
Expanded( Expanded(
child: Padding( child: Padding(

View File

@ -47,7 +47,7 @@ class _MessageListState extends State<MessageList> {
controller: ctrlr1, controller: ctrlr1,
child: Container( child: Container(
// Only show broken heart is the contact is offline... // Only show broken heart is the contact is offline...
decoration: Provider.of<ContactInfoState>(outerContext).status == "Authenticated" decoration: Provider.of<ContactInfoState>(outerContext).isOnline()
? null ? null
: BoxDecoration( : BoxDecoration(
image: DecorationImage( image: DecorationImage(