approve contacts, send messages, messages to provider model #36
|
@ -127,6 +127,27 @@ class MainActivity: FlutterActivity() {
|
|||
val end = (call.argument("end") as? Long) ?: 0;
|
||||
result.success(Cwtch.getMessages(profile, handle, start, end))
|
||||
}
|
||||
"AcceptContact" -> {
|
||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
||||
val handle = (call.argument("handle") as? String) ?: "";
|
||||
Cwtch.acceptContact(profile, handle);
|
||||
}
|
||||
"BlockContact" -> {
|
||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
||||
val handle = (call.argument("handle") as? String) ?: "";
|
||||
Cwtch.blockContact(profile, handle);
|
||||
}
|
||||
"DebugResetContact" -> {
|
||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
||||
val handle = (call.argument("handle") as? String) ?: "";
|
||||
Cwtch.debugResetContact(profile, handle);
|
||||
}
|
||||
"SendMessage" -> {
|
||||
val profile = (call.argument("ProfileOnion") as? String) ?: "";
|
||||
val handle = (call.argument("handle") as? String) ?: "";
|
||||
val message = (call.argument("message") as? String) ?: "";
|
||||
Cwtch.sendMessage(profile, handle, message);
|
||||
}
|
||||
"SendProfileEvent" -> {
|
||||
val onion = (call.argument("onion") as? String) ?: "";
|
||||
val jsonEvent = (call.argument("jsonEvent") as? String) ?: "";
|
||||
|
|
Binary file not shown.
|
@ -6,7 +6,6 @@
|
|||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_app/views/addeditprofileview.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
|
|
|
@ -8,11 +8,20 @@ abstract class Cwtch {
|
|||
void CreateProfile(String nick, String pass);
|
||||
// ignore: non_constant_identifier_names
|
||||
void LoadProfiles(String pass);
|
||||
|
||||
// todo: remove these
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendProfileEvent(String onion, String jsonEvent);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendAppEvent(String jsonEvent);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void AcceptContact(String profileOnion, String contactHandle);
|
||||
// ignore: non_constant_identifier_names
|
||||
void BlockContact(String profileOnion, String contactHandle);
|
||||
// ignore: non_constant_identifier_names
|
||||
void DebugResetContact(String profileOnion, String contactHandle);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> ACNEvents();
|
||||
// ignore: non_constant_identifier_names
|
||||
|
@ -27,6 +36,8 @@ abstract class Cwtch {
|
|||
Future<String> GetMessage(String profile, String handle, int index);
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<String> GetMessages(String profile, String handle, int start, int end);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendMessage(String profile, String handle, String message);
|
||||
|
||||
void dispose();
|
||||
}
|
||||
|
|
|
@ -28,26 +28,35 @@ class CwtchNotifier {
|
|||
));
|
||||
break;
|
||||
case "PeerCreated":
|
||||
print("xx peercreated $data");
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.add(ContactInfoState(
|
||||
profileOnion: data["ProfileOnion"],
|
||||
onion: data["RemotePeer"],
|
||||
nickname: data["nick"],
|
||||
status: data["status"],
|
||||
imagePath: data["picture"],
|
||||
isBlocked: data["authorization"] == "blocked",
|
||||
isInvitation: data["authorization"] == "unknown",
|
||||
savePeerHistory: data["saveConversationHistory"],
|
||||
numMessages: int.parse(data["numMessages"]),
|
||||
numUnread: int.parse(data["unread"]),
|
||||
));
|
||||
break;
|
||||
case "PeerStateChange":
|
||||
ContactInfoState contact = profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]);
|
||||
if (contact == null) {
|
||||
//todo: stopgap, as lc-g is supposed to handle this
|
||||
print("PSC -> adding " + data["ProfileOnion"] + " :: " + data["RemotePeer"]);
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.add(
|
||||
ContactInfoState(profileOnion: data["ProfileOnion"], onion: data["RemotePeer"], nickname: data["name"], status: data["ConnectionState"], isBlocked: data["authorization"] == "blocked"));
|
||||
} else {
|
||||
contact.status = data["ConnectionState"];
|
||||
if (contact != null) {
|
||||
if (data["ConnectionState"] != null) {
|
||||
contact.status = data["ConnectionState"];
|
||||
}
|
||||
if (data["authorization"] != null) {
|
||||
contact.isInvitation = data["authorization"] == "unknown";
|
||||
contact.isBlocked = data["authorization"] == "blocked";
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "NewMessageFromPeer":
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]).unreadMessages++;
|
||||
profileCN.getProfile(data["ProfileOnion"]).contactList.getContact(data["RemotePeer"]).totalMessages++;
|
||||
break;
|
||||
case "AppError":
|
||||
print("New App Error: $data");
|
||||
error.handleUpdate(data["Data"]);
|
||||
|
|
|
@ -19,6 +19,9 @@ typedef StartCwtchFn = void Function(Pointer<Utf8> dir, int len, Pointer<Utf8> t
|
|||
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef void_from_string_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
|
||||
typedef VoidFromStringStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||
|
||||
typedef access_cwtch_eventbus_function = Void Function();
|
||||
typedef NextEventFn = void Function();
|
||||
|
||||
|
@ -249,4 +252,49 @@ class CwtchFfi implements Cwtch {
|
|||
final utf8json = json.toNativeUtf8();
|
||||
SendAppBusEvent(utf8json, utf8json.length);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void AcceptContact(String profileOnion, String contactHandle) {
|
||||
var acceptContact = library.lookup<NativeFunction<string_string_to_void_function>>("c_AcceptContact");
|
||||
// ignore: non_constant_identifier_names
|
||||
final AcceptContact = acceptContact.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
AcceptContact(u1, u1.length, u2, u2.length);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void BlockContact(String profileOnion, String contactHandle) {
|
||||
var blockContact = library.lookup<NativeFunction<string_string_to_void_function>>("c_BlockContact");
|
||||
// ignore: non_constant_identifier_names
|
||||
final BlockContact = blockContact.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
BlockContact(u1, u1.length, u2, u2.length);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DebugResetContact(String profileOnion, String contactHandle) {
|
||||
var debugResetContact = library.lookup<NativeFunction<string_string_to_void_function>>("c_DebugResetContact");
|
||||
// ignore: non_constant_identifier_names
|
||||
final DebugResetContact = debugResetContact.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
DebugResetContact(u1, u1.length, u2, u2.length);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendMessage(String profileOnion, String contactHandle, String message) {
|
||||
var sendMessage = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_SendMessage");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SendMessage = sendMessage.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = message.toNativeUtf8();
|
||||
SendMessage(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -118,4 +118,28 @@ class CwtchGomobile implements Cwtch {
|
|||
|
||||
@override
|
||||
void dispose() => {};
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void AcceptContact(String profileOnion, String contactHandle) {
|
||||
cwtchPlatform.invokeMethod("AcceptContact", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void BlockContact(String profileOnion, String contactHandle) {
|
||||
cwtchPlatform.invokeMethod("BlockContact", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DebugResetContact(String profileOnion, String contactHandle) {
|
||||
cwtchPlatform.invokeMethod("DebugResetContact", {"ProfileOnion": profileOnion, "handle": contactHandle});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SendMessage(String profileOnion, String contactHandle, String message) {
|
||||
cwtchPlatform.invokeMethod("SendMessage", {"ProfileOnion": profileOnion, "handle": contactHandle, "message": message});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'cwtch/cwtch.dart';
|
||||
import 'main.dart';
|
||||
|
||||
////////////////////
|
||||
/// UI State ///
|
||||
|
@ -145,6 +147,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
status: contact["status"],
|
||||
imagePath: contact["picture"],
|
||||
isBlocked: contact["authorization"] == "blocked",
|
||||
isInvitation: contact["authorization"] == "unknown",
|
||||
savePeerHistory: contact["saveConversationHistory"],
|
||||
numMessages: contact["numMessages"],
|
||||
numUnread: contact["numUnread"]);
|
||||
|
@ -272,6 +275,41 @@ class ContactInfoState extends ChangeNotifier {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class MessageState extends ChangeNotifier {
|
||||
final String profileOnion;
|
||||
final String contactHandle;
|
||||
final int messageIndex;
|
||||
String _message;
|
||||
String _timestamp;
|
||||
bool _ackd = false;
|
||||
|
||||
MessageState({
|
||||
BuildContext context,
|
||||
this.profileOnion,
|
||||
this.contactHandle,
|
||||
this.messageIndex,
|
||||
}) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, messageIndex).then((jsonMessage) {
|
||||
dynamic messageWrapper = jsonDecode(jsonMessage);
|
||||
dynamic message = jsonDecode(messageWrapper['Message']);
|
||||
this._message = message['d'];
|
||||
this._timestamp = messageWrapper['Timestamp'];
|
||||
this._ackd = messageWrapper['Acknowledged'];
|
||||
});
|
||||
}
|
||||
|
||||
get message => this._message;
|
||||
get timestamp => this._timestamp;
|
||||
get ackd => this._ackd;
|
||||
|
||||
set ackd(bool newVal) {
|
||||
this._ackd = newVal;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/////////////
|
||||
/// ACN ///
|
||||
/////////////
|
||||
|
|
|
@ -1350,6 +1350,7 @@ ThemeData mkThemeData(Settings opaque) {
|
|||
accentColor: opaque.current().defaultButtonColor(),
|
||||
buttonColor: opaque.current().defaultButtonColor(),
|
||||
backgroundColor: opaque.current().backgroundMainColor(),
|
||||
highlightColor: opaque.current().hilightElementTextColor(),
|
||||
iconTheme: IconThemeData(
|
||||
color: opaque.current().mainTextColor(),
|
||||
),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_app/errorHandler.dart';
|
||||
import 'package:flutter_app/settings.dart';
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_app/widgets/contactrow.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../errorHandler.dart';
|
||||
import '../main.dart';
|
||||
import 'addcontactview.dart';
|
||||
import '../model.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_app/views/peersettingsview.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -14,9 +16,11 @@ class MessageView extends StatefulWidget {
|
|||
|
||||
class _MessageViewState extends State<MessageView> {
|
||||
final ctrlrCompose = TextEditingController();
|
||||
final focusNode = FocusNode();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
focusNode.dispose();
|
||||
ctrlrCompose.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
@ -31,13 +35,20 @@ class _MessageViewState extends State<MessageView> {
|
|||
IconButton(icon: Icon(Icons.list), onPressed: _pushContactSettings),
|
||||
IconButton(icon: Icon(Icons.push_pin), onPressed: _pushContactSettings),
|
||||
IconButton(icon: Icon(Icons.settings), onPressed: _pushContactSettings),
|
||||
IconButton(icon: Icon(Icons.bug_report_outlined), onPressed: _debugResetContact),
|
||||
],
|
||||
),
|
||||
body: MessageList(),
|
||||
body: Padding(padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 108.0), child: MessageList()),
|
||||
bottomSheet: _buildComposeBox(),
|
||||
);
|
||||
}
|
||||
|
||||
void _debugResetContact() {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.DebugResetContact(
|
||||
Provider.of<ContactInfoState>(context, listen: false).profileOnion,
|
||||
Provider.of<ContactInfoState>(context, listen: false).onion);
|
||||
}
|
||||
|
||||
void _pushContactSettings() {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext bcontext) {
|
||||
|
@ -56,43 +67,63 @@ class _MessageViewState extends State<MessageView> {
|
|||
));
|
||||
}
|
||||
|
||||
void _sendMessage() {
|
||||
//ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text);
|
||||
//todo: merge: Provider.of<FlwtchState>(context).cwtch.SendMessage(widget.profile.onion, widget.conversationHandle, jsonEncode(cm));
|
||||
void _sendMessage([String ignoredParam]) {
|
||||
ChatMessage cm = new ChatMessage(o: 1, d: ctrlrCompose.value.text);
|
||||
Provider.of<FlwtchState>(context, listen:false).cwtch.SendMessage(
|
||||
Provider.of<ContactInfoState>(context, listen:false).profileOnion,
|
||||
Provider.of<ContactInfoState>(context, listen:false).onion,
|
||||
jsonEncode(cm));
|
||||
ctrlrCompose.clear();
|
||||
focusNode.requestFocus();
|
||||
Provider.of<ContactInfoState>(context, listen:false).totalMessages++;
|
||||
}
|
||||
|
||||
Widget _buildComposeBox() {
|
||||
return Container(
|
||||
color: Opaque.current().backgroundMainColor(),
|
||||
height: 100,
|
||||
padding: EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Expanded(child: TextField(controller: ctrlrCompose)),
|
||||
Expanded(child: TextField(
|
||||
controller: ctrlrCompose,
|
||||
focusNode: focusNode,
|
||||
textInputAction: TextInputAction.send,
|
||||
onSubmitted: _sendMessage,
|
||||
)),
|
||||
SizedBox(
|
||||
width: 100,
|
||||
width: 90,
|
||||
height: 80,
|
||||
child: Column(children: <Widget>[
|
||||
ElevatedButton(
|
||||
Padding(padding: EdgeInsets.fromLTRB(2, 2, 2, 2), child: ElevatedButton(
|
||||
child: Icon(Icons.send, color: Opaque.current().mainTextColor()),
|
||||
style: ButtonStyle(
|
||||
fixedSize: MaterialStateProperty.all(Size(86, 40)),
|
||||
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
|
||||
),
|
||||
onPressed: _sendMessage,
|
||||
),
|
||||
)),
|
||||
Row(children: <Widget>[
|
||||
SizedBox(
|
||||
width: 45,
|
||||
Padding(padding: EdgeInsets.all(2), child: SizedBox(
|
||||
width: 41,
|
||||
child: ElevatedButton(
|
||||
child: Icon(Icons.emoji_emotions_outlined, color: Opaque.current().mainTextColor()),
|
||||
style: ButtonStyle(
|
||||
fixedSize: MaterialStateProperty.all(Size(41, 40)),
|
||||
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
|
||||
),
|
||||
onPressed: placeHolder,
|
||||
)),
|
||||
SizedBox(
|
||||
width: 45,
|
||||
))),
|
||||
Padding(padding: EdgeInsets.all(2), child: SizedBox(
|
||||
width: 41,
|
||||
child: ElevatedButton(
|
||||
child: Icon(Icons.attach_file, color: Opaque.current().mainTextColor()),
|
||||
style: ButtonStyle(
|
||||
fixedSize: MaterialStateProperty.all(Size(41, 40)),
|
||||
backgroundColor: MaterialStateProperty.all(Opaque.current().defaultButtonColor()),
|
||||
),
|
||||
onPressed: placeHolder,
|
||||
)),
|
||||
))),
|
||||
])
|
||||
]),
|
||||
),
|
||||
|
|
|
@ -35,8 +35,10 @@ class _ContactRowState extends State<ContactRow> {
|
|||
),
|
||||
),
|
||||
trailing: contact.isInvitation != null && contact.isInvitation
|
||||
? Column(children: <Widget>[Icon(Icons.favorite, color: Opaque.current().mainTextColor()), Icon(Icons.delete, color: Opaque.current().mainTextColor())])
|
||||
: Text("99+"), //(nb: Icons.create is a pencil and we use it for "edit", not create)
|
||||
? Wrap(direction: Axis.vertical, children: <Widget>[
|
||||
IconButton(padding: EdgeInsets.zero, iconSize: 16, icon: Icon(Icons.favorite, color: Opaque.current().mainTextColor()), onPressed: _btnApprove,),
|
||||
IconButton(padding: EdgeInsets.zero, iconSize: 16, icon: Icon(Icons.delete, color: Opaque.current().mainTextColor()), onPressed: _btnReject,)])
|
||||
: Text(contact.unreadMessages.toString()), //(nb: Icons.create is a pencil and we use it for "edit", not create)
|
||||
title: Text(
|
||||
contact.nickname,
|
||||
style: Provider.of<FlwtchState>(context).biggerFont,
|
||||
|
@ -69,4 +71,17 @@ class _ContactRowState extends State<ContactRow> {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
void _btnApprove() {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.AcceptContact(
|
||||
Provider.of<ContactInfoState>(context, listen: false).profileOnion,
|
||||
Provider.of<ContactInfoState>(context, listen: false).onion);
|
||||
}
|
||||
|
||||
void _btnReject() {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.BlockContact(
|
||||
Provider.of<ContactInfoState>(context, listen: false).profileOnion,
|
||||
Provider.of<ContactInfoState>(context, listen: false).onion);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,41 +1,14 @@
|
|||
import 'dart:convert';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../main.dart';
|
||||
import '../model.dart';
|
||||
import '../opaque.dart';
|
||||
|
||||
class MessageBubble extends StatefulWidget {
|
||||
MessageBubble({Key key, this.profile, this.contactOnion, this.messageIndex});
|
||||
final ProfileInfoState profile;
|
||||
final String contactOnion;
|
||||
final int messageIndex;
|
||||
|
||||
@override
|
||||
_MessageBubbleState createState() => _MessageBubbleState();
|
||||
}
|
||||
|
||||
class _MessageBubbleState extends State<MessageBubble> {
|
||||
String d = "", ts = "";
|
||||
bool ack = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
|
||||
print("requesting message " + widget.messageIndex.toString());
|
||||
Provider.of<FlwtchState>(context).cwtch.GetMessage(widget.profile.onion, widget.contactOnion, widget.messageIndex).then((jsonMessage) {
|
||||
print("got message: " + widget.messageIndex.toString() + ": " + jsonMessage);
|
||||
dynamic messageWrapper = jsonDecode(jsonMessage);
|
||||
dynamic message = jsonDecode(messageWrapper['Message']);
|
||||
setState(() {
|
||||
d = message['d'];
|
||||
ts = messageWrapper['Timestamp'];
|
||||
ack = messageWrapper['Acknowledged'];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
|
@ -50,13 +23,13 @@ class _MessageBubbleState extends State<MessageBubble> {
|
|||
),
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
d,
|
||||
Provider.of<MessageState>(context).message,
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
subtitle: Row(
|
||||
children: [
|
||||
Text("" + widget.messageIndex.toString()),
|
||||
ack ? Icon(Icons.check_circle_outline, color: Opaque.current().mainTextColor()) : Icon(Icons.hourglass_bottom_outlined, color: Opaque.current().mainTextColor())
|
||||
Text(Provider.of<MessageState>(context).timestamp),
|
||||
Provider.of<MessageState>(context).ackd ? Icon(Icons.check_circle_outline, color: Opaque.current().mainTextColor()) : Icon(Icons.hourglass_bottom_outlined, color: Opaque.current().mainTextColor())
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -10,17 +10,51 @@ class MessageList extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _MessageListState extends State<MessageList> {
|
||||
ScrollController ctrlr1 = ScrollController();
|
||||
int lastMessageCount = 0;
|
||||
double lastMaxScroll = 0.0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom(true));
|
||||
}
|
||||
|
||||
void _scrollToBottom(bool force) {
|
||||
if (ctrlr1.hasClients) {
|
||||
if (force || Provider.of<ContactInfoState>(context, listen: false).totalMessages > lastMessageCount) {
|
||||
if (ctrlr1.position.pixels == lastMaxScroll) {
|
||||
ctrlr1.jumpTo(ctrlr1.position.maxScrollExtent);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
lastMessageCount = Provider.of<ContactInfoState>(context, listen: false).totalMessages;
|
||||
lastMaxScroll = ctrlr1.position.maxScrollExtent;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setState(() => null);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext outerContext) {
|
||||
return ListView.builder(
|
||||
itemCount: Provider.of<ContactInfoState>(context).totalMessages,
|
||||
itemBuilder: (context, index) {
|
||||
return MessageBubble(
|
||||
profile: Provider.of<ProfileInfoState>(outerContext),
|
||||
contactOnion: Provider.of<ContactInfoState>(outerContext).onion,
|
||||
messageIndex: index,
|
||||
);
|
||||
},
|
||||
);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom(false));
|
||||
return Card(child: Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
controller: ctrlr1,
|
||||
child: ListView.builder(
|
||||
controller: ctrlr1,
|
||||
itemCount: Provider.of<ContactInfoState>(context).totalMessages,
|
||||
itemBuilder: (context, index) {
|
||||
return ChangeNotifierProvider(create: (_) => MessageState(
|
||||
context: context,
|
||||
profileOnion: Provider.of<ProfileInfoState>(outerContext).onion,
|
||||
contactHandle: Provider.of<ContactInfoState>(outerContext).onion,
|
||||
messageIndex: index,
|
||||
), child: MessageBubble());
|
||||
},
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -378,7 +378,7 @@ packages:
|
|||
name: win32
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
version: "2.0.5"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -36,16 +36,14 @@ dependencies:
|
|||
path_provider: ^2.0.0
|
||||
|
||||
# todo: flutter_driver causes version conflict. eg https://github.com/flutter/flutter/issues/44829
|
||||
# testing-related deps
|
||||
# testing-related deps
|
||||
integration_test: ^1.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_driver:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
#dev_dependencies:
|
||||
# flutter_lokalise: any
|
||||
|
||||
# alternatively: flutter pub run intl_translation:generate_from_arb --output-dir=lib/l10n --no-use-deferred-loading lib/intl/app_localizations.dart lib/l10n/intl_*.arb --api-token X --project-id Y
|
||||
|
|
|
@ -9,7 +9,6 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_app/opaque.dart';
|
||||
import 'package:flutter_app/settings.dart';
|
||||
import 'package:flutter_app/widgets/buttontextfield.dart';
|
||||
import 'package:flutter_app/widgets/cwtchlabel.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
|
|
@ -6,10 +6,8 @@
|
|||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_app/opaque.dart';
|
||||
import 'package:flutter_app/settings.dart';
|
||||
import 'package:flutter_app/widgets/cwtchlabel.dart';
|
||||
import 'package:flutter_app/widgets/textfield.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
|
Loading…
Reference in New Issue