adding invitations, other message handling improvements
continuous-integration/drone/pr Build is failing
Details
continuous-integration/drone/pr Build is failing
Details
This commit is contained in:
parent
adf8653d3a
commit
62028b2f2e
|
@ -148,6 +148,12 @@ class MainActivity: FlutterActivity() {
|
||||||
val message = (call.argument("message") as? String) ?: "";
|
val message = (call.argument("message") as? String) ?: "";
|
||||||
Cwtch.sendMessage(profile, handle, message);
|
Cwtch.sendMessage(profile, handle, message);
|
||||||
}
|
}
|
||||||
|
"SendInvitation" -> {
|
||||||
|
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
||||||
|
val handle = (call.argument("handle") as? String) ?: "";
|
||||||
|
val target = (call.argument("target") as? String) ?: "";
|
||||||
|
Cwtch.sendInvitation(profile, handle, target);
|
||||||
|
}
|
||||||
"SendProfileEvent" -> {
|
"SendProfileEvent" -> {
|
||||||
val onion = (call.argument("onion") as? String) ?: "";
|
val onion = (call.argument("onion") as? String) ?: "";
|
||||||
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
|
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
|
||||||
|
|
|
@ -41,6 +41,8 @@ abstract class Cwtch {
|
||||||
Future<String> GetMessages(String profile, String handle, int start, int end);
|
Future<String> GetMessages(String profile, String handle, int start, int end);
|
||||||
// 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 SendInvitation(String profile, String handle, String target);
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void ImportBundle(String profile, String bundle);
|
void ImportBundle(String profile, String bundle);
|
||||||
|
|
|
@ -301,6 +301,18 @@ class CwtchFfi implements Cwtch {
|
||||||
SendMessage(u1, u1.length, u2, u2.length, u3, u3.length);
|
SendMessage(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void SendInvitation(String profileOnion, String contactHandle, String target) {
|
||||||
|
var sendInvitation = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_SendInvitation");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final SendInvitation = sendInvitation.asFunction<VoidFromStringStringStringFn>();
|
||||||
|
final u1 = profileOnion.toNativeUtf8();
|
||||||
|
final u2 = contactHandle.toNativeUtf8();
|
||||||
|
final u3 = target.toNativeUtf8();
|
||||||
|
SendInvitation(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void ResetTor() {
|
void ResetTor() {
|
||||||
|
|
|
@ -143,6 +143,12 @@ class CwtchGomobile implements Cwtch {
|
||||||
cwtchPlatform.invokeMethod("SendMessage", {"ProfileOnion": profileOnion, "handle": contactHandle, "message": message});
|
cwtchPlatform.invokeMethod("SendMessage", {"ProfileOnion": profileOnion, "handle": contactHandle, "message": message});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void SendInvitation(String profileOnion, String contactHandle, String target) {
|
||||||
|
cwtchPlatform.invokeMethod("SendInvitation", {"ProfileOnion": profileOnion, "handle": contactHandle, "target": target});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void ResetTor() {
|
void ResetTor() {
|
||||||
|
|
|
@ -353,6 +353,9 @@ class MessageState extends ChangeNotifier {
|
||||||
final String contactHandle;
|
final String contactHandle;
|
||||||
final int messageIndex;
|
final int messageIndex;
|
||||||
String _message;
|
String _message;
|
||||||
|
int _overlay;
|
||||||
|
String _inviteTarget;
|
||||||
|
String _inviteNick;
|
||||||
DateTime _timestamp;
|
DateTime _timestamp;
|
||||||
String _senderOnion;
|
String _senderOnion;
|
||||||
String _senderImage;
|
String _senderImage;
|
||||||
|
@ -370,12 +373,16 @@ class MessageState extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
get message => this._message;
|
get message => this._message;
|
||||||
|
get overlay => this._overlay;
|
||||||
get timestamp => this._timestamp;
|
get timestamp => this._timestamp;
|
||||||
get ackd => this._ackd;
|
get ackd => this._ackd;
|
||||||
get senderOnion => this._senderOnion;
|
get senderOnion => this._senderOnion;
|
||||||
get senderImage => this._senderImage;
|
get senderImage => this._senderImage;
|
||||||
get loaded => this._loaded;
|
get loaded => this._loaded;
|
||||||
get signature => this._signature;
|
get signature => this._signature;
|
||||||
|
get isInvite => this.overlay == 100 || this.overlay == 101;
|
||||||
|
get inviteTarget => this._inviteTarget;
|
||||||
|
get inviteNick => this._inviteNick;
|
||||||
|
|
||||||
set ackd(bool newVal) {
|
set ackd(bool newVal) {
|
||||||
this._ackd = newVal;
|
this._ackd = newVal;
|
||||||
|
@ -396,6 +403,7 @@ class MessageState extends ChangeNotifier {
|
||||||
|
|
||||||
dynamic message = jsonDecode(messageWrapper['Message']);
|
dynamic message = jsonDecode(messageWrapper['Message']);
|
||||||
this._message = message['d'];
|
this._message = message['d'];
|
||||||
|
this._overlay = int.parse(message['o'].toString());
|
||||||
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp']);
|
this._timestamp = DateTime.tryParse(messageWrapper['Timestamp']);
|
||||||
this._senderOnion = messageWrapper['PeerID'];
|
this._senderOnion = messageWrapper['PeerID'];
|
||||||
this._senderImage = messageWrapper['ContactImage'];
|
this._senderImage = messageWrapper['ContactImage'];
|
||||||
|
@ -405,6 +413,23 @@ class MessageState extends ChangeNotifier {
|
||||||
this._signature = messageWrapper['Signature'];
|
this._signature = messageWrapper['Signature'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if this is an invite, get the contact handle
|
||||||
|
if (this.isInvite) {
|
||||||
|
if (message['d'].toString().length == 56) {
|
||||||
|
this._inviteTarget = message['d'];
|
||||||
|
var targetContact = Provider.of<ProfileInfoState>(context).contactList.getContact(this._inviteTarget);
|
||||||
|
this._inviteNick = targetContact == null ? message['d'] : targetContact.nickname;
|
||||||
|
} else {
|
||||||
|
var parts = message['d'].toString().split("||");
|
||||||
|
if (parts.length == 2) {
|
||||||
|
print("jsondecoding: "+utf8.fuse(base64).decode(parts[1].substring(5)));
|
||||||
|
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
|
||||||
|
this._inviteTarget = jsonObj['GroupID'];
|
||||||
|
this._inviteNick = jsonObj['GroupName'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._loaded = true;
|
this._loaded = true;
|
||||||
//update ackd last as it's changenotified
|
//update ackd last as it's changenotified
|
||||||
this._ackd = messageWrapper['Acknowledged'];
|
this._ackd = messageWrapper['Acknowledged'];
|
||||||
|
|
|
@ -23,6 +23,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
final ctrlrNick = TextEditingController(text: "");
|
final ctrlrNick = TextEditingController(text: "");
|
||||||
|
final ctrlrGroupAddr = TextEditingController(text: "");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -31,6 +32,10 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
||||||
if (nickname.isNotEmpty) {
|
if (nickname.isNotEmpty) {
|
||||||
ctrlrNick.text = nickname;
|
ctrlrNick.text = nickname;
|
||||||
}
|
}
|
||||||
|
final groupAddr = Provider.of<ContactInfoState>(context, listen: false).onion;
|
||||||
|
if (groupAddr.isNotEmpty) {
|
||||||
|
ctrlrGroupAddr.text = groupAddr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -68,7 +73,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
CwtchTextField(
|
CwtchTextField(
|
||||||
controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).onion),
|
controller: ctrlrGroupAddr,
|
||||||
)
|
)
|
||||||
]),
|
]),
|
||||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
|
|
|
@ -2,7 +2,9 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_app/views/peersettingsview.dart';
|
import 'package:flutter_app/views/peersettingsview.dart';
|
||||||
|
import 'package:flutter_app/widgets/DropdownContacts.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
import '../main.dart';
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
|
@ -18,6 +20,7 @@ class MessageView extends StatefulWidget {
|
||||||
class _MessageViewState extends State<MessageView> {
|
class _MessageViewState extends State<MessageView> {
|
||||||
final ctrlrCompose = TextEditingController();
|
final ctrlrCompose = TextEditingController();
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
|
String selectedContact = "";
|
||||||
|
|
||||||
// @override
|
// @override
|
||||||
// void didChangeDependencies() {
|
// void didChangeDependencies() {
|
||||||
|
@ -78,9 +81,22 @@ class _MessageViewState extends State<MessageView> {
|
||||||
|
|
||||||
void _sendMessage([String ignoredParam]) {
|
void _sendMessage([String ignoredParam]) {
|
||||||
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text);
|
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text);
|
||||||
Provider.of<FlwtchState>(context, listen: false)
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SendMessage(
|
||||||
.cwtch
|
Provider.of<ContactInfoState>(context, listen: false).profileOnion,
|
||||||
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).onion, jsonEncode(cm));
|
Provider.of<ContactInfoState>(context, listen: false).onion,
|
||||||
|
jsonEncode(cm));
|
||||||
|
_sendMessageHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sendInvitation([String ignoredParam]) {
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SendInvitation(
|
||||||
|
Provider.of<ContactInfoState>(context, listen: false).profileOnion,
|
||||||
|
Provider.of<ContactInfoState>(context, listen: false).onion,
|
||||||
|
this.selectedContact);
|
||||||
|
_sendMessageHelper();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sendMessageHelper() {
|
||||||
ctrlrCompose.clear();
|
ctrlrCompose.clear();
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
Future.delayed(const Duration(milliseconds: 80), () {
|
Future.delayed(const Duration(milliseconds: 80), () {
|
||||||
|
@ -103,23 +119,61 @@ class _MessageViewState extends State<MessageView> {
|
||||||
textInputAction: TextInputAction.send,
|
textInputAction: TextInputAction.send,
|
||||||
onSubmitted: _sendMessage,
|
onSubmitted: _sendMessage,
|
||||||
)),
|
)),
|
||||||
SizedBox(
|
Column(children:[SizedBox(
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 90,
|
height: 50,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.fromLTRB(2, 2, 2, 2),
|
padding: EdgeInsets.fromLTRB(2, 2, 2, 2),
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
child: Icon(Icons.send, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
child: Icon(Icons.send, size: 24, color: Provider.of<Settings>(context).theme.mainTextColor()),
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
fixedSize: MaterialStateProperty.all(Size(86, 40)),
|
fixedSize: MaterialStateProperty.all(Size(86, 50)),
|
||||||
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.defaultButtonColor()),
|
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.defaultButtonColor()),
|
||||||
),
|
),
|
||||||
onPressed: _sendMessage,
|
onPressed: _sendMessage,
|
||||||
))),
|
))),
|
||||||
|
SizedBox(
|
||||||
|
width: 86, height: 40,
|
||||||
|
child: IconButton(icon: Icon(Icons.insert_invitation, size: 12, color: Provider.of<Settings>(context).theme.mainTextColor()), onPressed: () => _modalSendInvitation(context))
|
||||||
|
),])
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void placeHolder() => {};
|
void placeHolder() => {};
|
||||||
|
|
||||||
|
void _modalSendInvitation(BuildContext ctx) {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: ctx,
|
||||||
|
builder: (BuildContext bcontext) {
|
||||||
|
return 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: <Widget>[
|
||||||
|
Text(AppLocalizations.of(bcontext).invitationLabel),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(ctx, listen: false), child: DropdownContacts(onChanged: (newVal) {
|
||||||
|
setState((){ this.selectedContact = newVal; });
|
||||||
|
})),
|
||||||
|
SizedBox(height: 20,),
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text(AppLocalizations.of(bcontext).inviteBtn, semanticsLabel: AppLocalizations.of(bcontext).inviteBtn),
|
||||||
|
onPressed: () {
|
||||||
|
this._sendInvitation();
|
||||||
|
Navigator.pop(bcontext);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../model.dart';
|
||||||
|
|
||||||
|
class DropdownContacts extends StatefulWidget {
|
||||||
|
DropdownContacts({this.onChanged,});
|
||||||
|
final Function(dynamic) onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_DropdownContactsState createState() => _DropdownContactsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DropdownContactsState extends State<DropdownContacts> {
|
||||||
|
String selected;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DropdownButton(value: this.selected, items: Provider.of<ProfileInfoState>(context, listen: false).contactList.contacts.map<DropdownMenuItem<String>>((ContactInfoState contact) {
|
||||||
|
return DropdownMenuItem<String>(value: contact.onion, child: Text(contact.nickname??contact.onion));
|
||||||
|
}).toList(), onChanged: (newVal) {
|
||||||
|
setState(() {
|
||||||
|
this.selected = newVal;
|
||||||
|
});
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged(newVal);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,139 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../main.dart';
|
||||||
|
import '../model.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../settings.dart';
|
||||||
|
|
||||||
|
class InvitationBubble extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
InvitationBubbleState createState() => InvitationBubbleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class InvitationBubbleState extends State<InvitationBubble> {
|
||||||
|
bool rejected = false;
|
||||||
|
FocusNode _focus = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
||||||
|
var isGroup = Provider.of<MessageState>(context).overlay == 101;
|
||||||
|
var isAccepted = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).inviteTarget) != null;
|
||||||
|
var prettyDate = "";
|
||||||
|
var borderRadiousEh = 15.0;
|
||||||
|
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
||||||
|
|
||||||
|
if (Provider.of<MessageState>(context).timestamp != null) {
|
||||||
|
// user-configurable timestamps prolly ideal? #todo
|
||||||
|
prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageState>(context).timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
var senderDisplayStr = "";
|
||||||
|
if (Provider.of<MessageState>(context).senderOnion != null) {
|
||||||
|
var contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
||||||
|
if (contact == null) {
|
||||||
|
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
||||||
|
} else {
|
||||||
|
senderDisplayStr = contact.nickname ?? contact.onion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var wdgSender = Center(widthFactor:1, child: SelectableText(senderDisplayStr + '\u202F',
|
||||||
|
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor())));
|
||||||
|
|
||||||
|
var messageStr = "";
|
||||||
|
if (fromMe) {
|
||||||
|
//todo: get group name?
|
||||||
|
messageStr = "You sent an invitation for "+(isGroup ? "a group" : Provider.of<MessageState>(context).message ?? "");
|
||||||
|
} else {
|
||||||
|
messageStr = (isGroup ? "You have been invited to join "+(Provider.of<MessageState>(context).inviteNick??"") : "This is a contact suggestion for:") + "\n" + (Provider.of<MessageState>(context).inviteTarget ?? "");
|
||||||
|
}
|
||||||
|
var wdgMessage = Center(widthFactor:1, child: SelectableText(
|
||||||
|
messageStr + '\u202F',
|
||||||
|
key: Key(myKey),
|
||||||
|
focusNode: _focus,
|
||||||
|
style: TextStyle(
|
||||||
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
));
|
||||||
|
|
||||||
|
Widget wdgDecorations;
|
||||||
|
if (fromMe) {
|
||||||
|
wdgDecorations = Center(
|
||||||
|
widthFactor: 1.0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(prettyDate, style: TextStyle(
|
||||||
|
fontSize: 9.0,
|
||||||
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()
|
||||||
|
), textAlign: fromMe ? TextAlign.right : TextAlign.left),
|
||||||
|
!fromMe
|
||||||
|
? SizedBox(width: 1, height: 1)
|
||||||
|
: Provider.of<MessageState>(context).ackd
|
||||||
|
? Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
||||||
|
: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
||||||
|
],
|
||||||
|
));
|
||||||
|
} else if (isAccepted) {
|
||||||
|
wdgDecorations = Text("Accepted!");
|
||||||
|
} else if (this.rejected) {
|
||||||
|
wdgDecorations = Text("Rejected.");
|
||||||
|
} else {
|
||||||
|
wdgDecorations = Center(widthFactor:1,child:Row(children: [
|
||||||
|
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text("Reject"), onPressed: _btnReject)),
|
||||||
|
Padding(padding: EdgeInsets.all(5), child: TextButton(child: Text("Accept"), onPressed: _btnAccept)),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
||||||
|
return Center(
|
||||||
|
widthFactor: 1.0,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
|
||||||
|
border:
|
||||||
|
Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Center(widthFactor: 1.0,child:Padding(
|
||||||
|
padding: EdgeInsets.all(9.0),
|
||||||
|
child: Row(mainAxisSize: MainAxisSize.min, children: [Center(widthFactor: 1,child: Padding(padding:EdgeInsets.all(4), child:Icon(Icons.group_add, size: 32))),
|
||||||
|
Center(widthFactor: 1.0,child: Column(
|
||||||
|
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations])),
|
||||||
|
])))));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _btnReject() {
|
||||||
|
//todo: how should we track inline invite rejections?
|
||||||
|
setState(()=>this.rejected = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _btnAccept() {
|
||||||
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
|
if (Provider.of<MessageState>(context, listen: false).overlay == 100) {
|
||||||
|
final setPeerAttribute = {
|
||||||
|
"EventType": "AddContact",
|
||||||
|
"Data": {"ImportString": Provider.of<MessageState>(context, listen: false).message},
|
||||||
|
};
|
||||||
|
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||||
|
} else {
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, Provider.of<MessageState>(context, listen: false).message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import '../model.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../settings.dart';
|
||||||
|
|
||||||
|
class MessageLoadingBubble extends StatefulWidget {
|
||||||
|
@override
|
||||||
|
MessageLoadingBubbleState createState() => MessageLoadingBubbleState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageLoadingBubbleState extends State<MessageLoadingBubble> {
|
||||||
|
FocusNode _focus = FocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
||||||
|
var prettyDate = "";
|
||||||
|
var borderRadiousEh = 15.0;
|
||||||
|
var myKey = Provider.of<MessageState>(context).profileOnion + "::" + Provider.of<MessageState>(context).contactHandle + "::" + Provider.of<MessageState>(context).messageIndex.toString();
|
||||||
|
|
||||||
|
if (Provider.of<MessageState>(context).timestamp != null) {
|
||||||
|
// user-configurable timestamps prolly ideal? #todo
|
||||||
|
prettyDate = DateFormat.yMd().add_jm().format(Provider.of<MessageState>(context).timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
var senderDisplayStr = "";
|
||||||
|
if (Provider.of<MessageState>(context).senderOnion != null) {
|
||||||
|
var contact = Provider.of<ProfileInfoState>(context).contactList.getContact(Provider.of<MessageState>(context).senderOnion);
|
||||||
|
if (contact == null) {
|
||||||
|
senderDisplayStr = Provider.of<MessageState>(context).senderOnion;
|
||||||
|
} else {
|
||||||
|
senderDisplayStr = contact.nickname ?? contact.onion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var wdgSender = SelectableText(senderDisplayStr,
|
||||||
|
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor()));
|
||||||
|
|
||||||
|
var wdgMessage = SelectableText(
|
||||||
|
"loading" + '\u202F',
|
||||||
|
key: Key(myKey),
|
||||||
|
focusNode: _focus,
|
||||||
|
style: TextStyle(
|
||||||
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
);
|
||||||
|
|
||||||
|
var wdgDecorations = Center(
|
||||||
|
widthFactor: 1.0,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(prettyDate,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 9.0,
|
||||||
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor() : Provider.of<Settings>(context).theme.messageFromOtherTextColor(),
|
||||||
|
),
|
||||||
|
textAlign: fromMe ? TextAlign.right : TextAlign.left),
|
||||||
|
!fromMe
|
||||||
|
? SizedBox(width: 1, height: 1)
|
||||||
|
: Provider.of<MessageState>(context).ackd
|
||||||
|
? Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
||||||
|
: Icon(Icons.hourglass_bottom_outlined, color: Provider.of<Settings>(context).theme.messageFromMeTextColor(), size: 12)
|
||||||
|
],
|
||||||
|
));
|
||||||
|
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
||||||
|
return Container(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor(),
|
||||||
|
border:
|
||||||
|
Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor() : Provider.of<Settings>(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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(9.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: fromMe ? [wdgMessage, wdgDecorations] : [wdgSender, wdgMessage, wdgDecorations]))));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,16 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_app/widgets/profileimage.dart';
|
import 'package:flutter_app/widgets/profileimage.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
import '../main.dart';
|
||||||
import '../model.dart';
|
import '../model.dart';
|
||||||
import '../settings.dart';
|
import '../settings.dart';
|
||||||
|
import 'invitationbubble.dart';
|
||||||
import 'messagebubble.dart';
|
import 'messagebubble.dart';
|
||||||
|
import 'messageloadingbubble.dart';
|
||||||
|
|
||||||
class MessageRow extends StatefulWidget {
|
class MessageRow extends StatefulWidget {
|
||||||
MessageRow({Key key}) : super(key: key);
|
MessageRow({Key key}) : super(key: key);
|
||||||
|
@ -18,7 +24,7 @@ class _MessageRowState extends State<MessageRow> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
var fromMe = Provider.of<MessageState>(context).senderOnion == Provider.of<ProfileInfoState>(context).onion;
|
||||||
|
|
||||||
Widget wdgBubble = Flexible(flex: 3, fit: FlexFit.loose, child: MessageBubble());
|
Widget wdgBubble = Flexible(flex: 3, fit: FlexFit.loose, child: Provider.of<MessageState>(context).loaded == true ? widgetForOverlay(Provider.of<MessageState>(context).overlay) : MessageLoadingBubble());
|
||||||
Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Provider.of<Settings>(context).theme.dropShadowColor());
|
Widget wdgIcons = Icon(Icons.delete_forever_outlined, color: Provider.of<Settings>(context).theme.dropShadowColor());
|
||||||
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
Widget wdgSpacer = Expanded(child: SizedBox(width: 60, height: 10));
|
||||||
var widgetRow = <Widget>[];
|
var widgetRow = <Widget>[];
|
||||||
|
@ -26,25 +32,55 @@ class _MessageRowState extends State<MessageRow> {
|
||||||
if (fromMe) {
|
if (fromMe) {
|
||||||
widgetRow = <Widget>[
|
widgetRow = <Widget>[
|
||||||
wdgSpacer,
|
wdgSpacer,
|
||||||
wdgIcons,
|
//wdgIcons,
|
||||||
wdgBubble,
|
wdgBubble,
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
var contact = Provider.of<ContactInfoState>(context);
|
var contact = Provider.of<ContactInfoState>(context);
|
||||||
Widget wdgPortrait = ProfileImage(
|
Widget wdgPortrait = GestureDetector(
|
||||||
|
onTap: _btnAdd,
|
||||||
|
child: ProfileImage(
|
||||||
diameter: 48.0,
|
diameter: 48.0,
|
||||||
imagePath: Provider.of<MessageState>(context).senderImage ?? contact.imagePath,
|
imagePath: Provider.of<MessageState>(context).senderImage ?? contact.imagePath,
|
||||||
//maskOut: contact.status != "Authenticated",
|
//maskOut: contact.status != "Authenticated",
|
||||||
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor());
|
border: contact.status == "Authenticated" ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()));
|
||||||
|
|
||||||
widgetRow = <Widget>[
|
widgetRow = <Widget>[
|
||||||
wdgPortrait,
|
wdgPortrait,
|
||||||
wdgBubble,
|
wdgBubble,
|
||||||
wdgIcons,
|
//wdgIcons,
|
||||||
wdgSpacer,
|
wdgSpacer,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow));
|
return Padding(padding: EdgeInsets.all(2), child: Row(mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, children: widgetRow));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget widgetForOverlay(int o) {
|
||||||
|
switch (o) {
|
||||||
|
case 1: return MessageBubble();
|
||||||
|
case 100:
|
||||||
|
case 101: return InvitationBubble();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _btnAdd() {
|
||||||
|
var sender = Provider.of<MessageState>(context, listen: false).senderOnion;
|
||||||
|
if (sender == null || sender == "") {
|
||||||
|
print("sender not yet loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
|
final setPeerAttribute = {
|
||||||
|
"EventType": "AddContact",
|
||||||
|
"Data": {"ImportString": sender},
|
||||||
|
};
|
||||||
|
final setPeerAttributeJson = jsonEncode(setPeerAttribute);
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(profileOnion, setPeerAttributeJson);
|
||||||
|
|
||||||
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context).successfullAddedContact));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue